我们正在开发世界上最方便的界面*用于查看日志

我们正在开发世界上最方便的界面*用于查看日志 如果您曾经使用 Web 界面查看日志,那么您可能已经注意到,这些界面通常很麻烦,而且(通常)不太方便且响应速度不快。 有些你可以习惯,有些绝对很糟糕,但在我看来,所有问题的原因是我们错误地处理了查看日志的任务:我们尝试创建一个 Web 界面,其中 CLI(命令行界面)效果更好。 我个人非常喜欢使用 tail、grep、awk 等,因此对我来说,处理日志的理想界面是类似于 tail 和 grep 的东西,但它也可以用于读取来自许多服务器的日志。 那当然是从 ClickHouse 读取它们!

*根据habra用户的个人意见 你的摇滚

认识 Logscli

我没有为我的界面起一个名字,而且,说实话,它是以原型的形式存在的,但如果你想立即看到源代码,那么欢迎你: https://github.com/YuriyNasretdinov/logscli (350 行选定的 Go 代码)。

机会

我的目标是制作一个对于那些习惯 tail/grep 的人来说似乎很熟悉的界面,即支持以下内容:

  1. 查看所有日志,不进行过滤。
  2. 留下包含固定子字符串的行(标志 -F у grep).
  3. 留下与正则表达式匹配的行(标志 -E у grep).
  4. 默认情况下,查看是按时间倒序排列的,因为最近的日志通常是首先感兴趣的。
  5. 显示每行旁边的上下文(选项 -A, -B и -C у grep,分别在每个匹配行之前、之后和周围打印 N 行)。
  6. 实时查看传入日志,带或不带过滤(本质上是 tail -f | grep).
  7. 该接口必须兼容 less, head, tail 和其他 - 默认情况下,应返回结果,且不限制其数量; 只要用户有兴趣接收它们,行就会作为流打印; 信号 SIGPIPE 应该像他们一样默默地中断日志流 tail, grep 和其他 UNIX 实用程序。

履行

我假设您已经以某种方式知道如何将日志传递到 ClickHouse。 如果没有的话我建议尝试一下 LSD и 小猫屋这篇关于日志传递的文章.

首先,您需要决定基本方案。 由于您通常希望接收按时间排序的日志,因此以这种方式存储它们似乎是合乎逻辑的。 如果有很多日志类别并且它们都属于同一类型,那么您可以将日志类别作为主键的第一列 - 这将允许您拥有一个表而不是多个表,这将是一个很大的优点插入ClickHouse(在有硬盘的服务器上,建议每秒插入数据不超过~1次 对于整个服务器).

也就是说,我们大约需要下表方案:

CREATE TABLE logs(
    category LowCardinality(String), -- категория логов (опционально)
    time DateTime, -- время события
    millis UInt16, -- миллисекунды (могут быть и микросекунды, и т.д.): рекомендуется хранить, если событий много, чтобы было легче различать события между собой
    ..., -- ваши собственные поля, например имя сервера, уровень логирования, и так далее
    message String -- текст сообщения
) ENGINE=MergeTree()
ORDER BY (category, time, millis)

不幸的是,我无法立即找到任何具有可以抓取和下载的真实日志的开源代码,所以我以此作为示例 2015年之前亚马逊产品的评论。 当然,它们的结构与文本日志的结构并不完全相同,但出于说明目的,这并不重要。

将亚马逊评论上传到 ClickHouse 的说明

让我们创建一个表:

CREATE TABLE amazon(
   review_date Date,
   time DateTime DEFAULT toDateTime(toUInt32(review_date) * 86400 + rand() % 86400),
   millis UInt16 DEFAULT rand() % 1000,
   marketplace LowCardinality(String),
   customer_id Int64,
   review_id String,
   product_id LowCardinality(String),
   product_parent Int64,
   product_title String,
   product_category LowCardinality(String),
   star_rating UInt8,
   helpful_votes UInt32,
   total_votes UInt32,
   vine FixedString(1),
   verified_purchase FixedString(1),
   review_headline String,
   review_body String
)
ENGINE=MergeTree()
ORDER BY (time, millis)
SETTINGS index_granularity=8192

在亚马逊数据集中,只有评论日期,但没有确切时间,所以让我们用随机数填充此数据。

您不必下载所有 tsv 文件并将自己限制在前 10-20 个文件中,以获得 16 GB RAM 无法容纳的相当大的数据集。 为了上传 TSV 文件,我使用了以下命令:

for i in *.tsv; do
    echo $i;
    tail -n +2 $i | pv |
    clickhouse-client --input_format_allow_errors_ratio 0.5 --query='INSERT INTO amazon(marketplace,customer_id,review_id,product_id,product_parent,product_title,product_category,star_rating,helpful_votes,total_votes,vine,verified_purchase,review_headline,review_body,review_date) FORMAT TabSeparated'
done

在 Google Cloud 中大小为 1000 GB 的标准持久磁盘(HDD)上(我选择这个大小主要是为了速度更高一点,尽管所需大小的 SSD 可能会更便宜)上传75 核上的速度约为 4 MB/秒。

  • 我必须预约我在Google工作,但我使用的是个人帐户,本文与我在公司的工作无关

我将用这个特定的数据集制作所有插图,因为这是我手头的全部。

显示数据扫描进度

由于在 ClickHouse 中,我们将对带有日志的表使用完整扫描,并且此操作可能会花费大量时间,并且如果找到很少的匹配项,则可能很长时间不会产生任何结果,因此建议能够显示查询的进度,直到收到结果的第一行。 为此,HTTP 接口中有一个参数允许您在 HTTP 标头中发送进度: send_progress_in_http_headers=1。 不幸的是,标准 Go 库无法读取收到的标头,但 ClickHouse 支持 HTTP 1.0 接口(不要与 1.1 混淆!),因此您可以打开到 ClickHouse 的原始 TCP 连接并将其发送到那里 GET /?query=... HTTP/1.0nn 并在没有任何转义或加密的情况下接收响应标头和正文,因此在这种情况下我们甚至不需要使用标准库。

从 ClickHouse 流式传输日志

ClickHouse 在相对较长的时间内(从 2019 年开始?)对 ORDER BY 查询进行了优化,因此像这样的查询

SELECT time, millis, message
FROM logs
WHERE message LIKE '%something%'
ORDER BY time DESC, millis DESC

它将立即开始返回消息中包含子字符串“something”的行,而无需等待扫描完成。

另外,如果 ClickHouse 本身在连接关闭时取消请求,那会非常方便,但这不是默认行为。 可以使用以下选项启用自动请求取消 cancel_http_readonly_queries_on_client_close=1.

Go 中 SIGPIPE 的正确处理

当你执行命令时 some_cmd | head -n 10,具体如何命令 some_cmd 停止执行时 head 减去10行? 答案很简单:当 head 结束,管道关闭,some_cmd 命令的标准输出开始有条件地指向“无处可去”。 什么时候 some_cmd 尝试写入封闭的管道, 它收到一个 SIGPIPE 信号,默认情况下该信号会静默终止程序.

在 Go 中,默认情况下也会发生这种情况,但是 SIGPIPE 信号处理程序还会在末尾打印“signal: SIGPIPE”或类似的消息,要清除此消息,我们只需按照我们想要的方式自行处理 SIGPIPE,即静默处理出口:

ch := make(chan os.Signal)
signal.Notify(ch, syscall.SIGPIPE)
go func() {
    <-ch
    os.Exit(0)
}()

显示消息上下文

通常,您希望查看发生某些错误的上下文(例如,哪个请求导致了恐慌,或者崩溃之前出现了哪些相关问题),并在 grep 这是使用 -A、-B 和 -C 选项完成的,它们分别显示消息之后、之前和周围指定的行数。

不幸的是,我还没有找到一种简单的方法在 ClickHouse 中执行相同的操作,因此为了显示上下文,会向结果的每一行发送一个像这样的附加请求(详细信息取决于排序以及之前是否显示上下文)或之后):

SELECT time,millis,review_body FROM amazon
WHERE (time = 'ВРЕМЯ_СОБЫТИЯ' AND millis < МИЛЛИСЕКУНДЫ_СОБЫТИЯ) OR (time < 'ВРЕМЯ_СОБЫТИЯ')
ORDER BY time DESC, millis DESC
LIMIT КОЛИЧЕСТВО_СТРОК_КОНТЕКСТА
SETTINGS max_threads=1

由于请求几乎是在 ClickHouse 返回相应行后立即发送的,因此它最终会进入缓存,并且通常请求执行得相当快并且消耗一点 CPU(通常该请求在我的虚拟机上大约需要 6 毫秒)。

实时显示新消息

为了(几乎)实时显示传入消息,我们只需每隔几秒执行一次请求,记住我们之前遇到的最后一个时间戳。

命令示例

典型的logscli命令在实践中是什么样子的?

如果您下载了我在文章开头提到的 Amazon 数据集,则可以运行以下命令:

# Показать строки, где встречается слово walmart
$ logscli -F 'walmart' | less

# Показать самые свежие 10 строк, где встречается "terrible"
$ logscli -F terrible -limit 10

# То же самое без -limit:
$ logscli -F terrible | head -n 10

# Показать все строки, подходящие под /times [0-9]/, написанные для vine и у которых высокий рейтинг
$ logscli -E 'times [0-9]' -where="vine='Y' AND star_rating>4" | less

# Показать все строки со словом "panic" и 3 строки контекста вокруг
$ logscli -F 'panic' -C 3 | less

# Непрерывно показывать новые строки со словом "5-star"
$ logscli -F '5-star' -tailf

引用

实用程序代码(无文档)可在 github 上找到: https://github.com/YuriyNasretdinov/logscli。 我很高兴听到您对我关于基于 ClickHouse 用于查看日志的控制台界面的想法的想法。

来源: habr.com

添加评论