loki的日誌查詢

厚礼蝎發表於2024-07-20

執行簡單查詢

獲取特定標籤相關的日誌

{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)

相關文章