日志一直在系统中占据这十分重要的地位,他是我们在系统发生故障时用来排查问题的利器,也是我们做操作审计的重要依据。那么如何记录好日志呢?选择什么框架来记录日志,是不是日志打越多越好,带着这些问题我们今天一起来讨论下springboot应用如何记录好日志。
在我们java工程中,日志框架一般分为两层,日志门面和日志实现。
日志门面
日志门面是一个抽象层,它定义了一组统一的日志接口给用户使用。隐藏了底层日志实现的细节,提供了一种与具体日志实现解耦的方式。常见的日志门面有SLF4J(Simple Logging Facade for Java)和Apache Commons Logging等。日志门面的作用包括:
日志实现
日志实现是一种具体的日志框架,常见的包括Logback、Log4j、Java Util Logging等。不同的日志实现可能提供不同的功能和性能特性。最基础的都提供了以下共功能:
总结一下,日志门面提供了统一的接口,让应用程序使用,而日志实现则负责将日志消息输出到具体的目标。那么我们可以直接用日志实现来记录日志吗,当然是可以的,但是使用日志门面,有他的优点:
%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level [%logger{36}] - %msg%n INFO ${LOG_PATH}/info/info-%d{yyyy-MM-dd}-%i.log 30 50MB %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level [%logger{36}] - %msg%n ERROR ${LOG_PATH}/error/error-%d{yyyy-MM-dd}-%i.log 30 50MB %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level [%logger{36}] - %msg%n
rollingPolicy 用于配置日志文件的滚动策略,决定何时创建新的日志文件或对现有日志文件进行归档。常见的日志滚动策略有以下几种:
TimeBasedRollingPolicy:按时间滚动日志文件。该策略会根据指定的时间模式,如日期或时间间隔,创建新的日志文件。可配置属性有:
SizeAndTimeBasedRollingPolicy:继承自TimeBasedRollingPolicy,支持按照文件大小滚动的特性,可以通过maxFileSize来配置:
FixedWindowRollingPolicy:按指定的固定窗口大小滚动日志文件。该策略会创建固定数量的日志文件,并在写满一个日志文件后,将日志写入下一个文件,循环使用这些日志文件。可以通过 minIndex 和 maxIndex 属性指定日志文件的索引范围。
在Logback中,可以使用Filter来对日志事件进行筛选和过滤。常用的过滤器有:ThresholdFilter和LevelFilter
ThresholdFilter:基于日志级别进行过滤,只有达到或超过指定级别的日志事件才会被接受。以下是一个示例配置:
WARN
LevelFilter:LevelFilter也是基于日志级别进行过滤,可以根据指定的最低日志级别(level)来决定是否接受或拒绝日志事件,允许更细粒度地控制不同appender或logger的过滤行为。以下是一个示例配置:
WARN DENY ACCEPT
在我们的应用中,经常需要记录每一次请求的参数、请求的结果、耗时以及请求异常时需要记录异常堆栈,用来在必要的时候排查问题。如果直接记录的话,存在日志和代码耦合,日志风格不统一,日志排查困难等问题,所以我们往往通过AOP的方式将日志和代码进行解耦,让程序员可以专注在业务的开发上。
@Aspect @Component @Slf4j public class LogAspect { @Around(value = "execution(* com.pinellia.framework.controller.*.*(..))") public Object logRequest(ProceedingJoinPoint joinPoint) throws Throwable { StopWatch stopWatch = new StopWatch(); stopWatch.start(); HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest(); String requestUrl = request.getRequestURL().toString(); Object result = null; try { result = joinPoint.proceed(); } catch (Throwable e) { log.error("Request url: {} failed...", requestUrl, e); throw e; } finally { stopWatch.stop(); log.info("Request url: {}, params: {}, response: {}, cost: {}", requestUrl, joinPoint.getArgs(), GsonUtil.toJson(result), stopWatch.getTotalTimeMillis()); } return result; } }