Springboot + AOP logging system

Oil ouyo 2022-02-13 08:52:45 阅读数:338

springboot aop logging

 Please add a picture description

1、 Preface

A good arrangement and division of log information can make us better locate the wrong place , If we use the debugger breakpoint mechanism , Let the problem recur , And thus locate the problem and solve the problem . Most of the time, we encounter difficult to locate bug I always do this when I'm young , If it's normal bug Trust use System.out.println( ) Can solve most of the difficult and miscellaneous diseases ( dog's head ). But when our service goes online , a large number of log The log cannot be printed directly on the console to locate the problem , Besides, you don't always drive your Shell, If it's closed, it's gone . So successfully debug the program 、 The key to monitoring and error reporting is logging .

2、 Code section

Introduce dependencies

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>

But in actual development, we don't need to add this dependency ,Spring Boot Default logging framework Logback+SLF4J. and spring-boot-starter-web Already included , So we just need to introduce web Components

Transmission document address ----

https://www.scalyr.com/blog/started-quickly-spring-boot-logging/#:~:text=Spring%20Boot%20comes%20with%20a%20preconfigured%20default%20logger,dependency%20already.%20That%E2%80%99s%20what%20we%20call%20zero-configuration%20logging.
image-20210913195822135
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

Next is the corresponding logback The configuration of the

logback-spring.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="10 seconds">
<!-- The log level is from low to high TRACE < DEBUG < INFO < WARN < ERROR < FATAL, If set to WARN, Less than WARN None of our messages will output -->
<!-- scan: When this property is set to true when , If the configuration file changes , Will be reloaded , The default value is true -->
<!-- scanPeriod: Set the time interval between changes in the monitoring profile , If no time unit is given , The default unit is milliseconds . When scan by true when , This property takes effect . The default time interval is 1 minute . -->
<!-- debug: When this property is set to true when , Will print out logback Internal log information , Real-time view logback Running state . The default value is false. -->
<contextName>logback</contextName>
<!-- name The value of is the name of the variable ,value The value of is the value defined by the variable . The value defined by will be inserted into logger In the context of . After defining variables , You can make “${}” To use variables . -->
<property name="log.path" value="--- The path where you want to output the log ---"/>
<!-- Color log -->
<!-- Configure format variables :CONSOLE_LOG_PATTERN Color log format -->
<!-- magenta: Magenta -->
<!-- boldMagenta: Rough red -->
<!-- cyan: Cyan -->
<!-- white: white -->
<!-- magenta: Magenta -->
<property name="CONSOLE_LOG_PATTERN" value="%yellow(%date{yyyy-MM-dd HH:mm:ss}) |%highlight(%-5level) |%blue(%thread) |%blue(%file:%line) |%green(%logger) |%cyan(%msg%n)"/>
<!-- Output to console -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<!-- This log appender It's for development , Configure only the lowest level , The console output log level is greater than or equal to this level of log information -->
<!-- for example : If you configure INFO Level , Even if the other locations in the back are configured with DEBUG Level of logging , It's not going to be exported -->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>INFO</level>
</filter>
<encoder>
<Pattern>${CONSOLE_LOG_PATTERN}</Pattern>
<!-- Set character set -->
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- output to a file -->
<!-- Time scrolling output level by INFO journal -->
<appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- The path and filename of the log file being logged -->
<file>${log.path}/log_info.log</file>
<!-- Log file output format -->
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
<!-- Rolling policy for loggers , By date , Record by size -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- Daily log archive path and format -->
<fileNamePattern>${log.path}/info/log-info-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!-- Log file retention days -->
<maxHistory>15</maxHistory>
</rollingPolicy>
<!-- This log file only records info Grade -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- Time scrolling output level by WARN journal -->
<appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- The path and filename of the log file being logged -->
<file>${log.path}/log_warn.log</file>
<!-- Log file output format -->
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
<charset>UTF-8</charset> <!-- Set character set here -->
</encoder>
<!-- Rolling policy for loggers , By date , Record by size -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${log.path}/warn/log-warn-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!-- Log file retention days -->
<maxHistory>15</maxHistory>
</rollingPolicy>
<!-- This log file only records warn Grade -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>WARN</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- Time scrolling output level by ERROR journal -->
<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- The path and filename of the log file being logged -->
<file>${log.path}/log_error.log</file>
<!-- Log file output format -->
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
<charset>UTF-8</charset> <!-- Set character set here -->
</encoder>
<!-- Rolling policy for loggers , By date , Record by size -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${log.path}/error/log-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!-- Log file retention days -->
<maxHistory>15</maxHistory>
</rollingPolicy>
<!-- This log file only records ERROR Grade -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- <logger> Used to set the log printing level of a package or a specific class 、 And the designation <appender>. <logger> There is only one name attribute , An optional one level And an optional addtivity attribute . name: Used to designate the recipient logger A package of constraints or a specific class . level: Used to set the print level , Case is irrelevant :TRACE, DEBUG, INFO, WARN, ERROR, ALL and OFF, If this property is not set , Then the current logger Will inherit the level of the superior . -->
<!-- Use mybatis When ,sql The sentence is debug Next will print , And here we only configure info, So I want to see sql In words , There are two operations : The first one <root level="INFO"> Change to <root level="DEBUG"> This will print sql, But there will be a lot of other news in the log The second is to give it alone mapper Directory configuration DEBUG Pattern , The code is as follows , This configuration sql The statement will print , Others are normal DEBUG Level : -->
<!-- development environment : Print console -->
<springProfile name="dev">
<!-- You can output debug journal , Include mybatis Of sql journal -->
<logger name="com.*.*.mapper" level="INFO"/>
<!-- root Nodes are required , Used to specify the most basic log output level , only one level attribute level: Used to set the print level , Case is irrelevant :TRACE, DEBUG, INFO, WARN, ERROR, ALL and OFF, The default is DEBUG It can contain zero or more appender Elements . -->
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="INFO_FILE"/>
<appender-ref ref="WARN_FILE"/>
<appender-ref ref="ERROR_FILE"/>
</root>
</springProfile>
<!-- Production environment : output to a file -->
<springProfile name="prod">
<root level="INFO">
<appender-ref ref="INFO_FILE"/>
<appender-ref ref="WARN_FILE"/>
<appender-ref ref="ERROR_FILE"/>
</root>
</springProfile>
</configuration>

Now? logback It's already configured , Each corresponding place is as detailed as possible , To write yml file ( The main part ), In fact, it is to specify the file location and current environment , Test or production

spring:
profiles:
active: dev
logging:
config: classpath:logback-spring.xml

To write AOP

Custom writing annotations

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyLog {

String value() default "";
}

important : Implementation method of custom annotation

@Aspect
@Component
public class SysLogAspect {

@Value("${spring.application.name}")// there projectName Self defined yml In the configuration is good 
private String projectName;
@Resource
private SysLogService sysLogService;
@Pointcut("@annotation(com.*.*.annotation.MyLog)")// Here you specify the specific location of your own annotation 
public void logPointCut() {

}
@AfterReturning("logPointCut()")
public void saveSysLog(JoinPoint joinPoint) {

try {

// Just customize the entity class here , More with their own business to decide 
SysLog sysLog = new SysLog();
// Access method 
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
// Method type 
MyLog myLog = method.getAnnotation(MyLog.class);
String operation = myLog.value();
sysLog.setOperation(operation);
// Method name 
String className = joinPoint.getTarget().getClass().getName();
String methodName = method.getName();
sysLog.setMethod(className + "." + methodName);
// Method parameter 
Object[] args = joinPoint.getArgs();
String params = Arrays.toString(args);
sysLog.setParam(params);
// Project name 
sysLog.setProjectName(projectName);
........... Various parameters
//ip
String ipAddress = HttpContextUtil.getIpAddress();
sysLog.setIp(ipAddress);
// Add logging 
// The method here can be realized by yourself ,MySQL The insertion method of 
sysLogService.save(sysLog);
} catch (Exception e) {

e.printStackTrace();
}
}

Here are the database fields , Other fields can be done with their own business

image-20210913195822135

Paste tools

public class HttpContextUtil {

public static final String UNKNOWN = "unknown";
public static HttpServletRequest getRequest() {

return ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes()))
.getRequest();
}
public static String getIpAddress() {

HttpServletRequest request = getRequest();
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {

ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {

ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {

ip = request.getHeader("HTTP_CLIENT_IP");
}
if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {

ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {

ip = request.getRemoteAddr();
}
return ip;
}
}

Write a controller Test it

 @MyLog(" The test method ")
@ApiOperation(" Test it ")
@PostMapping(value = "/test")
public R<Object> test(@RequestBody String info) {

return RUtils.success(info);
}

Open the test document and enter the parameters

image-20210913195822135

And look at the database

image-20210913195822135

Look at us again logback Whether there is log output in the path of the output log

[ Failed to transfer the external chain picture , The origin station may have anti-theft chain mechanism , It is suggested to save the pictures and upload them directly (img-kNw0AOTW-1631582728451)(C:\Users\XXXLOMG\AppData\Roaming\Typora\typora-user-images\image-20210913201221289.png)]

Click in info Look at it , Output according to the time of day

[ Failed to transfer the external chain picture , The origin station may have anti-theft chain mechanism , It is suggested to save the pictures and upload them directly (img-nyyyW6Kj-1631582728455)(C:\Users\XXXLOMG\AppData\Roaming\Typora\typora-user-images\image-20210913201309451.png)]

The above is a primary log system , If the above is not properly written , Welcome to correct ~

copyright:author[Oil ouyo],Please bring the original link to reprint, thank you. https://en.javamana.com/2022/02/202202130852424710.html