本文介绍SPL在各种场景的不同用法。
SPL在不同场景的功能定义
日志服务在不同场景中使用SPL,其功能定义存在差异,细节如下:
SPL功能的入口 | Logstore索引过滤结果作为输入 | 字段名大小写敏感 | 全文字段__line__ | |
实时消费 |
| 不支持。使用星号(
| 敏感 | 不支持 |
扫描查询 |
| 支持。先执行索引过滤,过滤结果再执行SPL处理。比如
| 不敏感 | 支持 |
Logtail采集 | Logtail采集配置 | 不支持。使用星号(
| 敏感 | 不支持 |
特殊字段处理
时间字段
在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语句,降低语句的复杂度和通道数,并查看原始数据是否过大。