日志查询
所有 LogQL 查询都包含一个**日志流选择器**。
可选地,日志流选择器后可接**日志流水线**。日志流水线是一组阶段表达式,它们链式连接并应用于选定的日志流。每个表达式都可以过滤、解析或修改日志行及其相应的标签。
以下示例展示了一个完整的日志查询
{container="query-frontend",namespace="loki-dev"} |= "metrics.go" | logfmt | duration > 10s and throughput_mb < 500
查询由以下部分组成
- 日志流选择器
{container="query-frontend",namespace="loki-dev"}
,它指定了loki-dev
命名空间中的query-frontend
容器。 - 日志流水线
|= "metrics.go" | logfmt | duration > 10s and throughput_mb < 500
,它将过滤掉包含单词metrics.go
的日志,然后解析每行日志以提取更多标签并使用它们进行过滤。
为避免转义特殊字符,在引用字符串时,可以使用 ``` ` ``` (反引号) 代替 ``` " ```。例如,``` `\w+` ``` 与 ``` "\\w+" ``` 相同。这在编写包含多个需要转义的反斜杠的正则表达式时特别有用。
日志流选择器
流选择器确定哪些日志流包含在查询结果中。日志流是日志内容的唯一来源,例如文件。更细粒度的日志流选择器可以减少搜索流的数量,使其达到可管理的规模。这意味着传递给日志流选择器的标签将影响查询执行的相对性能。
日志流选择器由一个或多个逗号分隔的键值对指定。每个键都是一个日志标签,每个值是该标签的值。花括号 ({
和 }
) 界定流选择器。
考虑以下流选择器
{app="mysql",name="mysql-backup"}
所有同时拥有 app
标签(值为 mysql
)和 name
标签(值为 mysql-backup
)的日志流都将包含在查询结果中。一个流可能包含其他标签和值对,但只有流选择器中指定的标签对用于确定哪些流将被包含在查询结果中。
适用于 Prometheus 标签选择器 的规则同样适用于 Grafana Loki 日志流选择器。
标签名称后面的 =
运算符是**标签匹配运算符**。支持以下标签匹配运算符
=
: 精确相等!=
: 不相等=~
: 正则表达式匹配!~
: 正则表达式不匹配
正则表达式日志流示例
{name =~ "mysql.+"}
{name !~ "mysql.+"}
{name !~ `mysql-\d+`}
注意:与行过滤器正则表达式不同,=~
和 !~
正则表达式运算符是完全锚定的。这意味着正则表达式必须匹配*整个*字符串,**包括换行符**。正则表达式字符 .
默认不匹配换行符。如果希望正则表达式点字符匹配换行符,可以使用单行标志,例如:(?s)search_term.+
匹配 search_term\n
。或者,可以将 \s
(匹配包括换行符在内的空白字符)与 \S
(匹配非空白字符)结合使用,以匹配包括换行符在内的所有字符。有关更多信息,请参阅 Google 的 RE2 语法。
正则表达式日志流换行符
{name =~ ".*mysql.*"}
: 不匹配包含换行符的日志标签值{name =~ "(?s).*mysql.*}
: 匹配包含换行符的日志标签值{name =~ "[\S\s]*mysql[\S\s]*}
: 匹配包含换行符的日志标签值
日志流水线
日志流水线可以附加到日志流选择器之后,以进一步处理和过滤日志流。它由一组表达式组成。每个表达式按从左到右的顺序对每行日志执行。如果某个表达式过滤掉一行日志,流水线将停止处理当前日志行,并开始处理下一行日志。
某些表达式可以改变日志内容和相应的标签,这些改变后的内容和标签随后可用于后续表达式的进一步过滤和处理。一个改变日志的示例如下
| line_format "{{.status_code}}"
日志流水线表达式分为四类
行过滤表达式
行过滤表达式对匹配的日志流聚合的日志执行分布式 grep
。它搜索日志行的内容,丢弃不匹配区分大小写表达式的行。
每个行过滤表达式都有一个**过滤运算符**,后跟文本或正则表达式。支持以下过滤运算符
|=
: 日志行包含字符串!=
: 日志行不包含字符串|~
: 日志行包含与正则表达式匹配的内容!~
: 日志行不包含与正则表达式匹配的内容
注意:与标签匹配器正则表达式运算符不同,|~
和 !~
正则表达式运算符不是完全锚定的。这意味着正则表达式字符 .
匹配所有字符,**包括换行符**。
行过滤表达式示例
保留包含子字符串“error”的日志行
|= "error"
使用此示例的完整查询
{job="mysql"} |= "error"
丢弃包含子字符串“kafka.server:type=ReplicaManager”的日志行
!= "kafka.server:type=ReplicaManager"
使用此示例的完整查询
{instance=~"kafka-[23]",name="kafka"} != "kafka.server:type=ReplicaManager"
保留包含以
tsdb-ops
开头并以io:2003
结尾的子字符串的日志行。一个使用正则表达式的完整查询{name="kafka"} |~ "tsdb-ops.*io:2003"
保留包含以
error=
开头,后跟一个或多个单词字符的子字符串的日志行。一个使用正则表达式的完整查询{name="cassandra"} |~ `error=\w+`
过滤运算符可以链式使用。过滤器按顺序应用。查询结果将满足所有过滤器。这个完整的查询示例将返回包含字符串 error
且不包含字符串 timeout
的结果。
{job="mysql"} |= "error" != "timeout"
使用 |~
和 !~
时,可以使用 Go(如 Golang)的 RE2 语法正则表达式。匹配默认是区分大小写的。通过在正则表达式前加上 (?i)
可以切换到不区分大小写的匹配。
虽然行过滤表达式可以放置在日志流水线中的任何位置,但几乎总是最好将它们放在开头。将它们放在开头可以提高查询的性能,因为它只在匹配的行上进行进一步处理。例如,虽然结果相同,但以下查询的执行速度将总是快于
{job="mysql"} |= "error" | json | line_format "{{.err}}"
将始终比以下查询运行得更快
{job="mysql"} | json | line_format "{{.message}}" |= "error"
应用日志流选择器后,行过滤表达式是过滤日志的最快方法。
行过滤表达式支持匹配 IP 地址。有关详细信息,请参阅匹配 IP 地址。
移除颜色代码
行过滤表达式支持从日志行中去除 ANSI 序列(颜色代码)
{job="example"} | decolorize
标签过滤表达式
标签过滤表达式允许使用日志行的原始标签和提取的标签进行过滤。它可以包含多个谓词(predicate)。
谓词包含一个**标签标识符**、一个**操作**和一个用于比较标签的**值**。
例如,对于 cluster="namespace"
,cluster 是标签标识符,操作是 =
,值是“namespace”。标签标识符总是位于操作的左侧。
我们支持多种**值**类型,这些类型会根据查询输入自动推断。
- 字符串是双引号或反引号括起来的,例如
"200"
或 `us-central1
`。 - 持续时间 是一系列十进制数字,每个数字带有可选的小数部分和单位后缀,例如“300ms”、“1.5h”或“2h45m”。有效的时间单位有“ns”、“us”(或“µs”)、“ms”、“s”、“m”、“h”。用于比较的标签标识符的值必须是带有单位后缀的字符串才能正确解析,例如“0.10ms”或“1h30m”。可选地,可以使用
label_format
修改值并在进行比较之前附加单位。 - 数字是浮点数 (64位),例如
250
,89.923
。 - 字节 是一系列十进制数字,每个数字带有可选的小数部分和单位后缀,例如“42MB”、“1.5KiB”或“20B”。有效的字节单位有“B”、“kB”、“MB”、“GB”、“TB”、“KB”、“KiB”、“MiB”、“GiB”、“TiB”。
字符串类型的功能与 日志流选择器 中使用的 Prometheus 标签匹配器完全相同。这意味着您可以使用相同的操作符 (=
,!=
,=~
,!~
)。
字符串类型是唯一可以过滤掉带有标签
__error__
的日志行的类型。
使用持续时间、数字和字节类型会在比较前转换标签值,并支持以下比较运算符
==
或=
用于相等比较。!=
用于不等比较。>
和>=
用于大于和大于等于比较。<
和<=
用于小于和小于等于比较。
例如,logfmt | duration > 1m and bytes_consumed > 20MB
如果标签值转换失败,日志行不会被过滤,而是会添加一个 __error__
标签。要过滤这些错误,请参阅流水线错误部分。
可以使用 and
和 or
链式连接多个谓词,它们分别表示 and
和 or
二元操作。and
可以等效地表示为逗号、空格或另一个竖线。标签过滤器可以放置在日志流水线中的任何位置。
这意味着以下所有表达式都是等效的
| duration >= 20ms or size == 20KB and method!~"2.."
| duration >= 20ms or size == 20KB | method!~"2.."
| duration >= 20ms or size == 20KB , method!~"2.."
| duration >= 20ms or size == 20KB method!~"2.."
The precedence for evaluation of multiple predicates is left to right. You can wrap predicates with parenthesis to force a different precedence.
这些示例是等效的
| duration >= 20ms or method="GET" and size <= 20KB
| ((duration >= 20ms or method="GET") and size <= 20KB)
These examples are equivalent
| duration >= 20ms or (method="GET" and size <= 20KB)
要先评估逻辑
and
,请使用括号,如本示例所示
标签过滤表达式是 unwrap 表达式之后唯一允许的表达式。这主要是为了允许过滤指标提取中的错误。
标签过滤表达式支持匹配 IP 地址。有关详细信息,请参阅匹配 IP 地址。
解析表达式
解析表达式可以从日志内容中解析和提取标签。这些提取的标签随后可以用于使用标签过滤表达式进行过滤或用于指标聚合。
所有解析器都会自动对提取的标签键进行清理,以遵循 Prometheus 指标命名约定。(它们只能包含 ASCII 字母和数字,以及下划线和冒号。不能以数字开头。)
{ "a.b": {c: "d"}, e: "f" }
->
{a_b_c="d", e="f"}
如果发生错误,例如日志行格式不符合预期,日志行不会被过滤掉,而是会添加一个新的 __error__
标签。
如果提取的标签键名称在原始日志流中已经存在,提取的标签键将附加 _extracted
关键字以区分这两个标签。您可以使用标签格式化表达式强制覆盖原始标签。但是,如果提取的键出现两次,则只保留第一个标签值。
Loki 支持 JSON、logfmt、pattern、regexp 和 unpack 解析器。
如果可能,使用预定义的解析器 json
和 logfmt
更容易。如果不能,则可以使用 pattern
和 regexp
解析器处理结构不寻常的日志行。pattern
解析器更容易编写且速度更快;它的性能也优于 regexp
解析器。单个日志流水线可以使用多个解析器。这对于解析复杂的日志非常有用。在多个解析器中有示例。
JSON
json 解析器有两种工作模式
不带参数
如果日志行是有效的 JSON 文档,将
| json
添加到您的流水线会提取所有 JSON 属性作为标签。嵌套属性使用_
分隔符展平为标签键。注意:**数组会被跳过**。
{ "protocol": "HTTP/2.0", "servers": ["129.0.1.1","10.2.1.3"], "request": { "time": "6.032", "method": "GET", "host": "foo.grafana.net", "size": "55", "headers": { "Accept": "*/*", "User-Agent": "curl/7.68.0" } }, "response": { "status": 401, "size": "228", "latency_seconds": "6.031" } }
例如,JSON 解析器将从以下文档中提取
"protocol" => "HTTP/2.0" "request_time" => "6.032" "request_method" => "GET" "request_host" => "foo.grafana.net" "request_size" => "55" "request_headers_Accept" => "*/*" "request_headers_User_Agent" => "curl/7.68.0" "response_status" => "401" "response_size" => "228" "response_latency_seconds" => "6.031"
带参数
在流水线中使用
| json label="expression", another="expression"
将仅提取指定的 JSON 字段到标签中。您可以以这种方式指定一个或多个表达式,与label_format
相同;所有表达式必须加引号。目前,我们仅支持字段访问 (
my.field
,my["field"]
) 和数组访问 (list[0]
),以及这些访问方式在任何嵌套级别的任意组合 (my.list[0]["field"]
)。{ "protocol": "HTTP/2.0", "servers": ["129.0.1.1","10.2.1.3"], "request": { "time": "6.032", "method": "GET", "host": "foo.grafana.net", "size": "55", "headers": { "Accept": "*/*", "User-Agent": "curl/7.68.0" } }, "response": { "status": 401, "size": "228", "latency_seconds": "6.031" } }
例如,JSON 解析器将从以下文档中提取
"first_server" => "129.0.1.1" "ua" => "curl/7.68.0"
例如,
| json first_server="servers[0]", ua="request.headers[\"User-Agent\"]
将从以下文档中提取If an array or an object returned by an expression, it will be assigned to the label in json format.
"server_list" => `["129.0.1.1","10.2.1.3"]` "headers" => `{"Accept": "*/*", "User-Agent": "curl/7.68.0"}`
例如,
| json server_list="servers", headers="request.headers"
将提取如果待提取的标签与原始 JSON 字段相同,表达式可以写成
| json <label>
例如,要将
servers
字段提取为标签,表达式可以写成如下"servers" => `["129.0.1.1","10.2.1.3"]`
| json servers
将提取
logfmt
注意 | json servers
与 | json servers="servers"
相同
json 解析器有两种工作模式
logfmt 解析器有两种工作模式
可以通过
| logfmt
添加 logfmt 解析器,它将从 logfmt 格式的日志行中提取所有键和值。at=info method=GET path=/ host=grafana.net fwd="124.133.124.161" service=8ms status=200
"at" => "info" "method" => "GET" "path" => "/" "host" => "grafana.net" "fwd" => "124.133.124.161" "service" => "8ms" "status" => "200"
将提取出以下标签
与 JSON 类似,在流水线中使用
| logfmt label="expression", another="expression"
将仅提取标签指定的字段。at=info method=GET path=/ host=grafana.net fwd="124.133.124.161" service=8ms status=200
例如,
| logfmt host, fwd_ip="fwd"
将从以下日志行中提取标签host
和fwd
"host" => "grafana.net" "fwd_ip" => "124.133.124.161"
并将 fwd
重命名为 fwd_ip
logfmt 解析器还支持以下标志
--strict
启用严格解析// accepted key/value pairs key=value key="value in double quotes" // invalid key/value pairs =value // no key foo=bar=buzz fo"o=bar
启用严格解析后,当 logfmt 解析器遇到任何格式不正确的键值对时,会立即停止扫描日志行并提前返回错误。
不带
--strict
标志时,解析器会跳过无效的键值对并继续解析日志行的其余部分。非严格模式提供了解析半结构化日志行的灵活性,但请注意,这只是尽力而为。--keep-empty
保留值为空的独立键
设置 --keep-empty
标志后,logfmt 解析器会将独立键(没有值的键)作为标签保留,其值设置为空字符串。如果使用标签提取参数明确请求独立键,则无需添加此标志。
| logfmt --strict
| logfmt --strict host, fwd_ip="fwd"
| logfmt --keep-empty --strict host
注意:如果使用标志,应紧跟在 logfmt 后面,在标签提取参数之前
Pattern
pattern 解析器允许通过定义模式表达式 (| pattern "<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" ""
此日志行可以使用以下表达式解析
以提取这些字段
"ip" => "0.191.12.2"
"method" => "GET"
"uri" => "/api/plugins/versioncheck"
"status" => "200"
"size" => "2"
"agent" => "Go-http-client/2.0"
模式表达式由捕获和字面量组成。
捕获是由 <
和 >
字符界定的字段名称。<example>
定义字段名称 example
。未命名的捕获显示为 <_>
。未命名的捕获会跳过匹配的内容。
捕获从日志行开头或前一组字面量开始匹配,到日志行结尾或下一组字面量结束。如果捕获不匹配,pattern 解析器将停止。
字面量可以是任何 UTF-8 字符序列,包括空白字符。
默认情况下,模式表达式锚定在日志行的开头。如果表达式以字面量开头,则日志行也必须以同一组字面量开头。如果不想将表达式锚定在开头,请在表达式的开头使用 <_>
。
考虑以下日志行
level=debug ts=2021-06-10T09:24:13.472094048Z caller=logging.go:66 traceID=0568b66ad2d9294c msg="POST /loki/api/v1/push (204) 16.652862ms"
要匹配 msg="
,请使用表达式
<_> msg="<method> <path> (<status>) <latency>"
以下情况会导致模式表达式无效
- 不包含任何命名捕获。
- 包含两个连续的捕获,且中间没有空白字符分隔。
正则表达式
与 logfmt 和 json 解析器不同(它们隐式提取所有值且不带参数),regexp 解析器接受单个参数 | regexp "<re>"
,该参数是使用 Golang RE2 语法 的正则表达式。
正则表达式必须至少包含一个命名子匹配(例如 (?P<name>re)
),每个子匹配将提取一个不同的标签。
例如,解析器 | regexp "(?P<method>\\w+) (?P<path>[\\w|/]+) \\((?P<status>\\d+?)\\) (?P<duration>.*)"
将从以下行中提取
POST /api/prom/api/v1/query_range (200) 1.5s
这些标签
"method" => "POST"
"path" => "/api/prom/api/v1/query_range"
"status" => "200"
"duration" => "1.5s"
unpack
unpack
解析器解析 JSON 日志行,从 Promtail 的 pack
阶段中解包所有嵌入的标签。**一个特殊属性 _entry
也将用于替换原始日志行**。
例如,对以下日志行使用 | unpack
{
"container": "myapp",
"pod": "pod-3223f",
"_entry": "original log message"
}
提取 container
和 pod
标签;并将 original log message
设置为新的日志行。
如果原始嵌入日志行具有特定格式,可以将 unpack
和 json
解析器(或任何其他解析器)结合使用。
行格式化表达式
行格式化表达式可以使用 text/template 格式重写日志行内容。它接受一个字符串参数 | line_format "{{.label_name}}"
,即模板格式。所有标签都作为变量注入到模板中,可以使用 {{.label_name}}
符号进行使用。
例如,以下表达式
{container="frontend"} | logfmt | line_format "{{.query}} {{.duration}}"
将提取并重写日志行,使其仅包含查询和请求持续时间。
您可以使用双引号字符串或反引号 `{{.label_name}}`
作为模板,以避免转义特殊字符。
line_format
也支持 math
函数。示例
如果我们有以下标签 ip=1.1.1.1
, status=200
和 duration=3000
(ms),我们可以将持续时间除以 1000
以获得秒值。
{container="frontend"} | logfmt | line_format "{{.ip}} {{.status}} {{div .duration 1000}}"
上面的查询将得到 line
为 1.1.1.1 200 3
此外,您还可以使用 __line__
函数访问日志行,使用 __timestamp__
函数访问时间戳。请参阅模板函数了解模板格式中可用的函数。
标签格式化表达式
| label_format
表达式可以重命名、修改或添加标签。它以逗号分隔的等式操作列表作为参数,支持同时进行多个操作。
当两边都是标签标识符时,例如 dst=src
,该操作将把 src
标签重命名为 dst
。
右侧也可以是模板字符串(双引号或反引号),例如 dst="{{.status}} {{.query}}"
,在这种情况下,dst
标签的值将被 text/template 评估的结果替换。这与 | line_format
表达式使用相同的模板引擎,这意味着标签作为变量可用,并且您可以使用相同的函数列表。
在这两种情况下,如果目标标签不存在,则会创建一个新的标签。
重命名形式 dst=src
会在将 src
标签重映射到 dst
标签后*删除* src
标签。但是,*模板*形式会保留引用的标签,因此 dst="{{.src}}"
会导致 dst
和 src
具有相同的值。
单个标签名称在每个表达式中只能出现一次。这意味着
| label_format foo=bar,foo="new"
是不允许的,但您可以使用两个表达式来实现所需的效果:| label_format foo=bar | label_format foo="new"
删除标签表达式
语法: |drop name, other_name, some_name="some_value"
| drop
表达式将删除流水线中给定的标签。例如,对于查询 {job="varlogs"}|json|drop level, method="GET"
,以及下面的日志行
{"level": "info", "method": "GET", "path": "/", "host": "grafana.net", "status": "200"}
结果将是
{host="grafana.net", path="/", status="200"} {"level": "info", "method": "GET", "path": "/", "host": "grafana.net", "status": "200"}
类似地,此表达式也可用于删除 __error__
标签。例如,对于查询 {job="varlogs"}|json|drop __error__
,以及下面的日志行
INFO GET / loki.net 200
结果将是
{} INFO GET / loki.net 200
使用正则表达式和多个名称的示例
对于查询 {job="varlogs"}|json|drop level, path, app=~"some-api.*"
,以及下面的日志行
{"app": "some-api-service", "level": "info", "method": "GET", "path": "/", "host": "grafana.net", "status": "200"}
{"app": "other-service", "level": "info", "method": "GET", "path": "/", "host": "grafana.net", "status": "200"}
结果将是
{host="grafana.net", job="varlogs", method="GET", status="200"} {"app": "some-api-service", "level": "info", "method": "GET", "path": "/", "host": "grafana.net", "status": "200"}
{app="other-service", host="grafana.net", job="varlogs", method="GET", status="200"} {"app": "other-service", "level": "info", "method": "GET", "path": "/", "host": "grafana.net", "status": "200"}
保留标签表达式
语法: |keep name, other_name, some_name="some_value"
| keep
表达式将仅保留流水线中指定的标签,并删除所有其他标签。
注意
keep 阶段不会删除 Loki 在查询时添加的 error 或 error_details 标签。要删除这些标签,请参阅drop 阶段。
查询示例
对于查询 {job="varlogs"}|json|keep level, method="GET"
,以及以下日志行
{"level": "info", "method": "GET", "path": "/", "host": "grafana.net", "status": "200"}
{"level": "info", "method": "POST", "path": "/", "host": "grafana.net", "status": "200"}
结果将是
{level="info", method="GET"} {"level": "info", "method": "GET", "path": "/", "host": "grafana.net", "status": "200"}
{level="info"} {"level": "info", "method": "POST", "path": "/", "host": "grafana.net", "status": "200"}
对于查询 {job="varlogs"}|json|keep level, tenant, app=~"some-api.*"
,以及以下日志行
{"app": "some-api-service", "level": "info", "method": "GET", "path": "/", "host": "grafana.net", "status": "200"}
{"app": "other-service", "level": "info", "method": "GET", "path": "/", "host": "grafana.net", "status": "200"}
结果将是
{app="some-api-service", level="info"} {"app": "some-api-service", "level": "info", "method": "GET", "path": "/", "host": "grafana.net", "status": "200"}
{level="info"} {"app": "other-service", "level": "info", "method": "GET", "path": "/", "host": "grafana.net", "status": "200"}