日志
代码
- github.com/Ekkoc2021/h…
- 涉及分支:
MDC实现日志链路追踪和MDC多线程问题
日志级别:越低输出越详细
TRACE < DEBUG < INFO < WARN < ERROR
log4j2日志
依赖
<!--日志-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!--导入log4j2,需要排除boot自带的日志-->
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<exclusions>
<!-- 排除自带的logback依赖 -->
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
日志输出模板配置
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
<!--<Configuration status="WARN" monitorInterval="30"> -->
<properties>
<property name="LOG_HOME">./service-logs</property>
</properties>
<Appenders>
<!--*********************控制台日志***********************-->
<Console name="consoleAppender" target="SYSTEM_OUT">
<!--设置日志格式及颜色-->
<PatternLayout
pattern="%style{%d{ISO8601}}{bright,green} %highlight{%-5level} [%style{%t}{bright,blue}] %style{%C{}}{bright,yellow}: %msg%n%style{%throwable}{red}"
disableAnsi="false" noConsoleNoAnsi="false"/>
</Console>
<!--*********************文件日志***********************-->
<!--all级别日志-->
<RollingFile name="allFileAppender"
fileName="${LOG_HOME}/all.log"
filePattern="${LOG_HOME}/$${date:yyyy-MM}/all-%d{yyyy-MM-dd}-%i.log.gz">
<!--设置日志格式-->
<PatternLayout>
<pattern>%d %p %C{} [%t] %m%n</pattern>
</PatternLayout>
<Policies>
<!-- 设置日志文件切分参数 -->
<!--<OnStartupTriggeringPolicy/>-->
<!--设置日志基础文件大小,超过该大小就触发日志文件滚动更新-->
<SizeBasedTriggeringPolicy size="100 MB"/>
<!--设置日志文件滚动更新的时间,依赖于文件命名filePattern的设置-->
<TimeBasedTriggeringPolicy/>
</Policies>
<!--设置日志的文件个数上限,不设置默认为7个,超过大小后会被覆盖;依赖于filePattern中的%i-->
<DefaultRolloverStrategy max="100"/>
</RollingFile>
<!--debug级别日志-->
<RollingFile name="debugFileAppender"
fileName="${LOG_HOME}/debug.log"
filePattern="${LOG_HOME}/$${date:yyyy-MM}/debug-%d{yyyy-MM-dd}-%i.log.gz">
<Filters>
<!--过滤掉info及更高级别日志-->
<ThresholdFilter level="info" onMatch="DENY" onMismatch="NEUTRAL"/>
</Filters>
<!--设置日志格式-->
<PatternLayout>
<pattern>%d %p %C{} [%t] %m%n</pattern>
</PatternLayout>
<Policies>
<!-- 设置日志文件切分参数 -->
<!--<OnStartupTriggeringPolicy/>-->
<!--设置日志基础文件大小,超过该大小就触发日志文件滚动更新-->
<SizeBasedTriggeringPolicy size="100 MB"/>
<!--设置日志文件滚动更新的时间,依赖于文件命名filePattern的设置-->
<TimeBasedTriggeringPolicy/>
</Policies>
<!--设置日志的文件个数上限,不设置默认为7个,超过大小后会被覆盖;依赖于filePattern中的%i-->
<DefaultRolloverStrategy max="100"/>
</RollingFile>
<!--info级别日志-->
<RollingFile name="infoFileAppender"
fileName="${LOG_HOME}/info.log"
filePattern="${LOG_HOME}/$${date:yyyy-MM}/info-%d{yyyy-MM-dd}-%i.log.gz">
<Filters>
<!--过滤掉warn及更高级别日志-->
<ThresholdFilter level="warn" onMatch="DENY" onMismatch="NEUTRAL"/>
</Filters>
<!--设置日志格式-->
<PatternLayout>
<pattern>%d %p %C{} [%t] %m%n</pattern>
</PatternLayout>
<Policies>
<!-- 设置日志文件切分参数 -->
<!--<OnStartupTriggeringPolicy/>-->
<!--设置日志基础文件大小,超过该大小就触发日志文件滚动更新-->
<SizeBasedTriggeringPolicy size="100 MB"/>
<!--设置日志文件滚动更新的时间,依赖于文件命名filePattern的设置-->
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
</Policies>
<!--设置日志的文件个数上限,不设置默认为7个,超过大小后会被覆盖;依赖于filePattern中的%i-->
<!--<DefaultRolloverStrategy max="100"/>-->
</RollingFile>
<!--warn级别日志-->
<RollingFile name="warnFileAppender"
fileName="${LOG_HOME}/warn.log"
filePattern="${LOG_HOME}/$${date:yyyy-MM}/warn-%d{yyyy-MM-dd}-%i.log.gz">
<Filters>
<!--过滤掉error及更高级别日志-->
<ThresholdFilter level="error" onMatch="DENY" onMismatch="NEUTRAL"/>
</Filters>
<!--设置日志格式-->
<PatternLayout>
<pattern>%d %p %C{} [%t] %m%n</pattern>
</PatternLayout>
<Policies>
<!-- 设置日志文件切分参数 -->
<!--<OnStartupTriggeringPolicy/>-->
<!--设置日志基础文件大小,超过该大小就触发日志文件滚动更新-->
<SizeBasedTriggeringPolicy size="100 MB"/>
<!--设置日志文件滚动更新的时间,依赖于文件命名filePattern的设置-->
<TimeBasedTriggeringPolicy/>
</Policies>
<!--设置日志的文件个数上限,不设置默认为7个,超过大小后会被覆盖;依赖于filePattern中的%i-->
<DefaultRolloverStrategy max="100"/>
</RollingFile>
<!--error及更高级别日志-->
<RollingFile name="errorFileAppender"
fileName="${LOG_HOME}/error.log"
filePattern="${LOG_HOME}/$${date:yyyy-MM}/error-%d{yyyy-MM-dd}-%i.log.gz">
<!--设置日志格式-->
<PatternLayout>
<pattern>%d %p %C{} [%t] %m%n</pattern>
</PatternLayout>
<Policies>
<!-- 设置日志文件切分参数 -->
<!--<OnStartupTriggeringPolicy/>-->
<!--设置日志基础文件大小,超过该大小就触发日志文件滚动更新-->
<SizeBasedTriggeringPolicy size="100 MB"/>
<!--设置日志文件滚动更新的时间,依赖于文件命名filePattern的设置-->
<TimeBasedTriggeringPolicy/>
</Policies>
<!--设置日志的文件个数上限,不设置默认为7个,超过大小后会被覆盖;依赖于filePattern中的%i-->
<DefaultRolloverStrategy max="100"/>
</RollingFile>
<!--json格式error级别日志-->
<RollingFile name="errorJsonAppender"
fileName="${LOG_HOME}/error-json.log"
filePattern="${LOG_HOME}/error-json-%d{yyyy-MM-dd}-%i.log.gz">
<JSONLayout compact="true" eventEol="true" locationInfo="true"/>
<Policies>
<SizeBasedTriggeringPolicy size="100 MB"/>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
</Policies>
</RollingFile>
</Appenders>
<Loggers>
<!-- 根日志设置,没有被特殊配置输出都会按照根日志配置进行输出:设置了多个appenderRef,且配置有过滤,日志会被区分存放在不同的日志文档中 -->
<Root level="debug">
<AppenderRef ref="allFileAppender" level="all"/> <!-- 所有日志放一起-->
<AppenderRef ref="consoleAppender" level="debug"/> <!-- 只输出debug级别日志-->
<AppenderRef ref="debugFileAppender" level="debug"/>
<AppenderRef ref="infoFileAppender" level="info"/>
<AppenderRef ref="warnFileAppender" level="warn"/>
<AppenderRef ref="errorFileAppender" level="error"/>
<AppenderRef ref="errorJsonAppender" level="error"/>
</Root>
<!-- 单独配置路径日志输出级别:可以使用 双标签配置 AppenderRef 去个性化输出 -->
<!--spring日志-->
<Logger name="org.springframework" level="debug"/>
<!--druid数据源日志-->
<Logger name="druid.sql.Statement" level="debug"/>
<!-- mybatis日志 -->
<!-- <Logger name="com.mybatis" level="debug"/>-->
<Logger name="org.hibernate" level="warn"/>
<Logger name="com.zaxxer.hikari" level="info"/>
<Logger name="org.quartz" level="info"/>
<Logger name="com.andya.demo" level="debug"/>
</Loggers>
</Configuration>
applicaiton.yaml
logging:
config: classpath:log4j2.xml # 配置文件位置
# 也能控制日志输出级别,只不过功能没有xml强大
level:
root: INFO
javax.activation: info
org.apache.catalina: INFO
org.apache.commons.beanutils.converters: INFO
org.apache.coyote.http11.Http11Processor: INFO
org.apache.http: INFO
org.apache.tomcat: INFO
org.springframework: INFO
com.chinamobile.cmss.bdpaas.resource.monitor: DEBUG
#单独配置特定包的SQL日志==>调试用
com.yang.home.cipherkey.mapper: DEBUG
# mybatis核心日志
org.mybatis: DEBUG
MDC实现日志链路追踪
spring有现成的日志链路追踪的解决方案:Spring Cloud Sleuth
MDC : Mapped Diagnostic Context (映射诊断上下文)
- 基于threadlocal实现,基本的api就是put和remove,执行结束完毕需要进行remove
通过log4j2实现轻量级日志链路追踪,在输入日志过程中携带一个流水号,方便定位和追踪。
需要考虑的问题:
- 流水号怎么生成,如何保证唯一性
- 如何为每个接口都设置流水号
- 解决多线程下流水号共享问题
- 服务间调用解决流水号共享问题==>调用时传递参数即可
流水号唯一性
UUID
- 其标准形式由32个十六进制数字组成 : 7fcf758e-af8a-41d8-80ba-9dc5e5151c37
- 重复概率极低,日志系统即使重复影响不会太大,Java提供现成的api接口,默认采用随机数生成
UUID.randomUUID().toString();
雪花算法
Twitter 提出的
原理:
- 雪花算法生成的id是一个64bit的 long 型的数值 首位固定为0,之后41位时间戳+5位机器id+5位服务id+12位序列号
- 41位毫米级时间戳意味着可以连续使用至少69年不重复=>是一个相对于某个时间的时间戳
- 机器id+服务id共可以配置1024个节点
- 序列号确保同一毫秒内生成不同,可以4096个值超过等下一秒
雪花算法的时间回拨问题:系统同步时间时,或者操作人员设置时间导致时间回拨,从而导致雪花算法生成重复的id
解决方案:缓存上次获取到的时间戳
- 保存上一次获取到的时间戳,检查到回拨问题直接异常
- 等待一段时间再获取
- 减少机器码位数,用于记录出现回拨问题次数,发现回拨问题,就+1
解决了回拨问题,就能完全保证分布式系统的序列号的唯一性,但是每次后加锁,性能消耗在日志序列号获取上有些得不偿失!
数据库自增
redis/mysql数据库自增值用于来做唯一id
AOP/拦截器设置请求MDC
还有一个过滤器也能实现类似的功能
给所有controller统一配置拦截器或者AOP
- 执行controller前设置用MDC设置序列号
- 执行controller后清除MDC序列号
多线程问题
创建线程的方式
基本都3种
- 继承thread 重写 run方法,调用start
- 实现runable接口 重写run方法,构建thread传入实现的类,调用start
- future:实现Callable接口->用callable实例创建FutureTask->用futuretask创建thread
扩展
- 线程池 ThreadPoolExecutor
- CompletableFuture : 能够执行异步任务
- spring提供@Async
解决方案
- 重新包装新的runable,callable,Supplier ,在执行最终的方法前进行一个设置MDC,执行完毕后清楚MDC,利用拉姆达表达式的闭包提供的功能完成跨线程的设置
- 统一使用线程池,使用线程装饰器(用springboot封装的线程池类)==>但是只能解决runable接口的任务(callable也会被处理),spring提供@Async也能被处理,CompletableFuture的Supplier额外处理