iT邦幫忙

2022 iThome 鐵人賽

DAY 23
5
DevOps

淺談DevOps與Observability系列 第 23

Loki LogQL - Log Queries

  • 分享至 

  • xImage
  •  

今天先來聊聊LogQL
在Grafana上操作顯示, 會比較有感

LogQL(Language to Query Logs from Loki) 是Loki用的查詢語法, 語法格式與操作收到PromQL啟蒙很深.

使用上很像是個分散式的grep log聚合檢視器. 採用pipeline的形式在串接.

LogQL用Label與Operator來進行過濾.

語法查詢上分為兩個部分

  1. Log queries : 對本文內容進行過濾查找, 並以stream的形式傳回結果
  2. Metric queries : 對log本文查詢進行擴展並允許基於查找結果來進行值的計算或聚合

利用這兩個部份就能在LogQL中組合出我們想要的功能

Log queries

Log Stream Selector

一個基本的Log查詢通常會由

Stream selector和log pipeline所組成

每一個查詢會包含一個stream selector, 通常會跟著log pipeline, 將值給傳遞下去.
每個expression表達式可以過濾,或者剖析, 甚至修改log內容或其標籤.

一個查詢動作就是把這兩個類型, 依據前後執行順序給組合起來就是

舉個例子

{container="query-frontend",namespace="loki-dev"} |= "metrics.go" | logfmt | duration > 10s and throughput_mb < 500

這查詢由

  • 1個log stream slector{container="query-frontend",namespace="loki-dev"}, 該selector指定了query-frontendcontainer在loki-devnamespace內的資料.
  • 1個log pipeline| logfmt | duration > 10s and throughput_mb < 500這組pipeline用來過濾有包含metrics.go的log, 然後將整行轉成logfmt的格式, 最後題取出durationthroughput_mb的label進行條件過濾.

Log stream slector

前後用{ xxxx }大括號包起來.
像上述範例{container="query-frontend",namespace="loki-dev"}
這跟PromQL的selector是一樣的形式.
http_requests_total{job="prometheus",group="canary"}
PromQL也是這樣使用label matcher, 找到job跟group這兩個label與對應值一樣的metrics data.

=Operator都一樣

  • =
    • 完全一樣
  • !=
    • 不一樣
  • =~
    • 正則表達式的匹配
  • !~
    • 正則表達式的不匹配

正則表達式舉例

//匹配mysql開頭的
{name =~ "mysql.+"}
//匹配不是mysql開頭的
{name !~ "mysql.+"}
//匹配不是mysql-開頭且後面有數字的
{name !~ `mysql-\d+`}

Log Pipeline

Log Pipeline expression執行方向為從左到右的順序執行.

Log pipeline expression能分成有三大類expression

  • Filtering expressions
    • Line filter expressions
      • |= 、 != 、 |~ 和 !~
      • 舉例 : {job="mysql"} |= "error" != "timeout", 對log內容篩選出有error, 又沒有timeout的
    • Label filter expressions
      • 字串類型的label, 可以用|= 、 != 、 |~ 和 !~
      • Duration, Number, Byte類型的可以用== 、 = 、 != 、 > 、 >= 、 < 、 <=
      • 舉例 : | duration >= 20ms or size == 20kb and method!~"2..", 篩選duration label的值 >= 20ms 或者 size label == 20kb 且 method不是2開頭的

  • Parsing expressions
    • 用來解析跟提取log內容, 並且把提取出來的內容, 做成新的label, 就能對這些label進行過濾或聚合操作
    • 支援的parsers有
      • JSON
      • logfmt
      • pattern
      • regexp
      • unpack

舉例:
如果我們的log格式是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",
    },
    "response": {
        "status": 401,
        "size": "228",
        "latency_seconds": "6.031"
    }
}

我們就能用| json JSON parsing expression,
它會把json的階層, 做扁平化, 用_給銜接每一層的key.

"protocol" => "HTTP/2.0"
"request_time" => "6.032"
"request_method" => "GET"
"request_host" => "foo.grafana.net"
"request_size" => "55"
"response_status" => "401"
"response_size" => "228"
"response_size" => "228"

我們就能夠對這些label做處理了
| json | line_format "{{.response_status}} == "401"

舉例2:
也能用來把選到的label的值, 附值給變數

{
    "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"
    }
}

針對上面的log, 執行以下的json parsing expression
| json first_server="servers[0]", ua="request.headers[\"User-Agent\"]

會得到下列的labels

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

其他的parser就差不多不多說了

  • Formatting expressions
    • Line format expressions
    • Label format expressions
      • 針對的是label內容進行改寫

舉例: Line format expressions
本來的log內容,
hmm...很好看阿XD

level=info ts=2020-10-23T20:32:18.094668233Z caller=metrics.go:81 org_id=29 traceID=1980d41501b57b68 latency=fast query="{cluster=\"ops-tools1\", job=\"cortex-ops/query-frontend\"} |= \"query_range\"" query_type=filter range_type=range length=15m0s step=7s duration=650.22401ms status=200 throughput_mb=1.529717 total_bytes_mb=0.994659
level=info ts=2020-10-23T20:32:18.068866235Z caller=metrics.go:81 org_id=29 traceID=1980d41501b57b68 latency=fast query="{cluster=\"ops-tools1\", job=\"cortex-ops/query-frontend\"} |= \"query_range\"" query_type=filter range_type=range length=15m0s step=7s duration=624.008132ms status=200 throughput_mb=0.693449 total_bytes_mb=0.432718

執行下述line format expression後

| line_format "{{ .ts}}\t{{.duration}}\ttraceID = {{.traceID}}\t{{ printf \"%-100.100s\" .query }} "

結果

2020-10-23T20:32:18.094668233Z  650.22401ms     traceID = 1980d41501b57b68  {cluster="ops-tools1", job="cortex-ops/query-frontend"} |= "query_range"
2020-10-23T20:32:18.068866235Z  624.008132ms    traceID = 1980d41501b57b68  {cluster="ops-tools1", job="cortex-ops/query-frontend"} |= "query_range"

舉例二 :
原來的label樣子是這樣

ithome=14

透過下述expression =

| label_format iThome=ithome

結果就

iThome=14

組合技範例

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"

透過expression

{job="loki-ops/query-frontend"} | logfmt | line_format "{{.msg}}" | regexp "(?P<method>\w+) (?P<path>[\w|/]+) \((?P<status>\d+?)\) (?P<duration>.*)"

這就是說先透過log stream sellector , 找到job是loki-ops/query-frontend
然後轉格式成logfmt
接著透過line_format改寫log, 只輸出msg欄位, 就是POST /api/prom/api/v1/query_range (200) 1.5s
最後透過regexp, 進行label的提取與新增.

最後結果就

method: POST
path: /api/prom/api/v1/query_range
status: 200
duration: 1.5s

今日小心得

hmm...會先講LogQL, 是因為查找log的情況比較多.
今天先講LogQL中的查找log.
官網有提供更多範例here
能自己試著玩看看

參考資料

LogQL - Log Queries
logfmt


上一篇
淺談Grafana Loki的發展與架構
下一篇
Loki LogQL - Metric Queries
系列文
淺談DevOps與Observability36
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言