用 Loki + Grafana 搭建轻量日志系统:采集、查询与告警
中小规模系统上日志系统,ELK 全家桶往往太重:光 Elasticsearch 的内存和磁盘规划就够折腾一阵。Loki 的思路不同——只索引日志的标签,不索引全文,存储和运维成本低得多,再配合 Grafana 与指标告警共用一个界面。本文在一台机器上把 Loki 日志系统搭起来:采集、查询、告警全流程走通,并重点讲清标签设计这条最容易踩的红线。
适用环境:一台 Linux 主机(示例 IP 192.0.2.20),Docker 与 Docker Compose 可用;日志来源先以本机文件与 systemd journal 为例,组件版本为 Loki 3.x、Grafana 11.x。
前置条件
- 主机已安装 Docker 与 Docker Compose v2,磁盘预留空间按日志量与保留期估算(先留 50 GB 起步)。
- 准备好目录规划:/opt/loki 下放 compose 文件与配置,数据卷独立挂载便于备份。
- 明确要采集的日志路径清单,以及每类日志对应的服务名——标签在采集端决定,进了 Loki 再改成本很高。
- Grafana 如已有独立部署可直接复用,只需给 Loki 开数据源;本文用 Compose 一并启动便于演示。
第一步:架构与组件分工
三件套分工明确:采集端(Promtail 或其继任者 Grafana Alloy)负责读日志文件、打标签、推送;Loki 负责接收、按标签索引、落盘存储和执行查询;Grafana 负责查询展示与告警。Promtail 已进入维护收尾阶段,新部署建议直接用 Alloy,它的配置语法是 River 格式,功能与 Promtail 对应;本文采集示例以 Promtail 的 YAML 写法为主,过渡到 Alloy 时对照组件名替换即可。单节点 Loki 用 monolithic 模式加本地文件系统存储即可起步,量大后再演进到对象存储与读写分离。
第二步:单机 Compose 部署
# docker-compose.yaml
services:
loki:
image: grafana/loki:3.1.0
container_name: loki
command: -config.file=/etc/loki/local-config.yaml
ports:
- "3100:3100"
volumes:
- ./loki-config.yaml:/etc/loki/local-config.yaml:ro
- loki-data:/loki
restart: unless-stopped
grafana:
image: grafana/grafana:11.2.0
container_name: grafana
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=change-me-please
volumes:
- grafana-data:/var/lib/grafana
restart: unless-stopped
volumes:
loki-data:
grafana-data:
Loki 的关键配置是保留期与存储路径,其余先用官方单机默认值:
# loki-config.yaml(节选关键项)
auth_enabled: false
server:
http_listen_port: 3100
common:
path_prefix: /loki
storage:
filesystem:
chunks_directory: /loki/chunks
rules_directory: /loki/rules
replication_factor: 1
ring:
kvstore:
store: inmemory
schema_config:
configs:
- from: 2024-01-01
store: tsdb
object_store: filesystem
schema: v13
index:
prefix: index_
period: 24h
limits_config:
retention_period: 720h # 全局保留 30 天
compactor:
working_directory: /loki/compactor
retention_enabled: true # 开启后保留期才真正生效
执行 docker compose up -d 后,curl http://192.0.2.20:3100/ready 返回 ready 即 Loki 就绪。Grafana 里添加 Loki 数据源,URL 填 http://loki:3100(同 compose 网络内用服务名)。
第三步:采集配置与标签设计
Promtail 两类典型采集:文件采集用静态 job 加路径匹配,journal 采集直接读 systemd 日志。标签设计是整套系统的地基,原则只有三条:标签只放低基数、用于筛选流的字段(服务名、环境、主机、日志级别);绝不放用户 ID、请求 ID、订单号这类高基数值——每个不同的标签组合都会生成一条独立的日志流,海量组合会把索引和内存打爆;宁可把高基数字段留在日志正文里靠查询时解析。
# promtail-config.yaml(节选)
server:
http_listen_port: 9080
positions:
filename: /tmp/positions.yaml
clients:
- url: http://192.0.2.20:3100/loki/api/v1/push
scrape_configs:
- job_name: app-files
static_configs:
- targets: [localhost]
labels:
job: app
host: node-01
__path__: /var/log/app/*.log
pipeline_stages:
- json:
expressions:
level: level
- labels:
level: # 从日志内容提取级别做标签(级别取值有限,属低基数)
- job_name: journal
journal:
max_age: 12h
labels:
job: systemd-journal
relabel_configs:
- source_labels: [__journal__systemd_unit]
target_label: unit
第四步:LogQL 常用查询
LogQL 查询分两段:前半是流选择器(按标签筛流),后半是管道(对正文过滤、解析、聚合)。常用写法:
# 1. 看某服务最近的 ERROR 日志
{job="app"} |= "ERROR"
# 2. JSON 解析后按字段过滤
{job="app"} | json | status >= 500
# 3. 正则提取字段再过滤
{job="app"} | regexp "(?P<method>GET|POST) (?P<path>/\\S+)" | path =~ "/api/.*"
# 4. 指标查询:最近 5 分钟每分钟错误数(可画图、可告警)
sum(rate({job="app"} |= "ERROR" [5m]))
# 5. 按服务分组统计错误量
sum by (job) (rate({job=~".+"} |~ "ERROR|FATAL" [5m]))
第五步:日志告警的最简配置
最省事的是在 Grafana 里用统一告警:新建 Alert rule,数据源选 Loki,查询填上面的 rate 表达式(如 5 分钟内错误速率),条件设为最近求值大于阈值(例如持续 3 分钟 > 0.1 条/秒),再绑定通知策略发到值班群 Webhook 或邮件。这样日志告警和指标告警在同一个界面管理,不必再单独维护 Loki 的 ruler 组件。规模上来后,再考虑把规则下沉到 ruler 用文件管理、纳入版本控制。
与 ELK 的取舍
Loki 赢在轻:不建全文索引,存储便宜、组件少、和 Prometheus/Grafana 生态天然一体;输在查:全文检索能力弱,按关键词大海捞针式的排查不如 Elasticsearch 顺手,复杂分析更吃亏。经验判断:日志主要用于「按服务、按时间段看错误和上下文」,且团队已在用 Grafana,选 Loki;日志要做审计检索、全文搜索和复杂聚合分析的,ELK(或 OpenSearch)更合适。两者也可以共存:核心审计日志进 ELK,应用运行日志进 Loki。
验证
往 /var/log/app/app.log 写一条 JSON 格式测试日志,在 Grafana 的 Explore 里选 Loki 数据源,执行 {job="app"} 应能看到该条;改用 |= "ERROR" 过滤确认管道生效;再执行 sum(rate({job="app"} |= "ERROR" [5m])) 能出数值。最后手动刷几条错误日志,观察告警规则进入 Pending 并触发通知,整条链路即通。
常见问题
推送被拒,日志报 out of order 或 label 冲突。写入被拒先看 Loki 返回信息:最常见是同一流里时间戳乱序(老版本严格,3.x 已放宽但跨度太大仍拒)和采集端给同一流打了互相冲突的标签。标签打错的典型场景是把动态值误设为标签,先在采集端用 promtail --dry-run 或 Alloy 的日志核对实际推出的标签组合。
查询很慢甚至超时。先缩小时间范围和标签范围:只给一个大 job 不加任何收敛标签的全量扫是最慢的查法。时间跨度大时把 max_query_length 等限制适当放开,并确认查询没有对超大结果做全文正则。根子上还是标签设计——流切得越合理,查询越快。
配了 retention_period 磁盘却不释放。保留期要靠 compactor 执行:retention_enabled 必须为 true,且 compactor 正常运行;另外 schema 的保留是按索引周期粒度删的,刚到期不会立刻见效,等一个压实周期再看磁盘趋势。
日志突然断流。查采集端 positions 文件是否损坏、目标日志是否被切割后改名导致路径匹配失效,以及 Loki 的 ingestion rate limit 是否因突发量把流限流(日志里有 rate limit 字样),按顺序排查基本都能定位。
小结
Loki 的搭建本身不难,难的是把标签纪律守住:低基数进标签,高基数留正文。先单机跑通采集、查询、告警三件套,再按量演进到对象存储和规则下沉,这套轻量日志系统足够支撑大多数中小团队的日常排障。







