上手Logback ,灵活记录你的日志。

基本介绍

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <property name="FILE_ERROR_PATTERN"
            value="${FILE_LOG_PATTERN:-%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS}} ${LOG_LEVEL_PATTERN:-%5p} ${PID:- } --- [%t] %-40.40logger{39} %file:%line: %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
  <include resource="org/springframework/boot/logging/logback/defaults.xml"/>
  
	<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
		<filter class="ch.qos.logback.classic.filter.LevelFilter">
			<level>debug</level>
		</filter>
		<encoder>
			<pattern>${CONSOLE_LOG_PATTERN}</pattern>
			<charset>UTF-8</charset>
		</encoder>
	</appender>

	<appender name="FILE_INFO" class="ch.qos.logback.core.rolling.RollingFileAppender">
		<!--如果只是想要 Info 级别的日志,只是过滤 info 还是会输出 Error 日志,因为 Error 的级别高, 所以我们使用下面的策略,可以避免输出 Error 的日志-->
		<filter class="ch.qos.logback.classic.filter.LevelFilter">
			<!--过滤 Error-->
			<level>ERROR</level>
			<!--匹配到就禁止-->
			<onMatch>DENY</onMatch>
			<!--没有匹配到就允许-->
			<onMismatch>ACCEPT</onMismatch>
		</filter>
		<!--日志名称,如果没有File 属性,那么只会使用FileNamePattern的文件路径规则如果同时有<File>和<FileNamePattern>,那么当天日志是<File>,明天会自动把今天的日志改名为今天的日期。即,<File> 的日志都是当天的。-->
		<!--<File>logs/info.spring-boot-demo-logback.log</File>-->
		<!--滚动策略,按照时间滚动 TimeBasedRollingPolicy-->
		<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
			<!--文件路径,定义了日志的切分方式——把每一天的日志归档到一个文件中,以防止日志填满整个磁盘空间-->
			<FileNamePattern>logs/spring-boot-demo-logback/info.created_on_%d{yyyy-MM-dd}.part_%i.log</FileNamePattern>
			<!--只保留最近90天的日志-->
			<maxHistory>90</maxHistory>
			<!--用来指定日志文件的上限大小,那么到了这个值,就会删除旧的日志-->
			<!--<totalSizeCap>1GB</totalSizeCap>-->
			<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
				<!-- maxFileSize:这是活动文件的大小,默认值是10MB,本篇设置为1KB,只是为了演示 -->
				<maxFileSize>2MB</maxFileSize>
			</timeBasedFileNamingAndTriggeringPolicy>
		</rollingPolicy>
		<!--<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">-->
		<!--<maxFileSize>1KB</maxFileSize>-->
		<!--</triggeringPolicy>-->
		<encoder>
			<pattern>${FILE_LOG_PATTERN}</pattern>
			<charset>UTF-8</charset> <!-- 此处设置字符集 -->
		</encoder>
	</appender>

	<appender name="FILE_ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
		<!--如果只是想要 Error 级别的日志,那么需要过滤一下,默认是 info 级别的,ThresholdFilter-->
		<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
			<level>Error</level>
		</filter>
		<!--日志名称,如果没有File 属性,那么只会使用FileNamePattern的文件路径规则如果同时有<File>和<FileNamePattern>,那么当天日志是<File>,明天会自动把今天的日志改名为今天的日期。即,<File> 的日志都是当天的。-->
		<!--<File>logs/error.spring-boot-demo-logback.log</File>-->
		<!--滚动策略,按照时间滚动 TimeBasedRollingPolicy-->
		<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
			<!--文件路径,定义了日志的切分方式——把每一天的日志归档到一个文件中,以防止日志填满整个磁盘空间-->
			<FileNamePattern>logs/spring-boot-demo-logback/error.created_on_%d{yyyy-MM-dd}.part_%i.log</FileNamePattern>
			<!--只保留最近90天的日志-->
			<maxHistory>90</maxHistory>
			<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
				<!-- maxFileSize:这是活动文件的大小,默认值是10MB,本篇设置为1KB,只是为了演示 -->
				<maxFileSize>2MB</maxFileSize>
			</timeBasedFileNamingAndTriggeringPolicy>
		</rollingPolicy>
		<encoder>
			<pattern>${FILE_ERROR_PATTERN}</pattern>
			<charset>UTF-8</charset> <!-- 此处设置字符集 -->
		</encoder>
	</appender>

	<root level="info">
		<appender-ref ref="CONSOLE"/>
		<appender-ref ref="FILE_INFO"/>
		<appender-ref ref="FILE_ERROR"/>
	</root>
</configuration>

这是一个比较基本的logback配置文件

首先看标签,每一对标签代表的是一种log输出方式

上面的配置文件中,有三对标签

第一组控制的是输出到控制台的日志

第二组输出日志到指定文件中,并且只输出info级别的日志

第三组输出日志到指定文件中,并且只输出error级别的日志

所以,最终会有在控制台打印日志的同时,还会生成一个info.log和一个error.log,文件名称可以按照指定格式生成。

光是编写标签的内容还不能生效,一定要在标签中指定使用。
格式为:

<appender-ref ref="CONSOLE"/>

ref的值为标签的name。

灵活运用,场景举例

场景1

某个类或某个包里的所有类的日志,你只想输出在log文件中,你并不想输出到控制台中。比如AOP中的日志,我不想输出到控制台,但我想记录到文件中。

我们需要引入一个新的标签,该标签与同级:

    <logger name="com.hariyoo.web.aspectj" additivity="false" level="info">
        <appender-ref ref="FILE"/>
        <appender-ref ref="FILE_JOB"/>
    </logger>

name的值为类全称或者是包路径。

additivity值为布尔值,为true时,不仅在中指定的范围内输出,还会在指定的范围中输出;如果是false,那么只会在当前的的范围中输出。

标签同 中使用方式相同。

所以,为了达成我们只想输出日志到文件中的期望,我们additivity的值写成false,标签不添加name为console的。

场景2

你的项目里,有一个接口会被外界定时或频繁的调用,并且在疯狂的在输出日志,影响你观察别的正在使用的接口日志。你需要将这个请求过程中的所有日志输出到另外的一个单独的日志文件中。

这个时候,我们需要自定义一个logback的过滤器,效果比较强大。同时要搭配spring的拦截器或AOP来实现,这里我使用AOP来完成整个操作。

AOP范例:

@Aspect
@Component
@Slf4j
public class AopLog {

	/**
     * 第一个*代表返回类型不限
     * 第二个*代表所有类
     * 第三个*代表所有方法
     * (..) 代表参数不限
     *
	 * 切入点
	 */
	@Pointcut("execution(public * com.hariyoo.web.controller.*Controller.*(..))")
	public void log() {}

	/**
	 * 环绕操作
	 *
	 * @param point 切入点
	 * @return 原方法返回值
	 * @throws Throwable 异常信息
	 */
	@Around("log()")
	public Object aroundLog(ProceedingJoinPoint point) throws Throwable {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = Objects.requireNonNull(attributes).getRequest();
        
        // 很关键
		MDC.put("marker",request.getRequestURI());

        log.info("【请求 URL】:{}", request.getRequestURL());
//        log.info("【请求 IP】:{}", request.getRemoteAddr());
        log.info("【请求类名】:{}", point.getSignature().getDeclaringTypeName());
        log.info("【请求方法名】:{}",point.getSignature().getName());
        Map<String, String[]> parameterMap = request.getParameterMap();
        if (!parameterMap.isEmpty()) {
            log.info("【请求参数】:{}", JSONUtil.toJsonStr(parameterMap));
        } else {
	        Object[] args = point.getArgs();
	        if (args.length == 1) {
		        log.info("【请求参数】:{}", JSONUtil.parseObj(args[0],false).toString());
	        } else {
		        log.info("【请求参数】:{}", JSONUtil.parseArray(args,false).toString());
	        }
        }

        long start = System.currentTimeMillis();

        Object result = point.proceed();
//		log.info("【返回值】:{}", JSONUtil.toJsonStr(result));

		long stop = System.currentTimeMillis();
		log.info("【请求耗时 ({})】:{} 毫秒",point.getSignature().getName(),stop-start);
		return result;
	}

}

这个AOP对所有Controller做日志增强。

注意!看关键的一行代码:

MDC.put("marker",request.getRequestURI());

MDC可以保存了一个值在调用去接口的线程中,我可以浅显的理解为给这个线程做了个全局的标记。

这里我记录下了当前请求的uri作为标记,为后续做逻辑判断使用。

当然了,MDC的作用可不止这么粗鄙,有兴趣可以继续深入了解。

接下来,我们写一个定义一个log过滤器类:

public class JobFilter extends Filter<ILoggingEvent> {
	private Boolean showInJobLog = true;

	private static final String SCHEDULED_JOB = "job/scheduledJob";

	private static final String UPLOAD_STREAM_JOB = "job/uploadStreamJob";

	private static final String SCHEDUL_THREAD = "scheduling";

	@Override
	public FilterReply decide(ILoggingEvent event) {
	    // 获取标记的uri
		String marker = MDC.get("marker");
		// 获取线程名
		String threadName = event.getThreadName();
		if (showInJobLog){
			if (StrUtil.contains(threadName,SCHEDUL_THREAD)
					||StrUtil.contains(marker,SCHEDULED_JOB)
					||StrUtil.contains(marker,UPLOAD_STREAM_JOB)){
				return FilterReply.ACCEPT;
			} else {
				return FilterReply.DENY;
			}
		} else {
			if (StrUtil.contains(threadName,SCHEDUL_THREAD)
					||StrUtil.contains(marker,SCHEDULED_JOB)
					||StrUtil.contains(marker,UPLOAD_STREAM_JOB)){
				return FilterReply.DENY;
			} else {
				return FilterReply.ACCEPT;
			}
		}

	}

	public void setShowInJobLog(Boolean showInJobLog) {
		this.showInJobLog = showInJobLog;
	}
}

看得出来,其实就是继承了Filter类,然后重写了decide方法,在其中进行逻辑判断。

逻辑实现也不难,就是如果线程名中包含"scheduling" 或者 MDC中包含 指定的路径,就返回 FilterReply.ACCEPT;否则返回 FilterReply.DENY。

当结果为 FilterReply.DENY ,表示不会记录日志;当结果为 FilterReply.ACCEPT 时,表示会记录日志。

你肯定注意到showInJobLog这个变量,这是为了在多个中复用这个过滤器。

最后,我们将这个过滤器应用到logback配置文件中。

<appender name="FILE_JOB" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <filter class="com.hariyoo.web.config.JobFilter">
        <showInJobLog>true</showInJobLog>
    </filter>
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
        <!--日志文件输出的文件名-->
        <FileNamePattern>${LOG_HOME}/bmhs-job.log.%d{yyyy-MM-dd}.log</FileNamePattern>
        <!--日志文件保留天数-->
        <MaxHistory>30</MaxHistory>
    </rollingPolicy>
    <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
        <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
        <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
    </encoder>
    <!--日志文件最大的大小-->
    <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
        <MaxFileSize>10MB</MaxFileSize>
    </triggeringPolicy>
</appender>

这个是为了专门记录频繁调用的接口,我们可以看到这个filter的使用方式:

<filter class="com.hariyoo.web.config.JobFilter">
    <showInJobLog>true</showInJobLog>
</filter>

我们设置变量showInJobLog为true,根据过滤器的逻辑,最终达到我们想要的效果。

但是这样还不够,我们同样要在name为"FILE"的添加这个过滤器,如果不添加,那么在另一个日志文件中还是会输出我们不想看到的那些日志,并且我们要将showInJobLog设置为false。

<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <filter class="com.hariyoo.web.config.JobFilter">
        <showInJobLog>false</showInJobLog>
    </filter>
    <--.....省略......-->
</appender>

OK,完事儿。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值