日志服务数据加工 - DSL语言介绍-阿里云开发者社区

开发者社区> 阿里云存储服务> 正文

日志服务数据加工 - DSL语言介绍

简介: 本文介绍日志服务数据加工 - DSL语言规范

概述

日志服务领域专用语言LOG DSL (Domain Specific Language)是日志服务数据加工使用的编排语言, 一种Python兼容的脚本语言. LOG DSL基于Python提供内置200个函数简化常见数据加工模式. 也支持用户自由定义的Python扩展(目前仅针对特定客户开放).

自由编排

LOG DSL通过一个Python兼容的DSL(领域专用语言)进行自由编排,对各种逻辑进行复杂组合, 可以满足大部分数据加工的需求和自由度.

例如, 可以自由编排达到如下一个场景:
image

完整的加工功能

支持近30种全局步骤函数, 支持通过各种参数调节行为, 且可以接受其他表达式函数的调用组合的结果作为参数, 其中控制的函数不仅可以搭配表达式函数, 也可以搭配其他步骤函数操作.

  • 控制: 支持基于条件判断后的流程分支, 包括if-else, if条件-操作配对组合, switch分派, compose组合等场景. 借助e_search等简单搜索语法可以对不同类型日志进行灵活的加工.
  • 事件操作: 支持对事件进行丢弃, 保留, 分裂, 输出, 复制等
  • 字段操作: 支持保留, 删除, 重命名字段等
  • 字段赋值: 支持基于任意表达式组合结果设置字段的值
  • 字段值提取: 支持基于正则表达式, GROK, KV, KV分隔符, CSV, TSV, PSV, syslog等方式提取字段中的多个值或键值对. 支持JSON提取并展开的完整攻略.
  • 字段富化: 支持基于字典, 表格进行映射或搜索, 其中搜索表格的映射方式尤其强. 支持从规则配置, 外部OSS, RDS, Logstore等资源获取富化的维表信息. 支持基于全量, 增量或改动日志对外部资源进行自动刷新.

灵活的函数库

目前提供近200个内置的表达式函数, 以便转换事件或控制全局函数的行为,覆盖了主流的数据加工的需求,包括:

  • 事件搜索: 提供类似Lucene语法的, 完整的正则表达式, 字符串, 泛字符, 数值比较, and/or/not等组合的条件过滤机制
  • 基本操作函数: 字段取值, 控制, 比较, 容器判断, 多字段操作等
  • 转换函数: 基础类型转换, 数字转换, 字典, 列表操作.
  • 算术函数: 基础计算, 多值计算比较, 数学计算, 数学参数等
  • 字符串函数: 多字段操作, 编码/解码, 排序、倒叙、替换, 常规规整, 查找判断, 切分, 格式化, 字符集判断等
  • 日期时间函数: 智能日期时间转换, 获取日期时间属性, 获取日期时间, 获取Unix时间戳, 获取日期时间字符串, 修改日期时间, 修改日期时间, 比较日期时间等
  • 正则表达式函数: 字段提取, 匹配判断, 替换, 切分
  • GROK支持: 支持GROK模式替换, 提供400种GROK内置模式.
  • JSON与XML函数: 提取过滤等
  • Protobuf: 支持基于Protobuf配置对Protobuf格式的内容进行转换与提取
  • 编解码: 支持SHA1/256/512等, MD5, HTML, URL, Base64等格式的文本进行单项或双向的编解码.

支持动态分发

支持根据业务需求, 将数据按照特定逻辑分发到不通的目标logstore. 目标logstore的名称甚至是动态计算或者外部第三方获取到的.

支持灵活富化

支持从本地资源, 外部资源(包括OSS, RDS, 日志服务Logstore)来获取, 支持从字典, 表格的常规映射到搜索表格的复杂映射. 外部资源加载支持自动刷新, snapshot

支持自定义UDF扩展

使用内置的200个函数(持续增加)基本可以完成大部分工作,因特殊场景, 不能满足需求的,可以提工单并获得及时支持。另一方面, 底层目前采用Python引擎,理论上任意Python的库稍加包装即可进入日志服务的数据加工,自定义UDF功能尚未全面开放,需提工单申请,

语言约束

ETL语言兼容Python, 但标准模式下约束了使用方式, 可以视为Python的子集. 除了基本的数据结构与表达方式外, 规则以函数方式进行编排.
白名单高级模式完全兼容Python, 标准模式与Python的语言区别如下:

类别 Python语法 标准模式支持 白名单高级模式
数据结构 数字, 字符串, 布尔 支持, 除"""形式字符串不支持, 参考数据结构 支持
数据结构 元祖, 列表, 集合, 字典 支持, 除集合set{1,2,3}不支持. 参考数据结构 支持
数据结构 对象定义 仅支持内置扩展数据结构,如表格, 日期时间对象等. 参考数据结构 支持
基本语法 操作符, 如加减乘除等 不直接支持, 需要通过函数方式支持, 参考基础语法 支持
基本语法 注释 支持, 参考基础语法 支持
基本语法 变量定义赋值 不支持, 需使用无状态方式调用传递 支持
函数 标准Python内置函数 不支持, 使用内置200+函数 支持
函数 函数调用 支持, 除解包调用不支持 支持
函数 自定义函数def或lambda 不支持, 提供200+事件与表达式函数, 且支持基于现有函数的自由组合调用 支持, 按照协议规范自由编写
模块 导入与使用Python标准库 不支持 支持
模块 线程与进程创建 不支持 支持
模块 导入第三方库 不支持 需提工单支持
模块 外部网络连接或命令调用 提供内置的资源连接器, 参考资源连接器 需提工单支持

函数式编排

标准模式的ETL语言是通过函数调用的方式完成编排的, 其工作原理可参考数据加工原理. 更具体的加工逻辑描述如下:
内置200+的函数, 主要分为2类:

全局操作函数

  • 接收事件, 处理并返回事件的函数, 且只有全局操作函数才能构建加工规则的每一步骤.

表达式函数

  • 通用型函数, 接收特定参数, 组合调用后作为传递给全局操作函数以定义更加灵活逻辑.

两者区别

函数类型 可做全局步骤 接受 返回 修改事件 可否组合调用
全局操作函数 事件 0到多条事件 大部分会 可以
表达式函数 各种数据(有些也接收事件) 特定数据结构 不会 可以

全局操作函数

接受事件并返回事件(单个, 列表或None)的函数. 除了全局函数外, 其他内置函数不能够放在每一个步骤的第一行.

一个ETL规则的形式如下:

全局函数1(..参数....)
全局函数2(..参数....)
全局函数3(..参数....)
全局函数4(..参数....)

子类

函数子类 描述 样例
流程控制函数 用于规则步骤流程控制, 接收事件, 基于条件做控制, 调用其他事件函数完成处理. e_if, e_switch, e_if_else
事件处理函数 对事件进行加工的主体函数, 会修改传递的事件.返回0到多条事件. e_drop_fields丢弃事件字段, e_kv提取并添加事件的键值对, e_dict_map做给事件做富化等

如下概览:

类型 函数 说明
流程控制 e_if 多个条件操作的配对操作
流程控制 e_if_else if-else操作
流程控制 e_switch 满足一个条件操作后跳出
流程控制 e_compose 组合
事件操作 e_drop 丢弃
事件操作 e_keep 保留
事件操作 e_split 分裂
事件操作 e_output 输出
事件操作 e_coutput 复制输出
字段操作 e_drop_fields 删除
字段操作 e_keep_fields 保留
字段操作 e_rename 重命名
字段值赋值 e_set 赋值
字段值提取 e_regex 正则提取
字段值提取 e_json json展开或提取
字段值提取 e_kv 自动提取键值对
字段值提取 e_kv_delimit 基于分隔符提取键值对
字段值提取 e_csv 逗号或其他分隔符提取
字段值提取 e_tsv tab分隔符提取
字段值提取 e_psv pipe分隔符提取
字段值提取 e_syslogrfc 根据syslog协议提取头
字段富化 e_dict_map 字典映射
字段富化 e_table_map 表格映射
字段富化 e_search_map 搜索映射
资源操作 res_local_update 设置任务参数上下文

加工逻辑样例

1. 基本处理

  • 默认加工框架会以流式方式读取源logstore的数据, 并将每一条日志事件以字典数据结构, 传递给加工规则中的逻辑, 每个设定的步骤都会顺序处理事件, 最终处理修改的事件, 会输出到默认的Logstore
  • 注意: 在传递的过程中事件的字段和值始终都是字符串形式.

    • 例如: e_set("f1", 200) 设置字段f1的值为200, 源事件: {"__time__": "1234567", "__topic__": "", "k1": "test"}, 经过这个函数处理后, 会变成: {"__time__": "1234567", "__topic__": "", "k1": "test", "f1": "200"}, 注意其中f1的值是"200"字符串形式.
  • 普通情况下: 规则中定义的每个事件函数会顺序执行, 每一个函数会对每个事件处理和修改, 返回一个修改的事件.

    • 例如e_set("type", "test")会对每个事件添加一个字段"type"值为"test", 下一个函数接收到的事件就是最新的.

2. 条件判断

  • 条件判断if: 某些步骤可以设定条件, 也就是不满足条件的事件会跳过本次操作. 相当于一个if的逻辑.

    • 例如e_if(e_match("status", "200"), e_regex("data", "ret: \d+", "result")), 会首先检查字段status是否为200, 不满足不会做任何操作. 如果满足, 则会对字段data用正则表达式提取出新字段result
  • 类似的e_if_else会进行if_else的操作.

3. 停止处理

  • 某些步骤可能返回0个事件, 表示删除事件, 例如e_if(str_islower(v("result")), e_drop()), 对每个字段result的值是小写字符串, 则丢弃这条事件. 这条事件被丢弃后, 后续的操作将不再进行. 自动重新开始下一条事件.
  • 输出事件可以视为一种特殊的停止处理, 例如e_output在提前输出事件到目标, 并删除事件, 其后续的操作也不会再进行.

    • 注意: 函数e_coutput会复制一份当前的事件输出, 并继续处理后续.

4. 分裂并行

  • 某些步骤也可能返回多个事件, 表示分裂事件, 例如e_split(data)表示, 根据字段data的值, 例如是"abc, xyz", 分裂成2条事件, 每条事件的字段data的值分别是abcxyz.
  • 分裂后的每条事件都会继续进行后续的步骤并最终输出(或被后续某一步删除).

接收事件并返回0-多条事件的函数, 可以作为全局操作步骤.

表达式函数

除了全局的操作函数外, ETL语言还提供了内置近200个表达式函数, 表达式的函数组合调用, 表达式函数接收特定参数, 返回特定值,一般是单个表达式函数或其调用组合,其形式如下:

全局操作1(表达式函数1(...), ....)
全局操作2(..., 表达式函数2(...), 表达式函数3(...),...)

子类

函数子类 描述 样例
事件检查函数 接收事件, 提取或检索返回特定信息的函数, 不会修改传递的事件. v返回事件字段的值, e_searche_match等返回事件是否符合特定条件等
资源函数 接受特定参数配置, 连接本地或外部资源, 返回数据, 一般是字典, 表格等类型. OSS, RDS, Logstore资源函数
控制函数 用于表达式逻辑操作, 接受特定参数, 基于条件做控制, 调用其他表达式函数返回. op_and, op_or, op_not, op_if, op_op_coalesce
一般表达式函数 表达式函数的主体, 接受固定或者其他函数的调用的结果, 返回特定的值. 字符串, 时间, 类型转换函数等

如下概览:

类型 函数 说明
事件检查函数 v, e_has, e_not_has, e_search, e_match, e_match_any, e_match_all等 获取事件字段值, 或判断字段或字段值是否符合特定内容
基础操作函数 部分op_* 函数 比较, 条件判断, 容器类计算, 一般性多值操作
转换函数 ct_*函数 数字,字符串,布尔之间的转换, 数字进制转换
算术函数 部分op_*函数, math_*函数等 数字的+-*/幂等计算, 数学计算, 多值计算等
字符串函数 str_*函数 字符串的所有相关操作与判断搜索等
日期时间函数 dt_*函数 Unix时间戳, 日期时间对象, 日期时间字符串转化, 时区调整, 圆整等
正则表达式函数 reg_*函数 正则提取, 检索, 替换, 分裂多值等
GROK函数 grok函数 提取grok模式返回对应正则表达式
JSON, XML, Protobuf函数 json_*, xml_*, pb_* 函数 对应提取或解析
编码解码类函数 url_*, html_*, md5_*, sha1_*, base64_*函数 相关单向或双向函数
列表函数 lst_*函数 列表相关构建,获取, 修改, 操作等
字典函数 dct_*函数 字典相关构建,获取, 修改, 操作等
资源函数 res_*函数 本地配置, RDS, Logstore等资源获取

基础语法

注释

规则中支持注释, 以便标记某些步骤的意思, 以#开头 或放在行尾巴均支持.

# 设置默认主题 (放在行首的注释)
e_set("__topic__", "access_log")   # 设置默认主题 (放在行尾的注释)

换行

一般函数调用的)是可以直接跨行的. 结构中存在,时, 在,的地方进行分隔也是可以的. 如果某个字符串过长需要换行时, 需要使用\表示上一行.

样例:

e_set("__topic__", "this is a very long long long .........." \
                              "......long text")

e_set("__topic__", "v1",
          "type", "v2",                # 逗号分隔的可以直接换行
          "length", 100)

函数调用方式

1. 基本调用方式:
e_set("abc", "xyz")
注意: 非命名参数一般都需要传递所有值.

2. 命名参数调用方式:
e_set("abc", "xyz", mode="fill")
注意:

  • 命名参数不传递, 会使用默认值. 某些情况有特殊要求. 参考每个函数的参数说明
  • 多个命名参数下, 可以根据选择传递, 顺序不要求:
    e_csv("data", ["f1", "f2", "f3"], sep='#', quote="|")

等价于
e_csv("data", ["f1", "f2", "f3"], quote="|", sep='#')

注意: 命名参数的顺序, 始终在非命名参数的后面.

3. 组合调用,

参数是其他函数的调用方式
e_set("abc", v("xyz"))
e_set("abc", str_lower(v("xyz")))

4. 变参
某些函数支持变参传递, 例如:
e_set("k1", "v1", "k2", "v2", ....)

带命名参数时, 命名参数放最后:
e_set("k1", "v1", "k2", "v2", ...., mode="fill")

5. 事件参数传递
注意到上面的函数并没有参数接受事件, 这点是默认的, 由框架自动传递.

操作符

标准模式下不支持操作符, 提供了对应函数达到一样效果.

场景操作 函数 样例
加 + op_add op_add(v("age"), 2)
减 - op_sub op_sub(v("age"), 2)
乘 * op_mul op_mul(v("size"), 2)
幂 ** op_pow op_pow(v("size"), 2)
整除 // op_div_floor op_div_floor(v("bytes"), 1024)
取模 % op_mod op_mod(v("age"), 10)
取负 - op_neg op_neg(v("profit"))
判断存在 in op_in op_in(["pass", "ok"], v("result"))
判断不存在 not in op_not_in op_in(["pass", "ok"], v("result"))
逻辑且 and op_and op_and(op_gt(v("age"), 18), op_lt(v("age"), 31))
逻辑或 or op_or op_or(op_le(v("age"), 18), op_gt(v("age"), 65))
逻辑否 not op_not op_not(op_gt(v("age"), 18))
判断等于 == op_eq op_eq(v("name"), "xiao ming")
判断不等于 != op_ne op_ne(v("name"), "xiao ming")
大于 > op_gt op_gt(ct_int(v("age")), )
大于等于 >= op_ge op_ge(ct_int(v("age")), 18)
小于 op_lt op_lt(ct_int(v("age")), 18)
小于等于 <= op_le op_le(ct_int(v("age")), 18)
字符串切片 [ ...] op_slice op_slice(v("message"), 0, 20)

例如, 赋值字段a为3600 * 6的话, 如下操作:


# * 
e_set("a", 3600 * 6)    # 非法
e_set("a", op_mul(3600, 6))    # 合法

# /
e_set("bytes_kb", v("bytes") / 1024)    # 非法
e_set("bytes_kb", op_div_floor(v("bytes"), 1024))    # 合法

真假判断

有一些函数会接收一个条件, 根据条件的值是True还是False来决定逻辑, 条件可以是一个固定值, 或者经过表达式返回的值.
ETL语言中, 条件判断并不要求对象一定是布尔类型的值, 也支持对任意类型值进行判断, 如下表格是各种类型的值的真假条件:

数据类型 True的条件 False的条件
布尔 True, true False, false
None - 总是False
数值 非0, 0.0 0, 0.0
字符串 非空 空串
字节 非空 空字节
元组 非空 空元组
列表 非空 空列表
字典 非空 空字典
表格 存在即为True 空对象(None)
日期时间 存在即为True 空对象(None)

以下样例, 会如注释时, 会丢弃事件

e_if(True, DROP)             # 总是
e_if(1, DROP)                # 总是
e_if(v("abc"), DROP)      # 存在字段abc, 且字段不为空时
e_if(str_isdigit(v("abc")), DROP) # 存在字段abc, 且字段的内容都是数字时

基本数据结构

整数

1, 2, 3, 4
例如, 用于值设置或者函数的参数传递, 例如:
e_set("f1", 100) 赋值字段f1的值为100

浮点

1.5, 2.3
例如:e_set("f1", 1.5) 赋值字段f1的值为1.5

字符串

  • "abc"等价于'abc' ,没有区别. 当字符串中包含"时, 可以'abc"xyz'这样. 避免使用\反转: "abc\"xyz"
  • "\\abc\\xyz", 相当于 \abc\xyz
  • r"\\10.64.1.1\share\folder" 所见即所得的字符串, 相当于\\10.64.1.1\share\folder

    • 常用于简化正则表达式修饰.
  • 多字节字符串以unicode表示, 和一般字符串一致. 例如 "中文" 的长度也是2.
  • 正则表达式也是以字符串形式表示.

注意: 函数e_search等接受的搜索字符串里面的字符串用双引号括起来, 不支持单引号.
e_search("domain: '/url/test.jsp'")是错误的, 只能e_search('domain: "/url/test.jsp"')

字节

b'abc'不同于字符串的内存编码形式. 某些特殊函数接收或者返回.

Nonenull, 表示无(不同于空字符串), 函数返回时表示空, 全局事件函数返回None表示删除. 许多函数也以None为命名参数的默认值.

列表

也叫数组: [1,2,3,4], 某些函数参数可能接收的是列表, 例如:
e_dict_map("dict data", ["f1", "f2", "f3"], ...)
某些函数在某些情况下返回的可能是列表, 例如: json_select选择了一个数组时.

元组(tuple)

(1, 2, 3, 4), 和列表功能一样, 某些函数参数可能接收的是元组.

字典

形式为{"key": "value", "k2": "v2", ...}的键值对组合, 其关键字一般是字符串, 其值可以是以上的各种形式. 事件是一种特殊的字典. 某些函数可能接受的特定格式的字典. 例如: {"key": [1,2, 3], "ke": {"k3": "va3"} }, 字典结构也应用于字典映射的输入数据.
存储格式以哈希方式, 关键字不能重复. 查找时无序.

布尔

True, False, true, false

表格

多列的表格结构, 可以从资源中加载多行CSV格式内容构建, 或者从RDS, Logstore等中加载多列数据获取. 主要用于映射(富化)或其他高级配置场景.

日期时间对象

表示日期时间的内存对象, 可以转换为Unix字符串或者格式化的时间字符串或者传递给其他dt_类函数进行进一步转换.

事件类型

基本类型

数据加工的日志的数据结构是以字典例如{"__topic__": "access_log", "content": "....."}表示.

  • 字典的关键字和值, 对应于日志的字段和值
  • 事件类的函数默认接受输入源的每个事件, 并返回一个事件,
  • 注意: 事件的关键字和值都是字符串. 进一步参考赋值自动转换
  • 注意: 关键字不能重复.

元字段

  • 时间字段: __time__: 是Unix时间戳的字符串

    • 其他也有主题: __topic__
    • 源: __source__

时间字段修改

  • 修改这个字段的值, 也就修改了日志的事件时间. 可以使用时间字符串对齐进行进一步的各种操作.
  • 删除了这个字段, 在输出日志时, 将会取当前时间戳作为新的事件的时间.

标记

  • 标记(tag), 日志存在标记, 区别于一般字段, 标记会以__tag__:名称的关键字格式存在.

    • 如果源logstore打开了服务器接收时间的日志, 则会存在tag: __tag__:__receive_time__
    • K8S的日志会存在许多容器类的tag, 例如: __tag__:__container_name__
    • 也可以添加修改tag, 例如添加一个tag名为"type":e_set("__tag__:type", "access_log")

赋值自动转换

事件的关键字和值都是字符串, 因此当对事件进行赋值或者设置新的字段值时, 会对结果进行自动字符串转换.
例如:

e_set("v1", 12.3)
e_set("v2", True)

会设置一个字段v1值为转换的字符串12.3和字段v2值为true.
设置None值时会被忽略.

类型 样例 转换类型 转换样例
整数 1 字符串 "1"
浮点 1.2 字符串 "1.0"
布尔 True 字符串 "true"
字节 b"123" 使用UTF8反转为字符串 "123"
元组 (1, 2, 3) json转换 "[1, 2, 3]"
列表 [1,2,3] json转换 "[1, 2, 3]"
字典 {"1":2, "3":4} json转换 "{"1": 2, "3": 4}"
日期时间 datetime(2018, 10, 10, 10, 10, 10) ISO格式转换 2018-10-10 10:10:10

固定标示

预定了一些固定表示, 以便简化代码或便于理解:

类型 标示 说明
布尔 true 真, 等价于True
布尔 false 假, 等价于False
None null 无, 等价于None
字符串 F_TAGS TAG字段正则表达式, 等价于"__tag__:.+"
字符串 F_META topic, source, TAG字段的正则表达式表示, 等价于`__tag__:.+ topic __source__`
字符串 F_TIME time字段的名称, 等价于__time__
字符串 F_PACK_META pack meta字段的正则表达式表示形式, 等价于`"__pack_meta__ __tag__:__pack_id__"`
字符串 F_RECEIVE_TIME 服务器接收时间的tag字段, 等价于"__tag__:__receive_time__"

JSON对象

一般指JSON表达式函数json_select或者json_parse解析提取后的对象, 并没有真正JSON对象, 本质上是上述基础数据结构的形式. 如下:

原始字符串 解析出的JSON对象 实际类型
1 1 整数
1.2 1.2 浮点
true True 布尔
false False 布尔
"abc" "abc" 字符串
null None None
["v1", "v2", "v3"] ["v1", "v2", "v3"] 列表
["v1", 3, 4.0] ["v1", 3, 4.0] 列表
{"v1": 100, "v2": "good"} {"v1": 100, "v2": "good"} 字典
{"v1": {"v11": 100, "v2": 200}, "v3": "good"} {"v1": {"v11": 100, "v2": 200}, "v3": "good"} 字典

进一步参考

欢迎扫码加入官方钉钉群获得实时更新与阿里云工程师的及时直接的支持:
image

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

分享:

阿里云存储基于飞天盘古2.0分布式存储系统,产品多种多样,充分满足用户数据存储和迁移上云需求。

官方博客
链接