基于Java日志平台的访问链路追踪实战
语雀完整版:
https://www.yuque.com/g/mingrun/embiys/ch6d86/collaborator/join?token=vyXkXgZ7PHOQhTI5&source=doc_collaborator# 《基于Java日志平台的访问链路追踪实战》
基于Java日志平台的访问链路追踪实战
一、Java日志体系
- 概述
- 日志接口
- JCL(Jakarta Commons Logging,Commons Logging ),Java Apache的项目
- SLF4J: Simple Logging Facade for Java,缩写Slf4j,是⼀一套简易易Java日志门面,只提供相关接口,和其他日志⼯工具之间需要桥接
- 发展历程
- sout、e.printstack()
- Log4j
- Log4j2:性能好,但是完全不兼容一代
- Jul(性能不行,jdk自带的)
- JCL(为了抽象一层接口,解决混乱问题)
- jcl接口下的默认实现是 log4j,没有引入就会使用 jul
- jcl推荐下面这种写法,来避免参数拼接造成的浪费
if (logger.isDebugEnabled()) {logger.debug("this is a debug info , message :" + msg);
}
- slf4j
- 他的实现是logback
- 他能桥接到 JCL那边去
- 他能桥接到 JCL那边去
- 配置详解
- 概述
- 日志级别
- trace: 路径跟踪
- debug: -般用于日常调式
- info: 打印重要信息
- warn:给出警告
- error: 出现错误或问题
而每个日志组件的具体级别划分稍有不同
- error: 出现错误或问题
- 日志组件
- appender: 日志输出目的地,负责日志的输出(输出到什么 地方, 文件、socket、kafka)
- logger:日志记录器,负责收集处理日志记录(如何处理日志)
- layout: 日志格式化,负责对输出的日志格式化(以什么形式展现)
- 日志建议
- 门面约束:使用 Log Facade 可以方便的切换具体的日志实现 ,实战中可以把pom中 日志实现框架依赖 scope设置为 runtime
- 单一原则,一个项目中只使用一个日志实现
- 依赖约束:pom中的optional选项设置为 true,这样别人继承你项目的时候 就不会继承你得依赖
- 避免传递
<exclusion><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId>
</exclusion>
- 写法注意
- 减少分析
- 精简至上
二、中间件
- 概述:
- 日志的⽣生命周期一般分为采集,传输,存储和分析四个环节,选⽤用中间件时所关注的⻆角度:性能、可靠性、插件⽀支持程度、配置复杂度
- 总结:elk成熟,logstatch性能弱于filebeat,kafka做存储性能高
- 安装 Es、分词器、es-head、log_statsh、Kibana、kafka、kafka-manager、filebeat
- 流程与配置
fileBeat的速度比logStatsh要快,所以日志文件是由 fileBeat来采集的,然后给kafka,然后logstatsh从kafka获取,然后输出给 es
- fileBeat
filebeat.inputs:
- type: logenabled: truepaths:- /root/logs/*.logfields:from: filebeat
output.kafka:enabled: truehosts: ["39.98.133.153:9103"]topic: filebeatcompression: gzip
- kafka补充知识
- 基本概念
- 基本概念
- offset管理
kafka允许consumer将当前消费的消息的offset提交到kafka中,这样如果consumer因异常退出后,下次启动仍然可以从上次记录的offset开始向后继续消费消息。
- offset管理
- logstash 配置
input {kafka {bootstrap_servers => ["39.98.133.153:9103"]group_id => "logstash"topics => ["filebeat"]consumer_threads => 1decorate_events => trueadd_field => {"from" => "filebeat"}codec => "json"}
}
filter {mutate {rename => { "[host][name]" => "host" }}
}
output {elasticsearch {hosts => "localhost:9200"index => "mylog"}stdout {}
}
三、项目搭建
- 设计目标
- 日志的内容应该包括 某次请求id(requestId )、某个用户会话id(sessionId)、某个终端请求id(terminalId),来做到快速检索
- 要能 满足集群、微服的使用
- 低侵入、低耦合,延时不能太高
- 日志的 缓存与防丢失
- 系统拓扑
- 具体搭建
- 创建项目 划分模块
- 引入依赖
logback-kafka-appender
和kafka-clients
- 引入依赖
- 加入logback配置文件,主要配置了
kafkaAppender
- 加入logback配置文件,主要配置了
- 自定义一个 kafkaAppender
三、项目实施
- 前端请求:前端一般请求到nginx,这一层有如下两个方案
- 第一种是使用 filebeat采集日志文件
- 第二种是使用,lua脚本方式,直接输出到kafka,其中,rid和tid在这一步生成
- rid:一个随机数
- tid:可以使用 user-agent
- 微服务层
- 在拦截器中,向logbean放入 ip地址、sid(登陆的时候 可以放入用户名),rid(随机数 ,请求Id), tid(ip、user-agent)
- 定义一个 注解@logInfo,定义切面 对加上这个注解的方法,方法执行前后 打印执行日志,方法中可以对日志进行封装,如下所示
public class LogUtil {private final static Logger logger = LoggerFactory.getLogger("kafka");public static void printInfo(String msg) {LogBean logBean = LogBean.logBeanThreadLocal.get();logBean.setMessage(msg);logger.info(logBean.toString());}
}
- 跨服务调用
- 项目实现总结
- 引入依赖
logback-kafka-appender
和kafka-clients
,然后就创建一个自定义的KafkaAppender
(统一定义这个一个),其中制定了kafka的配置,然后每个服务项目 都添加自己的logback.xml配置文件,并在其中配置上面自定义的 appender
- 引入依赖
- 定义一个实体类来存放日志信息(这条日志的信息、时间、IP地址、用户名之类的),同时这个实体类是在构造函数中 给其中一个threadLocal变量赋值
- 定义一个拦截器,将初始化上面这个实体类,并放入初始信息,IP地址、用户名等,再把他自己放到threadlocal变量中去,并打印下接收到了这个请求
- 然后自定义一个 logInfo注解,写个切面,对加了这个注解的方法做环绕通知,在方法之前前后打印日志,并标上方法名
- 要自己打印日志,就要先从上面这个 threadLocal中获取一个实体类对象,然后再把自己的msg放进去
- 日志内容
- rid(requestId):一次请求的唯一标示,⽣生成后一直传递到调⽤用结束
- 项目中使用随机字符,就是定义一个数组,里面放了字母和数字,然后随机去取,拼接成一个长度为10的字符串9
- sid(sessionId):⽤用户会话相关,涉及登陆时存在,不不登陆的操作为空
- 用户名-租户-用户Id
- tid(terminalId):同一个终端的请求标示,可以理理解为同一个设备。可能对应多个⽤用户的多次请求
- 从请求头中取出UserAgent(可以区分安卓、IOS、不同的浏览器)
- 另外还有Ip地址