一、一次线上故障排查花了4小时
2020年,用户反馈下单失败,但监控系统一切正常。
我登录服务器,用grep查日志。结果发现日志分散在8台机器上,每台机器的日志格式还不一样。花了2小时才找到报错的那条日志,又花了2小时才定位到原因——一个第三方支付接口超时。
从那以后,我们引入了统一的日志体系,同样的故障,现在10分钟就能定位。
二、日志规范
2.1 日志级别
日志级别使用规范: ERROR:系统错误,需要立即处理 - 未捕获的异常 - 外部服务不可用 - 数据不一致 WARN:潜在问题,需要关注 - 业务异常(用户输入错误) - 性能降级 - 接近阈值 INFO:关键业务流程 - 请求开始/结束 - 业务状态变更 - 外部调用 DEBUG:调试信息(生产环境关闭) - 方法入参/出参 - 中间变量 - SQL语句2.2 日志格式
/** * 统一日志格式 */@ConfigurationpublicclassLogConfig{/** * JSON格式日志(推荐) */@BeanpublicLoggingEventJsonFormatjsonFormat(){returnLoggingEventJsonFormat.builder().timestamp("@timestamp").level("level").thread("thread").logger("logger").message("message").traceId("traceId").spanId("spanId").build();}}// 日志输出示例:// {"@timestamp":"2024-01-01T12:00:00.000Z","level":"INFO","thread":"http-nio-8080-1","logger":"com.example.OrderService","message":"订单创建成功","traceId":"abc123","spanId":"def456","orderId":"ORD001","userId":"USR001","amount":99.9}2.3 MDC链路追踪
/** * MDC链路追踪 */@ComponentpublicclassTraceFilterimplementsFilter{@OverridepublicvoiddoFilter(ServletRequestrequest,ServletResponseresponse,FilterChainchain)throwsIOException,ServletException{HttpServletRequesthttpRequest=(HttpServletRequest)request;// 从请求头获取TraceIdStringtraceId=httpRequest.getHeader("X-Trace-Id");if(traceId==null){traceId=UUID.randomUUID().toString().replace("-","");}// 设置MDCMDC.put("traceId",traceId);MDC.put("spanId",generateSpanId());MDC.put("userId",getCurrentUserId(httpRequest));try{chain.doFilter(request,response);}finally{MDC.clear();}}}/** * Feign传递TraceId */@ConfigurationpublicclassFeignTraceConfig{@BeanpublicRequestInterceptortraceInterceptor(){returntemplate->{StringtraceId=MDC.get("traceId");if(traceId!=null){template.header("X-Trace-Id",traceId);}};}}三、ELK架构
3.1 架构图
┌─────────────────────────────────────────────────────────────────┐ │ ELK架构 │ │ │ │ ┌─────────┐ ┌─────────────┐ ┌──────────┐ │ │ │ 应用 │───▶│ Logstash │───▶│Elastic- │ │ │ │ 日志 │ │ (收集过滤) │ │ search │ │ │ └─────────┘ └─────────────┘ └──────────┘ │ │ │ │ │ │ │ ┌─────────────┐ │ │ │ └─────────────▶│ Filebeat │────────┘ │ │ │ (轻量采集) │ │ │ └─────────────┘ │ │ │ │ │ ┌─────────────┐ │ │ │ │ Kibana │◀─────┘ │ │ │ (可视化) │ │ │ └─────────────┘ │ │ │ └──────────────────────────────────────────────────────────────────┘3.2 Filebeat配置
# filebeat.ymlfilebeat.inputs:-type:logenabled:truepaths:-/var/log/app/*.logfields:app:order-serviceenv:productionfields_under_root:truemultiline:pattern:'^\d{4}-\d{2}-\d{2}'negate:truematch:afteroutput.logstash:hosts:["logstash:5044"]compression_level:3processors:-add_host_metadata:~-add_cloud_metadata:~3.3 Logstash配置
# logstash.confinput{beats{port=>5044}}filter{# 解析JSON格式日志json{source=>"message"target=>"log"}# 提取TraceIdmutate{add_field=>{"traceId"=>"%{[log][traceId]}"}}# 过滤DEBUG日志if[log][level]=="DEBUG"{drop{}}# 添加时间戳date{match=>["[log][@timestamp]","ISO8601"]target=>"@timestamp"}}output{elasticsearch{hosts=>["elasticsearch:9200"]index=>"app-logs-%{+YYYY.MM.dd}"template=>"/usr/share/logstash/templates/app-logs.json"template_overwrite=>true}}四、日志告警
4.1 基于ElastAlert的告警
# error_alert.yamlname:Error Log Alerttype:frequencyindex:app-logs-*# 5分钟内ERROR日志超过10条告警num_events:10timeframe:minutes:5filter:-term:log.level:"ERROR"# 排除已知的非关键错误filter:-term:log.level:"ERROR"-not:query:query_string:query:"message: *ConnectionReset*"alert:-"dingtalk"dingtalk:webhook:"https://oapi.dingtalk.com/robot/send?access_token=xxx"message:"⚠️ ERROR日志告警\n应用: {app}\n环境: {env}\n数量: {num_hits}\n最近一条: {message}"4.2 日志监控大盘
/** * 日志指标统计 */@Service@Slf4jpublicclassLogMetricsService{@AutowiredprivateMeterRegistrymeterRegistry;/** * 记录业务指标到日志 */publicvoidlogBusinessMetric(StringmetricName,doublevalue,String...tags){// 记录到监控系统TagsmetricTags=Tags.of(tags);meterRegistry.counter("business."+metricName,metricTags).increment(value);// 同时记录到日志(用于ELK分析)log.info("BusinessMetric: name={}, value={}, tags={}",metricName,value,Arrays.toString(tags));}}五、踩坑实录
坑1:日志太多导致磁盘满
DEBUG日志在生产环境开着,每天产生100GB日志,磁盘很快就满了。
解决:生产环境关闭DEBUG,设置日志滚动和自动清理。
坑2:日志格式不统一
每个服务日志格式不一样,ELK解析困难。
解决:统一JSON格式,强制规范。
坑3:日志中打印敏感信息
日志里打印了用户密码和手机号,被安全扫描发现。
解决:日志脱敏,禁止打印敏感信息。
坑4:异步日志丢失
使用异步日志,应用崩溃时缓冲区的日志丢失。
解决:配置ImmediateFlush,关键日志同步写入。
坑5:日志影响性能
在循环中打印日志,QPS下降了30%。
解决:避免在热路径打印日志,使用isDebugEnabled判断。
六、总结
日志体系要点:
| 环节 | 方案 |
|---|---|
| 规范 | 统一格式、级别、脱敏 |
| 采集 | Filebeat + Logstash |
| 存储 | Elasticsearch |
| 展示 | Kibana |
| 告警 | ElastAlert |
| 链路 | MDC + TraceId |
最佳实践:
- 统一JSON格式日志
- MDC传递链路信息
- 生产环境关闭DEBUG
- 日志脱敏
- 告警及时
血的教训:
好的日志体系不是锦上添花,是雪中送炭。出问题时,日志是你唯一的救命稻草。
思考题:你的日志体系是怎样的?排查问题快吗?
个人观点,仅供参考