Files
YG_FT/docs/生产级日志系统方案.md
2026-08-19 17:26:49 +08:00

15 KiB
Raw Blame History

好的,这是一份可以直接放在项目根目录的 日志规范要求.md,涵盖格式标准、分类分级、内容规范、链路追踪、性能安全、运维告警六大模块,每条规范都配有正反例,你的团队照着这个写代码就行。


生产级日志规范要求

版本v2.0 | 适用于所有后端服务Python/Java/Go/Node.js


一、核心原则

原则 说明
结构化 所有日志必须输出为 JSON 格式,便于自动化采集和分析
可追踪 每个请求链路必须有唯一的 traceId,贯穿全流程
有上下文 每条日志必须包含足够的业务信息,能独立理解发生了什么
高性能 异步打印,禁止在业务主流程中同步写磁盘
安全合规 敏感信息自动脱敏禁止打印密码、token、身份证号等
可告警 ERROR 日志必须触发实时告警,且有明确的错误分类

二、日志分类

生产环境必须按用途分流存储,禁止所有日志混写在同一文件

分类 文件名示例 用途 保留周期
业务日志 app-biz.log 记录核心业务流程(订单、支付、登录、任务状态变更等) 7天热存 + 30天冷存
系统日志 app-sys.log 记录框架、中间件、连接池、GC、线程池状态 7天
访问日志 app-access.log 记录所有 HTTP/RPC 请求的入参、出参、耗时 15天用于审计
错误日志 app-error.log 仅记录 ERROR 级别,含完整堆栈 30天用于复盘

配置要点

  • 业务日志和错误日志必须独立文件,便于快速定位异常
  • 框架类日志(如 httpxurllib3)归入系统日志,且生产环境设为 WARN 级别

三、日志格式标准

3.1 统一 JSON 格式

所有日志必须输出为以下 JSON 结构,字段名不得随意变更

{
  "@timestamp": "2026-08-19T10:30:45.123+08:00",
  "level": "INFO",
  "logger": "com.order.service.OrderService",
  "traceId": "abc-123-def-456",
  "spanId": "span-001",
  "userId": "U10086",
  "message": "订单状态更新成功",
  "fields": {
    "orderId": "ORD-20260819-001",
    "fromStatus": "PENDING",
    "toStatus": "PAID",
    "costMs": 23,
    "retryCount": 0
  },
  "file": "OrderService.java:156",
  "thread": "http-nio-8080-exec-8",
  "host": "pod-order-7x9k2",
  "app": "order-service",
  "env": "prod"
}

3.2 字段说明

字段 类型 必填 说明
@timestamp string ISO8601 格式,带时区(如 +08:00
level string DEBUG / INFO / WARNING / ERROR
logger string 日志记录器名称,通常为类名
traceId string 全局唯一追踪ID从入口生成全链路透传
spanId string 推荐 当前节点ID用于区分调用链中的不同服务
userId string 业务必填 操作用户标识,未登录可为空
message string 人类可读的日志摘要,简洁明了
fields object 结构化业务字段,所有动态数据放入此处
file string 推荐 代码文件名和行号
thread string 推荐 线程名
host string 推荐 主机名或 Pod 名称
app string 应用名称
env string dev / test / staging / prod
error object ERROR时必填 包含 typemessagestack_trace

3.3 ERROR 日志额外字段

level = ERROR 时,必须包含:

{
  "error": {
    "type": "ConnectionTimeoutError",
    "message": "连接下游服务超时",
    "stack_trace": "完整堆栈信息...",
    "root_cause": "socket timeout after 3000ms"
  }
}

四、日志内容规范

4.1 日志级别使用标准

级别 使用场景 示例
DEBUG 开发调试信息,生产环境默认关闭 变量值、中间计算结果
INFO 关键业务流程节点、状态变更、外部调用结果 订单创建成功、支付回调收到、任务状态变更
WARNING 可恢复的异常、降级处理、重试、资源使用超阈值 重试第3次成功、缓存穿透、磁盘使用率>80%
ERROR 业务失败、系统异常、需要人工介入的错误 支付失败、数据库连接断开、第三方接口返回500

4.2 INFO 级别日志内容要求

每条 INFO 日志必须回答 5W1H

Who谁操作 + What做了什么 + When何时 + Where哪个服务/节点) + Why上下文 + How结果如何

正例:

logger.info(
    "任务日志拉取成功",
    extra={
        "userId": "U10086",
        "fields": {
            "jobId": "ft_a016cd8885cd",
            "tailLines": 5000,
            "logSize": "2.3MB",
            "costMs": 42,
            "source": "frontend"
        }
    }
)

反例(禁止):

logger.info("get logs success")
logger.info(f"job {job_id} status is {status}")  # 禁止字符串拼接

4.3 WARNING/ERROR 日志内容要求

必须包含三要素

  1. 发生了什么what
  2. 为什么发生why—— 异常类型/错误码
  3. 业务上下文context—— 哪个业务对象失败了

正例:

logger.warning(
    "计算轮询检测到任务失败",
    extra={
        "fields": {
            "jobId": "ft_a016cd8885cd",
            "failureReason": "GPU资源不足",
            "errorCode": "RESOURCE_INSUFFICIENT",
            "retryCount": 3,
            "lastRetryTime": "2026-08-19T08:38:25.495+08:00"
        }
    }
)

反例(禁止):

logger.warning("compute polling reported failures")  # 没有任何上下文
logger.error(f"error: {e}")  # 只打了异常信息没有业务ID

4.4 禁止打印的内容

类别 说明
密码/密钥 任何形式的 passwordsecrettokenapi_key
个人隐私 身份证号、手机号(需脱敏)、银行卡号
超大对象 超过 1KB 的 JSON/列表/文本内容
循环日志 禁止在 for/while 循环内打印 INFO 及以上级别
异常堆栈重复 同一异常在一个请求中只打印一次完整堆栈

五、链路追踪TraceId

5.1 基本原则

  • 入口生成:网关/前端/定时任务入口生成全局唯一的 traceId32位UUID
  • 全链路透传:通过 HTTP HeaderX-Trace-Id、RPC Meta、消息队列 Property 向下游传递
  • 日志自动注入:所有日志输出自动追加 traceId,代码中无需手动传入
  • 跨线程传递:使用 MDCContextVars 实现跨线程/协程的透传

5.2 实现要求

# Python 示例:使用 logging 的 Filter 自动注入 traceId
class TraceIdFilter(logging.Filter):
    def filter(self, record):
        record.traceId = get_current_trace_id() or "N/A"
        return True

# 所有日志自动带上 traceId
logger.info("订单创建成功")  # 自动注入 traceId代码无需传参

绝对禁止traceId 字段值为 "-"null


六、性能与安全

6.1 性能要求

配置项 要求
异步打印 必须使用异步 Appender禁止同步刷盘阻塞业务线程
单文件大小 ≤ 1GB达到阈值自动滚动
滚动策略 按大小滚动(如 1GB或按天滚动
采样率 核心业务 100%,非核心(如健康检查、非关键查询)≤ 10%
禁止打印循环 循环体内不得打印 INFO 及以上日志
大对象截断 超过 1KB 的内容自动截断前500字符 + 后500字符

6.2 安全要求

要求 说明
敏感字段自动脱敏 mobileidCardpasswordtoken 等字段自动掩码
脱敏规则 手机号:138****5678;身份证:110***********1234
日志查询权限 生产日志平台必须有 RBAC 权限控制,禁止随意导出
审计追踪 谁在什么时候查询了哪些日志,必须记录审计日志

6.3 脱敏实现示例

# 脱敏工具函数
def mask_sensitive(data: dict) -> dict:
    sensitive_keys = {"password", "token", "api_key", "mobile", "id_card"}
    for key in sensitive_keys:
        if key in data:
            value = str(data[key])
            if len(value) >= 11:  # 手机号
                data[key] = value[:3] + "****" + value[-4:]
            elif len(value) >= 18:  # 身份证
                data[key] = value[:3] + "***********" + value[-4:]
    return data

七、运维与告警

7.1 日志采集架构

应用日志(本地文件)
    ↓
Filebeat轻量采集器
    ↓
Kafka削峰填谷保证不丢
    ↓
Logstash解析、过滤、脱敏
    ↓
Elasticsearch索引存储
    ↓
Kibana / Grafana查询展示

关键要求

  • 禁止应用直接写入 ES必须经过 Kafka 缓冲
  • Filebeat 采集失败时必须有本地持久化和重试机制

7.2 告警规则

条件 动作 优先级
同一服务 5 分钟内出现 ≥ 3 次 ERROR 钉钉/企微告警 + 电话P0级 最高
同一服务 10 分钟内 ERROR 率 > 5% 钉钉告警P1级
磁盘使用率 > 80% 钉钉告警P2级
单个 ERROR 堆栈重复出现 ≥ 10 次/分钟 聚合为一条告警,避免轰炸 -

7.3 错误聚合策略

  • 相同 error.type + 相同 logger + 相同堆栈前3行 → 视为同一类错误
  • 同一类错误 5 分钟内只发 1 条告警(防告警轰炸)
  • 告警内容必须包含:appenverror.type、首次发生时间、最近发生时间、累计次数

八、日志查询与使用规范

场景 查询方式 时效要求
日常运维 Kibana 按 traceIduserId 检索 实时
异常排查 app + level:ERROR + 时间范围 实时
业务审计 userId + logger:xxx + 时间范围 30分钟内
性能分析 costMs 排序,找出慢请求 实时
安全审计 查询所有访问日志,按 IP/用户筛选 按需

九、检查清单Code Review 必查)

检查项 通过标准
☐ JSON 格式 所有日志输出均为 JSON字段名符合规范
☐ traceId 所有日志都有 traceId,且不为 "-"
☐ userId 涉及用户操作的日志都有 userId
☐ 业务上下文 INFO 日志包含 fields有订单ID/任务ID等
☐ ERROR 日志 包含 error.type + stack_trace + 业务ID
☐ 敏感信息 无密码/手机号明文,有脱敏处理
☐ 异步打印 使用异步 Appender
☐ 日志级别 框架类日志 ≥ WARN业务日志分级合理
☐ 循环内日志 无循环内的 INFO 日志
☐ 日志分流 业务/系统/错误日志分文件存储

十、附:完整日志示例

示例一业务成功流程INFO

{
  "@timestamp": "2026-08-19T10:30:45.123+08:00",
  "level": "INFO",
  "logger": "app.services.job_service",
  "traceId": "tracer-abc123xyz789",
  "spanId": "span-001",
  "userId": "U10086",
  "message": "计算任务创建成功",
  "fields": {
    "jobId": "ft_a016cd8885cd",
    "jobType": "fine_tuning",
    "modelId": "model-llama2-7b",
    "datasetId": "ds-20260819-001",
    "gpuCount": 4,
    "estimatedTime": "2h30m",
    "costMs": 1523,
    "source": "api"
  },
  "file": "job_service.py:234",
  "thread": "MainThread",
  "host": "compute-pod-7x9k2",
  "app": "compute-service",
  "env": "prod"
}

示例二可恢复的警告WARNING

{
  "@timestamp": "2026-08-19T08:38:27.074+08:00",
  "level": "WARNING",
  "logger": "app.workers.compute_poller",
  "traceId": "tracer-xyz789abc123",
  "spanId": "span-002",
  "userId": "U10086",
  "message": "计算任务状态轮询检测到失败,进入重试",
  "fields": {
    "jobId": "ft_a016cd8885cd",
    "currentStatus": "FAILED",
    "failureReason": "GPU节点不可用",
    "errorCode": "NODE_UNAVAILABLE",
    "retryCount": 2,
    "maxRetries": 3,
    "nextRetryDelay": 30
  },
  "file": "compute_poller.py:45",
  "thread": "Thread-8",
  "host": "compute-pod-7x9k2",
  "app": "compute-service",
  "env": "prod"
}

示例三严重错误ERROR+ 告警

{
  "@timestamp": "2026-08-19T08:38:30.456+08:00",
  "level": "ERROR",
  "logger": "app.services.payment_service",
  "traceId": "tracer-pay-789xyz",
  "spanId": "span-003",
  "userId": "U10086",
  "message": "支付调用失败,订单状态回滚",
  "fields": {
    "orderId": "ORD-20260819-001",
    "amount": 299.00,
    "paymentMethod": "wechat",
    "retryCount": 3,
    "hasRollback": true
  },
  "error": {
    "type": "PaymentTimeoutException",
    "message": "支付网关超时等待响应超过5000ms",
    "stack_trace": "Traceback (most recent call last):\n  File \"payment_service.py:89\" ...",
    "root_cause": "upstream gateway 10.0.1.100:8080 connection timeout"
  },
  "file": "payment_service.py:156",
  "thread": "http-nio-8080-exec-12",
  "host": "order-pod-3f8k1",
  "app": "order-service",
  "env": "prod"
}

示例四完整的请求访问日志ACCESS

{
  "@timestamp": "2026-08-19T10:30:45.001+08:00",
  "level": "INFO",
  "logger": "app.middleware.access_log",
  "traceId": "tracer-abc123xyz789",
  "userId": "U10086",
  "message": "HTTP 请求完成",
  "fields": {
    "method": "POST",
    "path": "/api/v1/jobs",
    "statusCode": 200,
    "clientIp": "192.168.1.100",
    "userAgent": "Mozilla/5.0 ...",
    "requestSize": 2048,
    "responseSize": 512,
    "costMs": 1523,
    "requestBody": {"modelId": "model-llama2-7b", "datasetId": "ds-001"},  // 已脱敏
    "responseBody": {"jobId": "ft_a016cd8885cd", "status": "CREATED"}      // 已脱敏
  },
  "app": "compute-service",
  "env": "prod"
}

十一、附录:技术栈配置速查

Python (Logging + JSON)

import logging
import json
from pythonjsonlogger import jsonlogger

logger = logging.getLogger("app")
handler = logging.FileHandler("logs/app-biz.log")
formatter = jsonlogger.JsonFormatter(
    fmt="%(asctime)s %(levelname)s %(name)s %(traceId)s %(message)s",
    rename_fields={"asctime": "@timestamp", "name": "logger"}
)
handler.setFormatter(formatter)
logger.addHandler(handler)

Java (Logback + JSON)

<!-- logback-spring.xml -->
<appender name="JSON" class="ch.qos.logback.core.ConsoleAppender">
    <encoder class="net.logstash.logback.encoder.LogstashEncoder">
        <fieldNames>
            <timestamp>@timestamp</timestamp>
            <level>level</level>
            <thread>thread</thread>
            <logger>logger</logger>
        </fieldNames>
    </encoder>
</appender>

Go (Zap + JSON)

logger, _ := zap.NewProduction()
logger.Info("订单创建成功",
    zap.String("traceId", traceId),
    zap.String("userId", userId),
    zap.String("orderId", orderId),
    zap.Int64("costMs", costMs),
)