日志格式化规范:JSON格式标准、关键字段定义、日志脱敏规则

聊到日志格式化,很多同学第一反应是「不就是把日志打成JSON吗?」。嗯,这话对了一半。JSON格式确实是目前业界最通用的标准,但真正落地时,你会发现坑比想象中多得多。

我个人习惯把日志格式化拆成三个层次来看:结构标准、字段定义、安全合规。这三层缺一不可。今天我们就一个一个掰开揉碎了讲。

为什么非要用JSON?

你想想看,风控日志每天少则几百万条,多则上亿条。如果还是用那种「2024-01-01 12:00:00 | INFO | 用户登录成功」的纯文本格式,到了ELK或者ClickHouse里,你拿什么做结构化查询?

JSON的好处说白了就三点:

  • 自描述:字段名和值在一起,不用猜
  • 易解析:几乎所有日志平台都原生支持
  • 可扩展:加字段不影响老数据

我在项目中遇到过最典型的反面教材——某团队用「|」分隔符拼日志,结果业务字段里偶尔也出现「|」,解析直接崩了。从那以后,我定了一条铁律:风控日志必须用JSON

JSON格式标准:别小看这些细节

先看一个我推荐的日志格式模板:

{
  "requestId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "userId": "user_10086",
  "timestamp": 1704067200000,
  "level": "INFO",
  "logger": "com.risk.engine.RuleExecutor",
  "message": "规则命中:高风险交易拦截",
  "riskScore": 85.5,
  "ruleId": "RULE_001",
  "deviceInfo": {
    "ip": "192.168.1.1",
    "ua": "Mozilla/5.0 ..."
  },
  "extra": {}
}

这里有几个容易踩的坑:

避坑指南:我曾经见过一个团队把timestamp写成字符串格式「2024-01-01T12:00:00Z」,结果在ES里做时间范围查询时,因为时区转换问题导致数据对不上。后来我强制要求:所有时间戳统一用Unix毫秒级整数。排序、聚合、跨时区都稳如老狗。

另外,字段命名统一用小驼峰。别问我为什么不用下划线——因为Java和JavaScript的序列化库默认都是小驼峰,保持一致能省去很多字段映射的麻烦。

关键字段定义:这三个字段是命根子

风控日志里字段可能上百个,但真正核心的就三个:requestId、userId、timestamp。我称之为「风控日志三要素」。

字段 类型 说明 示例
requestId UUID字符串 全局唯一请求标识,用于全链路追踪 a1b2c3d4-e5f6-7890-abcd-ef1234567890
userId 字符串 用户唯一标识,脱敏后存储 user_10086
timestamp 长整型 事件发生时间,Unix毫秒级 1704067200000

requestId 这东西,说白了就是日志界的「身份证号」。一次请求从网关进来,经过风控引擎、规则计算、结果回调,所有环节的日志都带上同一个requestId。出了问题,直接grep这个ID,整条链路一目了然。

我记得有一次线上出了个误杀案例,用户正常交易被拦截。我通过requestId把从入参到规则计算到决策输出的所有日志拉出来,发现是规则引擎里一个缓存没刷新的bug。如果没有requestId,这种跨服务的问题定位,基本等于大海捞针。

userId 要注意的是脱敏。直接存手机号或身份证号?那是给自己挖坑。我建议统一用内部映射ID,比如「user_10086」这种格式。既保留了可追溯性,又规避了敏感数据泄露的风险。

timestamp 为什么强调毫秒级?因为风控场景下,同一秒内可能有多次操作。比如用户在一秒内连续点击了三次支付按钮,如果时间戳只精确到秒,你根本分不清先后顺序。毫秒级精度才能支撑这种高频场景的时序分析。

日志脱敏规则:安全合规是底线

这块内容,我建议你把它当成「红线」来看。合规部门查起来,可不是闹着玩的。

常见的脱敏场景分三类:

  1. 个人身份信息:手机号、身份证、邮箱
  2. 金融敏感信息:银行卡号、CVV、密码
  3. 设备指纹信息:IMEI、MAC地址

脱敏策略我一般用这几种:

  • 掩码:保留前后几位,中间用星号代替。比如手机号「138****1234」
  • 截断:只保留部分信息。比如身份证只保留后四位
  • 替换:用固定值替代。比如密码统一替换为「***」
  • 加密:对敏感字段做不可逆哈希
实战经验:我个人推荐用「掩码+哈希」的组合策略。举个例子,手机号在日志里同时存两个字段:phoneMask(138****1234)用于日常查询,phoneHash(SHA256后的值)用于精确匹配。这样既满足了合规要求,又不影响风控规则的准确性。

下面是一个脱敏工具的Python示例:

import re
import hashlib

def mask_phone(phone: str) -> str:
    """手机号脱敏:保留前3后4"""
    return re.sub(r'(\d{3})\d{4}(\d{4})', r'\1****\2', phone)

def mask_id_card(id_card: str) -> str:
    """身份证脱敏:保留前6后4"""
    return re.sub(r'(\d{6})\d{8}(\d{4})', r'\1********\2', id_card)

def hash_sensitive(value: str) -> str:
    """敏感字段哈希"""
    return hashlib.sha256(value.encode()).hexdigest()

# 使用示例
log_data = {
    "userId": "user_10086",
    "phone": mask_phone("13812345678"),
    "idCard": mask_id_card("110101199001011234"),
    "passwordHash": hash_sensitive("mypassword123")
}
小提示:脱敏逻辑一定要在日志输出层做,不要在业务代码里到处散落。我习惯写一个统一的LogSanitizer工具类,所有日志在序列化之前过一遍这个类。这样即使以后合规要求变了,改一个地方就行。

知识体系结构图

下面这张图把日志格式化的核心脉络梳理了一下,你可以对照着看看自己团队目前做到了哪一层:

日志格式化规范核心体系 JSON格式标准 关键字段定义 日志脱敏规则 统一小驼峰命名 时间戳毫秒级整数 嵌套结构规范化 requestId:全链路追踪 userId:脱敏后存储 timestamp:毫秒级精度 掩码:保留首尾 哈希:不可逆加密 统一脱敏层处理 标准化风控日志输出 可查询 · 可追踪 · 可审计 · 合规

看到这张图你应该能感受到,日志格式化不是简单地把字段拼成JSON就完事了。它背后是一整套从结构规范字段定义再到安全合规的体系。我见过太多团队在第一步「JSON格式」上就翻车了,更别提后面的脱敏和字段定义了。

最后说一句,日志格式化这件事,前期多花点心思,后期能省下无数排查问题的时间。你想想看,线上出了故障,日志格式乱七八糟,连requestId都找不到,那场面……嗯,经历过的人都懂。