springboot集成日志知识的梳理

 前言

        由于在平常开发中没有使用过日志,但是看到之前同事们写的代码文件中都存在日志打印,出于对程序猿行业的热爱,我希望我代码里面不能没有日志信息的打印,并让日志打印出现在合理位置,下面一起来学习日志。

1.0 什么是日志?

        日志就是记录一些程序运行轨迹,方便查询定位。就像我们开发过程中使用的debug测试每一步,找到对应节点的信息等等,由于平常生产环境没有控制台,所以我们不能将日志打印到控制台上面,所以我们要写入指定位置文件中,在固定时间进行清理。

        日志级别 TRACE < DEBUG < INFO < WARN < ERROR < FATAL

着重说一下 DEBUG、INFO

DEBUGdebug级别用来记录详细的信息,方便定位问题进行调试,在生产环境我们一般不开启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安全框架的认证与授权的过程!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

周小代

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值