使用 Loki 收集 nginx 日志
之前日志服務用的較多的一般是ELK,EFK,graylog等,但這些日志由java編寫,運行需要jdk,而且配置上面,還是有點復雜,比如需要對日志需要寫grok將復雜的日志進行匹配,好在后面出了可以根據(jù)分隔符的方式進行日志的提取,也就是dissect插件,可以根據(jù)分隔符進行分割。
ELK在日志方面給我的感覺是大而全,查詢匹配是杠杠的,Kibana圖表非常豐富。但如果面對大量的數(shù)據(jù),需要查詢,在不堆機器的情況下,還是會比較疲軟,查詢比較慢,之前公司每當突發(fā)流量的時候,由于日志寫入比較大,隊列都在kafka,es消費慢,導致無法實時出數(shù)據(jù)。
Loki 受到了 prometheus啟 發(fā),對日志進行打標簽的方式而非全文索引的方,而且也可以跟kubernetes集成。
Like Prometheus, but for logs!
1安裝Loki(使用Local方式)
安裝和運行
https://github.com/grafana/loki/releases/
找到要安裝的版本,我采用的是v2.1.0
下載Loki和Promtail, (Loki為日志的引擎,通過Promtail來發(fā)送日志到Loki)
在本機找一個目錄存放這兩個2進制文件
下載兩者的配置文件
wget https://raw.githubusercontent.com/grafana/loki/master/cmd/loki/loki-local-config.yaml
wget https://raw.githubusercontent.com/grafana/loki/master/cmd/promtail/promtail-local-config.yaml使用如下命令啟動Loki
./loki-linux-amd64 -config.file=loki-local-config.yamlroot@test:~$cd /usr/local/loki/
root@test:/usr/local/loki$ls
loki-linux-amd64 loki-local-config.yaml promtail-linux-amd64 promtail-local-config.yaml
root@test:/usr/local/loki$./loki-linux-amd64 -config.file=loki-local-config.yaml
2嘗試搜集nginx日志
所以首先對nginx默認的日志進行改造,讓他以json的方式進行輸出到目錄,然后用Promtail對其進行讀取。 讀取使用LogQL的json方式去讀取,這個LogQL內容填寫在grafana中。
nginx的部分配置改造
虛擬server配置
server {
server_name loki.test.com; # 域名設置
listen 8888;
access_log /var/log/nginx/loki_access.log promtail_json;
location / {
return 200 "It's ok!";
}
}
promtail_json日志格式配置
log_format promtail_json '{"@timestamp":"$time_iso8601",'
'"@version":"Promtail json",'
'"server_addr":"$server_addr",'
'"remote_addr":"$remote_addr",'
'"host":"$host",'
'"uri":"$uri",'
'"body_bytes_sent":$body_bytes_sent,'
'"bytes_sent":$body_bytes_sent,'
'"request":"$request",'
'"request_length":$request_length,'
'"request_time":$request_time,'
'"status":"$status",'
'"http_referer":"$http_referer",'
'"http_user_agent":"$http_user_agent"'
'}';
訪問127.0.0.1:8888,觀察日志已經正常輸出為json格式,請保證該json格式正確。
root@test:/etc/nginx/conf.d$tail -f /var/log/nginx/loki_access.log
{"@timestamp":"2021-03-06T01:54:42-05:00","@version":"Promtail json","server_addr":"127.0.0.1","remote_addr":"192.168.65.130","host":"127.0.0.1","uri":"/","body_bytes_sent":8,"bytes_sent":8,"request":"GET / HTTP/1.1","request_length":78,"request_time":0.000,"status":"200","http_referer":"-","http_user_agent":"curl/7.29.0"}
nginx日志改造完畢
Promtail配置文件修改
因為搜集日志是Promtail處理,所以自然而然是需要根據(jù)自己需求來配置Promtail的配置文件。
server:
http_listen_port: 9080
grpc_listen_port: 0
positions:
filename: /tmp/loki-positions.yaml # 記錄pos點
sync_period: 5s # 5s一次將當前讀取到的pos點同步至filename配置的文件內
clients:
- url: http://localhost:3100/loki/api/v1/push
scrape_configs:
- job_name: Loki
static_configs:
- labels: # 設定的部分標簽
job: Loki-nginx
host: localhost
app: nginx
__path__: /var/log/nginx/loki_access.log # 待讀取的nginx日志
LogQL json部分文檔理解
json的提取分為兩種方式,帶參數(shù)和不帶參數(shù)
不帶參數(shù)的方式
使用 |json來提取日志的json內容,前提是json內容為有效json格式。嵌套的字段會用”_”將內外層的key進行拼接。 忽略數(shù)組。
看一下官網中不帶參數(shù)方式的樣例
{
"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"
"response_status" => "401"
"response_size" => "228"
"response_size" => "228"
從輸出能看到,原本request字段內容為嵌套,所以request里面的內容的key驗證了如上第二點,使用”_”進行了拼接。同時servers由于是個數(shù)組,所以在解析后直接丟棄了servers這個key,驗證了第三點。
帶參數(shù)的方式
帶參數(shù)的方式,json只會根據(jù)參數(shù)來解開需要的部分(當單條json數(shù)據(jù)比較大的時候應該能省很多資源)。 使用| json label=”expression”, another=”expression”的方式來編寫該方法??梢源嬖诙鄠€參數(shù)
看一下官網中帶參數(shù)方式的樣例
使用| json first_server=”servers[0]”, ua=”request.headers["User-Agent"]進行提取
{
"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"
}
}
輸出結果為:
"first_server" => "129.0.1.1"
"ua" => "curl/7.68.0"
first_server和ua都為原先參數(shù)中指定的key
如果要提取整個對象,可以使用| json server_list=”servers”, headers=”request.headers 這樣就能得到如下輸出:
"server_list" => `["129.0.1.1","10.2.1.3"]`
"headers" => `{"Accept": "*/*", "User-Agent": "curl/7.68.0"}`
嘗試寫一條LogQL表達式
一條完整的LogQL表達式由兩部分構成:
a log stream selector,可以理解為,通過設定的label去匹配要抓取哪些日志。 a log pipeline,可以理解為表達式。比如json的提取。
比如如下表達式
{container="query-frontend",namespace="tempo-dev"} |= "metrics.go" | logfmt | duration > 10s and throughput_mb < 500
`{container=”query-frontend”,namespace=”tempo-dev”}`` 部分為log stream selector,后面部分為log pipeline。
編寫一個簡單的nginx日志需求
Loki-nginx日志中狀態(tài)碼為200的條數(shù)。 根據(jù)當前選定時間范圍,自動調整。
思考:
如何指定Loki-nginx,可以使用log stream selector的表達式來選定。 nginx日志已經轉變?yōu)榱薺son,所以可以用 |json來提取。如何獲取status字段的信息? |json后面直接跟隨|status即可,即 |json|status。如何根據(jù)當前選定的時間范圍?使用內置變量 [$\_\_interval]。條數(shù)該得用什么方法獲得?LogQL有內置函數(shù) count_over_time配合sum,這邊需要注意的是count_over_time是根據(jù)指定時間范圍返回日志條目的具體內容,所以還需要配合sum獲得時間段內的總數(shù)。
編寫:
首先選定Loki-nginx的日志, {job="Loki-nginx"}。使用count_over_time函數(shù)配合 [$\_\_interval]來獲取總共的條數(shù)。count_over_time({job="Loki-nginx"}[$\_\_interval])過濾status字段,讓其等于200,表達式 count_over_time({job="Loki-nginx"} | json | status = 200 [$\_\_interval]),此時會報錯,因為status為字符串,可以添加__error__=””讓其忽略轉換出現(xiàn)的異常。得到count_over_time({job="Loki-nginx"} | json | status = 200 \_\_error\_\_="" [$\_\_interval])此時在grafana上顯示為多條數(shù)據(jù),配合sum得到單獨一個數(shù)值。 最終的表達式為: sum(count_over_time({job="Loki-nginx"} | json | status = 200 __error__="" [$__interval]))

原文鏈接:https://kirakirazone.com/2021/03/06/Loki%E6%97%A5%E5%BF%97%E6%9C%8D%E5%8A%A101/
