As long as the log plays a lot, locate the BUG a shuttle

The full code covered in this article :GITEE

Or visit :gitee.com/topanda/spr…

If you write too much code, it is inevitable that there will be bugs. As a programmer who always walks at night, it is not uncommon to encounter online bugs once in a while.

When it comes to online bugs, no matter how big or small, it always makes people sweat. From finding online bugs to solving them, everyone has their hair on their back.

Then look at the scene of solving the BUG, often one person coding, the whole group of onlookers, imitation does not look like this, is to the BUG of disrespect.

All I heard was the crackling of keyboards, the pacing of the team, the shells, logs, and codes on the monitor.

The coders stare at the screen, drawing information from the rows of logs that correspond to the code, sometimes smiling, sometimes frowning, and sometimes looking as if they have lost a key log.

I don’t blame the programmer for making a big mistake, but the boss doesn’t care if the avalanche in the Alps has anything to do with your snowflakes in the Himalayas. The problem can’t be solved. The avalanche in the Alps is your fault.

At this point, a well-organized journaling may save the day.

Identify logging data

Common bugs on line are often caused by user calls that pass in data beyond the programming threshold.

Therefore, in addition to the specific business logs, it is necessary to record the parameter values passed in by the user.

For a call, the key information mainly includes: user request address, request mode, hit method, request input parameter, response status.

For some interfaces, it can even record the response data and request headers of requests. For the sake of analyzing interface performance, it can also consider recording the processing time and call time of user requests. In order to facilitate locating abnormal causes, it can also record additional abnormal information and abnormal stack information.

Of course, to better distinguish interfaces, we can also configure different readable names for different interface requests.

In combination with the appeal requirements, we provide a LogInfo object that is responsible for maintaining the critical data involved in an access:

import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;

import java.util.Date;
import java.util.Map;

@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class LogInfo {
    /** * Request path */
    private String uri;

    /**
     * 请求方法
     */
    private HttpMethod httpMethod;

    /** * request header */
    private Map<String, String> headers;

    /** * hits Controller */
    private Signature signature;

    /** * Request parameters */
    private Param[] args;
    /** * Method description */
    private String description;
    /** * request source IP */
    private String requestIp;

    /** * Request time */
    private Long duration;

    /** * Response status */
    private HttpStatus responseStatus;

    /** * Response data */
    private Object result;

    /** * error log */
    private Object errMessage;

    /** * Exception stack information */
    private Object errTrace;

    /** * call timestamp */
    private Date requestTime;
}
Copy the code

It is worth noting that in order to reduce the amount of data generated during log persistence, we chose to discard null data in LogInfo, so we annotated @jsoninclude (jsoninclude.include.non_NULL) on the LogInfo class definition.

The annotation was annotated because I later chose to convert the log object to JSON data through jackson-Databind when I persisted the log.

Clear control over how logging is done

The data logged by the LogInfo object is relatively large and complete. In practice, such detailed logging may not be necessary, or some of the data may need to be masked for special reasons, such as disabling exception stack information and response data

For this scenario, we consider providing a global configuration object AccessLogProperties that controls the validity of each parameter:

import com.live.configuration.log.handler.filters.Filter;
import com.live.configuration.log.handler.filters.NoneFilter;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

import java.io.Serializable;

/ * * *@author HanQi [[email protected]]
 * @version 1.0
 * @since2020/6/6 15:34:35 * /
@Data
@ConfigurationProperties("access.log")
public class AccessLogProperties implements Serializable.Cloneable {

    /** * Indicates whether to record the requested address and core data
    private boolean uri = true;
    /** * Specifies whether to record request headers. Used in special scenarios, */ is disabled by default
    private boolean headers = false;
    /** * Request header filter */
    private Class<? extends Filter> headersFilter = NoneFilter.class;
    /** * whether to record the request method */
    private boolean httpMethod = true;
    /** * Whether to record the hit method */
    private boolean signature = true;
    /** * Whether to record request parameters */
    private boolean params = true;

    /** * Whether to record the request source IP */
    private boolean ip = true;
    /** * Whether to record the request time */
    private boolean duration = true;
    /** * Whether to record the response status */
    private boolean status = true;
    /** * Whether to record the response body */
    private boolean result = true;

    /** * Key exception log */
    private boolean errMessage = true;
    /** * therefore stack information */
    private boolean errTrace = false;


    /** * call time */
    private boolean requestTime;

    @Override
    public AccessLogProperties clone(a) {

        AccessLogProperties clone = new AccessLogProperties();
        clone.setUri(this.uri);
        clone.setHeaders(this.headers);
        clone.setHeadersFilter(this.headersFilter);
        clone.setHttpMethod(this.httpMethod);
        clone.setSignature(this.signature);
        clone.setParams(this.params);
        clone.setIp(this.ip);
        clone.setDuration(this.duration);
        clone.setStatus(this.status);
        clone.setResult(this.result);
        clone.setErrMessage(this.errMessage);
        clone.setErrTrace(this.errTrace);
        clone.setRequestTime(this.requestTime);
        returnclone; }}Copy the code

There is a Filter attribute called headersFilter involved in AccessLogProperties, which points to a valid instance of Filter that filters the request headers to be logged. By design, we limit instances of Filter to provide no arguments Constructor.

public interface Filter {
    boolean filter(String str);
}
Copy the code

The default Filter implementation NoneFilter will Filter out all headers:

public class NoneFilter implements Filter {
    @Override
    public boolean filter(String str) {
        return false; }}Copy the code

In addition, the property definition in AccessLogProperties corresponds to the property in LogInfo, controlling whether or not the corresponding property takes effect.

In addition to the global configuration, for some interfaces, we may not need to record so much information, such as file download, file upload and other interface definitions, do not need to record the input and output parameters.

To do this, we provide the LogOptions annotation, which has exactly the same property definition and behavior as AccessLogProperties. The LogOptions annotation is used for local logging configuration and takes precedence over the global default:

import com.live.configuration.log.handler.filters.Filter;
import com.live.configuration.log.handler.filters.NoneFilter;

/** * Log option, this option can override, log default behavior **@author HanQi [[email protected]]
 * @version 1.0
 * @since2020/6/8 9:57:13 * /
public @interface LogOptions {

    /** * Indicates whether to record the requested address and core data
    boolean uri(a) default true;

    /** * Specifies whether to record request headers. Used in special scenarios, */ is disabled by default
    boolean headers(a) default false;

    /** * Request header filter */
    Class<? extends Filter> headersFilter() default NoneFilter.class;

    /** * whether to record the request method */
    boolean httpMethod(a) default true;

    /** * Whether to record the hit method */
    boolean signature(a) default true;

    /** * Whether to record request parameters */
    boolean params(a) default true;

    /** * Whether to record the request source IP */
    boolean ip(a) default true;

    /** * Whether to record the request time */
    boolean duration(a) default true;

    /** * Whether to record the response status */
    boolean status(a) default true;

    /** * Whether to record the response body */
    boolean result(a) default false;

    /** * Key message */
    boolean errMessage(a) default true;

    /** * Log stack information */
    boolean errTrace(a) default false;

    /** * call time */
    boolean requestTime(a) default true;
}

Copy the code

Clear how logging is used

After determining logging data and configuration content, we need to provide corresponding solutions for it, in the implementation, the basic idea is to intercept each request with the help of OncePerRequestFilter object, so as to record method invocation time, request processing time and response status, and according to the persistence strategy to complete the log persistence work.

However, because not be able to obtain interface and convenient in OncePerRequestFilter hit method of related data, so an additional StaticMethodMatcherPointcutAdvisor plane to intercept need logging method, complete LogInfo data acquisition work.

OncePerRequestFilter and data transfer between StaticMethodMatcherPointcutAdvisor done through LogHolder, LogHolder using ThreadLocal cache related log data in the request and the log configuration The data.

import java.util.Optional;

/** ** logger **@author HanQi [[email protected]]
 * @version 1.0
 * @since2020/6/8 11:31:06 * /
public class LogHolder {
    private static AccessLogProperties DEFAULT_ACCESS_LOG_PROPERTIES = new AccessLogProperties();

    /** * Current log data */
    private static final ThreadLocal<LogInfo> LOG_THREAD_LOCAL = new ThreadLocal<>();

    private static final ThreadLocal<AccessLogProperties> CONFIG_THREAD_LOCAL = ThreadLocal.withInitial(LogHolder::createDefaultConfig);

    public static boolean hasLog(a) {
        return Optional.ofNullable(LOG_THREAD_LOCAL.get()).isPresent();
    }

    public static LogInfo log(a) {
        return LOG_THREAD_LOCAL.get();
    }

    public static void log(LogInfo log) {
        LOG_THREAD_LOCAL.set(log);
    }

    public static void clear(a) {
        LOG_THREAD_LOCAL.remove();
        CONFIG_THREAD_LOCAL.remove();
    }

    public static AccessLogProperties config(a) {
        return CONFIG_THREAD_LOCAL.get();
    }

    public static void config(AccessLogProperties accessLogProperties) {
        CONFIG_THREAD_LOCAL.set(accessLogProperties);
    }

    public static AccessLogProperties createDefaultConfig(a) {
        return DEFAULT_ACCESS_LOG_PROPERTIES.clone();
    }

    public static void initDefaultConfig(AccessLogProperties accessLogProperties) { DEFAULT_ACCESS_LOG_PROPERTIES = accessLogProperties; }}Copy the code

Because the method invocation does not get the exact response status code in the invocation chain of a request, you also need to configure OncePerRequestFilter to be used together.

OncePerRequestFilter implementation class AccessLogOncePerRequestFilter business is not complicated, he mainly do four things:

  • Record the request invocation time
  • Record the request processing time
  • Record the request response status
  • callLogPersistenceLog persistence is complete
import com.live.configuration.log.entitys.AccessLogProperties;
import com.live.configuration.log.entitys.LogHolder;
import com.live.configuration.log.entitys.LogInfo;
import com.live.configuration.log.persistence.LogPersistence;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Date;

/** * Log **@author HanQi [[email protected]]
 * @version 1.0
 * @since2020/6/8 11:05:24 * /
@Slf4j
public class AccessLogOncePerRequestFilter extends OncePerRequestFilter {

    private LogPersistence logPersistence;

    public AccessLogOncePerRequestFilter(LogPersistence logPersistence) {
        this.logPersistence = logPersistence;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {

        try {
            doFilter(httpServletRequest, httpServletResponse, filterChain);
        } finally{ LogHolder.clear(); }}private void doFilter(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
        // Record the request time
        long start = System.currentTimeMillis();
        Date now = new Date();
        try {
            filterChain.doFilter(httpServletRequest, httpServletResponse);
        } finally {
            // Record the response time and response status 1
            long end = System.currentTimeMillis();
            if (LogHolder.hasLog()) {
                AccessLogProperties config = LogHolder.config();
                LogInfo log = LogHolder.log();
                if(config.isRequestTime()){
                    log.setRequestTime(now);
                }
                if (config.isDuration()) {
                    log.setDuration(end - start);
                }
                if(config.isStatus()) { log.setResponseStatus(HttpStatus.valueOf(httpServletResponse.getStatus())); } doLog(log); }}}protected void doLog(LogInfo logInfo) { logPersistence.persistence(logInfo); }}Copy the code

LogPersistence does the specific logging persistence work and defines a void Persistence () method to provide the corresponding capability:

import com.live.configuration.log.entitys.LogInfo;

/** * Log persistence **@author HanQi [[email protected]]
 * @version 1.0
 * @since2020/6/8 13:18:54 * /
public interface LogPersistence {

    void persistence(LogInfo log);
}
Copy the code

Based on current requirements, I provide DefaultLogPersistence with the help of conventional logging system to complete simple logging work:

import com.fasterxml.jackson.databind.ObjectMapper;
import com.live.configuration.log.entitys.LogInfo;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

/** * The default log persistence policy **@author HanQi [[email protected]]
 * @version 1.0
 * @since2020/6/8 13:20:00 * /
@Slf4j
public class DefaultLogPersistence implements LogPersistence {
    ObjectMapper objectMapper = new ObjectMapper();

    @Override
    @SneakyThrows
    public void persistence(LogInfo logInfo) {
        log.info("Access Log :{}", objectMapper.writeValueAsString(logInfo)); }}Copy the code

The actual log persistence strategy can be customized to your needs.

Responsible for making the code is AccessLogAutoConfiguration AccessLogOncePerRequestFilter came into effect, in this class we will AccessLogOncePerRequestFilter object is transformed into the spring – bean, and Global configuration object AccessLogProperties was introduced:

import com.live.configuration.log.entitys.AccessLogProperties;
import com.live.configuration.log.persistence.DefaultLogPersistence;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.web.filter.OncePerRequestFilter;

/ * * *@author HanQi [[email protected]]
 * @version 1.0
 * @since2020/6/8 14:50:15 * /
@Import({AccessLogProperties.class})
public class AccessLogAutoConfiguration {
    @Bean
    public OncePerRequestFilter oncePerRequestFilter(a) {
        return new AccessLogOncePerRequestFilter(newDefaultLogPersistence()); }}Copy the code

Defined roughly after the implementation of the scheme, detailed record which method of access log into the next issue to consider, the most common is the simplest method is to provide a separate annotation, annotate section finish logging work, therefore, we have provided a AccessLog annotations, the annotations only one value () properties for the interface Maintain a readable alias:

import java.lang.annotation.*;

/** ** *@author HanQi [[email protected]]
 * @version 1.0
 * @since2020/6/6 15:39:59 * /
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AccessLog {
    /** * Log name, optional */
    String value(a) default "";
}
Copy the code

In addition, considering that there may be times when we need to log all access, we chose the Spring native Mapping annotation as another candidate aspect after a brief investigation:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Meta annotation that indicates a web mapping annotation.
 *
 * @author Juergen Hoeller
 * @since 3.0
 * @see RequestMapping
 */
@Target({ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Mapping {

}
Copy the code

After all, the Mapping is spring relevant annotation of the interface map yuan note, in order to compatible with the yuan notes section support, we have provided a AnnotationAspectPointCutAdvisor, he is StaticMethodMatcherPointcutAdvisor Implementation class, the object is a named interceptionAnnotation annotation properties, to maintain the current processing method of plane is required annotations, at the same time, the object matches () method with the aid of AnnotationUtils. FindAnnotation () method, so the party Method supports meta-annotations on method annotations:

import com.live.configuration.log.handler.AccessLogProceedingJoinPointHandler;
import org.aopalliance.intercept.MethodInterceptor;
import org.springframework.aop.support.StaticMethodMatcherPointcutAdvisor;
import org.springframework.core.annotation.AnnotationUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

/** * Agree to intercept log **@author HanQi [[email protected]]
 * @version 1.0
 * @since2020/6/9 9:37:10 * /
public class AnnotationAspectPointCutAdvisor extends StaticMethodMatcherPointcutAdvisor {
    AccessLogProceedingJoinPointHandler handler;
    private Class<? extends Annotation> interceptionAnnotation;


    public AnnotationAspectPointCutAdvisor(Class<? extends Annotation> interceptionAnnotation, AccessLogProceedingJoinPointHandler handler) {
        this.interceptionAnnotation = interceptionAnnotation;
        this.handler = handler;
        setAdvice((MethodInterceptor) handler::handler);
    }

    @Override
    public boolean matches(Method method, Class
        aClass) {
        returnAnnotationUtils.findAnnotation(method, interceptionAnnotation) ! =null; }}Copy the code

In addition, the method also maintains a AccessLogProceedingJoinPointHandler processor, the CPU is responsible for the specific LogInfo filling work.

Don’t be too focused on AccessLogProceedingJoinPointHandler this name, originally from the design is not used to dealing with the MethodInvocation but handling ProceedingJoinPoint object.

AnnotationAspectPointCutAdvisor has two implementation classes, respectively used to handle the AccessLog and Mapping annotation:

MappingAnnotationAspectPointCutAdvisor:

import com.live.configuration.log.handler.AccessLogProceedingJoinPointHandler;
import org.springframework.web.bind.annotation.Mapping;

/ * * *@author HanQi [[email protected]]
 * @version 1.0
 * @since2020/6/9 10:04:04 * /
public class MappingAnnotationAspectPointCutAdvisor extends AnnotationAspectPointCutAdvisor {

    public MappingAnnotationAspectPointCutAdvisor(AccessLogProceedingJoinPointHandler handler) {
        super(Mapping.class, handler); }}Copy the code

AccessLogAnnotationAspectPointCutAdvisor:

import com.live.configuration.log.annotations.AccessLog;
import com.live.configuration.log.handler.AccessLogProceedingJoinPointHandler;

/ * * *@author HanQi [[email protected]]
 * @version 1.0
 * @since2020/6/9 10:04:31 * /
public class AccessLogAnnotationAspectPointCutAdvisor extends AnnotationAspectPointCutAdvisor {
    public AccessLogAnnotationAspectPointCutAdvisor(AccessLogProceedingJoinPointHandler handler) {
        super(AccessLog.class, handler); }}Copy the code

By design, the two classes are mutually exclusive, and only one class is valid at the same time.

Responsible for controlling this line is the EnableAccessLog annotation, which needs to be annotated on the SpringBoot boot class to enable logging facets:

import com.live.configuration.log.AccessLogAutoConfiguration;
import com.live.configuration.log.AccessLogImportSelector;
import org.springframework.context.annotation.Import;
import org.springframework.stereotype.Component;

import java.lang.annotation.*;

/** * Whether to enable log **@author HanQi [[email protected]]
 * @version 1.0
 * @since2020/6/8 10:59:01 * /
@Component
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import({AccessLogImportSelector.class, AccessLogAutoConfiguration.class})
public @interface EnableAccessLog {
    /** * Whether to block all requests */
    boolean enableGlobal(a) default false;
}
Copy the code

This annotation defines an enableGlobal() attribute with a default value of false, which controls which facets are in effect. When this value is true, access logs are logged for all interfaces that are directly or indirectly annotated with the Mapping element, otherwise access logs are logged for interfaces that are annotated with the AccessLog.

EnableAccessLog annotations by Import annotations introduced AccessLogImportSelector and AccessLogAutoConfiguration configuration object, the introduction of the work needed to complete the log object.

The AccessLogImportSelector responsible for according to enableGlobal () value to determine the introduction of MappingAnnotationAspectPointCutAdvisor or AccessLogAnnotationAspectPoin tCutAdvisor:

import com.live.configuration.log.annotations.EnableAccessLog;
import com.live.configuration.log.visitors.AccessLogAnnotationAspectPointCutAdvisor;
import com.live.configuration.log.visitors.MappingAnnotationAspectPointCutAdvisor;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata;

/ * * *@author HanQi [[email protected]]
 * @version 1.0
 * @since2020/6/8 17:19:50 * /
public class AccessLogImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        // The name of the annotation to be processedClass<? > annotationType = EnableAccessLog.class; AnnotationAttributes annotationAttributes = AnnotationAttributes.fromMap( annotationMetadata.getAnnotationAttributes(annotationType.getName(),false));// In normal scenarios, this parameter is never null.
        assertannotationAttributes ! =null;
        boolean global = annotationAttributes.getBoolean("enableGlobal");
        return newString[]{ (global ? MappingAnnotationAspectPointCutAdvisor.class : AccessLogAnnotationAspectPointCutAdvisor.class).getCanonicalName() }; }}Copy the code

We ignore AccessLogProceedingJoinPointHandler interface front, we provided a DefaultAccessLogProceedingJoinPointHandler implementation class, to complete the filling ability of log data according to the configuration:

import com.live.configuration.log.annotations.AccessLog;
import com.live.configuration.log.annotations.LogOptions;
import com.live.configuration.log.entitys.*;
import com.live.configuration.log.handler.filters.Filter;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
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.lang.reflect.Parameter;
import java.util.*;

/ * * *@author HanQi [[email protected]]
 * @version 1.0
 * @since2020/6/8 16:35:40 * /
@Component
public class DefaultAccessLogProceedingJoinPointHandler implements AccessLogProceedingJoinPointHandler {
    @Override
    public Object handler(MethodInvocation point) throws Throwable {

        Object result = null;
        Throwable throwable = null;
        try {
            // Process the request
            result = point.proceed();
        } catch (Exception e) {
            throwable = e;
        }


        // Process log parameters
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = Objects.requireNonNull(requestAttributes).getRequest();


        Method method = point.getMethod();

        LogInfo logInfo = new LogInfo();
        logInfo.setDescription(readMethodDescription(method));

        // Get the configuration object
        AccessLogProperties config = loadCustomConfig(AnnotationUtils.findAnnotation(method, LogOptions.class));

        if (config.isUri()) {
            logInfo.setUri(request.getRequestURL().toString());
        }
        if (config.isHttpMethod()) {
            logInfo.setHttpMethod(HttpMethod.resolve(request.getMethod()));
        }
        if (config.isHeaders()) {
            // load fetch
            Class<? extends Filter> headerFilter = config.getHeadersFilter();
            Filter filter = headerFilter.newInstance();
            Enumeration<String> enumeration = request.getHeaderNames();
            Map<String, String> matchHeaders = new HashMap<>();
            while (enumeration.hasMoreElements()) {
                String headerName = enumeration.nextElement();
                String headerValue = request.getHeader(headerName);
                if (filter.filter(headerName)) {
                    matchHeaders.put(headerName, headerValue);
                }
            }
            logInfo.setHeaders(matchHeaders);
        }

        if (config.isSignature()) {
            Signature signature1 = new Signature();
            signature1.setClassName(point.getThis().getClass().getCanonicalName());
            signature1.setMethodName(point.getMethod().getName());
            logInfo.setSignature(signature1);
        }

        if (config.isParams()) {
            logInfo.setArgs(castParams(method, point.getArguments()));
        }
        if (config.isIp()) {
            logInfo.setRequestIp(loadRealIp(request));
        }

        if(throwable ! =null) {
            if (config.isErrMessage()) {
                logInfo.setErrMessage(throwable.getMessage());
            }
            if(config.isErrTrace()) { logInfo.setErrTrace(throwable); }}else {
            if(config.isResult()) { logInfo.setResult(result); }}if(throwable ! =null) {
            throw throwable;
        }

        LogHolder.log(logInfo);
        // Return the request result
        return result;
    }


    public AccessLogProperties loadCustomConfig(LogOptions logOptions) {
        return Optional.ofNullable(logOptions).map(opt -> {
            AccessLogProperties properties = new AccessLogProperties();
            properties.setUri(opt.uri());
            properties.setHeaders(opt.headers());
            properties.setHeadersFilter(opt.headersFilter());
            properties.setHttpMethod(opt.httpMethod());
            properties.setSignature(opt.signature());
            properties.setParams(opt.params());
            properties.setIp(opt.ip());
            properties.setDuration(opt.duration());
            properties.setStatus(opt.status());
            properties.setResult(opt.result());
            properties.setErrMessage(opt.errMessage());
            properties.setErrTrace(opt.errTrace());

            LogHolder.config(properties);
            return properties;
        }).orElse(LogHolder.config());
    }

    /** * Read method description **@param* method method@returnMethod description or method name */
    protected String readMethodDescription(Method method) {
        if(! method.isAccessible()) { method.setAccessible(true);
        }
        // Get method description annotations
        String description = method.getName();
        // Read swagger annotation
        AccessLog accessLog = AnnotationUtils.findAnnotation(method, AccessLog.class);
        if(accessLog ! =null) {
            // Get the parameter description
            description = accessLog.value();
        }
        return description;
    }

    protected Param[] castParams(Method method, Object[] args) {
        if (args == null) {
            return null; } Class<? >[] types = method.getParameterTypes(); String[] argNames = Arrays.stream(method.getParameters()).map(Parameter::getName).toArray(String[]::new);

        Param[] params = new Param[args.length];
        for (int i = 0; i < args.length; i++) {
            Object o = args[i];
            // Read swagger annotation
            params[i] = Param.builder()
                    .argName(argNames[i])
                    .name(argNames[i])
                    .type(types[i])
                    .value(args[i])
                    .build();

        }
        return params;
    }

    protected String loadRealIp(HttpServletRequest request) {
        for (String header : Arrays.asList("x-forwarded-for"
                , "Proxy-Client-IP"
                , "WL-Proxy-Client-IP"
                , "HTTP_CLIENT_IP"
                , "HTTP_X_FORWARDED_FOR"
                , "X-Real-IP")) {
            String value = request.getHeader(header);
            if (StringUtils.hasText(value)) {
                if (value.contains(",")) {
                    String[] ips = value.split(",");
                    return ips[ips.length - 1];
                } else {
                    returnvalue; }}}return "unknown"; }}Copy the code

Simple to use

@GetMapping("normal")
public Object forceWrapperResult(a) {
    //  {
    // "success": true,
    // "code": "200",
    // "data": "normal",
    // "errorMessage": "",
    // "timestamp": 1591348788622
    / /}
    return "normal";
}
@AccessLog("Test log Annotations")
@GetMapping("log")
public String log(a) {
    return "test";
}

@GetMapping("log-param")
public String logParam(@Param("name") String name) {
    return "log-param";
}
Copy the code
[the INFO] 11:26:48. 422 [HTTP - nio - 8080 - exec] [] C.L.C.L.P.D efaultLogPersistence - Access log: {" uri ":" http://127.0.0.1:8080/live/examples/normal ", "httpMethod" : "GET", "signature" : {" className ":" com. Live. Controlle rs.ExamplesController","methodName":"forceWrapperResult"},"args":[],"description":"forceWrapperResult","requestIp":"unkn [INFO] 11:26:51.492 [http-nio-8080-exec-9] [] c.l.c.l.p.DefaultLogPersistence - Access log: {" uri ":" http://127.0.0.1:8080/live/examples/log ", "httpMethod" : "GET", "signature" : {" className ":" com. Live. Controllers. ExamplesController","methodName":"log"},"args":[],"description":" test log notes ","requestIp":"unknown","duration":2,"responseSta" Tus ":" OK ", "result" : "test"} [INFO] 11:26:52, 965 [HTTP - nio - 8080 - exec - 10] [] C.L.C.L.P.D efaultLogPersistence - Access log: {" uri ":" http://127.0.0.1:8080/live/examples/log-param ", "httpMethod" : "GET", "signature" : {" className ":" com. Live. Contro llers.ExamplesController","methodName":"logParam"},"args":[{"argName":"arg0","name":"arg0","type":"java.lang.String","va lue":null}],"description":"logParam","requestIp":"unknown","duration":2,"responseStatus":"OK","result":"log-param"}Copy the code

extension

There are more additions to this article, such as integrating sessions or user unique tokens into logging.

If the request raises an exception before the method is hit, and the method is not hit, the request is not logged.