通用参考

本文介绍SPL在各种场景的不同用法。

SPL在不同场景的功能定义

日志服务在不同场景中使用SPL,其功能定义存在差异,细节如下:

功能类型

Logstore索引过滤结果作为输入

字段名大小写敏感

全文字段__line__

Logtail采集

不支持。使用星号(*)表示Logtail采集的全部原始数据作为输入,比如

* | parse-json content

敏感

不支持

写入处理器

不支持。使用星号(*)表示写入的全部原始数据作为输入。

敏感

不支持

实时消费

不支持。使用星号(*)表示Logstore全部原始数据作为输入。比如

* | where msg like '%wrong%'

敏感

不支持

数据加工(新版)

不支持。使用星号(*)表示Logstore全部原始数据作为输入。

敏感

不支持

扫描查询

支持。先执行索引过滤,过滤结果再执行SPL处理。比如

error and msg:wrong | project level, msg

不敏感

支持

特殊字段处理

时间字段

在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中可以通过methodMETHOD等来过滤该字段。

重要

涉及日志服务扫描查询功能。更多信息,请参见扫描(Scan)查询

示例

where中使用大小写不敏感字段名。

  • SPL语句

    * | where METHOD like 'Post%'
  • 输入数据

    Method: 'PostLogstoreLogs'
  • 输出结果

    Method: 'PostLogstoreLogs'

字段名称冲突处理

在日志上传或者SPL运行期间,基于大小写敏感的处理可能会涉及字段名的冲突,如原始日志中同时存在Method和method字段名;针对不同的场景SPL会使用不同的方式进行字段冲突解决。

为了避免以下情况,建议在原始日志中规范输入的字段。

输入数据中存在冲突

在原始日志中包含大小写不敏感重复的字段时,如某一条日志同时存在Statusstatus两个字段,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-regexpparse-csvas明确指定的字段名,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值:

  1. SPL表达式中使用到的字段在输入数据中不存在时,则将其值视为null值进行计算。

  2. SPL表达式计算过程中出现异常,其计算结果即为null值。比如cast类型转换失败、数组越界等。

示例

  1. 字段不存在时,计算代入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
  1. 计算过程异常,计算结果为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语句,降低语句的复杂度和通道数,并查看原始数据是否过大。