概述
日志服务领域专用语言LOG DSL (Domain Specific Language)是日志服务数据加工使用的编排语言, 一种Python兼容的脚本语言. LOG DSL基于Python提供内置200个函数简化常见数据加工模式. 也支持用户自由定义的Python扩展(目前仅针对特定客户开放).
自由编排
LOG DSL通过一个Python兼容的DSL(领域专用语言)进行自由编排,对各种逻辑进行复杂组合, 可以满足大部分数据加工的需求和自由度.
例如, 可以自由编排达到如下一个场景:
完整的加工功能
支持近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
的值分别是abc
和xyz
. - 分裂后的每条事件都会继续进行后续的步骤并最终输出(或被后续某一步删除).
接收事件并返回0-多条事件的函数, 可以作为全局操作步骤.
表达式函数
除了全局的操作函数外, ETL语言还提供了内置近200个表达式函数, 表达式的函数组合调用, 表达式函数接收特定参数, 返回特定值,一般是单个表达式函数或其调用组合,其形式如下:
全局操作1(表达式函数1(...), ....)
全局操作2(..., 表达式函数2(...), 表达式函数3(...),...)
子类
函数子类 | 描述 | 样例 |
---|---|---|
事件检查函数 | 接收事件, 提取或检索返回特定信息的函数, 不会修改传递的事件. | v 返回事件字段的值, e_search 和e_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'
不同于字符串的内存编码形式. 某些特殊函数接收或者返回.
空
None
或 null
, 表示无(不同于空字符串), 函数返回时表示空, 全局事件函数返回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")
- 如果源logstore打开了服务器接收时间的日志, 则会存在tag:
赋值自动转换
事件的关键字和值都是字符串, 因此当对事件进行赋值或者设置新的字段值时, 会对结果进行自动字符串转换.
例如:
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"} |
字典 |
进一步参考
欢迎扫码加入官方钉钉群获得实时更新与阿里云工程师的及时直接的支持: