3、日志系统基础:日志级别、结构化日志格式与日志采集存储
在大模型服务的部署与运维中,日志系统是观测系统内部状态的第一入口。与传统的 Web 服务不同,大模型推理服务通常涉及长时请求、GPU 资源调度、Token 级流式输出、多模型版本切换等复杂场景,日志的维度更多、噪声更大、关联性更强。如果日志体系设计不合理,排障时往往面临“有日志但看不懂、有报错但查不到上下文、有指标但无法关联”的困境。本章将围绕日志级别、结构化格式、采集与存储三个核心模块展开,帮助读者建立一套适合大模型服务的高质量日志基础设施。
3.1 日志级别:从 DEBUG 到 FATAL 的合理运用
3.1.1 核心原理:日志级别是信息过滤的第一道闸门
日志级别的本质是对日志事件重要性和紧急程度的分级标注。在 Python 的 logging 模块中,标准级别从低到高依次为 DEBUG、INFO、WARNING、ERROR、CRITICAL。对于大模型服务,建议额外引入 FATAL 级别(或复用 CRITICAL)用于表示进程无法继续运行、必须立即人工介入的极端情况。级别的作用不仅是给人看的标签,更是运行时过滤机制——当日志级别设置为 INFO 时,DEBUG 日志根本不会被写入输出流,从而减少 I/O 开销和磁盘占用。
大模型推理服务有其特殊性:一次推理请求可能耗时数秒到数十秒,期间会产生大量中间日志(如 KV Cache 命中率、显存占用变化、采样参数等)。如果全部用 INFO 输出,日志量会呈指数级增长;如果全部用 DEBUG,则排障时无法快速定位。因此,合理的级别分配策略是日志系统设计的第一步。
3.1.2 级别划分策略与判断方法
以下是一套适用于大模型推理服务的日志级别分配参考表:
| 级别 | 适用场景 | 典型内容 | 生产环境建议 |
|---|---|---|---|
| DEBUG | 开发调试、问题复现 | 模型权重加载明细、每层推理耗时、KV Cache 命中率、采样候选 Token 列表 | 默认关闭,按需动态开启 |
| INFO | 关键生命周期事件 | 服务启动完成、模型加载成功、请求开始/结束、Token 数统计、推理耗时 | 默认开启,用于日常监控 |
| WARN | 潜在风险但不影响当前请求 | 显存接近上限、请求排队超时、模型版本即将过期、GPU 温度偏高 | 开启,需关注告警 |
| ERROR | 请求失败或功能异常 | 推理超时、OOM、非法输入、下游服务调用失败 | 开启,需立即响应 |
| FATAL | 进程级不可恢复错误 | CUDA 驱动异常、模型文件损坏、端口被占用导致启动失败 | 开启,触发紧急告警 |
判断某个日志应该放在哪个级别,可以遵循“三问法”:这条日志是否影响用户请求的成功与否?如果是,至少 ERROR;这条日志是否反映系统资源或性能的潜在风险?如果是,WARN 或 INFO;这条日志是否只有开发者在调试时才关心?如果是,DEBUG。
3.1.3 实战场景:动态调整日志级别
某大模型推理服务在生产环境出现偶发的高延迟,但 INFO 日志中无法看到具体瓶颈。运维人员通过管理接口将指定 Pod 的日志级别动态调整为 DEBUG,无需重启服务:
# 通过 HTTP 接口动态调整日志级别
curl -X POST http://localhost:8080/admin/log-level \
-H "Content-Type: application/json" \
-d '{"level": "DEBUG", "scope": "inference_engine"}'
调整后,DEBUG 日志输出了每个 Transformer 层的耗时分布,发现第 24 层的 Attention 计算耗时异常偏高,进一步定位到是某个 GPU 卡出现降频。整个过程从发现问题到定位根因仅用了 15 分钟,且没有中断线上服务。这个案例说明,日志级别必须支持运行时动态调整,否则排障代价会非常高。
3.2 结构化日志格式:让日志成为可解析的数据
3.2.1 核心原理:从文本到键值对
传统日志以纯文本形式输出,例如:2025-06-15 10:23:45 ERROR request failed。这种格式人类可读,但机器难以解析。结构化日志将日志内容组织为键值对或 JSON 格式,使得日志可以被 Logstash、Loki 等工具自动索引和查询。对于大模型服务,结构化日志的字段设计需要额外考虑模型相关维度。
一个标准的大模型推理日志条目应包含以下字段组:基础字段(时间戳、级别、服务名、主机名)、追踪字段(trace_id、span_id、request_id)、模型字段(model_name、model_version、framework)、性能字段(latency_ms、prompt_tokens、completion_tokens、gpu_utilization)、业务字段(user_id、session_id、prompt_hash)。
3.2.2 结构化日志的格式规范
推荐使用 JSON 作为结构化日志的载体,因为 JSON 具有自描述性、跨语言支持和广泛的工具链兼容性。以下是一个大模型推理服务的结构化日志示例:
{
"timestamp": "2025-06-15T10:23:45.123Z",
"level": "INFO",
"service": "llm-inference",
"host": "gpu-node-03",
"trace_id": "8f3a2b9c1d4e5f6a",
"request_id": "req_20250615_102345_001",
"model_name": "qwen2.5-72b",
"model_version": "v2.3.1",
"latency_ms": 2345.67,
"prompt_tokens": 128,
"completion_tokens": 512,
"total_tokens": 640,
"gpu_utilization": 87.5,
"gpu_memory_mb": 45200,
"status": "success"
}
在 Python 中,可以使用 python-json-logger 库快速实现结构化输出:
from pythonjsonlogger import jsonlogger
import logging
logger = logging.getLogger("llm_service")
handler = logging.StreamHandler()
formatter = jsonlogger.JsonFormatter(
"%(timestamp)s %(level)s %(name)s %(message)s",
rename_fields={"levelname": "level", "name": "service"}
)
handler.setFormatter(formatter)
logger.addHandler(handler)
logger.setLevel(logging.INFO)
3.2.3 案例拆解:通过结构化日志定位慢请求
某团队发现线上部分请求的响应时间超过 10 秒,但无法确定是模型推理慢还是网络传输慢。通过分析结构化日志中的 latency_ms 字段,发现所有慢请求的 prompt_tokens 都超过 2000,且 gpu_utilization 接近 100%。进一步按 model_version 分组统计,发现 v2.3.1 版本在长 prompt 场景下的平均耗时比 v2.3.0 高出 40%。最终定位到是 v2.3.1 版本引入了新的 Attention 实现,在长序列场景下存在效率回退。这个案例说明,结构化日志的价值不仅在于单条查询,更在于可以按任意字段进行聚合分析。
trace_id,并确保该 ID 在网关层生成、贯穿整个调用链。这样在排查跨服务问题时,可以通过一个 ID 拉取所有相关日志,避免在多个服务之间手动匹配时间戳。
3.3 日志采集与存储:ELK 与 Loki 的选型与实践
3.3.1 核心原理:采集与存储的分层架构
日志采集与存储的核心目标是解决“日志产生速度 > 消费速度”和“日志分散在多台机器”两个问题。典型的采集链路为:应用进程 → 日志文件/标准输出 → 采集 Agent(Filebeat/Fluentd/Promtail)→ 消息队列(可选)→ 存储引擎(Elasticsearch/Loki)→ 查询与告警。对于大模型服务,由于日志量巨大且包含敏感的业务数据,采集链路还需要考虑采样、脱敏和限流。
3.3.2 ELK 与 Loki 的对比选型
| 维度 | ELK(Elasticsearch + Logstash + Kibana) | Loki(Grafana Loki + Promtail) |
|---|---|---|
| 存储原理 | 全文索引,基于 Lucene | 压缩日志块,仅索引标签 |
| 查询语言 | Lucene 语法 / KQL | LogQL(类 PromQL) |
| 资源消耗 | 高(需要大量内存做索引) | 低(适合大规模日志) |
| 与指标联动 | 需额外集成 | 与 Prometheus/Grafana 原生集成 |
| 适用场景 | 复杂全文搜索、业务分析 | Kubernetes 环境、快速排障 |
| 大模型场景适配 | 适合需要深度分析 Token 分布、Prompt 内容的场景 | 适合快速检索 trace_id、按标签过滤 |
选型建议:如果团队已经深度使用 Grafana 监控体系,且日志主要用于排障和关联指标,优先选择 Loki;如果需要对日志内容做复杂的全文检索、聚合分析或长期留存,ELK 更合适。对于大模型服务,通常建议 Loki 作为第一优先,因为其资源消耗低,且与 Prometheus 指标联动方便。
3.3.3 操作流程:基于 Loki 的日志采集配置
以下是一个使用 Promtail 采集大模型推理服务日志并发送到 Loki 的配置示例:
# promtail.yaml
server:
http_listen_port: 9080
grpc_listen_port: 0
positions:
filename: /tmp/positions.yaml
clients:
- url: http://loki:3100/loki/api/v1/push
scrape_configs:
- job_name: llm-inference
static_configs:
- targets: [localhost]
labels:
job: llm-inference
service: llm-inference
__path__: /var/log/llm/*.log
pipeline_stages:
- json:
expressions:
level: level
trace_id: trace_id
model_name: model_name
latency_ms: latency_ms
- labels:
level:
trace_id:
model_name:
配置完成后,可以在 Grafana 中通过 LogQL 查询特定 trace_id 的所有日志:
{service="llm-inference"} |= "8f3a2b9c1d4e5f6a"
3.3.4 案例拆解:大模型服务的日志链路完整落地
某大模型平台团队需要为 20 个推理节点、日均 500 万请求搭建日志系统。初始方案采用 ELK,但运行一周后发现 Elasticsearch 集群内存占用超过 80%,且查询响应变慢。团队评估后切换为 Loki + Promtail 方案,具体实施如下:
- 采集层:每个推理节点部署 Promtail,从容器标准输出采集 JSON 结构化日志,提取
level、trace_id、model_name作为标签。 - 存储层:部署 Loki 集群(3 个 ingester + 3 个 querier),配置 15 天保留期,每日日志量约 200GB。
- 查询层:在 Grafana 中配置日志面板,与 Prometheus 的 GPU 利用率、请求 QPS 指标联动。
- 告警层:设置 LogQL 告警规则,例如 5 分钟内 ERROR 级别日志超过 10 条即触发告警。
切换后,资源消耗降低约 70%,查询速度提升 3 倍。更重要的是,通过 trace_id 标签可以秒级拉取一次请求的全链路日志,排障效率大幅提升。该案例的代价是需要额外维护 Promtail 的 pipeline 配置,且 LogQL 的查询能力相比 Lucene 语法有一定限制,但对于排障场景完全够用。