如果您曾经使用 Web 界面查看日志,那么您可能已经注意到,这些界面通常很麻烦,而且(通常)不太方便且响应速度不快。 有些你可以习惯,有些绝对很糟糕,但在我看来,所有问题的原因是我们错误地处理了查看日志的任务:我们尝试创建一个 Web 界面,其中 CLI(命令行界面)效果更好。 我个人非常喜欢使用 tail、grep、awk 等,因此对我来说,处理日志的理想界面是类似于 tail 和 grep 的东西,但它也可以用于读取来自许多服务器的日志。 那当然是从 ClickHouse 读取它们!
*根据habra用户的个人意见
认识 Logscli
我没有为我的界面起一个名字,而且,说实话,它是以原型的形式存在的,但如果你想立即看到源代码,那么欢迎你:
机会
我的目标是制作一个对于那些习惯 tail/grep 的人来说似乎很熟悉的界面,即支持以下内容:
- 查看所有日志,不进行过滤。
- 留下包含固定子字符串的行(标志
-F
уgrep
). - 留下与正则表达式匹配的行(标志
-E
уgrep
). - 默认情况下,查看是按时间倒序排列的,因为最近的日志通常是首先感兴趣的。
- 显示每行旁边的上下文(选项
-A
,-B
и-C
уgrep
,分别在每个匹配行之前、之后和周围打印 N 行)。 - 实时查看传入日志,带或不带过滤(本质上是
tail -f | grep
). - 该接口必须兼容
less
,head
,tail
和其他 - 默认情况下,应返回结果,且不限制其数量; 只要用户有兴趣接收它们,行就会作为流打印; 信号SIGPIPE
应该像他们一样默默地中断日志流tail
,grep
和其他 UNIX 实用程序。
履行
我假设您已经以某种方式知道如何将日志传递到 ClickHouse。 如果没有的话我建议尝试一下
首先,您需要决定基本方案。 由于您通常希望接收按时间排序的日志,因此以这种方式存储它们似乎是合乎逻辑的。 如果有很多日志类别并且它们都属于同一类型,那么您可以将日志类别作为主键的第一列 - 这将允许您拥有一个表而不是多个表,这将是一个很大的优点插入ClickHouse(在有硬盘的服务器上,建议每秒插入数据不超过~1次 对于整个服务器).
也就是说,我们大约需要下表方案:
CREATE TABLE logs(
category LowCardinality(String), -- категория логов (опционально)
time DateTime, -- время события
millis UInt16, -- миллисекунды (могут быть и микросекунды, и т.д.): рекомендуется хранить, если событий много, чтобы было легче различать события между собой
..., -- ваши собственные поля, например имя сервера, уровень логирования, и так далее
message String -- текст сообщения
) ENGINE=MergeTree()
ORDER BY (category, time, millis)
不幸的是,我无法立即找到任何具有可以抓取和下载的真实日志的开源代码,所以我以此作为示例
将亚马逊评论上传到 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
尝试写入封闭的管道,
在 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 上找到:
来源: habr.com