本文介紹SPL在各種場景的不同用法。
SPL在不同場景的功能定義
日志服務在不同場景中使用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
等來過濾該字段。
涉及日志服務掃描查詢功能。更多信息,請參見掃描(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保留字段沖突處理
涉及日志服務的實時消費和掃描查詢功能。
日志服務完整保留字段列表請參見保留字段。SPL讀取存儲在SLS的LogGroup結構數據作為輸入(LogGroup定義詳情請參見數據編碼方式),如果原始寫入日志服務的數據不符合標準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語句,降低語句的復雜度(例如正則表達式)和管道數。
內存超限
SPL語句中包含不同的指令,不同的指令在不同的數據場景下消耗的內存不同,SPL語句執行時會限制一定的內存Quota(默認內存Quota在Scan查詢、實時消費、Logtail采集可能有所不同),超過內存Quota后,SPL會執行失敗,并返回內存超限錯誤,這種情況下SPL語句執行得到的結果為空。
遇到此類錯誤,建議調整SPL語句,降低語句的復雜度和通道數,并查看原始數據是否過大。