日志规范与安全
日志采集规范
日志分级
| 级别 | 使用场景 | 示例 |
|---|---|---|
| ERROR | 系统级故障、业务异常(需要人工介入) | 数据库连接失败、第三方接口异常、业务校验不通过 |
| WARN | 潜在问题、降级触发、限流命中 | 接口响应超时熔断、缓存降级、请求频率限制 |
| INFO | 关键业务流程记录(审计、统计) | 订单创建、支付完成、用户注册 |
| DEBUG | 开发调试,生产环境默认关闭 | 函数入参、SQL 预编译语句 |
| TRACE | 详细跟踪,仅调试环境使用 | 方法间调用链、循环内变量值 |
日志内容规范
text
必须包含:
- 时间戳(精确到毫秒)
- 线程名
- 日志级别
- 类名(或 logger name)
- 具体的日志消息
推荐包含:
- traceId(链路追踪 ID)
- userId(操作人)
- 请求方法 + URI(Web 层)
- 关键参数(业务主键)
禁止包含:
- 密码、支付密码
- 身份证号、银行卡号
- 手机号、邮箱(无脱敏)
- 信用卡 CVV、支付 token
- 数据库连接字符串中的密码最佳实践
好的日志:
java
// 有明确的业务含义
log.info("订单创建成功 orderId={}, userId={}", orderId, userId);
// 包含关键上下文
log.warn("接口降级触发 service={}, fallback={}, reason={}", serviceName, fallback, reason);
// 异常时输出完整上下文
try {
paymentService.pay(order);
} catch (PaymentException e) {
log.error("支付失败 orderId={}, amount={}, error={}", orderId, amount, e.getMessage(), e);
}差的日志:
java
// 无意义
log.info("进来了");
// 丢失上下文
log.error(e.getMessage());
// 循环中大量输出(可能打爆磁盘)
for (int i = 0; i < 100000; i++) {
log.debug("processing item {}", i);
}
// 打印敏感信息
log.info("用户密码: {}", password);日志脱敏
通用脱敏工具
java
public class SensitiveDataUtils {
// 手机号: 138****1234
public static String maskPhone(String phone) {
if (phone == null || phone.length() < 7) return phone;
return phone.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2");
}
// 身份证: 110***********1234
public static String maskIdCard(String idCard) {
if (idCard == null || idCard.length() < 10) return idCard;
return idCard.replaceAll("(\\d{6})\\d{8,10}(\\w{4})", "$1********$2");
}
// 邮箱: a***@example.com
public static String maskEmail(String email) {
if (email == null || !email.contains("@")) return email;
return email.replaceAll("(^[\\w]{1})[\\w]+(@.*)", "$1***$2");
}
// 银行卡号: 6222 **** **** 1234
public static String maskBankCard(String cardNo) {
if (cardNo == null || cardNo.length() < 8) return cardNo;
return cardNo.replaceAll("(\\d{4})\\d{8,12}(\\d{4})", "$1 **** **** $2");
}
}Logback 脱敏方案
方案一:自定义 Converter
java
public class SensitiveDataConverter extends MessageConverter {
@Override
public String convert(ILoggingEvent event) {
String message = event.getFormattedMessage();
// 手机号脱敏
message = message.replaceAll("1[3-9]\\d{9}", "$0".replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2"));
// 身份证脱敏
message = message.replaceAll("\\d{17}[\\dXx]", "********ID_CARD_MASKED********");
return message;
}
}xml
<!-- 注册 Converter -->
<conversionRule conversionWord="msg" converterClass="com.example.logging.SensitiveDataConverter"/>方案二:JSON 序列化层脱敏(推荐)
java
// 定义脱敏注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Sensitive {
SensitiveType value();
enum SensitiveType { PHONE, ID_CARD, EMAIL, BANK_CARD, PASSWORD }
}
// 自定义序列化器
@Component
public class SensitiveSerializer extends JsonSerializer<String> {
@Override
public void serialize(String value, JsonGenerator gen, SerializerProvider provider) {
String masked = switch (sensitiveType) {
case PHONE -> SensitiveDataUtils.maskPhone(value);
case ID_CARD -> SensitiveDataUtils.maskIdCard(value);
// ...
};
gen.writeString(masked);
}
}分布式链路日志
基于 traceId 的完整链路
在微服务架构中,一个请求会经过多个服务,需要 traceId 串联:
text
┌──────────┐
│ API 网关 │ ← 生成全局 traceId
└─────┬────┘
│ traceId=a1b2c3
┌──────────┼──────────┐
│ │ │
┌────┴────┐ ┌───┴────┐ ┌──┴────┐
│ 订单服务 │ │ 库存服务│ │ 支付服务│
└────┬────┘ └───┬────┘ └───┬───┘
│ traceId │ │
│ a1b2c3 │ │
└──────────┴──────────┘利用 ELK 搜索链路
text
# 查询 traceId 为 a1b2c3 的所有日志
traceId: a1b2c3
# 按时间排序即可看到完整请求链路
@timestamp: asc与 SkyWalking / Zipkin 结合
日志只提供基础链路查询,分布式追踪(APM)提供更详细的调用链分析:
| 工具 | 能力 | 与日志关系 |
|---|---|---|
| SkyWalking | 完整调用链、拓扑图、性能分析 | traceId 可以与日志关联 |
| Zipkin / Brave | 调用链可视化、延迟分析 | 通过 traceId 关联日志 |
| MDC + ELK | 基础链路日志搜索 | 低成本方案 |
日志安全最佳实践
防止日志注入
攻击者可能在输入中注入换行符伪造日志:
java
// 恶意输入: "User login failed\n[INFO] User admin login success"
log.warn("User login failed: {}", username);
// 输出两条日志,第二条伪造了成功登录
// 防御:过滤换行符
public static String sanitizeLog(String message) {
return message.replaceAll("[\r\n]", "_");
}日志文件安全
text
- 日志文件权限设为 640(owner 读写,group 读)
- 日志目录禁止 public 访问
- 禁止将日志写入 Web 可访问路径
- 生产环境日志定期归档和清理
- 日志文件不包含数据库密码、API Key合规要求
text
- 用户操作日志保留 > 180 天
- 敏感操作(登录、支付、权限变更)保留 > 1 年
- 支持日志审计导出
- 日志不可篡改(WORM 存储)