一、日志框架
使用slf4j作为日志门面,logback作为实际的日志框架。
二、logback文件的改造方案
1.重新设计日志格式:在原有的日志格式基础上,新增了颜色渲染,修改了不合理的格式。
<property name="ENCODER_PATTERN" value="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%boldYellow(%thread)] %X{tl} [%highlight(%-5level)] [%boldCyan(%logger{50})] [%X{key1:-}] [%X{key2:-}] %.-2048msg %n" />
2.定义不同的Appender:
- 使用日志规范中已有的Appender:APPLICATION_DEBUG、APPLICATION_INFO、APPLICATION_WARN、APPLICATION_ERROR、CONSOLE。
- 统一身份认证服务无需使用异步日志。
**3.定义不同的Logger
**- 对ORM框架,定义新的Logger,打印sql语句,但不输出为日志文件。
- 对三方包如:com.alibaba.nacos.client、org.hibernate 定义不同的logger
4.根据Profile的配置,选择日志输出的级别。
<springProfile name="dev"><!-- 根据application.yml配置指定级别 -->
<root level="DEBUG">
<!-- 控制台输出 -->
<appender-ref ref="STDOUT"/>
<!-- 文件输出 -->
<appender-ref ref="ERROR"/>
<appender-ref ref="INFO"/>
<appender-ref ref="WARN"/>
<appender-ref ref="DEBUG"/>
<appender-ref ref="TRACE"/>
</root>
</springProfile>
<springProfile name="prod">
<root level="INFO">
<!-- 控制台输出 -->
<appender-ref ref="STDOUT"/>
<!-- 文件输出 -->
<appender-ref ref="ERROR"/>
<appender-ref ref="INFO"/>
<appender-ref ref="WARN"/>
</root>
</springProfile>
三、Getting Started(user-management-service)
1.引入依赖
<dependency>
<groupId>com.yomahub</groupId>
<artifactId>tlog-all-spring-boot-starter</artifactId>
<version>1.5.1</version>
</dependency>
2.logback-spring.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<!-- %d{yyyy-MM-dd HH:mm:ss.SSS}: 输出日期时间,使用指定的格式。-->
<!-- %thread: 输出线程名。-->
<!-- %X{tl}: 输出MDC(Mapped Diagnostic Context)中名为 tl 的值。-->
<!-- %highlight(%-5level): 输出日志级别,并通过 highlight 进行着色。-->
<!-- %logger{50}: 输出日志记录器名称,最多显示50个字符。-->
<!-- %X{key1:-}: 输出MDC中名为 key1 的值,如果没有找到则显示 -。-->
<!-- %X{key2:-}: 输出MDC中名为 key2 的值,如果没有找到则显示 -。-->
<!-- %.-2048msg: 输出日志消息,最多显示2048个字符。-->
<!-- %n: 输出换行符。–>-->
<configuration>
<!-- 排除不需要打印的日志 -->
<logger name="com.xxl.job.core.thread.ExecutorRegistryThread" level="OFF" />
<logger name="com.xxl.job.core.thread.ExecutorRegistryThread.run" level="OFF" />
<!-- 增加TLog MDC Listener -->
<contextListener class="com.yomahub.tlog.core.enhance.logback.TLogLogbackTTLMdcListener"/>
<!--定义参数,后面可引用使用-->
<property name="LOG_PATH" value="./logs" />
<property name="LOG_FILE_DEBUG" value="${LOG_PATH}/debug" />
<property name="LOG_FILE_INFO" value="${LOG_PATH}/info" />
<property name="LOG_FILE_WARN" value="${LOG_PATH}/warn" />
<property name="LOG_FILE_ERROR" value="${LOG_PATH}/error" />
<property name="ENCODER_PATTERN" value="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%boldYellow(%thread)] %X{tl} [%highlight(%-5level)] [%boldCyan(%logger{50})] [%X{key1:-}] [%X{key2:-}] %.-2048msg %n" />
<property name="ROLLING_SUFFIX" value=".%d{yyyy-MM-dd}.%i.log" />
<property name="ROLLING_MAX_HISTORY" value="15" />
<property name="ROLLING_MAX_FILE_SIZE" value="50MB" />
<property name="ROLLING_TOTAL_SIZE_CAP" value="20GB" />
<!--异步appender-->
<property name="ASYNC_QUEUE_SIZE" value="256" />
<!--设为0表示队列达到80%,也不丢弃任务-->
<property name="ASYNC_DISCARDING_THRESHOLD" value="0" />
<!--日志上下文关闭后,AsyncAppender继续执行写任务的时间,单位毫秒-->
<property name="ASYNC_MAX_FLUSH_TIME" value="1000" />
<!--队列满了直接丢弃要写的消息-->
<property name="ASYNC_NEVER_BLOCK" value="true" />
<!--是否包含调用方的信息,false则无法打印类名方法名行号等-->
<property name="ASYNC_CALLER_DATA" value="true" />
<!--异步appender-->
<!--输出日志到文件中-->
<appender name="APPLICATION_DEBUG" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!--不输出ERROR级别的日志-->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>DEBUG</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<encoder class="com.yomahub.tlog.core.enhance.logback.AspectLogbackEncoder">
<pattern>${ENCODER_PATTERN}</pattern>
<charset>utf8</charset>
</encoder>
<!--根据日期滚动输出日志策略-->
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/%d{yyyy-MM-dd}/debug-log-%i.log</fileNamePattern>
<maxHistory>${ROLLING_MAX_HISTORY}</maxHistory>
<maxFileSize>${ROLLING_MAX_FILE_SIZE}</maxFileSize>
<totalSizeCap>${ROLLING_TOTAL_SIZE_CAP}</totalSizeCap>
</rollingPolicy>
</appender>
<!--输出日志到文件中-->
<appender name="APPLICATION_INFO" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!--不输出ERROR级别的日志-->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<encoder class="com.yomahub.tlog.core.enhance.logback.AspectLogbackEncoder">
<pattern>${ENCODER_PATTERN}</pattern>
<charset>utf8</charset>
</encoder>
<!--根据日期滚动输出日志策略-->
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/%d{yyyy-MM-dd}/info-log-%i.log</fileNamePattern>
<maxHistory>${ROLLING_MAX_HISTORY}</maxHistory>
<maxFileSize>${ROLLING_MAX_FILE_SIZE}</maxFileSize>
<totalSizeCap>${ROLLING_TOTAL_SIZE_CAP}</totalSizeCap>
</rollingPolicy>
</appender>
<!--输出日志到文件中-->
<appender name="APPLICATION_WARN" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!--不输出ERROR级别的日志-->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>WARN</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<encoder class="com.yomahub.tlog.core.enhance.logback.AspectLogbackEncoder">
<pattern>${ENCODER_PATTERN}</pattern>
<charset>utf8</charset>
</encoder>
<!--根据日期滚动输出日志策略-->
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/%d{yyyy-MM-dd}/warn-log-%i.log</fileNamePattern>
<maxHistory>${ROLLING_MAX_HISTORY}</maxHistory>
<maxFileSize>${ROLLING_MAX_FILE_SIZE}</maxFileSize>
<totalSizeCap>${ROLLING_TOTAL_SIZE_CAP}</totalSizeCap>
</rollingPolicy>
</appender>
<!--错误日志输出文件-->
<appender name="APPLICATION_ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!--只输出ERROR级别的日志-->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<encoder class="com.yomahub.tlog.core.enhance.logback.AspectLogbackEncoder">
<pattern>${ENCODER_PATTERN}</pattern>
<charset>utf8</charset>
</encoder>
<!--根据日期滚动输出日志策略-->
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/%d{yyyy-MM-dd}/error-log-%i.log</fileNamePattern>
<maxHistory>${ROLLING_MAX_HISTORY}</maxHistory>
<maxFileSize>${ROLLING_MAX_FILE_SIZE}</maxFileSize>
<totalSizeCap>${ROLLING_TOTAL_SIZE_CAP}</totalSizeCap>
</rollingPolicy>
</appender>
<!--异步打印日志,任务放在阻塞队列中,如果队列达到80%,将会丢弃TRACE,DEBUG,INFO级别的日志任务,对性能要求不是太高的话不用启用-->
<appender name="ASYNC_APPLICATION_DEBUG" class="com.yomahub.tlog.core.enhance.logback.async.AspectLogbackAsyncAppender">
<!--队列的深度,该值会影响性能,默认256-->
<queueSize>${ASYNC_QUEUE_SIZE}</queueSize>
<!--设为0表示队列达到80%,也不丢弃任务-->
<discardingThreshold>${ASYNC_DISCARDING_THRESHOLD}</discardingThreshold>
<!--日志上下文关闭后,AsyncAppender继续执行写任务的时间,单位毫秒-->
<maxFlushTime>${ASYNC_MAX_FLUSH_TIME}</maxFlushTime>
<!--队列满了直接丢弃要写的消息-->
<neverBlock>${ASYNC_NEVER_BLOCK}</neverBlock>
<!--是否包含调用方的信息,false则无法打印类名方法名行号等-->
<includeCallerData>${ASYNC_CALLER_DATA}</includeCallerData>
<!--One and only one appender may be attached to AsyncAppender,添加多个的话后面的会被忽略-->
<appender-ref ref="APPLICATION_INFO"/>
</appender>
<!--异步打印日志,任务放在阻塞队列中,如果队列达到80%,将会丢弃TRACE,DEBUG,INFO级别的日志任务,对性能要求不是太高的话不用启用-->
<appender name="ASYNC_APPLICATION_INFO" class="com.yomahub.tlog.core.enhance.logback.async.AspectLogbackAsyncAppender">
<!--队列的深度,该值会影响性能,默认256-->
<queueSize>512</queueSize>
<!--设为0表示队列达到80%,也不丢弃任务-->
<discardingThreshold>${ASYNC_DISCARDING_THRESHOLD}</discardingThreshold>
<!--日志上下文关闭后,AsyncAppender继续执行写任务的时间,单位毫秒-->
<maxFlushTime>${ASYNC_MAX_FLUSH_TIME}</maxFlushTime>
<!--队列满了直接丢弃要写的消息-->
<neverBlock>${ASYNC_NEVER_BLOCK}</neverBlock>
<!--是否包含调用方的信息,false则无法打印类名方法名行号等-->
<includeCallerData>${ASYNC_CALLER_DATA}</includeCallerData>
<!--One and only one appender may be attached to AsyncAppender,添加多个的话后面的会被忽略-->
<appender-ref ref="APPLICATION_INFO"/>
</appender>
<appender name="ASYNC_APPLICATION_WARN" class="com.yomahub.tlog.core.enhance.logback.async.AspectLogbackAsyncAppender">
<!--队列的深度,该值会影响性能,默认256-->
<queueSize>${ASYNC_QUEUE_SIZE}</queueSize>
<!--设为0表示队列达到80%,也不丢弃任务-->
<discardingThreshold>${ASYNC_DISCARDING_THRESHOLD}</discardingThreshold>
<!--日志上下文关闭后,AsyncAppender继续执行写任务的时间,单位毫秒-->
<maxFlushTime>${ASYNC_MAX_FLUSH_TIME}</maxFlushTime>
<!--队列满了直接丢弃要写的消息-->
<neverBlock>${ASYNC_NEVER_BLOCK}</neverBlock>
<!--是否包含调用方的信息,false则无法打印类名方法名行号等-->
<includeCallerData>${ASYNC_CALLER_DATA}</includeCallerData>
<includeCallerData>true</includeCallerData>
<!--One and only one appender may be attached to AsyncAppender,添加多个的话后面的会被忽略-->
<appender-ref ref="APPLICATION_WARN"/>
</appender>
<appender name="ASYNC_APPLICATION_ERROR" class="com.yomahub.tlog.core.enhance.logback.async.AspectLogbackAsyncAppender">
<!--队列的深度,该值会影响性能,默认256-->
<queueSize>${ASYNC_QUEUE_SIZE}</queueSize>
<!--设为0表示队列达到80%,也不丢弃任务-->
<discardingThreshold>${ASYNC_DISCARDING_THRESHOLD}</discardingThreshold>
<!--日志上下文关闭后,AsyncAppender继续执行写任务的时间,单位毫秒-->
<maxFlushTime>${ASYNC_MAX_FLUSH_TIME}</maxFlushTime>
<!--队列满了直接丢弃要写的消息-->
<neverBlock>${ASYNC_NEVER_BLOCK}</neverBlock>
<!--是否包含调用方的信息,false则无法打印类名方法名行号等-->
<includeCallerData>${ASYNC_CALLER_DATA}</includeCallerData>
<includeCallerData>true</includeCallerData>
<!--One and only one appender may be attached to AsyncAppender,添加多个的话后面的会被忽略-->
<appender-ref ref="APPLICATION_ERROR"/>
</appender>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="com.yomahub.tlog.core.enhance.logback.AspectLogbackEncoder">
<pattern>${ENCODER_PATTERN}</pattern>
<charset>utf8</charset>
</encoder>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>DEBUG</level>
</filter>
</appender>
<logger name="org.mybatis" value="INFO" level="INFO"/>
<logger name="org.springframework" value="INFO" level="INFO"/>
<logger name="org.apache" value="INFO" level="INFO"/>
<logger name="org.hibernate" value="INFO" level="INFO"/>
<logger name="sun.rmi" value="ERROR" level="ERROR"/>
<logger name="java.io" value="ERROR" level="ERROR"/>
<logger name="javax.management" value="ERROR" level="ERROR"/>
<logger name="javax.activation" value="ERROR" level="ERROR"/>
<logger name="com.alibaba.nacos.client" value="ERROR" level="ERROR"/>
<logger name="sun.net.www.protocol" value="ERROR" level="ERROR"/>
<logger name="com.netflix.loadbalancer" value="ERROR" level="ERROR"/>
<logger name="com.baomidou.mybatisplus.core.MybatisConfiguration.addMappedStatement" value="ERROR" level="ERROR"/>
<logger name="com.netflix.zuul.http" value="INFO" level="INFO"/>
<logger name="com.alibaba.cloud.seata" value="INFO" level="INFO"/>
<logger name="com.baomidou.mybatisplus" value="ERROR" level="ERROR"/>
<logger name="io.lettuce.core" value="ERROR" level="ERROR"/>
<logger name="com.apache.ibatis" level="ERROR"/>
<logger name="springfox" level="ERROR"/>
<logger name="Validator" level="ERROR"/>
<logger name="io.netty" level="ERROR"/>
<logger name="io.seata" value="ERROR" level="ERROR"/>
<logger name="org.redisson" value="ERROR" level="ERROR"/>
<springProfile name="dev"><!-- 根据application.yml配置指定级别 -->
<!--rootLogger是默认的logger-->
<root level="DEBUG">
<appender-ref ref="CONSOLE" />
<appender-ref ref="APPLICATION_DEBUG" />
<appender-ref ref="APPLICATION_INFO" />
<appender-ref ref="APPLICATION_WARN" />
<appender-ref ref="APPLICATION_ERROR" />
</root>
</springProfile>
<springProfile name="prod">
<!--rootLogger是默认的logger-->
<root level="INFO">
<appender-ref ref="CONSOLE" />
<appender-ref ref="APPLICATION_INFO" />
<appender-ref ref="APPLICATION_WARN" />
<appender-ref ref="APPLICATION_ERROR" />
</root>
</springProfile>
</configuration>
###3.添加入参打印过滤器
@Configuration
public class FilterConfig {
@Bean
public FilterRegistrationBean<LoggingFilter> loggingFilter() {
FilterRegistrationBean<LoggingFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new LoggingFilter());
registrationBean.addUrlPatterns("/*"); // 设置过滤路径
return registrationBean;
}
}
@WebFilter(urlPatterns = "/*")
@Slf4j
public class LoggingFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws ServletException, IOException {
HttpServletRequest request = (HttpServletRequest)servletRequest;
String url = request.getRequestURI();
// 打印请求参数
if (isJson(request)) {
RequestWrapper bodyRequestWrapper = new RequestWrapper(request);
String body = bodyRequestWrapper.getBodyString();
log.info("[TLOG]开始请求URL[{}],参数为:{}", url, body);
} else {
String parameters = JSONUtil.toJsonStr(request.getParameterMap());
log.info("[TLOG]开始请求URL[{}],参数为:{}", url, parameters);
}
filterChain.doFilter(servletRequest,servletResponse);
}
@Override
public void destroy() {
}
/**
* 判断本次请求的数据类型是否为json
*
* @param request request
* @return boolean
*/
private boolean isJson(HttpServletRequest request) {
if (request.getContentType() != null) {
return request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE) ||
request.getContentType().equals(MediaType.APPLICATION_JSON_UTF8_VALUE);
}
return false;
}
}
项目使用中的日志规范
//如非必要,不要打印error级别的日志,避免频繁报警
log.error("这个错误很严重,程序阻断了,要记监控,发到报警");
//可以使用warn记录用户输入参数错误的情况,避免在用户投诉时无所适从
log.warn("这个错误不应该有,程序可以继续往下走,但是太没面子了");
//对于经常访问的接口,记录info日志时思考:这些日志真的有人看吗?看到这些日志你能做什么?能不能给问题排查带来好处?
log.info("就是想记录一下业务走到哪里了");
//主要包含运行时的细节问题,无需考虑访问流量,发生在联调阶段,输出一些关键的变量、函数调用、以及程序执行的流程等信息
//某些三方传入的变量推荐使用info
log.debug("作为一个经常写bug的程序员,多打点日志很合理");
log.trace("从来没用过,他们说,是为了追踪");
//进行日志打印预判断,提升代码运行效率,提高系统性能。
if(log.isDebugEnabled()){
log.debug("别嫌麻烦,不想被开除最好这样做");
}
TLog的基本使用
- 自定义业务标签打印:使用 TLog 记录方法的入参信息,以便在日志中了解方法调用时传入的参数值。
@GetMapping("/test")
@TLogAspect(value = "id")
public void test(Integer id){
log.error("我是错误日志");
List<UcUserSoleCode> userSoleCodeList = ucUserSoleCodeService.selectUserSoleCodes(Arrays.asList(1, 2, 3, 4, 5, 748374));
System.out.println(userSoleCodeList.toString());
}
id传入11时:
[2023-10-11 13:19:51.119] [http-nio-16100-exec-4] <0> [id:11] [INFO ] [c.y.t.web.interceptor.TLogWebInvokeTimeInterceptor] [TLOG]结束URL[/BigData/test]的调用,耗时为:57316毫秒
- MDC模式:logback的xml中预留了业务标签。可以用于业务追踪。
@GetMapping("/test")
public void test(Integer id){
//会占位logback文件中ENCODER_PATTERN中的key1和key2占位符
//
MDC.put("key1","我是key1");
MDC.put("key2","我是key2");
MDC.put("key3","我是key3");
log.error("我是错误日志");
}
示例:[2023-10-11 13:19:51.119] [http-nio-16100-exec-4] [DEBUG] [org.springframework.web.servlet.DispatcherServlet] [我是key1] [我是key2] Completed 200 OK
-
线程池打印:如果需要在多线程环境中记录日志,可以配置 TLog 使用线程池来执行日志记录操作,确保线程安全。
-
远程调用打印:如果需要将日志信息发送到远程日志服务器或集中式日志系统,可以配置 TLog 实现远程日志记录。