今天先來聊聊LogQL
在Grafana上操作顯示, 會比較有感
LogQL(Language to Query Logs from Loki) 是Loki用的查詢語法, 語法格式與操作收到PromQL啟蒙很深.
使用上很像是個分散式的grep log聚合檢視器. 採用pipeline的形式在串接.
LogQL用Label與Operator來進行過濾.
語法查詢上分為兩個部分
利用這兩個部份就能在LogQL中組合出我們想要的功能
一個基本的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
這查詢由
{container="query-frontend",namespace="loki-dev"}
, 該selector指定了query-frontend
container在loki-dev
namespace內的資料.| logfmt | duration > 10s and throughput_mb < 500
這組pipeline用來過濾有包含metrics.go
的log, 然後將整行轉成logfmt的格式
, 最後題取出duration
與throughput_mb
的label進行條件過濾.前後用{ 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 expression執行方向為從左到右的順序執行.
Log pipeline expression能分成有三大類expression
|= 、 != 、 |~ 和 !~
{job="mysql"} |= "error" != "timeout"
, 對log內容篩選出有error, 又沒有timeout的|= 、 != 、 |~ 和 !~
== 、 = 、 != 、 > 、 >= 、 < 、 <=
| duration >= 20ms or size == 20kb and method!~"2.."
, 篩選duration label的值 >= 20ms 或者 size label == 20kb 且 method不是2開頭的舉例:
如果我們的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就差不多不多說了
舉例: 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
能自己試著玩看看