Skip to content

日志系统

项目地址:https://github.com/zhaobao1830/misscmszb

行为日志

参考文档:https://doc.cms.talelin.com/server/spring-boot/logger.html#%E6%90%9C%E7%B4%A2%E5%9B%BE%E4%B9%A6

使用

比如要对删除图片这个行为做日志记录,可以添加@Logger注解

BookController.java

java
package com.zb.misscmszb.controller.v1;

import com.zb.misscmszb.core.annotation.GroupRequired;
import com.zb.misscmszb.core.annotation.Logger;
import com.zb.misscmszb.core.annotation.PermissionMeta;
import com.zb.misscmszb.core.exception.NotFoundException;
import com.zb.misscmszb.dto.book.CreateOrUpdateBookDTO;
import com.zb.misscmszb.model.BookDO;
import com.zb.misscmszb.service.BookService;
import com.zb.misscmszb.vo.CreatedVO;
import com.zb.misscmszb.vo.DeletedVO;
import com.zb.misscmszb.vo.UpdatedVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.validation.constraints.Positive;
import java.util.List;

@RestController
@RequestMapping("/v1/book")
@Validated
public class BookController {

    @Autowired
    private BookService bookService;

    @GetMapping("/delete/{id}")
    @Logger(template = "删除图书")
    @GroupRequired
    @PermissionMeta(value = "删除图书", module = "图书")
    public DeletedVO deleteBook(@PathVariable("id") @Positive(message = "{id.positive}") Integer id) {
        BookDO book = bookService.getById(id);
        if (book == null) {
            throw new NotFoundException(10022);
        }
        bookService.deleteById(book.getId());
        return new DeletedVO(14);
    }
}

备注

行为日志是基于权限功能实现的,所以除了使用 @Logger注解,还需要加上@PermissionMeta注解和LoginRequired、GroupRequired、AdminRequired三个注解中的一个

因为LoggerImpl的handle方法里,需要通过LocalUser.getLocalUser()获取当前登录用户,而LocalUser.setLocalUser()方法只有在LoginRequired、GroupRequired、AdminRequired三个注解的时候才执行

实现

一、自定义行为日志拦截器

LogInterceptor.java

java
package com.zb.misscmszb.core.interceptors;

import com.zb.misscmszb.core.annotation.Logger;
import com.zb.misscmszb.core.annotation.PermissionMeta;
import com.zb.misscmszb.core.interceptors.interfaces.LoggerResolver;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;

/**
 * 行为日志拦截器
 */
public class LogInterceptor implements HandlerInterceptor {

    @Autowired
    private LoggerResolver loggerResolver;

    /**
     * 后置处理
     *
     * @param request      请求
     * @param response     响应
     * @param handler      处理器
     * @param modelAndView 视图
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
        // 记录日志
        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            Method method = handlerMethod.getMethod();
            Logger logger = method.getAnnotation(Logger.class);
            if (logger != null) {
                PermissionMeta meta = method.getAnnotation(PermissionMeta.class);
                // parse template and extract properties from request,response and modelAndView
                loggerResolver.handle(meta, logger, request, response);
            }
        }
    }
}

二、在WebConfiguration将自定义的拦截类添加到拦截器中

java
package com.zb.misscmszb.core.configuration;

import com.zb.misscmszb.core.interceptors.AuthorizeInterceptor;
import com.zb.misscmszb.core.interceptors.LogInterceptor;
import com.zb.misscmszb.core.interceptors.RequestLogInterceptor;
import com.zb.misscmszb.module.file.FileUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.nio.file.FileSystems;
import java.nio.file.Path;

@Configuration
public class WebConfiguration implements WebMvcConfigurer {

    @Value("${auth.enabled:false}")
    private boolean authEnabled;

    @Value("${request-log.enabled:false}")
    private boolean requestLogEnabled;

    @Value("${cms.file.store-dir:assets/}")
    private String dir;

    @Value("${cms.file.serve-path:assets/**}")
    private String servePath;

    @Autowired
    private AuthorizeInterceptor authorizeInterceptor;

    @Autowired
    private RequestLogInterceptor requestLogInterceptor;

    @Autowired
    private LogInterceptor logInterceptor;

    // 将自定义的拦截类添加到拦截器中
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        if (authEnabled) {
            //开发环境忽略签名认证
            registry.addInterceptor(authorizeInterceptor)
                    .excludePathPatterns(getDirServePath());
        }
        if (requestLogEnabled) {
            registry.addInterceptor(requestLogInterceptor);
        }
        registry.addInterceptor(logInterceptor);
    }
  
}

三、在CmsConfiguration配置

java
package com.zb.misscmszb.core.configuration;

import com.zb.misscmszb.core.interceptors.AuthorizeInterceptor;
import com.zb.misscmszb.core.interceptors.LogInterceptor;
import com.zb.misscmszb.extension.token.DoubleJWT;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * cms配置文件
 */
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(CmsProperties.class)
public class CmsConfiguration {

    @Autowired
    private CmsProperties properties;

    @Bean
    @ConditionalOnProperty(prefix = "zb.cms", value = "logger-enabled", havingValue = "true")
    public LogInterceptor logInterceptor() {
        return new LogInterceptor();
    }
}

日志系统

参考文档:https://doc.cms.talelin.com/server/spring-boot/logging.html#%E4%BD%BF%E7%94%A8

日志系统基于 spring-boot 和 logback,在此之上提供了日志记录文件和请求日志记录两个功能。

使用

一、新建请求日志拦截器类RequestLogInterceptor,在该类上加上@Slf4j注解

java
package com.zb.misscmszb.core.interceptors;

import com.zb.misscmszb.core.util.IPUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.AsyncHandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 请求日志拦截器
 */
@Slf4j
public class RequestLogInterceptor implements AsyncHandlerInterceptor {

    private final ThreadLocal<Long> startTime = new ThreadLocal<>();

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        startTime.set(System.currentTimeMillis());
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        log.info("[{}] -> [{}] from: {} costs: {}ms",
                request.getMethod(),
                request.getServletPath(),
                IPUtil.getIPFromRequest(request),
                System.currentTimeMillis() - startTime.get()
        );
        startTime.remove();
    }
}

二、新建logback-spring.xml

改配置类是logback的默认配置类,必须叫这个名字

xml
<configuration>
    <!--给控制台输出增加颜色-->
    <conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter"/>
    <conversionRule conversionWord="wex"
                    converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/>
    <conversionRule conversionWord="wEx"
                    converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/>
    
    <property name="CONSOLE_LOG_PATTERN"
              value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
    
    <!--输出到控制台-->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <Pattern>${CONSOLE_LOG_PATTERN}</Pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>
    
    <!--输出到 access 文件夹-->
    <appender name="FILE_ACCESS" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>logs/access/%d{yyyy-MM,aux}/%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <totalSizeCap>1GB</totalSizeCap>
            <maxHistory>30</maxHistory>
            <maxFileSize>5MB</maxFileSize>
        </rollingPolicy>
        <encoder>
            <charset>UTF-8</charset>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%X{req.remoteAddr}:%X{req.remotePort}] "%X{req.method} %X{req.requestURI} %X{req.protocol}" %X{res.status} %X{req.bodyBytesSent} "%X{req.referer}" "%X{req.userAgent}"%n
            </pattern>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>INFO</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>
    
    <!--输出到 error 文件夹-->
    <appender name="FILE_ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>logs/error/%d{yyyy-MM,aux}/%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <totalSizeCap>1GB</totalSizeCap>
            <maxHistory>30</maxHistory>
            <maxFileSize>5MB</maxFileSize>
        </rollingPolicy>
        <encoder>
            <charset>UTF-8</charset>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] "%X{req.method} %X{req.requestURI} %X{req.protocol}" %-5level %logger{50} - %msg%n
            </pattern>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>
    
    <!--输出到 warn 文件夹-->
    <appender name="FILE_WARN" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>logs/warn/%d{yyyy-MM,aux}/%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <totalSizeCap>1GB</totalSizeCap>
            <maxHistory>30</maxHistory>
            <maxFileSize>5MB</maxFileSize>
        </rollingPolicy>
        <encoder>
            <charset>UTF-8</charset>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] "%X{req.method} %X{req.requestURI} %X{req.protocol}" %-5level %logger{50} - %msg%n
            </pattern>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>WARN</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>
    
    <!--输出到 info 文件夹-->
    <appender name="FILE_INFO" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>logs/info/%d{yyyy-MM,aux}/%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <totalSizeCap>1GB</totalSizeCap>
            <maxHistory>30</maxHistory>
            <maxFileSize>5MB</maxFileSize>
        </rollingPolicy>
        <encoder>
            <charset>UTF-8</charset>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] "%X{req.method} %X{req.requestURI} %X{req.protocol}" %-5level %logger{50} - %msg%n
            </pattern>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>INFO</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>
    
    <!--输出到 debug 文件夹-->
    <appender name="FILE_DEBUG" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>logs/debug/%d{yyyy-MM,aux}/%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <totalSizeCap>1GB</totalSizeCap>
            <maxHistory>30</maxHistory>
            <maxFileSize>5MB</maxFileSize>
        </rollingPolicy>
        <encoder>
            <charset>UTF-8</charset>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] "%X{req.method} %X{req.requestURI} %X{req.protocol}" %-5level %logger{50} - %msg%n
            </pattern>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>DEBUG</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>
    
    <!--输出到 trace 文件夹-->
    <appender name="FILE_TRACE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>logs/trace/%d{yyyy-MM,aux}/%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <totalSizeCap>1GB</totalSizeCap>
            <maxHistory>30</maxHistory>
            <maxFileSize>5MB</maxFileSize>
        </rollingPolicy>
        <encoder>
            <charset>UTF-8</charset>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] "%X{req.method} %X{req.requestURI} %X{req.protocol}" %-5level %logger{50} - %msg%n
            </pattern>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>TRACE</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!--    不知道这个有什么用-->
    <logger name="org.apache.ibatis.cache.decorators.LoggingCache" level="INFO" additivity="false">
        <appender-ref ref="FILE_ACCESS"/>
    </logger>
    
    <springProfile name="dev">
        <root level="INFO">
            <appender-ref ref="CONSOLE"/>
            <appender-ref ref="FILE_ERROR"/>
            <appender-ref ref="FILE_WARN"/>
            <appender-ref ref="FILE_INFO"/>
            <appender-ref ref="FILE_DEBUG"/>
            <appender-ref ref="FILE_TRACE"/>
        </root>
    </springProfile>
    
    <springProfile name="prod">
        <root level="INFO">
            <appender-ref ref="FILE_ERROR"/>
            <appender-ref ref="FILE_WARN"/>
            <appender-ref ref="FILE_INFO"/>
            <appender-ref ref="FILE_DEBUG"/>
            <appender-ref ref="FILE_TRACE"/>
        </root>
    </springProfile>
    
    <springProfile name="test">
        <root level="INFO">
            <appender-ref ref="CONSOLE"/>
        </root>
    </springProfile>

</configuration>

三、在yml文件添加日志的配置

yml
# 开启权限拦截
auth:
  enabled: true

# 开启http请求日志记录
request-log:
  enabled: true

#日志配置
logging:
  level:
    # web信息日志等级
    web: debug
    # SQL日志记录
    com.zb.misscmszb.mapper: debug

上面这些步骤做完,就可以使用日志系统和请求日志俩个功能了

备注

如果想在控制窗口显示执行的sql,只要在yml文件加入如下配置就行,这里用的是mybatis-plus

yml
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

如有转载或 CV 的请标注本站原文地址