好的,这是一份可以直接放在项目根目录的 `日志规范要求.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天(用于复盘) | **配置要点**: - 业务日志和错误日志必须独立文件,便于快速定位异常 - 框架类日志(如 `httpx`、`urllib3`)归入系统日志,且生产环境设为 WARN 级别 --- ## 三、日志格式标准 ### 3.1 统一 JSON 格式 所有日志必须输出为以下 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时必填 | 包含 `type`、`message`、`stack_trace` | ### 3.3 ERROR 日志额外字段 当 `level = ERROR` 时,必须包含: ```json { "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(结果如何) ``` **✅ 正例:** ```python logger.info( "任务日志拉取成功", extra={ "userId": "U10086", "fields": { "jobId": "ft_a016cd8885cd", "tailLines": 5000, "logSize": "2.3MB", "costMs": 42, "source": "frontend" } } ) ``` **❌ 反例(禁止):** ```python logger.info("get logs success") logger.info(f"job {job_id} status is {status}") # 禁止字符串拼接 ``` ### 4.3 WARNING/ERROR 日志内容要求 **必须包含三要素**: 1. 发生了什么(what) 2. 为什么发生(why)—— 异常类型/错误码 3. 业务上下文(context)—— 哪个业务对象失败了 **✅ 正例:** ```python logger.warning( "计算轮询检测到任务失败", extra={ "fields": { "jobId": "ft_a016cd8885cd", "failureReason": "GPU资源不足", "errorCode": "RESOURCE_INSUFFICIENT", "retryCount": 3, "lastRetryTime": "2026-08-19T08:38:25.495+08:00" } } ) ``` **❌ 反例(禁止):** ```python logger.warning("compute polling reported failures") # 没有任何上下文 logger.error(f"error: {e}") # 只打了异常信息,没有业务ID ``` ### 4.4 禁止打印的内容 | 类别 | 说明 | |------|------| | 密码/密钥 | 任何形式的 `password`、`secret`、`token`、`api_key` | | 个人隐私 | 身份证号、手机号(需脱敏)、银行卡号 | | 超大对象 | 超过 1KB 的 JSON/列表/文本内容 | | 循环日志 | 禁止在 for/while 循环内打印 INFO 及以上级别 | | 异常堆栈重复 | 同一异常在一个请求中只打印一次完整堆栈 | --- ## 五、链路追踪(TraceId) ### 5.1 基本原则 - **入口生成**:网关/前端/定时任务入口生成全局唯一的 `traceId`(32位UUID) - **全链路透传**:通过 HTTP Header(`X-Trace-Id`)、RPC Meta、消息队列 Property 向下游传递 - **日志自动注入**:所有日志输出自动追加 `traceId`,代码中无需手动传入 - **跨线程传递**:使用 `MDC` 或 `ContextVars` 实现跨线程/协程的透传 ### 5.2 实现要求 ```python # 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 安全要求 | 要求 | 说明 | |------|------| | **敏感字段自动脱敏** | 对 `mobile`、`idCard`、`password`、`token` 等字段自动掩码 | | **脱敏规则** | 手机号:`138****5678`;身份证:`110***********1234` | | **日志查询权限** | 生产日志平台必须有 RBAC 权限控制,禁止随意导出 | | **审计追踪** | 谁在什么时候查询了哪些日志,必须记录审计日志 | ### 6.3 脱敏实现示例 ```python # 脱敏工具函数 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 条告警**(防告警轰炸) - 告警内容必须包含:`app`、`env`、`error.type`、首次发生时间、最近发生时间、累计次数 --- ## 八、日志查询与使用规范 | 场景 | 查询方式 | 时效要求 | |------|----------|----------| | 日常运维 | Kibana 按 `traceId` 或 `userId` 检索 | 实时 | | 异常排查 | 按 `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) ```json { "@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) ```json { "@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)+ 告警 ```json { "@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) ```json { "@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) ```python 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) ```xml @timestamp level thread logger ``` ### Go (Zap + JSON) ```go logger, _ := zap.NewProduction() logger.Info("订单创建成功", zap.String("traceId", traceId), zap.String("userId", userId), zap.String("orderId", orderId), zap.Int64("costMs", costMs), ) ```