What
日志指的是系统运行时进行的一些记录,可以指定输入到控制台也可以输入到文本文件中。
Why
这个东西很重要,体现了职业素养
程序运行起来基本就是一个黑箱,如果程序的行为与预期的不一致,那就是出了Bug, 这时候我们需要快速定位Bug 作为开发人员难免跟Bug打交道, 解决问题是开发的基本技能 但是解决问题的方式,步骤以及反思的程度都更加体现开发者的职业素养
职业素养
职业素养一方面体现了能力与素质; 另一方面它又强调了持续的积累与养成,作为开发人员,基本技能不够熟练,当然谈不上职业素养。但是仅仅能快速地编写代码,却不关心代码背后的意义,不能迅速的判断、解决程序运行中的各种问题,不能对自己的代码产生自信,这也是缺乏职业素养的体现。 缺乏职业素养, 可能导致的后果:
- 无法迅速解决问题,浪费过多的时间,导致产出低下
- 给加班增加了前置条件
快速定位Bug
1. 单步调试
使用IDE的debug模式, 进行单步调试,一步一步的跟踪,同时观察变量值与逻辑分支 缺点:
- 耗时,debug模式一般比较慢
- 耗力,debug过程中会经过其他业务, 而这些其他业务也需要开发者关注,它们也有可能是产生Bug的原因
2. 日志
开发者通过在特定的地方打日志 通过日志的输出可以,可以帮助快速定位Bug,尤其是在类似黑盒的生产环境中 目前已经有很多日志框架如Log4j, LogBack等开源框架可以使用
- 只能在开发环境使用,单步调试只能由开发者手动进行
How
1. 通过开源日志框架
常见的日志框架Log4j, Log4j2, LogBack等 可以将日志输出到控制台与文件中, 以及对日志文件的维护。 SpringBoot自带LogBack日志框架,我基本上使用这个日志框架
2. LogBack + Lombok
1. 使用LogBack
依赖
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.1.2</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.1.2</version>
</dependency>
<dependency>
<groupId>org.logback-extensions</groupId>
<artifactId>logback-ext-spring</artifactId>
<version>0.1.1</version>
</dependency>
2. 配置文件
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<property name="log.root.level" value="DEBUG"/>
<property name="log.other.level" value="ERROR"/>
<property name="log.base"
value="C:/ideaWorkplace/om-systemV4"/>
<property name="log.moduleName" value="omSystem"/>
<property name="log.max.size" value="20MB"/>
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<Pattern>%date{yyyy-MM-dd HHmmss.SSS} %-5level [%thread]%logger{56}.%method:%L -%msg%n</Pattern>
</encoder>
</appender>
<appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
<File>${log.base}/${log.moduleName}.log
</File>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<FileNamePattern>${log.base}/archive/${log.moduleName}_all_%d{yyyy-MM-dd}.%i.log.zip
</FileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>${log.max.size}</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>%date{yyyy-MM-dd HHmmss.SSS} %-5level [%thread]%logger{56}.%method:%L -%msg%n</pattern>
</layout>
</appender>
<root level="INFO">
<appender-ref ref="stdout"/>
<appender-ref ref="file"/>
</root>
</configuration>
3. Lombok
- Maven依赖
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>
4. 日志使用
- 类上使用注解
@Slf4j
- 日志记录 使用以下方法
log.info()
log.error()
log.warn()
2. 使用AOP使用统一日志接口
目的:打印函数的名称,输入参数名,参数类型,参数值
使用AOP的环绕通知 见
类名
可通过joinPoint获得
String className = joinPoint.getSignature().getDeclaringType().getName();
函数名
可通过joinPoint获得
String methodName = joinPoint.getSignature().getName();
参数名
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
//参数值
Object[] args = joinPoint.getArgs();
//参数名
String[] parameterNames = signature.getParameterNames();
//参数类型
Class[] parameterTypes = signature.getParameterTypes();
StringBuilder stringBuilder = new StringBuilder();
for(int i = 0; i < parameterNames.length; i++) {
stringBuilder.append(parameterTypes[i] + " : " + parameterNames[i] + " : " + args[i] +"\n");
}
String parameters = stringBuilder.toString();
获得以上元素即可输出到日志文件里
切面类如下
@Pointcut("@annotation(com.atoz.omsystem.annotation.GetParamLog)")
public void getParam() {
}
//获得函数名, 参数名, 参数值,执行时间
@Around(value = "getParam()")
public void getParams(ProceedingJoinPoint joinPoint) throws Throwable {
Date start = new Date();
String className = joinPoint.getSignature().getDeclaringType().getName();
String methodName = joinPoint.getSignature().getName();
String entranceName = className + "." + methodName;
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
//参数值
Object[] args = joinPoint.getArgs();
//参数名
String[] parameterNames = signature.getParameterNames();
//参数类型
Class[] parameterTypes = signature.getParameterTypes();
StringBuilder stringBuilder = new StringBuilder();
for(int i = 0; i < parameterNames.length; i++) {
stringBuilder.append(parameterTypes[i] + " : " + parameterNames[i] + " : " + args[i] +"\n");
}
String parameters = stringBuilder.toString();
log.info("入口为 {} \n 参数信息: \n{}", entranceName , stringBuilder.toString());
Object proceed = joinPoint.proceed();
Date end = new Date();
long millSecond = end.getTime() - start.getTime();
long second = millSecond/1000;
log.info("执行时间为 {} s", second);
}