loki的日志查询

执行简单查询

获取特定标签相关的日志

{container="... "}

标签过滤器,用于获取指定容器的所有日志

{container="..."} |= `status`

带有行过滤器的标签过滤器,用于获取指定容器上含有“status”字串的日志行

{container="..."} | json | status=`404`

获取指定容器上的json格式的日志中,字段“status”的值为404的日志行

Loki 的数据格式

Loki 标签

  • Loki的标签机制类似于Prometheus,但Loki不使用指标名称,而只使用标签集
    • Loki基于标签集标识时间序列(日志流),标签的不同组合分别代表着不同的时间序列
    • 更改标签集中的任何标签值,包括添加或删除标签,都会创建一个新的时间序列
  • Loki 对标签命名的限制与Prometheus相同
    • 支持使用字母数字下划线冒号,且要能够匹配RE2规范的正则表达式
      • 需要注意的是,冒号专用于定义告警规则,因此不能被exporter或instrumetation使用
    • 标签中不支持使用的字符,要转换为下划线,例如,标签app.kubernetes.io/name 应写为app_kubernetes_io_name
  • 使用标签时的注意事项
    • Loki有一些方法能够创建和使用动态标签
    • 对于一个特定标签集,其标签取值的存在组合过多时,将会导致“高基数(High cardinality)”问题
    • 对于高基数标签,Loki会构建一个巨大的索引,并需要将大量的小chunk刷新到对象存储中,这将会显著降低Loki的性能

日志查询

  • 受PromQL的启发,Loki也提供了专用于日志查询的语言LogQL,它支持两种查询类型
    • 日志查询:返回查询的日志条目
    • 指标查询:基于过滤规则,在日志查询得到的日志条目中执行过滤操作
  • LogQL由两部分组成
    • 日志流选择器(log stream selector):即标签匹配器(label matchers),过滤器表达式,用于挑选时间序列
    • 日志管道(log pipeline):可选,附加在日志流选择器后面,用于在日志流选择器挑选出的日志行中进行日志信息过滤,完成进一步地挑选操作
      • 包含一组表达式,它们按顺序自左而右,对每个日志行进行过滤
      • 每个表达式都可以过滤解析改变日志行的内容及各自的标签

Log pipeline

  • 关于Log pipeline
    • 由一系列表达式组成,自左而右依次执行,它就像一个管道(pipeline),逐步完成日行处理
    • 查询结果需要满足所有的过滤器条件,即多个过滤器表达式间遵循“与”逻辑
    • 任何表达式过滤掉一个日志行后,pipeline将终止该行的处理操作,并转入下一日志行的处理
    • 个别表达式能够改变日志内容及相应的标签,且可由后续表达进行进一步过滤和处理
  • Log pipeline中的表达式大体可以分为三类
    • 过滤表达式:包括“行过滤器表达式”和“标签过滤器表达式”
    • 解析器表达式
    • 格式化表达式:包括“行格式化表达式”和“标签格式化表达式”

过滤器操作符

行过滤器操作符

  • |= 日志行包含指定的字符
  • != 日志行不包含指定的字符
  • |~ 日志行含有正则表达式模式的匹配项,遵循子串锚定机制
  • !~ 日志行不含有正则表达式模式的匹配项,遵循子串锚定机制

标签过滤器操作符

  • = 等值比较
  • != 不等
  • =~ 正则表达式模式匹配,遵循完全锚定机制
  • !~ 正则表达式模式不匹配,遵循完全锚定机制
  • >, >= 大于,大于或等于
  • <, <= 小于,小于或等于

过滤器表达式

行过滤器表达式(line filter expression)

  • 针对那些由“log stream selector”挑选出的各日志流上的每个日志行内容进行过滤操作,过滤掉那些不能匹配到的日志行
  • 其工作方式类似于grep,但以分布式方式针对各日志流进行
  • LogQL支持多个行过滤器串联,它们以“与”逻辑进行协同
    • {job="mysql"} |= `error` != `timeout`,表示含有“error”但不包含“timeout”的行

示例

包含POST的行

{container="loki-loggen-apache-common-1"} |= `POST`


不包含POST的行

{container="loki-loggen-apache-common-1"} != `POST`


正则模式匹配请求路径中包含networks的行

{container="loki-loggen-apache-common-1"} |~ `/networks.*`

在上面基础上,同时要求GET方法请求的

{container="loki-loggen-apache-common-1"} |~ `/networks.*` |= `GET`

标签过滤器表达式(label filter expression)

  • 针对那些由“log stream selector”挑选出的各日志流上的每个日志行标签进行过滤操作,过滤掉那些不能匹配到的日志行
  • 可基于原有标签进行过滤,也可先解析日志行内容并转换标签格式,而后再针对解析生的标签进行标签过滤
    • 负责解析并置换标签格式的即为“解析器表达式”
    • Loki支持json、logfmt、pattern、regular expression(regexp)和unpack几种解析器
  • LogQL支持以“and”或者“or”组合多个标签过滤器
    • 其中的“and”,可以用逗号,、空格" "或管道符|替代,它们的意义相同
    • and的优先级高于or,同一优先级的过滤器,执行顺序为由左而右
  • 行过滤器的执行性能要优于标签过滤器,因此,建议要尽可能地将行过滤器放在log pipeline中靠前的位置

标签过滤器需要先用解析表达式或者解析器解析之后,再使用标签过滤器来过滤

这里以json解析器为例

原本的数据

通过json解析器解析后

可以看到,多出了好多标签

然后再使用标签过滤器表达式来过滤


过滤method字段等于POST的日志

{filename="/var/log/generated-logs.txt"} | json | method=`POST`


过滤method字段不等于POST的日志

{filename="/var/log/generated-logs.txt"} | json | method!=`POST`


过滤request字段是以/harness 开头的日志

{filename="/var/log/generated-logs.txt"} | json | request=~`^/harness.*`


过滤status字段大于503的日志

{filename="/var/log/generated-logs.txt"} | json | status > 503

日志查询示例

  • 标签匹配器
    • 查询指定容器中的日志行
      • {filename="/var/log/generated-logs.txt"}
      • {container="getting-started_loggen-apache_1"}
    • 查询满足label matcher所有过滤条件的日志行
      • {filename="/var/log/generated-logs.txt", http_status=~"(4|5).."}
  • 行过滤器
    • 进行日志行过滤,显示“user-identifier”不为“-”的日志行
      • {filename="/var/log/generated-logs.txt", http_status=~"(4|5).."} !~ "\"user-identifier\":\"-\""
    • 进行日志行过滤,显示“user-identifier”不为“-”,且协议为“HTTP/2.0”的日志行
      • {filename="/var/log/generated-logs.txt", http_status=~"(4|5).."} !~ "\"user-identifier\":\"-\"" |= "\"protocol\":\"HTTP/2.0\""
  • 原始标签过滤器(原始标签)
    • 进行日志行过滤,打印“user-identifier”不为“-”,且标签http_method的值为PUT或DELETE的行
      • {filename="/var/log/generated-logs.txt", http_status=~"(4|5).."} !~ "\"user-identifier\":\"-\"" | http_method=~"PUT|DELETE"

解析器表达式(Parser expression)

  • 关于Parser expression
    • 负责解析日志行内容,并从中提取、生成标签
    • 这些解析后生成的标签,可用于“标签过滤器表达式”进行过滤或聚合
    • 提取的标签键会由解析器进行清理,以满足Prometheus指标名称的约定
    • 若出现错误,则日志行不会被过滤,而是会添加一个__error__标签
    • 若提取的标签同原始日志流标签相同,则会在提取的标签键后面附加_extracted后缀
    • 提取后重复出现的标签,仅会保留第一个
  • Loki支持json、logfmt、pattern、regexp和unpack解析器
    • json解析器:解析json格式的日志内容为标签
    • logfmt解析器:解析logfmt格式的日志内容为标签
    • pattern解析器:基于自定义的pattern表达式,从日志行中显式提取字段生成标签
    • regexp解析器:基于自定义的正则表达式,从日志行中显式提取字段生成标签
    • unpack解析器:从promtail的pack stage解压迁入的所有标签

Json解析器

Loki的json解析器,支持两个运行模式

不带参数

  • 添加“| json”到log pipeline,即可提取所有json属性作为标签
  • 嵌套属性中的点号分隔符将被展平为“_”,例如“request.host”,将展平为“request_host”键
  • 数组(值为列表型或字形的数据的属性)会被忽略

原数据

json不带参解析

{filename="/var/log/generated-logs.txt"} | json

带参数

  • 添加| json label="expression", another="expression"到log pipeline,即可提取指定的json属性作为标签
  • 要提取的标签与原始JSON字段相同,则表达式可以写为| json <label>,若表达式返回的是数组或对象,则仍会以 json 格式分配给标签
  • 支持引用数组(值为列表型或对象的数据的属性)里的第一个元素值
  • 所有表达式都必须加引号

只保留指定字段做标签

{filename="/var/log/generated-logs.txt"} | json referer,protocol,bytes

只在原来的基础上多了referer,protocol,bytes三个标签

也可以把获取到的对应值赋给新的标签

{filename="/var/log/generated-logs.txt"} | json abc="referer",bcd="protocol"

将解析后的referer字段赋给标签abc, 把解析后的protocol字段赋给bcd标签


或者多层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 first_server="servers[0]", ua="request.headers[\"User-Agent\"]

最后的结果

"first_server" => "129.0.1.1"
"ua" => "curl/7.68.0"

logfmt解析器

无参数

可以使用 logfmt 解析器添加,并将从 logfmt 格式的日志行中提取所有键和值。

这种 k=v 类型的就是 logfmt 格式

at=info method=GET path=/ host=grafana.net fwd="124.133.124.161" service=8ms status=200

解析器表达式

| logfmt

结果

"at" => "info"
"method" => "GET"
"path" => "/"
"host" => "grafana.net"
"fwd" => "124.133.124.161"
"service" => "8ms"
"status" => "200"

带参数

解析器表达式

| logfmt host, fwd_ip="fwd"

结果

"host" => "grafana.net"
"fwd_ip" => "124.133.124.161"

pattern解析器

pattern解析器语法

  • 添加 | pattern `<pattern-expression>` 到log pipeline,即可从日志内容中显式提取字段创建新标签
  • 指定的表达式<pattern-expression >,要能够同日志行的结构相匹配

示例:apache combined日志解析

  • {container="getting-started_loggen-apache_1"} | pattern `<ip> - <user> <_> "<method> <uri> <_>" <status> <size> <_> "<agent>" <_>`
  • 模式要完全匹配到日志结构

原数据

37.169.170.57 - berge2050 [03/Jun/2024:07:59:09 +0000] "HEAD /harness/expedite/granular/portals HTTP/1.0" 301 5958


只要是中间用空格隔开,就可以使用pattern解析器

{container="loki-loggen-apache-common-1"} | pattern `<ip> - <hostname> [<date_info>] "<method> <referer> <_>"`

解析结果

然后再基于标签过滤器过滤

{container="loki-loggen-apache-common-1"} | pattern `<ip> - <hostname> [<date_info>] "<method> <referer> <_>"` | referer =~ `^/transparent.*`

结果

regexp解析器

regexp解析器语法

  • regexp使用单个参数的语法结构,形如“| regexp ""” ,正则表达式模式需要满足Google RE2的语法规范
  • 提供的正则表达式必须包含至少一个命名子匹配(例如(?Pre)),每个子匹配将提取不同的标签

示例:apache combined日志解析

  • {container="getting-started_loggen-apache_1"} | regexp "^(?P<remote_host>\S+) (?P<user_identifier>\S+) (?P\S+) \[(?P[^\]]+)\] "(?P[^"]+)" (?P\d+) (?P<bytes_sent>\d+) "(?P[^"]+)" "(?P<user_agent>[^"]+)"$"
  • 模式并不要求完全匹配到日志结构
  • 过于复杂,建议使用新式的pattern解析器
{container="loki-loggen-apache-common-1"} | regexp `(?P<IP>[\d\.]+) - (?P<user>[^ ]+) \[(?P<date_time>.*)\] \"(?P<request>.*)\"`

结果

删除和保留标签

例如,用json解析器举例

原数据

{filename="/var/log/generated-logs.txt"} | json 

删除部分标签

表达式删除管道中的给定标签

{filename="/var/log/generated-logs.txt"} | json | drop bytes,filename,method,user_identifier,protocol,referer

保留部分标签

表达式将仅保留管道中的指定标签,并删除所有其他标签。

{filename="/var/log/generated-logs.txt"} | json | keep datetime,host,method,request,status

聚合

指标查询 |Grafana Loki 文档

  • 针对查询出的日志行数进行的聚合计算,称为日志范围聚合
  • 日志范围聚合的方法
    • 给定一个LogQL查询表达式,后跟一个时长,时长表示方式同PromQL
      • 例如:{container="getting-started_loggen-apache_1"}[5m]
    • 对带有时长范围的LogQL查询表达式,进行聚合函数计算
  • 支持的聚合函数
    • rate(log-range):计算选出的每个日志流上的日志条目,在给定时间范围内的生成速率,计算每秒的条目数
    • count_over_time(log-range):统计给定时间范围内每个日志流的条目数,
    • bytes_rate(log-range):计算先出的每个日志流的日志大小在给定时间范围内的平均增长速率
    • bytes_over_time(log-range):计算给定时长范围内选出的每个日志流增长的字节数
    • absent_over_time(log-range):判定选出的每个日志流在指定时间范围内是否存在日志条目,存在日志条目则返回空向量,不存在日志条目,则返回单元素向量;

例子

计算 MySQL 作业过去五分钟内的所有日志行。

count_over_time({job="mysql"}[5m])

统计文件/var/log/generated-logs.txt 状态码为5xx的每个日志流5分钟内的日志条目数

count_over_time({filename="/var/log/generated-logs.txt", http_status=~"5.."}[5m])

内置聚合运行符

PromQL 一样,LogQL 支持内置聚合运算符的子集,这些运算符可用于聚合单个向量的元素,从而生成元素较少但具有聚合值的新向量:

  • sum:计算标签的总和
  • avg:计算标签的平均值
  • min:选择最小标签
  • max:选择标签上的最大值
  • stddev:计算标签上的总体标准差
  • stdvar:计算标签上的总体标准方差
  • count:计算向量中的元素数
  • topk:按样本值选择最大的 k 个元素
  • bottomk:按样本值选择最小的 k 个元素
  • sort:返回按样本值升序排序的向量元素。
  • sort_desc:与排序相同,但按降序排序。

聚合运算符可用于聚合所有标签值,也可以通过包含一个或一个子句来聚合一组不同的标签值:without``by

语法表达式

<aggr-op>([parameter,] <vector expression>) [without|by (<label list>)]

分组聚合:先分组、后聚合

  • without:从结果向量中删除由without子句指定的标签,未指定的那部分标签则用作分组标准;
  • by:功能与without刚好相反,它仅使用by子句中指定的标签进行聚合,结果向量中出现但未被by子句指定的标签则会被忽略;
    • 为了保留上下文信息,使用by子句时需要显式指定其结果中原本出现的job、instance等一类的标签

例子

以status分组统计st不为2xx或3xx的日志数量和

sum(count_over_time({container="loki-loggen-apache-common-1"} | pattern `<ip> - <user> [<date_tiem>] "<request>" <st> <other>` | st !~ `(2|3)..` [5m])) by (status)
posted @ 2024-07-20 09:39  厚礼蝎  阅读(168)  评论(0编辑  收藏  举报