【参考】
- LogQL官网:grafana.com/docs/loki/l…
- 关于Loki以及Grafana的安装,查看我之前的文章:juejin.cn/post/712349…
- How to create metrics out of logs using LogQL from Loki - with Cyril Tovena: www.youtube.com/watch?v=rrP…
1. 什么是LogQL?
- 全称:
Log query language。 - 可以从
Loki中搜索日志的一种语言。 - 借鉴了
PromQL。 - distributed
grep
有两种形式的Query:
- Log queries:按行返回log。
-->【本文内容】 - Metric queries:计算统计query的结果,可以使用sum等函数。
2. 存储在Loki中的日志
存储在Loki中的日志,是按行存储的:
点击单条日志,可以看到日志的内容是有标签的(Log labels):
而LogQL就是可以快速按各种条件检索出日志的一种手段(语言)。
3. Log queries
先举个例子:
{container="query-frontend",namespace="loki-dev"} |= "metrics.go" |= "org_id=5842"
以上这个Log query,想要取出的日志需要符合:
- 左边的括号是label的match,即取出log label为container的,值为query-frontend的,log label的namespace为loki-dev的日志。
- 右边是pipeline的形式,用的是grep的思路,即日志的值包含metrics.go,并且值包含org_id=5842。
3.1 Log stream selector
可以使用如下的语法来匹配:
=: exactly equal!=: not equal=~: regex matches(通过正则表达式匹配)!~: regex does not match(通过正则,表示不等于)
我们上述的例子{container="query-frontend",namespace="loki-dev"}只使用了第1个,即完全匹配的形式取出日志。
正则举例: {container =~ "query.+"}:表示匹配标签为container,值需要匹配正则:[query.+],那么上述的query-frontend也会被匹配到。
【Log stream selector这一部分如其名字,是帮助我们通过一系列的条件(lebel的match),选择出我们想要的log。】
3.2 Log pipeline
Log pipeline主要是加在Log stream selector后面,用来做过滤的,也可能是对内容的重写。
【重要】Log pipeline expressions主要分为以下几种:
Filter相关:line filter expressions、label filter expressions,这两个表达式是用来做过滤的,例如日志的内容需要匹配某个字符串(具体来说:取出某个应用程序的结果包含error的日志)。Parse相关:Parsing expressions,这个主要是将日志的内容按某种形式(如JSON,正则)提取出来,放到Label中。Formatting相关:line format expressions、label format expressions,这块主要是日志内容或label内容的修改、重写。
3.2.1 Line filter expression
这个有点像grep,我们传入想要匹配或是不匹配的条件,然后它从loki日志中选择出想要的line:
|=: Log中包含字符!=: Log中不包含字符|~: Log中包含正则匹配!~: Log中不包含正则匹配
举例:
{job="mysql"} |= "error" != "timeout"表示取出label为job的,值为mysql,并且log内容中包含error字符的,但不包含timeout字符的日志。{job="varlogs"} |~ "Invalid user (bob|redis)"表示取出label为job,值为varlogs,并且按正则匹配的日志,匹配Invalid user bob或是Invalid user redis。
3.2.2 Label filter expressions
对于Log的内容,支持以下几种类型:
- String:双引号或单引号引用的字符。字符的匹配和上述的Log stream selector一样,即
=,!=,=~,!~。 - Duration:一些数字如300ms, 1.5h等,支持的单位有:“ns”, “us” (or “µs”), “ms”, “s”, “m”, “h”.
- Number:数字,如250, 89.923等。
- Bytes:一些数字,如42MB, 1.5Kib. 支持的单位有:“b”, “kib”, “kb”, “mib”, “mb”, “gib”, “gb”, “tib”, “tb”, “pib”, “pb”, “eib”, “eb”.
Duration, number或是Bypes类型,支持以下匹配:
==or=:等于!=:不等于>and>=:大于,大于等于<and<=:小于,小于等于
(这个章节比较难懂,可以跳过,后面有具体的例子。)
3.2.3 Parser expression
Parser expression可以从Log内容中转换或是提取labels。这些被提取出来的labels可以被用在filter中(如上述的label filter或是metric聚合中)。
支持:JSON, logfmt, pattern, regexp and unpack parsers.
- (1)
JSON parser
比如我们的Log query ={container="feign-test"} |= "test"
通过这个我们检索到的日志如下:
点击单条我们可以看到日志的Label如下:
那么当我们加了JSON parser后:{container="feign-test"} |= "test" | json,可以看到JSON parser会将我们的日志中的内容,也转为label,从而使得label多了:log, stream以及time:
但如果你不想要stream以及time放到label上,只想让JSON parser将log放进来,也是可以的:
{container="feign-test"} |= "test" | json logLabel="log":
这里的json logLabel="log"的意思是,只需要取log这一个字段,并将这个字段中的值,赋给label=logLabel:
- (2)
logfmt parser
如一个日志内容为:
at=info method=GET
那么通过logfmt parser的转换,label中会多了以下labels:
"at" => "info"
"method" => "GET"
当然也可能会转换错误,如果报错的话,会多一个__error__的标签,如下图:
- (3)
Pattern parser
按某种格式来抓取label,语法为:patt<br>ern "<pattern-expression>":
官网给的例子,日志内容如:
0.191.12.2 - - [10/Jun/2021:09:14:29 +0000] "GET /api/plugins/versioncheck HTTP/1.1" 200 2 "-" "Go-http-client/2.0" "13.76.247.102, 34.120.177.193" "TLSv1.2" "US" ""
partern expression为
<ip> - - <_> "<method> <uri> <_>" <status> <size> <_> "<agent>" <_>
那么转换为label如下:
"ip" => "0.191.12.2"
"method" => "GET"
"uri" => "/api/plugins/versioncheck"
"status" => "200"
"size" => "2"
"agent" => "Go-http-client/2.0"
我试了下我自己的日志,比如我的日志内容为:
那么如何将日志中log的部分提取出来,放到label中,新的label名字叫logContent,并且提取stream的内容,label就叫stream呢?
(因为我的整个日志格式就是json,如果用json parser就非常容易,详见上面的demo):
用Pattern parser也能做,就是麻烦了点,我们可以设置log query = {container="feign-test"} |= "test"| pattern "{\"log\":\"<logContent>\",\"stream\":\"<strem>\",\"time":
即:
查询出来的日志,点击详情,可以看到多出了两个新的label,即:logContent和stream:
-
(4)
Regular expression
按正则匹配,语法为regexp "<re>": -
(5)
unpack
可以unpack promtail的pack stage,通常与json结合使用。
在#3.2.3一开始,我们就有说到通过Parser expression提取出来的labels可以被用在filter中(如上述的label filter或是metric聚合中)。
【那么怎么理解呢?】
比如我们通过以下log query选出4条想要的log,(条件比较复杂,可以略过,主要的目的是选出几条log):
可以看到我们选出了4条log:
通过JSON Parser,选择出stream的内容 --> 加上新的标签,名字叫newStream:
log query为:{container=~"otel-collector|feign-test"} |~ "2022-07-27T03:46:58.925977489Z|2022-07-27T07:57:05.332989418Z" | json newStream="stream":
目标中的4条日志,都会有这个newStream的标签,并且两条的值是stdout,另外两条是stderr,这时候就可以使用label filter,选出标签内容为stderr的两条了:
可以看到结果只剩两条了:
3.2.4 line format expressions
line format表达式可以帮助我们重写log的内容,使用的是text/template格式。语法是:| line_format "{{.label_name}}"。
可以和Parser expression结合使用。
例如,我们的日志内容为:
{"log":"{\"key*test*2\":\"a\"}\n","stream":"stdout","time":"2022-07-27T07:57:05.332989418Z"}
我们通过JSON Parser,将上述的key为log的内容提取到label中(反复用这个在举例):
即log query = {container=~"feign-test"} |~ "test" | json log="log"
日志搜索结果:
可以看到结果中已经有log这个标签了:
接下来,我们用line format expression,将log这个标签的内容重写到log line中:
重写后的日志内容:
3.2.5 Labels format expression
语法为:label_format
Labels format expression可以重命名、修改或增加label。
例如log query = {container=~"feign-test"} |~ "test",查询出来的Log有如下labels:
我们可以把label为app的,重命名为newApp,语法是<新的label>=<现有的label>:
查询出来的结果中,已经没有app这个标签了,取而代之的是newApp:
4. 一些例子
4.1 多个filter例子
如Log query为:
{cluster="ops-tools1", namespace="loki-dev", job="loki-dev/query-frontend"} |= "metrics.go" !="out of order" | logfmt | duration > 30s or status_code!="200"
- 首先是Log stream selector,即第1个{}的内容。
- 然后是Line filter:日志内容匹配"metrics.go",并且不匹配"out of order"。
- 然后是通过logfmt:将日志的内容加到label中。
- 最后是Label filter expressions,即上述3.2.2的内容,即按label过滤:duration > 30s或是status_code不等于200。
4.2 多个parsers例子
log日志的内容为:
level=debug ts=2020-10-02T10:10:42.092268913Z caller=logging.go:66 traceID=a9d4d8a928d8db1 msg="POST /api/prom/api/v1/query_range (200) 1.5s"
Log query为:
{job="loki-ops/query-frontend"} | logfmt | line_format "{{.msg}}" | regexp "(?P<method>\w+) (?P<path>[\w|/]+) \((?P<status>\d+?)\) (?P<duration>.*)"
- 首先是Log stream selector,即第1个{}的内容。
- logfmt,会将上述日志内容的level, ts, caller, traceID, msg添加到日志标签中。
- line_format表示重写日志为某个内容,这里取的标签为msg,即把上述的日志内容转为:
POST /api/prom/api/v1/query_range (200) 1.5s。 - regexp,正则表达式的Parser,regexp后面双引号中的格式进行label的提取,新添加的label有:method, path, status以及duration。
4.3 Formatting例子
如日志内容为:
Log query如下:
- 首先是Log stream selector,即第1个{}的内容。
- Line filter:取出日志内容包含"metrics.go",但不包含"loki-canary"的日志
- 接着是logfmt,将日志的内容提取为labels,新的label有:level, ts, caller...等等。
- Label filter expressions,即取出标签为query的,值不为空字符串的日志。
- 接下来做的是Label format,
label_format query="{{ Replace .query "\\n" \"\" -1 }}"表示修改query label中的值,将上述日志中的query内容一些特殊符号替换掉。 - 最后是Line format expression,将日志的内容重新输出格式为:
"{{ .ts}}\t{{.duration}}\ttraceID = {{.traceID}}\t{{ printf "%-100.100s" .query }} "
最终的结果为:
2020-10-23T20:32:18.094668233Z 650.22401ms traceID = 1980d41501b57b68 {cluster="ops-tools1", job="loki-ops/query-frontend"} |= "query_range"
5. 总结
LogQL分两部分,第一部分Log queries,即本文的内容。另外Metrics相关的,留到下一篇文章再讲。
关于Log queries,官网给出的图如下:
首先是Log stream selector,即{}的部分。
然后就是三大块:可以按日志进行过滤,或是将日志内容parse成label,再按label进行过滤,最后是format部分,即修改label或是将日志内容重新按某个格式进行输出。