Nginx日志加工分析

本文涉及的产品
对象存储 OSS,20GB 3个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 介绍如何使用阿里云日志服务-数据加工处理Nginx日志,帮助后续分析、可视化以及报警。

数据加工服务简介

数据加工服务是阿里云SLS推出的面向日志ETL处理的服务,主要解决数据加工过程中转换、过滤、分发、富化等场景。

img1.png
接下来,我们以nginx日志解析为例, 帮助大家快速入门阿里云日志服务的数据加工。

用于实验的Nginx日志

假设我们通过极简模式采集了Nginx默认日志。默认的nginx 日志format如下

log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                     '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

通过使用极简模式采集Nginx日志,样例如下:
img2.png

数据加工交互界面

点击 准备数据加工的Logstore,在查询栏的上方,有一个“数据加工”的开关,打开
img3.png

数据加工的代码编辑、预览、发布相关界面如下图:
img4.png

一般情况下,我们都可以使用快速预览模式,如果我们的数据加工涉及到使用mysql、oss等维表,可以使用高级预览做实际预览测试。

对Nginx日志进行数据加工 (Step by Step)

Step1. 使用正则抽取基础字段

对于极简模式采集的日志,内容都在一个字段叫content的字段里,不利于我们做分析。可以通过数据加工正则函数,抽取nginx日志里的字段,使用到的加工函数如下:

# 用于将源字段里的内容,通过正则捕获组抽取出对应的字段
e_regex("源字段", "正则或有命名捕获正则", "目标字段名或数组(可选)")

针对Nginx日志,使用以下语句进行正则抽取

# 通用字段抽取
e_regex("content",'(?<remote_addr>[0-9:\.]*) - (?<remote_user>[a-zA-Z0-9\-_]*) \[(?<local_time>[a-zA-Z0-9\/ :\-\+]*)\] "(?<request>[^"]*)" (?<status>[0-9]*) (?<body_bytes_sent>[0-9\-]*) "(?<refer>[^"]*)" "(?<http_user_agent>[^"]*)"')

通过正则抽取以后,可以看到日志的字段增加了refer、remote_addr、remote_user、request等字段。

img5.png

Step2. 处理时间字段

当前提取到的localtime不易读,我们把它解析成易读的格式,会用到的以下数据加工函数:

# 用于设置字段值
e_set("字段名", "固定值或表达式函数")

# 将时间字符串解析为日期时间对象
dt_strptime('值如v("字段名")', "格式化字符串")

# 将日期时间对象按照指定格式转换为字符串
dt_strftime(日期时间表达式, "格式化字符串")

实现思路,先通过 dt_strptime 将local_time的时间转化为日期时间对象,然后再通过dt_strftime将日期时间对象转化为标准的日期时间字符串。 针对Nginx local_time的转化,使用如下数据加工语句:

e_set("local_time", dt_strftime(dt_strptime(v("local_time"),"%d/%b/%Y:%H:%M:%S %z"),"%Y-%m-%d %H:%M:%S"))

实现效果如下:
img6.png

Step3. 解析request uri

可以看到request字段由 METHOD URI VERSION组成,我们希望对 requst字段进行抽取,获取到请求的METHOD、URI以及VERSION,并且将请求URI中的请求的参数变成字段,方便后续进行查询。可以用以下函数来做实现

# 使用正则将request uri抽取
e_regex("源字段名", "正则或有命名捕获正则", "目标字段名或数组(可选)", mode="fill-auto")

# 进行urldecode
url_decoding('值如v("字段名")’)

# 设置字段值
e_set("字段名", "固定值或表达式函数", ..., mode="overwrite")

# 将request_uri中的key=value的组合抽取成字段 值的模式
e_kv("源字段正则或列表", sep="=", prefix="")

实现语句

e_regex("request", "(?<request_method>[^\s]*) (?<request_uri>[^\s]*) (?<http_version>[^\s]*)")
e_set("request_uri", url_decoding(v("request_uri")))
e_kv("request_uri", prefix="uri_")

实现效果
img7.png

Step4. http code状态码映射

每一个http状态码都代表了不同的含义,下面是一份http状态码的映射表, 我们可以通过e_table_map 函数来将状态码的信息扩展到我们的日志字段中,方便后续做统计分析。
img8.png

涉及到的数据加工函数如下:

# 用来做字段富化,类似sql里join的功能
e_table_map("表格如通过tab_parse_csv(...)解析得到的",
            "源字段列表或映射列表如[('f1', 'f1_new'), ('f2', 'f2_new')]", 
            "目标字段列表")

# 用来把csv文件解析成table对象
tab_parse_csv(CSV文本数据, sep=',', quote='"')

# code的映射关系维度表是一个csv文件,存在oss上,使用res_oss_file
res_oss_file(endpoint="OSS的endpoint", ak_id="OSS的AK_ID", 
             ak_key="OSS的AK_KEY", bucket="OSS的bucket", file="在OSS中存的文件地址", 
             change_detect_interval="定时更新时间,默认为0")

实际使用到的DSL语句如下

# http状态码映射
e_table_map(
      tab_parse_csv(
           res_oss_file(endpoint="oss-cn-shanghai.aliyuncs.com",
              ak_id='',ak_key='',
              bucket="etl-test", 
              file="http_code.csv", format='text')),
              [("status","code")],
              [("alias","http_code_alias"),
               ("description","http_code_desc"),
               ("category","http_code_category")])

看一下映射后的效果
img9.png

Step5. 通过UserAgent判断客户端操作系统

我们想了解客户用的是什么os版本,可以通过user agent里的字段用正则匹配来判断,用到dsl语句

# 取某个字段的值
v("字段名")

# 获取ua相关信息
ua_parse_all("带useragent信息的内容")

# 展开json字段, 因为ua_parse_all得到的是一个json对象,为了展开到一级字段使用e_json做展开
# 模式有 simple、full、parent、root 参考https://help.aliyun.com/document_detail/125488.html#section-o7x-7rl-2qh
e_json("源字段名", fmt="模式", sep="字段分隔符")

# 丢弃临时产生的字段
e_drop_fields("字段1", "字段2")

用到的dsl语句

# 通过User Agent解析获得客户端信息
e_set("ua",ua_parse_all(v("http_user_agent")))
e_json("ua", fmt='full',sep='_')
e_drop_fields("ua",regex=False)

加工效果
img10.png

Step6. 非200的日志投递到指定logstore

可以使用e_output函数来做日志投递,用regex_match做字段匹配

# 条件判断if
e_if("条件1如e_match(...)", "操作1如e_regex(...)", "条件2", "操作2", ....)

# 判断是否相等
op_ne(v("字段名1"), v("字段名2"))

# output发送到目标名称,目标名称在数据加工保存任务的时候配置对应的logstore信息
e_output(name="指定的目标名称")

实际的dsl语句

# 分发非200的日志
e_if(op_ne(v("http_code_alias"),"2xx"), e_output(name="nginx-log-bad"))

在预览里看到这个效果。(保存加工的时候,需要设置好对应project、logstore的ak信息)
img11.png

完整的DSL代码以及上线流程

好了,通过一步一步的开发调试,现得到完整的DSL代码如下

# 通用字段抽取
e_regex("content",'(?<remote_addr>[0-9:\.]*) - (?<remote_user>[a-zA-Z0-9\-_]*) \[(?<local_time>[a-zA-Z0-9\/ :\-]*)\] "(?<request>[^"]*)" (?<status>[0-9]*) (?<body_bytes_sent>[0-9\-]*) "(?<refer>[^"]*)" "(?<http_user_agent>[^"]*)"')

# 设置localttime
e_set("local_time", dt_strftime(dt_strptime(v("local_time"),"%d/%b/%Y:%H:%M:%S"),"%Y-%m-%d %H:%M:%S"))

# uri字段抽取
e_regex("request", "(?<request_method>[^\s]*) (?<request_uri>[^\s]*) (?<http_version>[^\s]*)")
e_set("request_uri", url_decoding(v("request_uri")))
e_kv("request_uri", prefix="uri_")

# http状态码映射
e_table_map(
      tab_parse_csv(
           res_oss_file(endpoint="oss-cn-shanghai.aliyuncs.com",
              ak_id='',ak_key='',
              bucket="etl-test", 
              file="http_code.csv", format='text')),
              [("status","code")],
              [("alias","http_code_alias"),
               ("description","http_code_desc"),
               ("category","http_code_category")])

# 通过User Agent解析获得客户端信息
e_set("ua",ua_parse_all(v("http_user_agent")))
e_json("ua", fmt='full',sep='_')
e_drop_fields("ua",regex=False)

# 分发非200的日志
e_if(op_ne(v("http_code_alias"),"2xx"), e_coutput(name="nginx-log-bad"))

在页面提交代码以后,保存数据加工

配置目标logstore信息,默认走完加工逻辑的日志都会发送到第一个目标logstore,我们在代码里指定了e_output到指定logstore,因此还需要第二个目标,并且目标的名字和e_output里指定的目标名称一致。

img12.png

保存完即完成上线,可以在数据处理-加工下看到该任务,点击进去可以看到加工延迟等信息。

img13.png

Nginx日志查询、可视化、报警

查询

假设,我们想对nginx异常的日志进行查询,以便做问题定位。我们可以针对需要查询的字段开启索引
img14.png

开启索引可以使用“自动生成索引”来自动帮助生成字段索引列表(否则需要手工填写字段)
img15.png

点击确定后,即完成索引配置。比如我们要查询 uri_item_name参数值为“测试商品_89”的错误情况,可以使用这样的query

* and uri_item_name : 测试商品_89

img16.png

可视化

我们关心每个请求的http请求是否成功,对于异常状态码,想做相应的统计,以方便我们做问题发现。 可以使用如下query统计 nginx-log-bad(非2xx类请求的logstore)状态码分布

* | select http_code_alias,count(*) as c 
         from log where http_code_alias is not null 
             group by http_code_alias order by c

使用饼图做可视化
img17.png

点击,“添加到仪表盘”,即可完成仪表盘的创建
img18.png

添加完成后,在左侧仪表盘就
img19.png

报警

针对非2xx的状态码,我们不仅要做可视化,还需要对它进行报警。 可以在刚才创建的仪表盘里,点击“告警->创建”,根据提示创建相应的告警规则。
img20.png

img21.png
img30.jpg

总结

本次训练营,以最常见的Nginx日志作为例,介绍数据加工在日志的转化、处理、富化、转发等场景下相关的算子使用,帮助大家入门SLS的数据加工。除了文中提到的算子,数据加工还有200+的算子以支持更多的日志处理的场景,相关内容可以参考如下:
a) SLS数据加工整体简介:https://help.aliyun.com/document_detail/125384.html
b) SLS数据加工函数总览:https://help.aliyun.com/document_detail/159702.html
在数据加工之后,针对相关字段建立索引,可以方便地对日志内容进行各种场景的分析,并且针对有需要的场景设置对应的报警,提升系统的稳定性。

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
目录
相关文章
|
1月前
|
存储 SQL 监控
|
1月前
|
运维 监控 安全
|
1月前
|
监控 关系型数据库 MySQL
分析慢查询日志
【10月更文挑战第29天】分析慢查询日志
38 3
|
1月前
|
监控 关系型数据库 数据库
怎样分析慢查询日志?
【10月更文挑战第29天】怎样分析慢查询日志?
35 2
|
2月前
|
存储 缓存 关系型数据库
MySQL事务日志-Redo Log工作原理分析
事务的隔离性和原子性分别通过锁和事务日志实现,而持久性则依赖于事务日志中的`Redo Log`。在MySQL中,`Redo Log`确保已提交事务的数据能持久保存,即使系统崩溃也能通过重做日志恢复数据。其工作原理是记录数据在内存中的更改,待事务提交时写入磁盘。此外,`Redo Log`采用简单的物理日志格式和高效的顺序IO,确保快速提交。通过不同的落盘策略,可在性能和安全性之间做出权衡。
1674 14
|
2月前
|
存储 消息中间件 大数据
大数据-69 Kafka 高级特性 物理存储 实机查看分析 日志存储一篇详解
大数据-69 Kafka 高级特性 物理存储 实机查看分析 日志存储一篇详解
42 4
|
2月前
|
SQL 分布式计算 Hadoop
Hadoop-19 Flume Agent批量采集数据到HDFS集群 监听Hive的日志 操作则把记录写入到HDFS 方便后续分析
Hadoop-19 Flume Agent批量采集数据到HDFS集群 监听Hive的日志 操作则把记录写入到HDFS 方便后续分析
48 2
|
3月前
|
缓存 监控 算法
分析慢日志文件来优化 PHP 脚本的性能
分析慢日志文件来优化 PHP 脚本的性能
08-06-06>pe_xscan 精简log分析代码 速度提升一倍
08-06-06>pe_xscan 精简log分析代码 速度提升一倍
|
4月前
|
应用服务中间件 nginx
nginx error日志 client intended to send too large body: 1434541 bytes 如何处理?
【8月更文挑战第27天】nginx error日志 client intended to send too large body: 1434541 bytes 如何处理?
335 6