本文介紹SPL在各種情境的不同用法。
SPL在不同情境的功能定義
Log Service在不同情境中使用SPL,其功能定義存在差異,細節如下:
功能類型 | Logstore索引過濾結果作為輸入 | 欄位名大小寫敏感 | 全文欄位__line__ |
Logtail採集 | 不支援。使用星號(
| 敏感 | 不支援 |
寫入處理器 | 不支援。使用星號( | 敏感 | 不支援 |
即時消費 | 不支援。使用星號(
| 敏感 | 不支援 |
資料加工(新版) | 不支援。使用星號( | 敏感 | 不支援 |
掃描查詢 | 支援。先執行索引過濾,過濾結果再執行SPL處理。比如
| 不敏感 | 支援 |
特殊欄位處理
時間欄位
在SPL執行過程中,SLS日誌時間欄位類型始終保持為數實值型別INTEGER
或者BIGINT
。SLS日誌欄位包括資料時間戳記欄位__time__
和資料時間納秒部分欄位__time_ns_part__
。
如需更新資料時間,須使用extend指令操作,且確保新實值型別為INTEGER
或者BIGINT
。其他指令均不可操作時間欄位,其行為如下:
project、project-away、project-rename:預設保留時間欄位,不可將其重新命名,也不可將其覆蓋。
parse-regexp、parse-json:如果提取結果包含時間欄位,則將其忽略。
樣本
從已有的時間字串中提取時間欄位值。
SPL語句
* | parse-regexp time, '([\d\-\s:]+)\.(\d+)' as ts, ms | extend ts=date_parse(ts, '%Y-%m-%d %H:%i:%S') | extend __time__=cast(to_unixtime(time_s) as INTEGER) | extend __time_ns_part__=cast(ms as INTEGER) * 1000000 | project-away ts, ms
輸入資料
time: '2023-11-11 01:23:45.678'
輸出結果
__time__: 1699637025 __time_ns_part__: 678000000 time: '2023-11-11 01:23:45.678'
欄位名包含特殊字元
如果日誌中出現帶空格或者特殊字元的欄位,可以通過加雙引號的方式來引用。例如日誌中有欄位名為A B
,其中包含空格,
那麼在SPL中可以通過"A B"
來使用。使用例子如下:
* | where "A B" like '%error%'
欄位名大小寫不敏感
在SLS掃描查詢中,使用SPL時,SPL指令中引用的欄位名大小寫是不敏感的。例如日誌中欄位名為Method
,在SPL中可以通過method
、METHOD
等來過濾該欄位。
涉及Log Service掃描查詢功能。更多資訊,請參見掃描(Scan)查詢。
樣本
where中使用大小寫不敏感欄位名。
SPL語句
* | where METHOD like 'Post%'
輸入資料
Method: 'PostLogstoreLogs'
輸出結果
Method: 'PostLogstoreLogs'
欄位名稱衝突處理
在日誌上傳或者SPL運行期間,基於大小寫敏感的處理可能會涉及欄位名的衝突,如原始日誌中同時存在Method和method欄位名;針對不同的情境SPL會使用不同的方式進列欄位衝突解決。
為了避免以下情況,建議在原始日誌中規範輸入的欄位。
輸入資料中存在衝突
在原始日誌中包含大小寫不敏感重複的欄位時,如某一條日誌同時存在Status
、status
兩個欄位,SPL會隨機選取其中一個欄位作為輸入,捨棄另一列;舉例如下:
SPL語句
* | extend status_cast = cast(status as bigint)
輸入資料
Status: '200' status: '404'
處理結果
第一種可能結果,保留Status欄位值
Status: '200' -- 保留第1列,捨棄第2列 status_cast: '200'
第二種可能結果,保留status欄位值
status: '404' -- 保留第2列,捨棄第1列 Status_cast: '404'
運行結果中存在衝突
情境1:未經處理資料欄位衝突
在SPL運行過程中,可能產生大小寫不敏感的同名的欄位,這種情況SPL會隨機播放其中一列作為輸出;例如日誌欄位中包含一個JSON字串類型的欄位,在使用parse-json
的過程中,可能將同名欄位暴露出來,舉例如下:
SPL語句
* | parse-json content
輸入資料
content: '{"Method": "PostLogs", "method": "GetLogs", "status": "200"}'
輸出結果
第一種可能結果,保留Method欄位
content: '{"Method": "PostLogs", "method": "GetLogs", "status": "200"}' Method: 'PostLogs' -- 保留Method status: '200'
第二種可能結果,保留method欄位
content: '{"Method": "PostLogs", "method": "GetLogs", "status": "200"}' method: 'GetLogs' -- 保留method status: '200'
情境2:新產生資料欄位衝突:
為了避免歧義,對於SPL指令中產生的明確的新欄位名,此類指令包括extend
的欄位名和parse-regexp
、parse-csv
中as
明確指定的欄位名,SPL的輸出結果仍會保持新欄位名大小寫。
舉例:extend
一個新欄位Method
,結果欄位名仍會保持Method
的大小寫。
SPL語句
* | extend Method = 'Post'
輸入資料
Status: '200'
輸出結果
Status: '200' Method: 'Post'
SLS保留欄位衝突處理
涉及Log Service的即時消費和掃描查詢功能。
Log Service完整保留欄位列表請參見保留欄位。SPL讀取儲存在SLS的LogGroup結構資料作為輸入(LogGroup定義詳情請參見資料編碼方式),如果原始寫入Log Service的資料不符合標準LogGroup編碼規範,即某些保留欄位並未按照標準編碼在對應位置,而是放在LogContent中,則SPL在讀取此類保留欄位策略如下:
對於
__source__
、__topic__
、__time__
和__time_ns_part__
欄位,SPL會從標準LogGroup編碼結果中讀取值,忽略同名的LogContent欄位。對於以
__tag__:
為首碼的Tag欄位,SPL將優先從標準LogGroup編碼結果中讀取值,未取到再從LogContent中取值。例如對於欄位__tag__:ip
,優先從LogTag列表中讀取key為ip
的欄位,如果不存在,再從LogContent中的自訂日誌欄位中讀取key為__tag__:ip
的 Log 欄位。
全文欄位__line__
涉及SLS掃描查詢功能。
如果在控制台或者GetLogstoreLogs介面中想對原始日誌進行過濾,可以使用__line__欄位。
樣本
在日誌中搜尋關鍵詞error。
* | where __line__ like '%error%'
如果日誌中有欄位名為__line__,需要使用反引號包裹,即
`__line__`
來引用日誌中的欄位。
* | where `__line__` ='20'
新舊值保留與覆蓋
在SPL指令執行過程中,其輸出的目標欄位與輸入資料中已有欄位重名時,該欄位的取值策略如下:
欄位值保留與覆蓋策略與extend指令無關,extend指令的重名欄位取值策略為直接使用新值。
新舊實值型別不一致
直接保留輸入欄位原始值。
樣本
樣本1:project重新命名欄位重名。
SPL語句
* | extend status=cast(status as BIGINT) -- status類型轉為BIGINT | project code=status -- code舊實值型別為VARCHAR,新值為BIGINT,直接保留舊值
輸入資料
status: '200' code: 'Success'
輸出結果
code: 'Success'
樣本2:parse-json提取欄位重名。
SPL語句
* | extend status=cast(status as BIGINT) -- status類型轉為BIGINT | parse-json content -- status舊實值型別為BIGINT,新值為VARCHAR,直接保留舊值
輸入資料
status: '200' content: '{"status": "Success", "body": "this is test"}'
輸出結果
content: '{"status": "Success", "body": "this is test"}' status: 200 body: 'this is test'
新舊實值型別一致
如果輸入值為null,直接使用新值填充。否則,由指令中指定的mode
參數確定,定義如下表。
如果指令沒有定義mode
參數,則其預設值為overwrite
。
模式 | 說明 |
overwrite | 使用新值覆蓋舊值作為欄位值。 |
preserve | 保留舊值作為欄位值,捨棄新值。 |
樣本
樣本1:project重新命名欄位重名,且類型相同,mode預設值為overwrite。
SPL語句
* | project code=status -- code新舊實值型別均為VARCHAR,根據overwrite引用新值
輸入資料
status: '200' code: 'Success'
輸出結果
code: '200'
樣本2:parse-json提取欄位重名,且類型相同,mode預設值為overwrite。
SPL語句
* | parse-json content -- status新舊實值型別均為VARCHAR,根據overwrite引用新值
輸入資料
status: '200' content: '{"status": "Success", "body": "this is test"}'
輸出結果
content: '{"status": "Success", "body": "this is test"}' status: 'Success' body: 'this is test'
樣本3:parse-json提取欄位重名,且類型相同,mode指定為preserve。
SPL語句
* | parse-json -mode='preserve' content -- status新舊實值型別均為VARCHAR,根據preserve保留舊值
輸入資料
status: '200' content: '{"status": "Success", "body": "this is test"}'
輸出結果
content: '{"status": "Success", "body": "this is test"}' status: '200' body: 'this is test'
資料類型轉換
初始類型
除日誌時間欄位外,資料處理SPL的輸入欄位的初始資料類型均為VARCHAR。在後續的處理邏輯中,如果涉及到強資料類型時,需要進行資料類型轉換。
樣本
篩選出狀態代碼為5xx的訪問日誌時,需要將status欄位的類型轉為BIGINT之後再進行比較。
* -- status欄位的初始類型為VARCHAR
| where cast(status as BIGINT) >= 500 -- 將status欄位的類型轉為BIGINT,再進行比較
類型保持
在SPL處理資料過程中,使用extend指令對欄位進行資料類型轉換後,後續的處理邏輯將沿用轉換後的資料類型。
樣本
* -- Logstore作為輸入資料,除時間欄位外,所有欄位初始類型為VARCHAR
| where __source__='127.0.0.1' -- 對__source__欄位進行過濾
| extent status=cast(status as BIGINT) -- 將status欄位的類型轉為BIGINT
| project status, content
| where status>=500 -- status欄位的類型保持為BIGINT,可以直接與數字500做比較
SPL運算式null值處理
產生null值
SPL處理資料過程中,如下兩個情境將產生null值:
SPL運算式中使用到的欄位在輸入資料中不存在時,則將其值視為null值進行計算。
SPL運算式計算過程中出現異常,其計算結果即為null值。比如cast類型轉換失敗、數組越界等。
樣本
欄位不存在時,計算代入null值。
SPL語句
* | extend withoutStatus=(status is null)
輸入資料
# 條目1
status: '200'
code: 'Success'
# 條目2
code: 'Success'
輸出結果
# 條目1
status: '200'
code: 'Success'
withoutStatus: false
# 條目2
code: 'Success'
withoutStatus: true
計算過程異常,計算結果為null值。
SPL語句
* | extend code=cast(code as BIGINT) -- code欄位轉為BIGINT失敗 | extend values=json_parse(values) | extend values=cast(values as ARRAY(BIGINT)) | extend last=arr[10] -- 數組越界
輸入資料
status: '200' code: 'Success' values: '[1,2,3]'
輸出結果
status: '200' code: null values: [1, 2, 3] last: null
消除null值
為了消除計算過程中的null值,需使用COALESCE運算式將多個值按優先順序聯合,擷取第一個非null值作為最終計算結果。在所有運算式計算結果都為null時,也可以設定最終預設值。
樣本
讀取數組最後一個元素,如果數組為空白,預設值為0。
SPL語句
* | extend values=json_parse(values) | extend values=cast(values as ARRAY(BIGINT)) | extend last=COALESCE(values[3], values[2], values[1], 0)
輸入資料
# 條目1 values: '[1, 2, 3]' # 條目2 values: '[]'
輸出結果
# 條目1 values: [1, 2, 3] last: 3 # 條目2 values: [] last: 0
錯誤處理
語法錯誤
SPL語法錯誤指使用者在編寫SPL語句時不符合文法結構,比如指令名稱錯誤、關鍵詞引用錯誤、類型設定錯誤等,SPL語法錯誤發生時,SPL不會對資料進行處理,需要根據報錯,修改對應的錯誤。
資料錯誤
資料錯誤,指在SPL啟動並執行過程中,函數或者轉換出現錯誤,SPL會將結果欄位置為null
,由於每一行資料都有可能出現錯誤,SPL會隨機採樣部分錯誤返回,可以根據實際資料內容忽略或者修改SPL語句。
資料錯誤不會影響SPL整個執行過程,SPL語句仍會返回處理的結果,出錯的欄位的值為null
。可以根據實際情況忽略此類錯誤。
運行逾時
SPL語句中包含不同的指令,不同的指令在不同的資料情境下消耗的時間不同。當SPL整個語句的執行時間超過預設逾時時間後(預設逾時時間在Scan查詢、即時消費、Logtail採集可能有所不同),SPL語句會停止執行,並返回逾時錯誤,這種情況下SPL語句執行得到的結果為空白。
遇到此類錯誤,建議調整SPL語句,降低語句的複雜度(例如Regex)和管道數。
記憶體超限
SPL語句中包含不同的指令,不同的指令在不同的資料情境下消耗的記憶體不同,SPL語句執行時會限制一定的記憶體Quota(預設記憶體Quota在Scan查詢、即時消費、Logtail採集可能有所不同),超過記憶體Quota後,SPL會執行失敗,並返回記憶體超限錯誤,這種情況下SPL語句執行得到的結果為空白。
遇到此類錯誤,建議調整SPL語句,降低語句的複雜度和通道數,並查看未經處理資料是否過大。