#log #log-file #sql-query #log-parser #query #sql #log-format

app logq

具有 SQL 接口的 Web 服务器日志文件命令行工具包

20 个版本

0.1.19 2021 年 9 月 23 日
0.1.18 2021 年 1 月 4 日
0.1.15 2020 年 12 月 31 日
0.1.11 2020 年 1 月 22 日
0.1.9 2019 年 8 月 22 日

#1693命令行工具

Download history 129/week @ 2024-03-31

每月 56 次下载

Apache-2.0 或 BSD-3-Clause

315KB
7K SLoC

logq - 使用 Rust 实现的命令行工具分析 PartiQL 中的日志文件

Build Status codecov

此项目处于 alpha 阶段,欢迎 PR。

logq 是一个命令行工具,可轻松通过 PartiQL(兼容 SQL-92)接口分析、查询和聚合 Web 服务器日志文件。目前支持以下格式:

  1. AWS 经典弹性负载均衡器
  2. AWS 应用负载均衡器
  3. AWS S3 访问日志(初步支持)
  4. Squid 原生格式(初步支持)

未来将支持更多日志格式,并理想情况下可以通过配置进行自定义,如 GoAccess 所做的那样。

安装

cargo install logq

查询平面日志的示例

从日志文件中投影 timestampbackend_and_port 字段,并打印前三个记录。

> logq query 'select timestamp, backend_processing_time from it order by timestamp asc limit 3' --table it:elb=data/AWSELB.log

+-----------------------------------+----------+
| 2015-11-07 18:45:33.007671 +00:00 | 0.618779 |
+-----------------------------------+----------+
| 2015-11-07 18:45:33.054086 +00:00 | 0.654135 |
+-----------------------------------+----------+
| 2015-11-07 18:45:33.094266 +00:00 | 0.506634 |
+-----------------------------------+----------+

在 5 秒时间内汇总发送的字节数。

> logq query 'select t, sum(sent_bytes) as s from it group by time_bucket("5 seconds", timestamp) as t' --table it:elb=data/AWSELB.log
+----------------------------+----------+
| 2015-11-07 18:45:30 +00:00 | 12256229 |
+----------------------------+----------+
| 2015-11-07 18:45:35 +00:00 | 33148328 |
+----------------------------+----------+

以 5 秒为时间范围选择 90% 分位数后端处理时间。

> logq query 'select t, percentile_disc(0.9) within group (order by backend_processing_time asc) as bps from it group by time_bucket("5 seconds", timestamp) as t' --table it:elb=data/AWSELB.log
+----------------------------+----------+
| 2015-11-07 18:45:30 +00:00 | 0.112312 |
+----------------------------+----------+
| 2015-11-07 18:45:35 +00:00 | 0.088791 |
+----------------------------+----------+

要折叠 URL 路径部分,以便它们映射到相同的 Restful 处理器,可以使用 url_path_bucket

> logq query 'select time_bucket("5 seconds", timestamp) as t, url_path_bucket(request, 1, "_") as s from it limit 10' --table it:elb=data/AWSELB.log
+----------------------------+----------------------------------------------+
| 2015-11-07 18:45:30 +00:00 | /                                            |
+----------------------------+----------------------------------------------+
| 2015-11-07 18:45:30 +00:00 | /img/_/000000000000000000000000              |
+----------------------------+----------------------------------------------+
| 2015-11-07 18:45:30 +00:00 | /favicons/_                                  |
+----------------------------+----------------------------------------------+
| 2015-11-07 18:45:30 +00:00 | /images/_/devices.png                        |
+----------------------------+----------------------------------------------+
| 2015-11-07 18:45:30 +00:00 | /stylesheets/_/font-awesome.css              |
+----------------------------+----------------------------------------------+
| 2015-11-07 18:45:30 +00:00 | /favicons/_                                  |
+----------------------------+----------------------------------------------+
| 2015-11-07 18:45:30 +00:00 | /mobile/_/register-push                      |
+----------------------------+----------------------------------------------+
| 2015-11-07 18:45:30 +00:00 | /img/_/205/2r1/562e37d9208bee5b70f56836.anim |
+----------------------------+----------------------------------------------+
| 2015-11-07 18:45:30 +00:00 | /img/_/300/2r0/54558148eab71c6c2517f1d9.jpg  |
+----------------------------+----------------------------------------------+
| 2015-11-07 18:45:30 +00:00 | /                                            |
+----------------------------+----------------------------------------------+

以不同的格式输出,您可以通过 --output 指定格式,目前支持 jsoncsv

> logq query --output csv 'select t, sum(sent_bytes) as s from it group by time_bucket("5 seconds", timestamp) as t' --table it:elb=data/AWSELB.log
2015-11-07 18:45:35 +00:00,33148328
2015-11-07 18:45:30 +00:00,12256229
> logq query --output json 'select t, sum(sent_bytes) as s from it group by time_bucket("5 seconds", timestamp) as t' --table it:elb=data/AWSELB.log
[{"t":"2015-11-07 18:45:30 +00:00","s":12256229},{"t":"2015-11-07 18:45:35 +00:00","s":33148328}]

您可以使用图形命令行工具在终端中图形化数据集。例如,termgraph 是柱状图的不错选择

> logq query --output csv 'select backend_and_port, sum(sent_bytes) from it group by backend_and_port' --table it:elb=data/AWSELB.log | termgraph

10.0.2.143:80: ▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 20014156.00
10.0.0.215:80: ▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 25390392.00

或者您可以使用 spark 绘制随时间变化的处理时间

> logq query --output csv 'select host_name(backend_and_port) as h, backend_processing_time from it where h = "10.0.2.143"' --table it:elb=data/AWSELB.log | cut -d, -f2 | spark
▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁██▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

如果您不清楚执行过程,可以解释查询计划。

> logq explain 'select t, sum(sent_bytes) as s from it group by time_bucket("5 seconds", timestamp) as t'
Query Plan:
GroupBy(["t"], [NamedAggregate { aggregate: Sum(SumAggregate { sums: {} }, Expression(Variable("sent_bytes"), Some("sent_bytes"))), name_opt: Some("s") }], Map([Expression(Function("time_bucket", [Expression(Variable("const_000000000"), None), Expression(Variable("timestamp"), Some("timestamp"))]), Some("t")), Expression(Variable("sent_bytes"), Some("sent_bytes"))], DataSource(Stdin)))

要了解字段,请参阅表模式。

> logq schema elb
+--------------------------+-------------+
| timestamp                | DateTime    |
+--------------------------+-------------+
| elbname                  | String      |
+--------------------------+-------------+
| client_and_port          | Host        |
+--------------------------+-------------+
| backend_and_port         | Host        |
+--------------------------+-------------+
| request_processing_time  | Float       |
+--------------------------+-------------+
| backend_processing_time  | Float       |
+--------------------------+-------------+
| response_processing_time | Float       |
+--------------------------+-------------+
| elb_status_code          | String      |
+--------------------------+-------------+
| backend_status_code      | String      |
+--------------------------+-------------+
| received_bytes           | Integral    |
+--------------------------+-------------+
| sent_bytes               | Integral    |
+--------------------------+-------------+
| request                  | HttpRequest |
+--------------------------+-------------+
| user_agent               | String      |
+--------------------------+-------------+
| ssl_cipher               | String      |
+--------------------------+-------------+
| ssl_protocol             | String      |
+--------------------------+-------------+
| target_group_arn         | String      |
+--------------------------+-------------+
| trace_id                 | String      |
+--------------------------+-------------+

要了解当前支持的日志格式。

> logq schema 
The supported log format
* elb

查询嵌套 jsonl 日志的示例

对于这种 jsonl 格式

{"a": 1, "b": "123", "c": 456.1, "d": [0, 1, 2], "e": {"f": {"g": 1}}}
{"a": 1, "b": "123", "d": [1, 2, 3], "e": {"f": {"g": 2}}}
{"a": 1, "b": "456", "d": [4, 5, 6], "e": {"f": {"g": 3}}}

我们可以像这样查询日志

logq run query 'select x, count(*) as x from it group by d[0] as x' --table it:jsonl=data/structured.log --output=json
[{"x":1},{"x":1},{"x":1}]
logq run query 'select b, e.f.g from it' --table it:jsonl=data/structured.log --output=json
[{"b":"123","g":1},{"b":"123","g":2},{"b":"456","g":3}]

可用函数

函数名称 描述 输入类型 输出类型
url_host 从请求中检索主机 请求 字符串
url_port 从请求中检索端口 请求 字符串
url_path 从请求中检索路径 请求 字符串
url_fragment 从请求中检索片段 请求 字符串
url_query 从请求中检索查询字符串 请求 字符串
url_path_segments 从请求中检索路径段 请求 字符串
url_path_bucket 将路径段映射到给定的字符串 请求,整数,字符串 字符串
time_bucket 将时间戳放入给定的间隔中 字符串,日期时间 日期时间
date_part 获取具有给定单位的日期时间的部分 字符串,日期时间 浮点数
host_name 从主机中检索主机名 主机 字符串
host_port 从主机中检索端口 主机 字符串

聚合函数

函数名称 描述 输入类型
avg 计算数字的平均值 整数或浮点数
count 计算记录的数量 任何
first 获取记录中的第一个 任何
last 获取记录中的最后一个 任何
min 获取记录中的最小值 任何
max 获取记录中的最大值 任何
sum 获取数字的总和 整数或浮点数
percentile_disc 计算百分位数的记录 浮点数
approx_percentile 计算近似百分位数的记录 浮点数

动机

通常在日常工作中,当您在解决生产问题时,AWS CloudWatch或内部ELK可能没有提供某些度量标准。然后您会从公司的存档中下载原始访问日志,并编写一个一次性脚本来分析它。然而,这种方法有几个缺点。

  1. 您花费大量时间解析日志格式,但并未专注于计算帮助解决您生产问题的度量标准。
  2. 大多数日志格式都很常见,我们最好将其抽象出来,让每个人都能从共享抽象中受益。
  3. 对于Web服务器日志案例,日志量通常很大,可能是几百MB,甚至几GB。在脚本语言中做这个会让你不耐烦地等待它在本地的运行。

当然,您可以对AWS Athena或ELK这样的分析工具进行微调以分析大量数据,但很多时候您只是想临时分析日志,而不必设置东西或额外付费。此外,现代笔记本电脑/PC实际上足够强大,可以分析GB级的日志量,只是实现不够高效。在Rust中实现logq的目的是为了解决这些不便和担忧。

为什么不用TextQL,或者将空格分隔的字段插入到sqlite中?

TextQL是用Python实现的,对于较小的案例来说还不错。在AWS ELB高流量日志文件的情况下,它通常可以达到GB级的体积,并且速度较慢。此外,TextQL和sqlite都有限制它们的SQL函数和数据类型,这使得像URL、HTTP请求行和User-Agents这样的领域处理变得繁琐。

另一个重要原因是,我们希望支持jsonl格式(json的行),这是一种嵌套的半结构化数据,需要扩展SQL以便能够有效地查询。

此外,在Web流量分析的使用案例中,您想要回答的问题可能类似于“在给定的时间段内,对于忽略URL路径段中的user_id的这个Restful端点,第99个百分位数是多少?”拥有提供方便功能的软件来提取或规范化日志中的信息会更容易。

路线图

  • 使用命令行标志指定表名及其后端文件。
  • 将语法解析器作为单独的cargo crate分离出来。
  • 支持完整的PartiQL规范,教程在此
  • 性能优化,避免不必要的解析
  • 更多支持的函数
  • 具有任意间隔(从纪元开始)的时间桶
  • 优化解析器以兼容SQL-92
  • 流模式与tail -f协同工作
  • 可定制的Reader,遵循GoAccess的风格
  • 更多支持的日志格式
  • 插件quickjs用于用户定义函数
  • 使用Hyperloglog实现APPROX_COUNT_DISTINCT
  • 为重复查询构建索引

依赖

~12MB
~225K SLoC