【Monitoring】LogQL学习(一):Log queries部分

1,667 阅读8分钟

【参考】

1. 什么是LogQL?

  • 全称:Log query language。
  • 可以从Loki中搜索日志的一种语言。
  • 借鉴了PromQL
  • distributed grep

有两种形式的Query:

  • Log queries:按行返回log。--> 【本文内容】
  • Metric queries:计算统计query的结果,可以使用sum等函数。

2. 存储在Loki中的日志

存储在Loki中的日志,是按行存储的: image.png

点击单条日志,可以看到日志的内容是有标签的(Log labels): image.png

而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。 未命名文件 (4).png

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主要分为以下几种:

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聚合中)。

支持:JSONlogfmtpatternregexp and unpack parsers.

  • (1) JSON parser
    比如我们的Log query = {container="feign-test"} |= "test"

通过这个我们检索到的日志如下: image.png

点击单条我们可以看到日志的Label如下: image.png

那么当我们加了JSON parser后:{container="feign-test"} |= "test" | json,可以看到JSON parser会将我们的日志中的内容,也转为label,从而使得label多了:log, stream以及time: image.png

但如果你不想要stream以及time放到label上,只想让JSON parser将log放进来,也是可以的: {container="feign-test"} |= "test" | json logLabel="log"
这里的json logLabel="log"的意思是,只需要取log这一个字段,并将这个字段中的值,赋给label=logLabel: image.png

  • (2) logfmt parser
    如一个日志内容为:
at=info method=GET

那么通过logfmt parser的转换,label中会多了以下labels:

"at" => "info"
"method" => "GET"

当然也可能会转换错误,如果报错的话,会多一个__error__的标签,如下图: image.png

  • (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"

我试了下我自己的日志,比如我的日志内容为: image.png

那么如何将日志中log的部分提取出来,放到label中,新的label名字叫logContent,并且提取stream的内容,label就叫stream呢?
(因为我的整个日志格式就是json,如果用json parser就非常容易,详见上面的demo):

image.png

用Pattern parser也能做,就是麻烦了点,我们可以设置log query = {container="feign-test"} |= "test"| pattern "{\"log\":\"<logContent>\",\"stream\":\"<strem>\",\"time"

即: image.png

查询出来的日志,点击详情,可以看到多出了两个新的label,即:logContent和stream: image.png

  • (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): image.png

可以看到我们选出了4条log: image.png

通过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"image.png

目标中的4条日志,都会有这个newStream的标签,并且两条的值是stdout,另外两条是stderr,这时候就可以使用label filter,选出标签内容为stderr的两条了: image.png

可以看到结果只剩两条了: image.png


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"

日志搜索结果: image.png

可以看到结果中已经有log这个标签了: image.png

接下来,我们用line format expression,将log这个标签的内容重写到log line中:

image.png

重写后的日志内容: image.png

3.2.5 Labels format expression

语法为:label_format

Labels format expression可以重命名、修改或增加label。

例如log query = {container=~"feign-test"} |~ "test",查询出来的Log有如下labels: image.png

我们可以把label为app的,重命名为newApp,语法是<新的label>=<现有的label>: image.png

查询出来的结果中,已经没有app这个标签了,取而代之的是newApp: image.png

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例子

如日志内容为: image.png

Log query如下: image.png

  • 首先是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,官网给出的图如下: image.png

首先是Log stream selector,即{}的部分。

然后就是三大块:可以按日志进行过滤,或是将日志内容parse成label,再按label进行过滤,最后是format部分,即修改label或是将日志内容重新按某个格式进行输出。