日志系统
项目地址: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
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
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将自定义的拦截类添加到拦截器中
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配置
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注解
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的默认配置类,必须叫这个名字
<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文件添加日志的配置
# 开启权限拦截
auth:
enabled: true
# 开启http请求日志记录
request-log:
enabled: true
#日志配置
logging:
level:
# web信息日志等级
web: debug
# SQL日志记录
com.zb.misscmszb.mapper: debug
上面这些步骤做完,就可以使用日志系统和请求日志俩个功能了
备注
如果想在控制窗口显示执行的sql,只要在yml文件加入如下配置就行,这里用的是mybatis-plus
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl