前言
由于在平常开发中没有使用过日志,但是看到之前同事们写的代码文件中都存在日志打印,出于对程序猿行业的热爱,我希望我代码里面不能没有日志信息的打印,并让日志打印出现在合理位置,下面一起来学习日志。
1.0 什么是日志?
日志就是记录一些程序运行轨迹,方便查询定位。就像我们开发过程中使用的debug测试每一步,找到对应节点的信息等等,由于平常生产环境没有控制台,所以我们不能将日志打印到控制台上面,所以我们要写入指定位置文件中,在固定时间进行清理。
日志级别 TRACE
< DEBUG
< INFO
< WARN
< ERROR
< FATAL
。
着重说一下 DEBUG、INFO
DEBUG | debug级别用来记录详细的信息,方便定位问题进行调试,在生产环境我们一般不开启DEBUG |
INFO | 用来记录关键代码点的信息,以便代码是否按照我们预期的执行,生产环境通常会设置INFO级别 |
级别越高日志输出越少,因为它不会输出比自己低的日志信息!!!
2.0 springboot支持哪些日志组件?
日志组件
Logback Spring Boot 约定的默认配置
log4j
log4j2
slf4j
JUL
springboot选择的是:slf4j + logback,一般首选强烈推荐使用它
3.0 日志组件的配置?
3.1 导入依赖
<!--slf4j依赖-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.32</version>
</dependency>
<!-- logback 依赖 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
3.2 配置日志级别
# slf4j日志配置
logging:
# 配置级别
level:
root: info
#分包配置级别,即不同的目录下可以使用不同的级别
#com.example.springb_web.controller: debug
3.3 进行初步测试
@RequestMapping("register")
public ResponseResult toRegister(){
String msg = "hello slf4j";
logger.info("slf4j info--->咱俩第一次见面->{}",msg);
logger.debug("slf4j debug--->咱俩第一次见面->{}",msg);
return new ResponseResult(200,"ok");
}
发现配置info级别,debug不输出?? 后来发现是springboot约定大于配置,它本身已经有一个配置,叫做base.xml,它里面已经设置root level=“INFO” 已经定义了日志输出级别为 INFO 。这就是为什么,我看到控制台没有输出 DEBUG 那条日志。
3.4 开始配置日志
这里不能用 level: root: info fa
# slf4j日志配置
logging:
config: classpath:logback.xml
配置文件详情
不用修改配置,直接拿来用
只需要修改对应日志存储路径<property>
修改对应路径下的日志级别即可!!
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<!-- 定义日志存放目录 -->
<!-- <springProperty scope="context" name="app_log" source="spring.application.name" />-->
<property name="logPath" value="D:/源码/ajax交互源码/mall/log/" />
<!-- 日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,如果设置为WARN,则低于WARN的信息都不会输出 -->
<!-- 1,consolo控制台日志 -->
<appender name="consoleLog" class="ch.qos.logback.core.ConsoleAppender">
<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - %msg%n</pattern>
</layout>
</appender>
<!-- 2,debug日志,记录查询语句 -->
<appender name="fileDebugLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>DEBUG</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<!-- 设置日志输出格式 -->
<encoder>
<pattern>%date %-5level %logger{5} - %msg%n</pattern>
<!-- 设置编码格式,以防中文乱码 -->
<charset class="java.nio.charset.Charset">UTF-8</charset>
</encoder>
<!-- 当前日这个类型的所有日志。当日志超出下面设置的大小会分割压缩放到debug文件夹下,但debug.log不改变,直到第二天才会重置 -->
<File>${logPath}/debug.log</File>
<!--滚动策略,靠这个来生成不同文件。 TimeBasedRollingPolicy 不能设置文件大小,所以用SizeAndTimeBasedRollingPolicy-->
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!--日志存放路径-->
<fileNamePattern>>${logPath}/%d{yyyy-MM-dd}/debug/debug-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<!-- 日志文件保留天数,超过这个则删除旧的日志 -->
<MaxHistory>15</MaxHistory>
<!-- 日志文件最大值,超过则进行分割 -->
<maxFileSize>5MB</maxFileSize>
<!-- 日志保留最大的值,超过这个则删除旧的日志 -->
<totalSizeCap>2GB</totalSizeCap>
<!-- 设置这个启动时MaxHistory才生效,才会删日志 -->
<cleanHistoryOnStart>true</cleanHistoryOnStart>
</rollingPolicy>
</appender>
<!-- 3,info普通日志 -->
<appender name="fileInfoLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<!-- 设置日志输出格式 -->
<encoder>
<pattern>%date %-5level %logger{5} - %msg%n</pattern>
<!-- 设置编码格式,以防中文乱码 -->
<charset class="java.nio.charset.Charset">UTF-8</charset>
</encoder>
<!-- 当前日这个类型的所有日志。当日志超出下面设置的大小会分割压缩放到info文件夹下,但info.log不改变,直到第二天才会重置 -->
<File>${logPath}/info.log</File>
<!--滚动策略,靠这个来生成不同文件。 TimeBasedRollingPolicy 不能设置文件大小,所以用SizeAndTimeBasedRollingPolicy-->
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!--日志存放路径-->
<fileNamePattern>${logPath}/%d{yyyy-MM-dd}/info/info-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<!-- 日志文件保留天数,超过这个则删除旧的日志 -->
<MaxHistory>15</MaxHistory>
<!-- 日志文件最大值,超过则进行分割 -->
<maxFileSize>2MB</maxFileSize>
<!-- 日志保留最大的值,超过这个则删除旧的日志 -->
<totalSizeCap>1GB</totalSizeCap>
<!-- 设置这个启动时MaxHistory才生效,才会删日志 -->
<cleanHistoryOnStart>true</cleanHistoryOnStart>
</rollingPolicy>
</appender>
<!-- 4,警告日志 -->
<appender name="fileWarnLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>WARN</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<!-- 设置日志输出格式 -->
<encoder>
<pattern>%date %-5level %logger{5} - %msg%n</pattern>
<!-- 设置编码格式,以防中文乱码 -->
<charset>UTF-8</charset>
</encoder>
<!-- 当前日这个类型的所有日志。当日志超出下面设置的大小会分割压缩放到info文件夹下,但warn.log不改变,直到第二天才会重置 -->
<File>${logPath}/warn.log</File>
<!--滚动策略,靠这个来生成不同文件。 TimeBasedRollingPolicy 不能设置文件大小,所以用SizeAndTimeBasedRollingPolicy-->
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!--日志存放路径。注意路径后面如果加.zip等压缩文件类型结尾的,超过单个文件最大值进行分割时会自动对文件进行压缩-->
<fileNamePattern>${logPath}/%d{yyyy-MM-dd}/warn/warn-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<!-- 日志文件保留天数,超过这个则删除旧的日志 -->
<MaxHistory>15</MaxHistory>
<!-- 日志文件最大值,超过则进行分割 -->
<maxFileSize>5MB</maxFileSize>
<!-- 日志保留最大的值,超过这个则删除旧的日志 -->
<totalSizeCap>1GB</totalSizeCap>
<!-- 设置这个启动时MaxHistory才生效,才会删日志 -->
<cleanHistoryOnStart>true</cleanHistoryOnStart>
</rollingPolicy>
</appender>
<!-- 5,错误日志 -->
<appender name="fileErrorLog" 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>
<pattern>%date %-5level %logger{5} - %msg%n</pattern>
<!-- 设置编码格式,以防中文乱码 -->
<charset class="java.nio.charset.Charset">UTF-8</charset>
</encoder>
<!-- 当前日这个类型的所有日志。当日志超出下面设置的大小会分割压缩放到info文件夹下,但error.log不改变,直到第二天才会重置 -->
<File>${logPath}/error.log</File>
<!--滚动策略,靠这个来生成不同文件。 TimeBasedRollingPolicy 不能设置文件大小,所以用SizeAndTimeBasedRollingPolicy-->
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!--日志存放路径。注意路径后面如果加.zip等压缩文件类型结尾的,超过单个文件最大值进行分割时会自动对文件进行压缩-->
<fileNamePattern>${logPath}/%d{yyyy-MM-dd}/error/error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<!-- 日志文件保留天数,超过这个则删除旧的日志 -->
<MaxHistory>15</MaxHistory>
<!-- 日志文件最大值,超过则进行分割 -->
<maxFileSize>5MB</maxFileSize>
<!-- 日志保留最大的值,超过这个则删除旧的日志 -->
<totalSizeCap>1GB</totalSizeCap>
<!-- 设置这个启动时MaxHistory才生效,才会删日志 -->
<cleanHistoryOnStart>true</cleanHistoryOnStart>
</rollingPolicy>
</appender>
<root level="info">
<appender-ref ref="consoleLog" />
<appender-ref ref="fileInfoLog" />
<appender-ref ref="fileErrorLog" />
<appender-ref ref="fileWarnLog" />
</root>
<!-- 系统模块级别全局日志控制 -->
<logger name="com.fei" level="info"/>
<!-- Spring日志级别控制 -->
<logger name="org.springframework" level="warn" />
<!-- 记录sql语句 -->
<logger name="com.fei.mapper" level="debug" >
<appender-ref ref="fileDebugLog" />
</logger>
</configuration>
3.5 有日志哪些使用方式
3.5.1 直接 LoggerFactory.getLogger(当前类的class对象)
3.5.2 使用注解的方式
4.0 通过AOP代理做日志
通过动态代理实现日志,步骤如下:
4.1 首先导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
4.2 自定义一个接口
自定义接口的目的就是为了,获取使用该接口的方法的详细信息进行打印到日志文件中。
package com.fei.annotation;
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SysLog {
String operMoudle() default "";//操作模块
String operMethod() default "";//操作方法
String operDes() default "";//操作描述
}
4.3 再写一个切面类
写一个通知方法,参数使用JoinPoint,它其中包含方法的详细信息!!
package com.fei.pojo;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.fei.annotation.SysLog;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
@Aspect
@Component
public class SysLogger {
private final Logger logger = LoggerFactory.getLogger(SysLogger.class);
@Pointcut("@annotation(com.fei.annotation.SysLog)")
public void operLoggerPointCut(){
logger.info("===============系统操作日志===============");
}
@Pointcut("execution(public * com.fei.controller.*.*(..))")
public void controllerMethod(){}
@Before("operLoggerPointCut()")
public void logBefore(JoinPoint joinPoint){
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
HttpServletRequest request = ((ServletRequestAttributes)requestAttributes).getRequest();
StringBuilder requestLog = new StringBuilder();
Signature signature = joinPoint.getSignature();
Method method = ((MethodSignature) signature).getMethod();
SysLog sysAnnotation = method.getAnnotation(SysLog.class);//利用反射获取到自定义注解
// 打印请求内容
logger.info("===============请求内容开始===============");
logger.info("操作模块:"+sysAnnotation.operMoudle());
logger.info("操作方法:"+sysAnnotation.operMethod());
logger.info("操作描述:"+sysAnnotation.operDes());
logger.info("请求地址:" + request.getRequestURL().toString());
logger.info("请求IP:" + request.getRemoteAddr());
logger.info("请求方式:" + request.getMethod());
logger.info("请求类方法:" + joinPoint.getSignature());
logger.info("请求类方法参数值:" + Arrays.toString(joinPoint.getArgs()));
// 处理请求参数
String[] paramNames = ((MethodSignature) signature).getParameterNames();
Object[] paramValues = joinPoint.getArgs();
int paramLength = null == paramNames ? 0 : paramNames.length;
if (paramLength == 0) {
requestLog.append("请求参数 = {} ");
} else {
requestLog.append("请求参数 = [");
for (int i = 0; i < paramLength - 1; i++) {
requestLog.append(paramNames[i]).append("=").append(JSONObject.toJSONString(paramValues[i])).append(",");
}
requestLog.append(paramNames[paramLength - 1]).append("=").append(JSONObject.toJSONString(paramValues[paramLength - 1])).append("]");
}
logger.info("请求参数明细:"+requestLog.toString());
logger.info("===============请求内容结束===============");
}
@AfterReturning(returning = "o", pointcut = "operLoggerPointCut()")
public void logResultVOInfo(Object o){
logger.info("--------------返回内容开始----------------");
logger.info("Response内容:" + JSON.toJSONString(o));
logger.info("--------------返回内容结束----------------");
}
@AfterThrowing(pointcut = "controllerMethod()", throwing = "ex")
public void doAfterThrowing(JoinPoint joinPoint, Exception ex) {
String methodName = joinPoint.getSignature().getName();
List<Object> args = Arrays.asList(joinPoint.getArgs());
System.out.println("连接点方法为:" + methodName + ",参数为:" + args + ",异常为:" + ex);
}
}
4.4 测试接口
@SysLog(operMoudle="测试接口",operMethod="test", operDes = "第一次日志接口测试")
@RequestMapping("/toLogin")
public String toLogin(){
return "login";
}
日志内容也在对应文件中写入,最后呈现的效果如下:
最后写一下学习上述内容出现的错误:
Description:
Failed to bind properties under 'logging.level' to java.util.Map<java.lang.String, org.springframework.boot.logging.LogLevel>:
Property: logging.level
Value: "info"
Origin: class path resource [application.yml] - 30:10
Reason: org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [java.lang.String] to type [java.util.Map<java.lang.String, org.springframework.boot.logging.LogLevel>]
Action:
Update your application's configuration
Disconnected from the target VM, address: '127.0.0.1:59163', transport: 'socket'
Process finished with exit code 0
删除application.yml 中的日志级别即可!!因为我配置文件中已经配置了日志级别了,所以yml不需要再配置!!
之后的博客,我会更新springsecurity安全框架的认证与授权的过程!!