日志告警接入大模型,真正难的不是把一段文本发给模型,而是控制数据边界、调用次数和结论可信度。一个更稳妥的方案是:日志系统先筛选事实,告警系统负责聚合,独立服务完成回查、脱敏、去重和裁剪,模型只处理有限上下文,最后把带证据的候选摘要交给值班人员确认。
这类方案适合已经使用 Loki、Grafana 和 Alertmanager 的小团队,也适合运行在云服务器或 Kubernetes 上、希望减少告警噪声但不能接受日志外泄的业务。它不是“自动根因分析器”,而是一层受控的故障摘要辅助能力。
先把组件职责划清楚
建议把链路拆成五层,每层只承担自己擅长的工作:
- 采集代理:解析结构化日志,在进入集中式日志系统前删除明确禁止记录或外发的字段。Grafana 生态中的 Promtail 已结束支持周期,新部署应评估 Grafana Alloy 等受支持的采集方式;如果暂时保留旧配置,也应安排迁移和回归测试。
- Loki:保存日志,并通过 LogQL 对服务、环境、级别等低基数维度做确定性筛选。
- Alertmanager:按告警名、服务和环境分组,执行抑制、路由和重试。
- 摘要服务:接收 Webhook 后校验来源和标签,使用白名单查询回查有限时间窗,完成脱敏、去重、长度限制和幂等控制。
- 模型与通知层:模型生成结构化候选摘要,服务端校验输出,再写入工单、值班群或事件系统。
标签设计尤其重要。service、env、level 这类可枚举字段通常适合作为标签;request_id、用户 ID、完整 URL 等高基数字段应保留在日志内容中。把每个请求标识都做成标签,会增加流数量和索引压力,规模越大越容易拖累查询和运维成本。
采集阶段:结构化解析,但不要只依赖一次脱敏
应用优先输出 JSON 日志,并在采集端抽取必要字段。下面是 Alloy 的简化示意,具体组件参数应按实际版本和日志格式调整:
loki.process "application" {
forward_to = [loki.write.default.receiver]
stage.json {
expressions = {
level = "level",
service = "service",
env = "env",
message = "message",
}
}
stage.labels {
values = {
level = "",
service = "",
env = "",
}
}
// 对明确格式的凭据做第一道替换;复杂字段仍需在服务端复查
stage.replace {
expression = "(?i)(authorization|api[_-]?key|token|password)=?[^ ,]+"
replace = "$1=[REDACTED]"
}
}采集端脱敏不是安全边界。嵌套 JSON、异常堆栈、旧文件副本和应用自身的日志缓存都可能绕过这一步。因此应同时做到:源代码禁止记录密钥;采集端删除或替换明显敏感字段;摘要服务再次扫描;审计日志不保存未经处理的完整提示词。
告警阶段:先聚合,再触发模型调用
不要为每一条 error 日志单独调用模型。先按时间窗口聚合,再交给 Alertmanager 分组。例如,下面的规则只表达“错误日志在五分钟内持续超过阈值”,阈值需要根据业务基线调整:
groups:
- name: application-log-alerts
interval: 1m
rules:
- alert: ApplicationErrorBurst
expr: |
sum by (service, env) (
count_over_time({job="application", level="error"}[5m])
) > 20
for: 2m
labels:
severity: warning
annotations:
summary: "服务 {{ $labels.service }} 错误日志持续增加"20 只是示例值。流量有明显昼夜变化时,可以按历史分位数设置阈值,或者改用错误量与请求量的比例;如果请求总量本身不可靠,比例告警反而会制造更多噪声。
Alertmanager 可以把同一服务的一组告警合并后再发送:
route:
receiver: log-summary-webhook
group_by: [alertname, service, env]
group_wait: 30s
group_interval: 5m
repeat_interval: 2h
receivers:
- name: log-summary-webhook
webhook_configs:
- url: http://log-summarizer:8080/alerts
send_resolved: true摘要服务要验证 Webhook 来源,不能只依赖一个难以猜测的 URL。内网访问控制、反向代理鉴权、签名头或双向 TLS 都可以作为方案,选择哪一种取决于现有网络和密钥管理体系。
摘要服务:白名单回查、脱敏和幂等缺一不可
Webhook 的注解不能直接拼成提示词,也不能让模型自由生成 LogQL。服务端应只接受白名单中的 service、env 和时间范围,由代码生成固定查询模板,并限制查询结果的数量和每行长度。
一个可执行的处理顺序如下:
- 校验请求格式、鉴权信息和告警状态,只处理
firing,恢复通知按需进入另一条复盘流程。 - 检查服务名和环境是否在允许列表中,拒绝任意标签值和任意查询语句。
- 根据告警开始时间回查有限窗口,例如前后各几分钟,而不是把整段历史日志交给模型。
- 先按日志时间和内容指纹去重,再做二次脱敏;对每行设置长度上限,最终只保留有限条数。
- 为每条日志分配序号,把序号和时间戳一起传给模型,要求模型只能引用这些序号作为证据。
- 用告警指纹做幂等键,并把处理状态写入数据库或缓存,避免 Alertmanager 重试导致重复调用。
查询参数可以类似这样设置:
params = {
"query": '{job="application",service="order-api",level="error"}',
"start": start_ns,
"end": end_ns,
"limit": 200,
"direction": "backward",
}这里的 service 不应直接来自未校验的用户输入,查询模板也应根据租户边界生成。模型接口则应设置较短的连接超时和有限重试,只对网络失败或明确可重试的服务端错误退避;鉴权失败、参数错误和结构解析失败应进入人工可见的失败队列。
让模型输出“事实、假设、动作”三种信息
提示词的重点不是让模型写一段听起来确定的结论,而是强制它区分证据等级。可以要求输出以下 JSON 结构:
{
"observations": [
{"text": "5 分钟内 order-api 出现 42 条超时日志", "evidence": [3, 8, 17]}
],
"hypotheses": [
{"text": "可能与下游依赖响应变慢有关", "evidence": [8, 17], "confidence": "low"}
],
"next_steps": [
"检查同一时间窗内下游调用耗时和连接池指标"
],
"evidence": [
{"id": 3, "timestamp": "...", "excerpt": "..."}
]
}服务端收到结果后至少校验四件事:字段是否齐全;证据编号是否存在且只引用已发送日志;内容长度是否超限;输出中是否再次出现密钥或个人信息。解析失败时不要阻断原始告警,应退化为确定性模板,例如服务名、时间窗、匹配数量和 Loki 查询入口。
模型不可用时也要能工作。原始告警应先送达,摘要可以异步生成;达到并发上限或每日预算时,发送普通告警而不是静默丢弃。这样模型只是增强层,不会成为监控链路的单点故障。
哪些场景值得接入,哪些场景不值得
适合接入: 多实例重复报错、需要跨日志流整理时间线、值班人员需要快速获得排查入口的服务。前提是日志已经结构化,且能明确哪些字段允许进入模型上下文。
不适合直接接入: 包含支付信息、身份凭证或大规模个人数据的原始日志;日志格式混乱、没有服务和环境边界的系统;告警规则本身还没有稳定基线的项目。此时先改造日志和告警,比接入模型更重要。
落地时可以分三步:先用非敏感测试日志跑通查询、脱敏和降级;再接入一个低风险服务,记录调用量、超时率、误报和人工修改情况;最后再扩大范围,并为每个服务设置数据策略、预算和回滚开关。
总结
日志告警智能摘要的核心不是“让模型判断根因”,而是建立清晰的证据链:日志系统筛选可验证事实,Alertmanager 负责聚合和路由,摘要服务控制数据访问、脱敏、裁剪、幂等与审计,模型只生成带引用的候选说明。
只要原始告警始终可达、敏感字段有多层防护、每次调用都可追踪,个人站长和小团队也可以把这类能力部署在云服务器或容器环境中,并逐步观察它是否真正减少了值班噪声。