前言

Pipeline 编写较为麻烦,为此,DataKit 中内置了简单的调试工具,用以辅助大家来编写 Pipeline 脚本。

调试 grok 和 pipeline

指定 pipeline 脚本名称,输入一段文本即可判断提取是否成功

Pipeline 脚本必须放在 /pipeline 目录下。

$ datakit pipeline your_pipeline.p -T '2021-01-11T17:43:51.887+0800  DEBUG io  io/io.go:458  post cost 6.87021ms'
Extracted data(cost: 421.705µs): # 表示切割成功
{
"code" : "io/io.go: 458", # 对应代码位置
"level" : "DEBUG", # 对应日志等级
"module" : "io", # 对应代码模块
"msg" : "post cost 6.87021ms", # 纯日志内容
"time" : 1610358231887000000 # 日志时间(Unix 纳秒时间戳) "message": "2021-01-11T17:43:51.887+0800 DEBUG io io/io.g o:458 post cost 6.87021ms"
}

提取失败示例(只有 message 留下了,说明其它字段并未提取出来):

$ datakit pipeline other_pipeline.p -T '2021-01-11T17:43:51.887+0800  DEBUG io  io/io.g o:458  post cost 6.87021ms'
{
"message": "2021-01-11T17:43:51.887+0800 DEBUG io io/io.g o:458 post cost 6.87021ms"

如果调试文本比较复杂,可以将它们写入一个文件(sample.log),用如下方式调试:

$ datakit pipeline your_pipeline.p -F sample.log

更多 Pipeline 调试命令,参见 datakit help pipeline。

Grok 通配搜索

由于 Grok pattern 数量繁多,人工匹配较为麻烦。DataKit 提供了交互式的命令行工具 grokq(grok query):

datakit tool --grokq
grokq > Mon Jan 25 19:41:17 CST 2021 # 此处输入你希望匹配的文本
2 %{DATESTAMP_OTHER: ?} # 工具会给出对应对的建议,越靠前匹配月精确(权重也越大)。前面的数字表明权重。
0 %{GREEDYDATA: ?} grokq > 2021-01-25T18:37:22.016+0800
4 %{TIMESTAMP_ISO8601: ?} # 此处的 ? 表示你需要用一个字段来命名匹配到的文本
0 %{NOTSPACE: ?}
0 %{PROG: ?}
0 %{SYSLOGPROG: ?}
0 %{GREEDYDATA: ?} # 像 GREEDYDATA 这种范围很广的 pattern,权重都较低 # 权重越高,匹配的精确度越大
grokq > Q # Q 或 exit 退出
Bye!

Windows 下,请在 Powershell 中执行调试。

多行如何处理

在处理一些调用栈相关的日志时,由于其日志行数不固定,直接用 GREEDYDATA 这个 pattern 无法处理如下情况的日志:

 1 2022-02-10 16:27:36.116 ERROR 1629881 --- [scheduling-1] o.s.s.s.TaskUtils$LoggingErrorHandler    : Unexpected error occurred in scheduled task
2
3 java.lang.NullPointerException: null
4
5 at com.xxxxx.xxxxxxxxxxx.xxxxxxx.impl.SxxxUpSxxxxxxImpl.isSimilarPrize(xxxxxxxxxxxxxxxxx.java:442)
6
7 at com.xxxxx.xxxxxxxxxxx.xxxxxxx.impl.SxxxUpSxxxxxxImpl.lambda$getSimilarPrizeSnapUpDo$0(xxxxxxxxxxxxxxxxx.java:595)
8
9 at java.util.stream.ReferencePipeline$3$1.accept(xxxxxxxxxxxxxxxxx.java:193)
10
11 at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(xxxxxxxxx.java:1382)
12
13 at java.util.stream.AbstractPipeline.copyInto(xxxxxxxxxxxxxxxx.java:481)
14
15 at java.util.stream.AbstractPipeline.wrapAndCopyInto(xxxxxxxxxxxxxxxx.java:471)
16
17 at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(xxxxxxxxx.java:708)
18
19 at java.util.stream.AbstractPipeline.evaluate(xxxxxxxxxxxxxxxx.java:234)
20
21 at java.util.stream.ReferencePipeline.collect(xxxxxxxxxxxxxxxxx.java:499)
22
此处可以使用 GREEDYLINES 规则来通配,如(/usr/local/datakit/pipeline/test.p):
add_pattern('_dklog_date', '%{YEAR}-%{MONTHNUM}-%{MONTHDAY} %{HOUR}:%{MINUTE}:%{SECOND}%{INT}')
grok(_, '%{_dklog_date:log_time}\\s+%{LOGLEVEL:Level}\\s+%{NUMBER:Level_value}\\s+---\\s+\\[%{NOTSPACE:thread_name}\\]\\s+%{GREEDYDATA:Logger_name}\\s+(\\n)?(%{GREEDYLINES:stack_trace})' # 此处移除 message 字段便于调试
drop_origin_data()

将上述多行日志存为 multi-line.log,调试一下:

$ datakit --pl test.p --txt "$(<multi-line.log)" 

得到如下切割结果:

{
"Level": "ERROR", "Level_value": "1629881",
"Logger_name": "o.s.s.s.TaskUtils$LoggingErrorHandler : Unexpected error occurred in scheduled task",
"log_time": "2022-02-10 16:27:36.116",
"stack_trace": "java.lang.NullPointerException: null\n\tat com.xxxxx.xxxxxxxxxxx.xxxxxxx.impl.SxxxUpSxxxxxxImpl.isSimilarPrize(xxxxxxxxxxxxxxxxx.java:442)\n\tat com.xxxxx.xxxxxxxxxxx.xxxxxxx.impl.SxxxUpSxxxxxxImpl.lambda$getSimilarPrizeSnapUpDo$0(xxxxxxxxxxxxxxxxx.java:595)\n\tat java.util.stream.ReferencePipeline$3$1.accept(xxxxxxxxxxxxxxxxx.java:193)\n\tat java.util.ArrayList$ArrayListSpliterator.forEachRemaining(xxxxxxxxx.java:1382)\n\tat java.util.stream.AbstractPipeline.copyInto(xxxxxxxxxxxxxxxx.java:481)\n\tat java.util.stream.AbstractPipeline.wrapAndCopyInto(xxxxxxxxxxxxxxxx.java:471)\n\tat java.util.stream.ReduceOps$ReduceOp.evaluateSequential(xxxxxxxxx.java:708)\n\tat java.util.stream.AbstractPipeline.evaluate(xxxxxxxxxxxxxxxx.java:234)\n\tat java.util.stream.ReferencePipeline.collect(xxxxxxxxxxxxxxxxx.java:499)", "thread_name": "scheduling-1"
}

Pipeline 字段命名注意事项

在所有 Pipeline 切割出来的字段中,它们都是指标(field)而不是标签(tag)。由于行协议约束,我们不应该切割出任何跟 tag 同名的字段。这些 Tag 包含如下几类:

  • DataKit 中的全局 Tag

  • 日志采集器中自定义的 Tag

另外,所有采集上来的日志,均存在如下多个保留字段。我们不应该去覆盖这些字段,否则可能导致数据在查看器页面显示不正常。

字段名 类型 说明
source string(tag) 日志来源
service string(tag) 日志对应的服务,默认跟 service 一样
status string(tag) 日志对应的等级
message string(field) 原始日志
time int 日志对应的时间戳

当然我们可以通过特定的 Pipeline 函数覆盖上面这些 tag 的值。

一旦 Pipeline 切割出来的字段跟已有 Tag 重名(大小写敏感),都会导致如下数据报错。故建议在 Pipeline 切割中,绕开这些字段命名。

# 该错误在 DataKit monitor 中能看到
same key xxx in tag and field

完整 Pipeline 示例

这里以 DataKit 自身的日志切割为例。DataKit 自身的日志形式如下:

2021-01-11T17:43:51.887+0800  DEBUG io  io/io.go:458  post cost 6.87021ms 

编写对应 pipeline:

# pipeline for datakit log
# Mon Jan 11 10:42:41 CST 2021
# auth: tanb grok(_, '%{_dklog_date:log_time}%{SPACE}%{_dklog_level:level}%{SPACE}%{_dklog_mod:module}%{SPACE}%{_dklog_source_file:code}%{SPACE}%{_dklog_msg:msg}')
rename("time", log_time) # 将 log_time 重名命名为 time
default_time(time) # 将 time 字段作为输出数据的时间戳
drop_origin_data() # 丢弃原始日志文本(不建议这么做)

这里引用了几个用户自定义的 pattern,如 _dklog_date、_dklog_level。我们将这些规则存放 <datakit安装目录>/pipeline/pattern 下。

注意,用户自定义 pattern 如果需要==全局生效==(即在其它 Pipeline 脚本中应用),必须放置在 <DataKit安装目录/pipeline/pattern/> 目录下):

$ cat pipeline/pattern/datakit
# 注意:自定义的这些 pattern,命名最好加上特定的前缀,以免跟内置的命名冲突(内置 pattern 名称不允许覆盖)
# 自定义 pattern 格式为:
# <pattern-name><空格><具体 pattern 组合>
_dklog_date %{YEAR}-%{MONTHNUM}-%{MONTHDAY}T%{HOUR}:%{MINUTE}:%{SECOND}%{INT}
_dklog_level (DEBUG|INFO|WARN|ERROR|FATAL)
_dklog_mod %{WORD}
_dklog_source_file (/?[\w_%!$@:.,-]?/?)(\S+)?
_dklog_msg %{GREEDYDATA}

现在 pipeline 以及其引用的 pattern 都有了,就能通过 DataKit 内置的 pipeline 调试工具,对这一行日志进行切割:

# 提取成功示例
$ ./datakit --pl dklog_pl.p --txt '2021-01-11T17:43:51.887+0800 DEBUG io io/io.go:458 post cost 6.87021ms'
Extracted data(cost: 421.705µs):
{
"code": "io/io.go:458",
"level": "DEBUG",
"module": "io",
"msg": "post cost 6.87021ms",
"time": 1610358231887000000
}

FAQ

Pipeline 调试时,为什么变量无法引用?

Pipeline 为:

json(_, message, "message")
json(_, thread_name, "thread")
json(_, level, "status")
json(_, @timestamp, "time")

其报错如下:

[E] new piepline failed: 4:8 parse error: unexpected character: '@' 

A: 对于有特殊字符的变量,需将其用两个 ` 修饰一下:

json(_, `@timestamp`, "time") 

参见【 Pipeline 的基本语法规则 】https://docs.guance.com/developers/pipeline/#basic-syntax

Pipeline 调试时,为什么找不到对应的 Pipeline 脚本?

命令如下:

$ datakit pipeline test.p -T "..."
[E] get pipeline failed: stat /usr/local/datakit/pipeline/test.p: no such file or directory

A: 调试用的 Pipeline 脚本,需将其放置到 /pipeline 目录下。

如何在一个 Pipeline 中切割多种不同格式的日志?

在日常的日志中,因为业务的不同,日志会呈现出多种形态,此时,需写多个 Grok 切割,为提高 Grok 的运行效率,可根据日志出现的频率高低,优先匹配出现频率更高的那个 Grok,这样,大概率日志在前面几个 Grok 中就匹配上了,避免了无效的匹配。

在日志切割中,Grok 匹配是性能开销最大的部分,故避免重复的 Grok 匹配,能极大的提高 Grok 的切割性能。

grok(_, "%{NOTSPACE:client_ip} %{NOTSPACE:http_ident} ...")
if client_ip != nil {
# 证明此时上面的 grok 已经匹配上了,那么就按照该日志来继续后续处理
...
} else {
# 这里说明是不同的日志来了,上面的 grok 没有匹配上当前的日志
grok(_, "%{date2:time} \\[%{LOGLEVEL:status}\\] %{GREEDYDATA:msg} ...") if status != nil {
# 此处可再检查上面的 grok 是否匹配上...
} else {
# 未识别的日志,或者,在此可再加一个 grok 来处理,如此层层递进
}
}

如何丢弃字段切割

在某些情况下,我们需要的只是日志==中间的几个字段==,但不好跳过前面的部分,比如

200 356 1 0 44 30032 other messages

其中,我们只需要 44 这个值,它可能代码响应延迟,那么可以这样切割(即 Grok 中不附带 :some_field 这个部分):

grok(_, "%{INT} %{INT} %{INT} %{INT:response_time} %{GREEDYDATA}") 

add_pattern() 转义问题

大家在使用 add_pattern() 添加局部模式时,容易陷入转义问题,比如如下这个 pattern(用来通配文件路径以及文件名):

(/?[\w_%!$@:.,-]?/?)(\S+)? 

如果我们将其放到全局 pattern 目录下(即 pipeline/pattern 目录),可这么写:

# my-testsource_file (/?[\w_%!$@:.,-]?/?)(\S+)?

  

如果使用 add_pattern(),就需写成这样:

# my-test.padd_pattern('source_file', '(/?[\\w_%!$@:.,-]?/?)(\\S+)?')

即这里面反斜杠需要转义。

如何编写 Pipeline 脚本的更多相关文章

  1. 让Jenkins执行GitHub上的pipeline脚本

    本文是<Jenkins流水线(pipeline)实战>系列的第二篇,上一篇搭建好了Jenkins环境并执行了一个简单的pipeline任务,当时我们直接在Jenkins网页上编写pipel ...

  2. 编写shell脚本遇到的问题

    运行shell脚本提示“syntax error near unexpected token for((i=0;i<$length;i++))”: 原因是因为Linux下的换行符是 \n 而你在 ...

  3. linux 使用文本编辑器编写shell脚本执行权限不够

    在linux下,自己编写的脚本需要执行的时候,需要加上执行的权限 解决方式:chmod 777 test.sh

  4. Gradle 1.12 翻译——第十三章 编写构建脚本

    有关其它已翻译的章节请关注Github上的项目:https://github.com/msdx/gradledoc/tree/1.12,或訪问:http://gradledoc.qiniudn.com ...

  5. LoadRunner利用ODBC编写MySql脚本

    最近做了几周的LoadRunner测试,有一些心得,记录下来,以便以后查找. LoadRunner测试数据库是模拟客户端去连接数据库服务器,因此,需要协议(或者说驱动的支持).LoadRunner本身 ...

  6. 实践作业2:黑盒测试实践——编写自动化脚本并拍摄测试过程视频 Day 6

    下午下课之后小组成员一起交流了一下实验过程遇到的一些问题,并汇总了下各个项目完成情况 该实验目前(写博客是时间)基本完成,具体情况如下 (1)分析系统需求 .(done) (2)设计测试用例.(don ...

  7. 使用Python的requests模块编写请求脚本

    requests模块可用来编写请求脚本. 比如,使用requests的post函数可以模拟post请求: resp = requests.post(url, data = content) url即为 ...

  8. 在windows下编写shell脚本

    注意两点: 1.第一行:#!/bin/bash 2.将文档格式转换为unix,因为在windows下编写shell脚本回车符是\n\r,而linux下的回车符是\n,所以在linux下运行脚本的时候, ...

  9. linux 的基本操作(编写shell 脚本)

    终于到shell 脚本这章了,在以前笔者卖了好多关子说shell脚本怎么怎么重要,确实shell脚本在linux系统管理员的运维工作中非常非常重要.下面笔者就带你正式进入shell脚本的世界吧. 到现 ...

随机推荐

  1. 运行 vue 项目时报错

    INFO Starting development server... ERROR Error: C - D:\T32890\Desktop\my-project\node_modules\@vue\ ...

  2. 5.27 NOI 模拟

    \(T1\)约定 比较水的\(dp\)题 上午想到了用区间\(dp\)求解,复杂度\(O(n^5),\)貌似没开\(long\ long\)就爆掉了 正解还是比较好想的,直接枚举从何时互不影响然后转移 ...

  3. mybatis 05: mybatis中的动态代理

    mybatis中动态代理存在的意义 图示 图示分析 分层说明:界面层为第一层,业务逻辑层(接口层 + 实现层)为第二层,数据访问层(接口层 + 实现层)为第三层 业务逻辑层和数据访问层:分别分两层来开 ...

  4. 【喜讯】Apache DolphinScheduler 荣获 “2020 年度十大开源新锐项目”

    经 10000+ 开发者公开票选,20+专家评审. 10+ 主编团打分,历经数月打磨,11 月 19 日,由InfoQ 发起并组织的[2020中国技术力量年度榜单评选]结果正式揭晓. 2020 年度十 ...

  5. python包合集-cffi

    一.cffi cffi是连接Python与c的桥梁,可实现在Python中调用c文件.cffi为c语言的外部接口,在Python中使用该接口可以实现在Python中使用外部c文件的数据结构及函数. 二 ...

  6. java-异常处理和线程的一些简单方法及使用

    1.1 子类重写父类含有throws声明异常抛出的方法时的规则: 1.允许不再抛出任何异常. 2.仅抛出部分异常. 3.抛出父类方法抛出异常的子类型异常. 4.不可以抛出额外异常. 5.不能抛出父类方 ...

  7. 定语从句关系代词只能用 that 的情况

    当先行词被形容词最高级.序数词,以及 the only.the very.the right 等修饰时,关系代词只能用 that. This is the most interesting movie ...

  8. jsp获取多选框组件的值

    jsp获取多选框组件的值 1.首先写一个带有多选框的前台页 1 <%@ page language="java" contentType="text/html; c ...

  9. 爬虫简介与excel表格操作

    爬虫简介与excel表格操作 re模块简介 1.在python中使用正则表达式的话那么re模块就是选择之一 import re # 导入re模块 2.在re模块中使用findall找到所有我们给他的值 ...

  10. 造序列(构造,DP)

    题面 Sample Input 7 8 7 10 31 20 100 869120 Sample Output 6 1 1 4 5 1 4 7 1 9 1 9 8 1 0 8 1 9 4 9 1 0 ...