使用 Loki + Promtail + Grafana 搭建輕量級容器日志分析平臺
摘要
本文介紹如何通過 Docker Compose 快速搭建 Loki 日志存儲、Promtail 日志采集和 Grafana 日志可視化/告警的完整流程。用最小化示例演示核心配置、常見問題排查和告警規則設置,幫助讀者快速上手。
1、部署步驟
當前環境:
- host4:192.168.0.224 (已部署prometheus+grafana) 上期搭建過程
- host1:192.168.0.221(Web主機, 已部署tomcat, mysql)
1.1、部署server端
1.1.1. 創建Loki專用docker-compose文件(host4)
#創建網絡
docker network create monitor-netmkdir -p /opt/monitor/loki && cd /opt/monitor/loki
nano docker-compose.yml
services:# 新增Loki日志服務loki:image: grafana/loki:3.4.1container_name: lokirestart: unless-stoppednetworks:- monitor-netports:- "3100:3100" # HTTP 接口(讀取/寫入日志)- "9096:9096" # GRPC 端口(可選對外暴露給開發者調試)command:- "--config.file=/etc/loki/local-config.yml"- "--config.expand-env=true"- "--target=all" #讓 Loki 在單體(monolithic)模式下啟動,所有組件都在同一個進程里
# - "--pattern-ingester.enabled=false" # 直接禁用volumes:- /etc/localtime:/etc/localtime:ro- /etc/timezone:/etc/timezone:ro- loki_data:/loki- ./loki-config.yml:/etc/loki/local-config.yml:ro
# - /data:/data # 掛載整個數據目錄environment:- TZ=Asia/Shanghaihealthcheck:test: ["CMD-SHELL", "wget -qO- http://localhost:3100/ready > /dev/null || exit 1"]interval: 15stimeout: 10sretries: 30start_period: 90s # 給 Loki 更多啟動時間deploy:resources:limits:cpus: '1' # 中等負載日志量memory: 2G # 保留緩沖區空間# 新增Promtail(采集host4自身日志)promtail:image: grafana/promtail:3.4.1user: "0:996"container_name: promtailrestart: unless-stoppednetworks:- monitor-netvolumes:- /etc/localtime:/etc/localtime:ro- /etc/timezone:/etc/timezone:ro- /var/lib/docker/containers:/var/lib/docker/containers:ro- /var/run/docker.sock:/var/run/docker.sock:ro- ./promtail-config.yml:/etc/promtail/config.ymlcommand: - "-config.file=/etc/promtail/config.yml"environment:- TZ=Asia/Shanghai- LOKI_URL=http://loki:3100depends_on:loki:condition: service_healthydeploy:resources:limits:cpus: '0.3'memory: 300Mvolumes:loki_data:
networks:monitor-net:external: true
小結:將宿主機時區掛載到容器,避免時間偏移;Loki 部署在 host4,Promtail 同時采集本機日志。
1.1.2. 配置文件詳解
1.Loki配置 (loki-config.yml
):
auth_enabled: falseserver:http_listen_port: 3100grpc_listen_port: 9096log_level: debuggrpc_server_max_concurrent_streams: 1000common:instance_addr: 127.0.0.1path_prefix: /tmp/lokistorage:filesystem:chunks_directory: /tmp/loki/chunksrules_directory: /tmp/loki/rulesreplication_factor: 1ring:kvstore:store: inmemoryquery_range:results_cache:cache:embedded_cache:enabled: truemax_size_mb: 100limits_config:reject_old_samples: false # 允許舊時間戳日志reject_old_samples_max_age: 168h # 接受7天內的舊日志metric_aggregation_enabled: trueschema_config:configs:- from: 2020-10-24store: tsdbobject_store: filesystemschema: v13index:prefix: index_period: 24hpattern_ingester:enabled: true # 禁用此組件(單體模式不需要)metric_aggregation:loki_address: localhost:3100ruler:alertmanager_url: http://localhost:9093frontend:encoding: protobuf
生產建議:
- 將
filesystem
改為s3
或gcs
- 添加
retention_period: 720h
# 日志保留30天
3. Grafana數據源配置 (grafana-datasources.yml
):
nano grafana-datasources.yml
apiVersion: 1
datasources:
- name: Lokitype: lokiaccess: proxyurl: http://loki:3100isDefault: falseversion: 1jsonData:maxLines: 2000derivedFields: # 日志關聯追蹤(關鍵配置!)- datasourceUid: 'prometheus' # 關聯PrometheusmatcherRegex: 'traceID=(\w+)' # 從日志提取TraceIDname: 'TraceID'url: '$${__value.raw}' # 跳轉到Trace查看
作用:實現日志與分布式追蹤(Jaeger/Tempo)的聯動
4. Promtail配置(host4自身日志)
nano promtail-config.yml
server:http_listen_port: 9080grpc_listen_port: 0 # 禁用gRPCpositions:filename: /tmp/positions.yamlclients:- url: http://loki:3100/loki/api/v1/pushscrape_configs:- job_name: host1_dockerlog # Docker服務發現docker_sd_configs:- host: unix:///var/run/docker.sockrefresh_interval: 15srelabel_configs: # 標簽重寫規則- source_labels: ['__meta_docker_container_name']regex: '/(.*)' # 提取容器名(去除前綴/)target_label: 'container'action: replace- source_labels: ['__meta_docker_container_log_stream']target_label: 'logstream'pipeline_stages:- docker: {}- job_name: host4_syslogstatic_configs:- targets: [localhost]labels:job: sysloghost: host4__path__: /var/log/*log
標簽意義:
host
: 服務器標識
container
: 容器名稱
job
: 日志類型(syslog/docker)
1.1.3.啟動容器 Loki 與 Promtail
cd /opt/monitor/lokidocker compose up -d
驗證:
docker exec loki curl -s http://localhost:3100/ready
應返回ready
。docker logs promtail | grep -i error
確認無明顯報錯。
1.1.4. 監控Loki自身
在Prometheus配置中prometheus.yml
添加:
scrape_configs:- job_name: 'loki'static_configs:- targets: ['loki:3100'] # 容器內地址- job_name: 'promtail'static_configs:- targets: ['promtail:9080'] # 容器內地址
1.2、多主機采集配置
1.2.1. 在host1創建Promtail配置
cat > promtail-config-host1.yml <<EOF
server:http_listen_port: 9080positions:filename: /tmp/positions.yamlclients:- url: http://192.168.0.224:3100/loki/api/v1/push # 使用IP地址確保可達scrape_configs:
- job_name: host1_dockerlogdocker_sd_configs:- host: unix:///var/run/docker.sockrefresh_interval: 15srelabel_configs:- source_labels: ['__meta_docker_container_name']regex: '/(.*)'target_label: 'container'replacement: '$1'action: replace- source_labels: ['__meta_docker_container_log_stream']target_label: 'logstream'pipeline_stages:- docker: {}- job_name: host1_syslogstatic_configs:- targets: [localhost]labels:job: sysloghost: host1__path__: /var/log/**/*.log # 遞歸匹配所有日志- job_name: tomcat-host1static_configs:- targets: [localhost]labels:job: tomcathost: host1app: webapp__path__: /tomcat_logs/*.log # 使用掛載路徑pipeline_stages:- regex:expression: '^(?P<timestamp>\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}.\d{3}) (?P<level>\w+)'- labels:level:
clients.url
填寫 Loki-Service 的 IP__path__
確保只采集.log
文件,避免提取到.gz
產生“timestamp too old”報錯
1.2.2. 啟動host1的Promtail
nano docker-compose.yaml
services:promtail_host1:image: grafana/promtail:3.4.1container_name: promtail_host1network_mode: hostrestart: unless-stoppedvolumes:- /etc/localtime:/etc/localtime:ro- /etc/timezone:/etc/timezone:ro- /var/lib/docker/containers:/var/lib/docker/containers:ro- /var/run/docker.sock:/var/run/docker.sock:ro- /var/log:/var/log:ro # 系統日志目錄- /var/lib/docker/volumes/app_tomcat_logs/_data:/tomcat_logs:ro # Tomcat 日志目錄- ./promtail-config-host1.yml:/etc/promtail/config.ymlcommand:- "-config.file=/etc/promtail/config.yml"- "-config.expand-env=true"environment:TZ: "Asia/Shanghai"
docker compose up -d
或者直接啟動
docker run -d --name promtail_host1 \--network=host \-v /etc/localtime:/etc/localtime:ro \-v /etc/timezone:/etc/timezone:ro \-v /var/lib/docker/containers:/var/lib/docker/containers:ro \-v /var/run/docker.sock:/var/run/docker.sock:ro \-v /var/log:/var/log:ro \-v /var/lib/docker/volumes/app_tomcat_logs/_data:/tomcat_logs:ro \-v ./promtail-config-host1.yml:/etc/promtail/config.yml \grafana/promtail:3.4.1 \-config.file=/etc/promtail/config.yml
注意:記得掛載
/etc/localtime
,確保容器時間與宿主機一致。
檢查Promtail狀態
docker logs -f promtail_host1
docker logs promtail_host1 --tail 50 | grep -i "error\|warn"
1.3 驗證
需安裝jq
apt update && apt install -y jq
1.3.1. 檢查標簽集合是否完整
#檢查標簽集合
curl -sG "http://localhost:3100/loki/api/v1/label/host/values" | jq
# 應該輸出"status": "success","data": ["host1","host4"]
# 查詢所有標簽鍵(在host4執行)
curl -sG "http://localhost:3100/loki/api/v1/labels" | jq
#應輸出"status": "success","data": ["app","container","filename","host","job","logstream","service_name"]
1.3.2. 驗證主機日志采集
# 檢查host1的日志是否采集成功(在host4執行)
curl -sG "http://localhost:3100/loki/api/v1/query_range" \--data-urlencode 'query={host="host1"}' \--data-urlencode 'limit=5' | jq '.data.result[].values'
1.3.3. 檢查容器日志
# 檢查Tomcat日志(在host4執行)
curl -sG "http://localhost:3100/loki/api/v1/query_range" \--data-urlencode 'query={container="ry-tomcat"}' \--data-urlencode 'limit=5' | jq# 檢查MySQL錯誤日志
curl -sG "http://localhost:3100/loki/api/v1/query_range" \--data-urlencode 'query={container="ry-mysql", logstream="stderr"}' \--data-urlencode 'limit=5' | jq
1.3.4.其他調試
1.網絡連通性測試:
# 在host1上測試連接Loki
docker exec promtail_host1 curl -v http://192.168.0.224:3100/ready
2.promethus Target 狀態
https://192.168.0.224/prometheus/targets
3.grafana 數據源狀態
https://192.168.0.224/grafana/connections/datasources/
2、配置Grafana日志分析
2.1、Grafana配置
2.1.1:創建基礎日志查詢
- 訪問Grafana:http://192.168.0.224:3000
- 左側菜單 ? Explore ? 選擇Loki數據源
- 輸入查詢:
{host="host1"}
- 點擊"Run query"查看所有host1的日志
2.1.2:創建專用儀表板
- 左側菜單 ? Dashboards ? New ? Import
- 導入預置的Loki儀表板(ID:
13639
)- 這將自動創建完整的日志監控儀表板-設置-保存
2.1.2:配置錯誤日志實時監控面板
- 在查詢編輯區:
- 數據源選擇:Loki
- 輸入查詢語句:
{host="host1"} |~ "(?i)error|exception|fail"
- 點擊 Run query
- 右側面板配置:
- Visualization 選擇:Logs
- Options 選項卡:
- Show log details:ON
- Deduplication:Exact match
- Panel options:
- Title:輸入 “Host1 錯誤日志”
- Description:添加描述(可選)
- 點擊右上角 Apply 保存面板
2.1.3:添加 Tomcat 應用日志分析面板
-
在儀表板頁面,點擊頂部 Add panel 圖標(+號)
-
選擇 Add new panel
-
配置查詢:
{app="webapp"} | logfmt | detected_level != "info"
怎么確定哪些字段(labels)可用?
-
curl -sG "http://localhost:3100/loki/api/v1/query_range" --data-urlencode 'query={host="host1"}' --data-urlencode 'limit=1' | jq '.data.result[]' {"stream": {"detected_level": "unknown","filename": "/var/log/auth.log","host": "host1","job": "syslog","service_name": "syslog"},
-
可以隨便點擊一條日志可以看到Fields
-
-
右側配置Extract fields(可選):
- Visualization:Logs
- 在 Transform 選項卡:
- 搜索 ? Extract fields
- 選擇 logfmt 解析器
配置項 說明 Source 選擇你日志的源字段:– 如果日志行在“Line”字段里,就選 Line
。– 如果日志在某個自定義 label,下拉里也能看到labels.<xxx>
,但一般默認Line
。Format 如果你日志是 JSON,就選 JSON
;如果你的日志行是以 key=value 形式(logfmt)寫的,就選Key+value pairs
;如果不確定,可以先試Auto
,Grafana 會自動探測。+ Add path 點擊后會出現一個新的行,用來指定要抽取的字段名。 Replace all fields 通常保持關閉(Off)。關閉時,它會保留原來的所有列,只把新抽取出來的字段加進來。開了之后,面板里只會剩下抽取后的字段。 - 只有當你希望拆出“labels 里沒有、卻只在原文文本里出現的那段”時,才考慮用 Extract fields 從“Line”里做復雜的解析。但絕大部分場景下,生產端/Promtail 端把好用的 KV 先丟到 labels 就已經足夠,Grafana 只要把這些 labels 拆一拆就能用了
設置面板標題:“Tomcat 非 Info 日志” --> Apply
2.1.4:創建日志源分布餅圖
-
再次 Add panel
-
輸入查詢:
sum by (job) (count_over_time({host="host1"}[5m]))
-
可視化配置:
- 右上角Visualization搜索:Pie chart
- Pie chart type:Pie
-
設置標題:“Host1 日志源分布”
2.1.5:使用 “Explore” 功能
-
左側菜單 ? Explore(指南針圖標)
-
選擇 Loki 數據源
-
在查詢框中輸入:
{container="ry-tomcat"} | logfmt | level = "error"
或者手動選擇
Builder
-
點擊 Run query 查看結果
-
點擊右上角 Add to dashboard 可將此查詢保存為面板
- 點擊右上角 “Add to dashboard” → 選擇 “Existing dashboard” → 指定 “Host1 日志監控” → 點擊 “Add”隨后會彈出一個面板編輯的對話框,你可以給它取個名稱(例如 “Tomcat 錯誤等級日志”),調好格式(Logs、Color by level、Deduplication 等),然后點擊 “Apply” 就會把它放到儀表板里。一旦完成,進入儀表板就能看到一個新的“Tomcat 錯誤等級日志”面板。
最后別忘了按儀表板右上角的“Save”圖標,把整個儀表板保存一次
- 輸入名字(例如 “Host1 日志監控”),選擇合適的文件夾,然后點擊 Save。
- 這樣即使下次重啟 Grafana,或者別人在別的瀏覽器打開,你都能看到剛才做的所有面板。
2.2、 為儀表板添加模板變量(Variables),讓它更靈活
目前很多查詢里,你都寫死了 {host="host1"}
、{app="webapp"}
、{job="tomcat"}
。如果后續想讓同一個儀表板用于監控不同主機/不同應用/不同環境,建議改用變量(Dashboard → Settings → Variables)來動態控制。簡單示例:
-
創建
host
變量-
Dashboard 右上角點擊 Settings(齒輪) → Variables → New
-
Name:
host
-
Type: Query
-
Data source: 選擇你的 Loki
-
Query:
- Query type : Label values
- Label : host
這會列出當前時段(Global 時間范圍)內所有打上過
host
標簽的值,比如host1
、host2
等。 -
Preview of values:確認下拉里能看到
host1
。 -
保存。
-
-
創建
job
變量-
同樣再添加一個變量:
-
Name:
job
-
Type: Query
-
Data source: Loki
-
Query:
- Query type : Label values
- Label : job
這一步會列出在選定 host(比如
host1
)范圍內可見的所有 job 標簽值(如tomcat
、syslog
)。
-
-
這樣當你從下拉里先選
host1
,下面job
下拉就會自動只展示host1
上跑過的 job。
-
-
修改面板 Query,讓它使用變量
-
例如,之前寫死的“Tomcat 錯誤日志”面板:
{host="host1", job="tomcat"} |~ "(?i)error|exception|fail"
改成:
{host="$host", job="$job"} |~ "(?i)error|exception|fail"
-
“Host1 日志源分布”餅圖原來是:
sum by (job) (count_over_time({host="host1"}[5m]))
改成:
sum by (job) (count_over_time({host="$host"}[5m]))
-
這樣在儀表板左上角會自動多出兩個下拉菜單
[ host ] [ job ]
,你切換主機或切換 job,所有面板都會聯動刷新。
-
-
再把“Tomcat 應用日志分析”用同樣思路改寫為:
{host="$host", app="$job"} | logfmt | detected_level!="info"
- 或者把
app
、job
再區分開。 - 關鍵點是:所有原來寫死的標簽都改用
${變量名}
,多一個靈活性。
- 或者把
好處:
- 同一個儀表板可以橫向監控多臺主機、多套應用,只要有人在下拉里選,這些面板就自動幫你刷新。
- 也方便后續別人拿這套 Dashboard 只改少量變量就能直接套用,不用再手動跑那些長查詢。
2.3、在 Promtail 端或 Loki pipeline 里提取 msg
2.3.1.在 Loki 查詢中用 regexp
把“消息體”抽到 msg
Label
假設你的 Tomcat 日志長這樣(沒有現成的 msg=
字段,只有純文本):
21-May-2025 20:30:32.098 INFO [main] org.apache.catalina.startup.HostConfig.deployWAR Deploying web application archive [/opt/tomcat/webapps/ROOT.war]
21-May-2025 20:30:32.059 ERROR [main] org.apache.catalina.loader.WebappClassLoaderBase.clearReferencesThreads The web application [ROOT] appears to have started a thread named [quartzScheduler_Worker-7] which has failed to stop it. This is very likely to create memory leak. Stack trace of [quartzScheduler_Worker-7] [Thread-5]
我們想把 Deploying web application archive …
、The web application [ROOT] appears to have started…
這一整段當作 msg
。常見的做法是:
- 先用
| logfmt
處理一遍,保證所有的app="webapp"
、detected_level="error"
之類的 KV 都被拆進了labels
; - 再接一個
| regexp
管道,把]
(也就是最后一個方括號加一個空格)之后的所有字符,用一個 named capture 抓到msg
label。
例如,你當前的查錯誤日志語句(近 30 分鐘)可能是:
{host="host1"} | logfmt | detected_level="error"
要讓它“把 msg
也抽出來”,可以改成:
swift復制編輯{host="host1"}
| logfmt
| detected_level="error"
| regexp "\\] (?P<msg>.*)"
解釋一下這行:
{host="host1"}
:篩選出 Host1 上的所有日志;| logfmt
:把已經打到detected_level
、app
等 KV 拿出來,并存到labels
;| detected_level="error"
:只保留detected_level
是error
的那一條;| regexp "\\] (?P<msg>.*)"
:- 這里的正則
\\]
匹配到日志中最后一個]
(也就是[main]
、包名這種結束的那個),以及緊跟著的一個空格; (?P<msg>.*)
則把這個]
后面所有剩余的字符全部“抓”到一個名叫msg
的 label 里。
- 這里的正則
做完這一步之后,Loki 會把每條符合條件的日志多加一個標簽 msg="<日志正文>"
。你可以在 Grafana → Explore 里把這條管道跑一遍,展開任意一條錯誤日志,檢查 Fields,就會看到多了一個 msg
字段,并且它的值就是那條日志最后面的文字內容。
2.3.2.直接用正則從 syslog 行里提取 msg
假設你的 syslog 行像這樣:
May 21 20:30:32 host1 zabbix_agentd[12345]: UserParameter=/foo/bar Check OK
May 21 20:31:00 host1 zabbix_agentd[12345]: ERROR: something went wrong
最簡單的做法是在 Loki 查詢里,用:
{host="host1", job="syslog"}
| regexp ": (?P<msg>.*)"
意思是:
:
匹配到冒號和一個空格(因為進程后面總是 “進程名[PID]: ”),(?P<msg>.*)
則把余下全部當作msg
這個標簽。
完整示例:
{host="host1", job="syslog"}
| regexp ": (?P<msg>.*)"
|~ "(?i)error"
解釋:
{host="host1", job="syslog"}
:先把所有 Host1 上的 syslog 日志挑出來;| regexp ": (?P<msg>.*)"
:把每行中 “冒號 空格 后面剩余的那段” 都存到labels.msg
;|~ "(?i)error"
:按正則再只保留包含(不區分大小寫)“error” 的行。
在 Explore 里測試:
-
切到 Explore → 選 Loki 數據源 → Logs 模式,把上面三行拷進去,點 Run query。
-
下方返回的日志行,點擊任一行最右側的“>”展開它,你會看到:
Fields ───────────────────────── host host1 job syslog msg ERROR: something went wrong
如果有多種情況,第一行的
msg
也會是 “UserParameter=/foo/bar Check OK” 這種。只要能看到msg
,說明我們的正則生效了。
2.3.3、在 Promtail/Loki Pipeline 里預先提取 msg
有時候我們會把“提取消息體”這件事提前放在 Promtail 端做,或者在 Loki 的 pipeline_stages
(如果你在 Loki 單獨搭了一個 ingester pipeline)里就已幫你把 msg
做成了 label。此時,Grafana 就可以跳過“使用 regexp
抽取 msg” 這一步,直接用 TopK 去統計現成的 msg
。
舉例:如果你在 Promtail 的配置里,已經用過 json
stage 或者 regex
stage,把像下面這樣的配:
scrape_configs:
- job_name: tomcat-host1static_configs:- targets: [ localhost ]labels:job: tomcathost: host1app: webapp__path__: /tomcat_logs/*.logpipeline_stages:- regex:expression: '\\] (?P<msg>.*)' # 比如,提取日志中“] ”后面的全部內容到 msg
# expression: '^(?P<timestamp>\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}.\d{3}) (?P<level>\w+)'- labels:msg:
# level:
#測試
echo "03-Jun-2025 11:00:00.000 INFO [main] com.example.Test Something happened MSG_TAG" >> /var/lib/docker/volumes/app_tomcat_logs/_data/test4.log
那么進到 Loki 里的每條日志就自動帶有 msg="<正文>"
這個標簽。此時,你在 Grafana 里只要寫:
topk(10, sum by (msg) (count_over_time({host="$host", job="tomcat", detected_level="error"}[30m])
))
就能直接拿到 Top 10 的 msg
次數,無需再在 Query 里加 | regexp
。然后同樣把它可視化為 Table,Field Name
用 ${msg}
,就能列出“過去 30 分鐘最熱的 10 條錯誤消息”。
?? 注意事項
避免 label 爆炸:
把 level
做成 label 沒問題,因為值有限(INFO、ERROR、WARN 等),但 msg
做 label 不建議用于大規模生產環境,因為它的值太多樣,容易導致 label cardinality 爆炸。建議只在調試期間用 msg
做 label。
2.4、增加更細化的統計面板
除了“錯誤日志”、“非 Info 日志”、“日志源分布”,可以再添加一些下列常見的、對運維和分析很有幫助的面板:
2.4.1. 日志量趨勢折線圖
-
Query:
sum by (job) (rate({host="$host"}[1m]))
或者
sum by (job) (count_over_time({host="$host"}[1m]))
這個指標能實時顯示各 job 在“過去 1 分鐘”里的日志條數變化趨勢,比餅圖側重“時間序列”更直觀。
-
Visualization:選擇 Time series(或 Graph),設置 legend 為
${job}
。 -
這樣可以觀察日志峰值、流量突增、平穩期等。
2.4.2.各級別日志分布柱狀圖(Bar chart)
-
Query:
sum by (detected_level) (count_over_time({host="$host", job="$job"}[5m]))
- 這會告訴你在所選主機+應用下,“過去 5 分鐘”里各個
ERROR
/WARN
/INFO
/DEBUG
級別日志的數量。
- 這會告訴你在所選主機+應用下,“過去 5 分鐘”里各個
-
Visualization:選擇 Bar chart → X 軸填
detected_level
,Y 軸是 數量。 -
既能看到“錯誤日志劇增”,也能做服務健康度的大致評估。
-
Top N 錯誤消息(Table)
-
如果你已經在 Loki pipeline 或 在 Grafana Transform 里把“消息”字段(msg)拆出來,那就可以做一個 Table 面板,列出“過去 30 分鐘最常出現的 10 條錯誤文本”:
topk(10, sum by (msg) (count_over_time({host="$host", detected_level="error"}[30m])))
-
Visualization:Table
-
這樣你一眼就能看到,當前系統里“最熱”的那幾條錯誤堆棧或關鍵字。
-
-
異常數量告警閾值面板(Stat/Gauge)
-
Query:
sum by (job) (count_over_time({host="$host", detected_level="error"}[1m]))
或者
sum(count_over_time({host="$host", detected_level="error"}[5m]))
-
Visualization:Stat(或者 Gauge)
-
配置一個閾值:比如當“過去 1 分鐘錯數量 > 5”時變紅、提示報警。
-
也可以把它做成 Alert 規則,當數值超過閾值時觸發通知。
-
-
日志大小/行數占比面板(Bar gauge)
-
Query:
sum by (job, detected_level) (count_over_time({host="$host"}[5m]))
-
然后把 Visualization 選 Time series 并打開 “Stacking” → “Normal” 模式,這樣能看到在一段時間里,各 job、各級別的日志是如何堆疊,哪個 job 占的百分比多、哪個級別最熱。
-
3、為關鍵面板添加告警(Alerting)
3.1、配置 Grafana SMTP(編輯 grafana.ini)
Grafana 要能發郵件,必須在其配置文件 grafana.ini
中啟用并填寫 smtp
段。下面以“用 Docker Compose 方式部署 Grafana”為例,演示如何掛載、修改和重啟。
3.1.1. 準備一個自定義的 grafana.ini
-
編輯配置文件
mkdir -p grafana-provisioning/confignano grafana-provisioning/config/grafana.ininano grafana.ini
-
在該目錄下新建
grafana.ini
,將默認配置復制過來并加以修改。最簡單的做法是從 Grafana 官方倉庫拷貝 “默認grafana.ini
”(也可以先在已有容器里導出一份)。這里我們直接創建一個精簡版,專門覆蓋 SMTP 部分:#################################### SMTP / Emailing qq為例 ############################# [smtp] ;enabled = false ; ← 把 enabled 改為 true enabled = true host = smtp.qq.com:465 ; ← 修改為你的 SMTP 服務器地址和端口 user = 123321@qq.com ; ← SMTP 登錄用戶名 # If your SMTP server requires a password, 設置成真實密碼 password = osdfdsfsadfsa # 郵箱SMTP授權碼(非登錄密碼) ; If your SMTP server SPEFICALLY不需要登錄,以空用戶名為例 ; user = ; password = ;skip_verify = false ; 如果你想跳過 TLS 驗證,可改成 true from_address = 123321@qq.com ; ← 郵件 From 字段,與上面的郵箱保持一致 from_name = Grafana ; ← 發件人名稱 ; EHLO identity in SMTP dialog (defaults to instance_name if set, else hostname of the system) ;ehlo_identity = # ****************************************************** # 下面三行是 TLS/SSL 相關,如果你的 SMTP 需要 SSL 直接用 465 端口,則: ; startTLS policy (defaults to true if port is 587) #startTLS_policy = NoStartTLS # 例如: #host = smtp.example.com:465 #skip_verify = true #startTLS_policy = NoStartTLS # ******************************************************# 如果你的 SMTP 服務地址是 Gmail,可以參考: # host = smtp.gmail.com:587 # user = yourgmail@gmail.com # password = your_gmail_app_password ; ← 注意:Gmail 要用 “應用專用密碼” # from_address = yourgmail@gmail.com # from_name = Grafana################## 下面其余配置保持默認不動 ####################### [log] mode = console level = info[paths] data = /var/lib/grafana logs = /var/log/grafana plugins = /var/lib/grafana/plugins provisioning = /etc/grafana/provisioning
- 以上內容示例了最小化啟用 SMTP 的方式,實際生產中可以保留
grafana.ini
里更多其他模塊的配置。重點是把[smtp]
里的enabled = true
、host
、user
、password
、from_address
、from_name
等項補充好。 - 注意:
host = smtp.example.com:587
:如果你的 SMTP 只在 587/STARTTLS 上跑,就填:587
,如果是真正的 SSL over TLS(如某些服務用 465 端口),就寫smtp.xxx.com:465
并設置startTLS_policy = NoStartTLS
、skip_verify = true
。user
/password
:請按真實賬戶或“應用專用密碼”填寫。from_address
:建議與user
保持一致或在你所使用域名下的一個死信箱。
- 以上內容示例了最小化啟用 SMTP 的方式,實際生產中可以保留
-
保存
grafana-provisioning/config/grafana.ini
,確保文件權限正常且 Docker 進程可以讀到它:ls -l grafana-provisioning/config/grafana.ini
3.1.2. 修改 Docker Compose,將自定義 grafana.ini 掛載進容器
假設你原來已有一個類似下面的 docker-compose.yml
(含 Grafana 服務):
services:grafana:volumes:# 這一行需要掛載我們自己的 grafana.ini:- ./grafana-provisioning/config/grafana.ini:/etc/grafana/grafana.ini
將以上 docker-compose.yml
放到 ~/grafana/
目錄下,調整好相對路徑后,執行:
docker compose down # 如果 Grafana 已經在跑,先停掉
docker compose up -d # 再后臺啟動docker exec -it grafana cat /etc/grafana/grafana.ini
3.1.3. 驗證 Grafana 內部是否正確加載了 SMTP 配置
-
查看 Grafana 容器日志,確認它沒有報 “failed to load grafana.ini” 或者 “smtp disabled” 等錯誤:
docker logs grafana --tail 50
如果看到開頭有大段 “Starting Grafana …” 然后沒有特別的關于 SMTP 的 WARN/ERROR,就說明配置文件沒問題。
-
進入 Grafana Web UI (假設地址為
http://<你的服務器IP>:3000
),登錄后:- 左側菜單 → “Alerting” → “Contact points”(舊版面板可能在 “Alerting → Notification channels”)
- 點擊右上角 “New contact point”(或 “New channel”)。
- 如果 SMTP 配置生效,當你選擇 “Email” 這個類型時,表單下方不會出現 “SMTP is not configured” 的警告,而是可以直接填寫郵箱相關字段(例如 “To” 地址等)。
-
填入收件地址,點擊
Test
, 驗證郵箱是否能收到郵件, 通過后保存
-
Notification policies
通知策略默認選擇剛創建的郵箱
3.2、為關鍵面板添加告警(Alerting)
日志分析如果僅是“看”和“追蹤”,有時當問題發生時你才去察覺。更高級的做法是:
-
定位告警場景
- 例如“過去 5 分鐘里 Tomcat 錯誤日志條數 > 10,就要發郵件/短信告警”。
- 或者 “syslog 里出現關鍵字 ‘CRITICAL’ 超過 5 條,就要通知值班人員立即干預”。
-
在面板里創建 Alert 規則
-
打開某個面板(比如前面做的 Stat 面板,顯示“過去 1 分鐘內 Tomcat 錯誤日志條數”),點擊右上角 **+**號。
-
Create alert rule ,新建一個告警:
-
Query:保留你當前的 LogQL Metric 查詢,如
sum(count_over_time({host="host1", job="tomcat"}|detected_level="error" [1m]))
-
如果你只在 Grafana 查詢層用
| logfmt
拆detected_level
,那就只能按管道過濾-
#這里的level是在promtail-config-host1.yml配置的 - job_name: tomcat-host1static_configs:- targets: [ localhost ]labels:job: tomcathost: host1app: webapp__path__: /tomcat_logs/*.logpipeline_stages:- regex:expression: '^(?P<timestamp>\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}.\d{3}) (?P<level>\w+)'- labels:level:
-
-
條件(Condition):
IS ABOVE 10
。 -
觸發時:
Notification policies
→ 選擇你已配置好的通知渠道(Email、Slack、PagerDuty 等) -
測試是否有數據, 最近沒有日志可以調時間范圍, 如:1天
-
-
測試規則: 5分鐘內10調info就觸發
-
sum(count_over_time({host="host1", job="tomcat"} | detected_level = `info` [5m]))
-
-
-
保存告警后,只要指標持續超過閾值,Grafana 就會主動推送警報給你。
-
注意:
- Loki 原生對日志告警的支持是“基于 LogQL Metric”查詢,所以你要先把“Error 日志條數”寫成
count_over_time(...)
這種 Metric,再去做閾值判斷。- 如果你在 5 分鐘內累積出現了 20 條錯誤,那么
count_over_time(...[5m])
會在“此時” 直接返回 20,符合范圍就報警。- 最好把告警面板放在儀表板最顯眼的位置,比如第一行第一個格子,這樣一目了然。
4. 常見問題 & 解決
4.1 Promtail 報 timestamp too old
- 原因:采集到舊歸檔(
.gz
)文件里的日志行,造成時間戳比 Lokireject_old_samples_max_age
還要早。 - 解決:
- 把
promtail-config-host1.yml
中的__path__: /var/log/**/*.log
改為只匹配.log
,不含.gz
; - 或者在
scrape_configs
下加ignore_older: 24h
,忽略超過 24 小時前修改的文件。
- 把
4.2 Alert Rule 報 “No data” 或 “failed to execute query”
- 常見原因:在 Alert 編輯頁沒把查詢模式切為 Metrics,而是留在 Logs;
- 輸入的 LogQL 語法寫錯,比如把
.Status
寫成.State
; - 告警條件寫成
IS ABOVE 0
,但查詢表達式返回的是 instant vector,需用sum(count_over_time(...))
明確聚合成單值。 - 解決:確保 Mode = Metrics,Condition 中引用 query(A, 1m, now),并用
.Status
而非.State
;設置“無數據→OK、錯誤→Alerting”。
4.3 容器時區與宿主機不一致,導致日志時間錯位
- 原因:默認 Docker 容器時區為 UTC,Promtail 采集時若不指定時區,可能把無時區后綴的日志當成 UTC 解析,造成時差。
- 解決:
- 在所有關鍵容器(Promtail、Tomcat)里掛載
/etc/localtime:/etc/localtime:ro
和/etc/timezone:/etc/timezone:ro
; - 或者在 Promtail pipeline 中,用
timestamp
階段手動指定帶有+0800
的格式。
- 在所有關鍵容器(Promtail、Tomcat)里掛載
docker run -d \--name ry-tomcat \-v /etc/localtime:/etc/localtime:ro \-v /etc/timezone:/etc/timezone:ro \-e TZ=Asia/Shanghai \tomcat:9
或在 docker-compose.yml
中配置:
services:tomcat:image: tomcat:9volumes:- /etc/localtime:/etc/localtime:ro- /etc/timezone:/etc/timezone:roenvironment:TZ: "Asia/Shanghai"
4.4 Loki 啟動報錯:both grpc_client_config and (query_frontend_grpc_client or query_scheduler_grpc_client) are set
-
問題描述:使用 Loki 3.x 時出現如下錯誤,導致容器無法啟動:
failed parsing config: both `grpc_client_config` and (`query_frontend_grpc_client` or `query_scheduler_grpc_client`) are set at the same time.
-
原因分析:
- 在 Loki v3.0+ 中,模塊間通信的配置方式有所改變;
- 你啟用了
target=all
單體模式,此時 不應再顯式設置grpc_client_config
; - 同時配置
grpc_client_config
和新的query_frontend_grpc_client
或query_scheduler_grpc_client
會導致配置沖突。
-
解決方法:
-
如果使用
target=all
單體模式(即 Loki 所有模塊集中部署在一個進程中):- 刪除配置文件中
grpc_client_config:
相關段落; - 讓模塊間使用內存自動通信,無需再顯式配置 gRPC 客戶端;
- 刪除配置文件中
-
示例修改前:
querier:frontend_worker:grpc_client_config:...
修改后:
querier:frontend_worker:frontend_address: 127.0.0.1:9095
-
-
建議:
- 使用
target=all
模式時,配置應盡可能簡潔; - 保持
frontend_address
這類模塊間引用,但移除grpc_client_config
; - 或者使用 Loki 官方
local-config.yaml
為基礎,逐步擴展。
- 使用
4.5 儀表板面板無數據但Explore查詢正常
問題現象:
- 在Grafana儀表板中添加新面板后顯示"No data"
- 相同查詢在Explore界面能正常返回數據
- 日志數據實際存在于Loki中
根本原因:
-
時間范圍沖突:
- 儀表板使用固定時間范圍(如
now-1h
),而面板配置了不同的相對時間 - 示例:儀表板設置為
Last 6 hours
,但面板覆蓋為Last 15 minutes
- 儀表板使用固定時間范圍(如
-
面板級數據源配置錯誤:
# 錯誤配置示例(使用未定義變量) datasource: $prometheus # 應該用具體數據源名
-
變量未設置默認值:
- 當查詢使用
{app="$application"}
時 - 變量
$application
未設置默認值或默認值不匹配
- 當查詢使用
-
面板緩存未刷新:
- Grafana未自動更新面板查詢結果
解決方案:
-
檢查時間范圍:
- 編輯面板 → 右側Time options → 關閉
Override relative time
- 在儀表板右上角設置合理時間范圍(推薦
Last 6 hours
)
- 編輯面板 → 右側Time options → 關閉
-
修正數據源配置:
- 編輯面板 → 查詢編輯器底部 → Data source
- 直接選擇
Loki
(避免使用變量)
-
設置變量默認值:
# 儀表板設置 → Variables - name: applicationquery: label_values(app)default: webapp # 關鍵設置!
-
強制刷新數據:
- 點擊面板右上角 ? → Refresh
- 瀏覽器按
Ctrl+Shift+R
清除緩存
4.6 容器日志中缺失關鍵標簽(如host
)
問題現象:
- 在Grafana中無法通過
{host="host1"}
過濾日志 label_values(host)
返回空列表
排查步驟:
-
檢查Promtail配置:
docker exec promtail_host1 cat /etc/promtail/config.yml # 確認static_configs中有host標簽
-
驗證標簽注入:
curl -sG "http://loki:3100/loki/api/v1/series?match[]={job='syslog'}" | jq
解決方案:
- job_name: host_logsstatic_configs:- targets: [localhost]labels:host: host1 # 明確設置主機標簽job: syslog__path__: /var/log/*.log
4.7 Grafana日志面板顯示原始JSON不解析
問題現象:
日志顯示為完整JSON字符串:
{"time":"2023-01-01T12:00:00Z", "level":"error", "message":"Failed to connect"}
解決方案:
-
添加解析管道:
pipeline_stages:- json:expressions:timestamp: timelevel: levelmsg: message- labels:level: # 提取為標簽
-
Grafana字段提取:
-
在Explore或面板中:
{job="webapp"} | json
-
點擊 Detected fields → 選擇要顯示的字段
-
5. 小結
- Loki + Promtail + Grafana 提供了一個輕量級、可橫向擴展的容器日志分析方案,適合云原生場景。
- 關鍵點在于:Promtail 負責采集并打標簽,Loki 高效存儲帶標簽的日志、Grafana 負責可視化與告警。
- 本文從兩臺服務器(host4、host1)端到端演示了:
- Docker Compose 快速部署 Loki 與 Promtail,掛載時區、排除舊日志、采集所有容器和系統日志;
- 在 Grafana 中創建多個面板:實時錯誤日志、Tomcat 非 Info 日志、日志源分布、動態變量、Top N 錯誤消息、日志量趨勢等;
- 完整地將告警功能(SMTP 配置、Panel 告警規則)集成到 Grafana,達到主動通知運維的目的。
- 通過“常見問題 & 解決”部分,將遇到的各類坑點(時間戳過舊、Alert Rule 無數據、郵件模板失效、容器時區不同步)一并列出,方便以后快速排查。
后續優化思路:
- 將 Loki 存儲由本地文件系統切換到 S3/GCS,提高可擴展性與持久化能力。
- 在 Promtail 端對日志做更細粒度的 pipeline 處理(如 geoip、user-agent 解析),把有價值的字段提前打成 labels。
- 利用 Grafana 的高級 Dashboard 功能(變量聯動、模板化面板),實現多機房、多環境一鍵切換。
- 將告警規則提取到 Loki Ruler 或 Alertmanager,做更靈活的分組路由與抖動策略。