The need for MDC

The logging framework

Logging frameworks are also more mature:

slf4j

log4j

logback

log4j2

There is no need to duplicate the wheel, it is generally recommended to integrate with SLF4J for later replacement with other frameworks.

Use of logs

Almost all applications require logging, but not every development does.

The main problems are as follows:

(1) Logs are too small, and problems cannot be located

(2) Too many logs, find the problem is very troublesome, the server disk is also a lot of pressure

(3) The log level is improperly controlled

(4) There is no unique identifier throughout the call link

Let’s focus on the fourth question.

Why do YOU need a unique identifier

For the most common Web applications, each request can be considered a new thread.

In the case of higher concurrency, our log will have an interlude. When we look at the log, we see logs that are not part of the current request, and it looks very tired. So you need a filter condition that connects the entire life cycle of a request, known as a traceId.

For example traceId=’202009021658001′, run the following command:

grep 202009021658001 app.log
Copy the code

You can filter out all logs corresponding to the link.

So how do you do that?

Implementation approach

(1) Generate a unique traceId

This one is relatively simple, such as UUID or something like that, just make sure it’s unique.

(2) This traceId is printed during log output

It is natural to have the following code:

logger.info("TraceId: {} Controller layer request parameters: {}", traceId, req);
Copy the code

defects

Many projects are implemented this way, and there are several problems with this approach:

(1) Parameter transfer is required

For example, from Controller = biz = service, because of a traceId, all of our methods need an extra parameter to accept this value.

Very inelegant

(2) traceId output is required

Remember to print this value every time, or not correlate.

If certain methods forget to output, it will be strange for us to look at the log based on traceId.

(3) Increased complexity

We need to block out this extra traceId for each log, and as a lazy person, we don’t want to block out this code.

So, is there a solution to this problem?

The SLF4J MDC exists to solve this problem.

MDC application scenarios

In the program, we sometimes need to trace the entire call link when the log is printed.

The most common approach is to pass an attribute, such as traceId, consistently down from the outermost layer.

This causes every method to have this extra parameter, just to print an identifier, which is not recommended.

MDC is used for this scenario.

A simple example

Common implementation version

Set manually before and after method calls.

This article presents aop in a more flexible and convenient way, with the same principle. The code is also more elegant.

Aop-based approach

Defining interceptors

import com.baomidou.mybatisplus.toolkit.IdWorker;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;

/** * log interceptor *@author binbin.hou
 * @date2018/12/7 * /
@Component
@Aspect
public class LogAspect {

    /** * Limit the number of trace times */
    private static final String TRACE_ID = "TRACE_ID";

    /** * block all public methods */ under the entry
    @Pointcut("execution(public * com.github.houbb.. * (..) )"
    public void pointCut(a) {}/** * Intercepting processing **@paramPoint Point information *@return result
     * @throws Throwable if any
     */
    @Around("pointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        / / add the MDC
        MDC.put(TRACE_ID, IdWorker.getIdStr());
        Object result = point.proceed();
        / / remove the MDC
        MDC.remove(TRACE_ID);
        returnresult; }}Copy the code

Idworker.getidstr () is just used to generate a unique identifier, you can use UUID etc instead.

For more methods of generating unique identifiers, see:

A distributed ids

The AOP aspect is generally recommended to be placed at the entry of the call.

(1) Controller layer entrance

(2) MQ consumption entry

(3) External RPC request entry

Define logback. XML

With MDC defined, we can use it in the log configuration file.

<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
    <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%X{TRACE_ID}] [%thread] %logger{50} - %msg%n</pattern>
</encoder>
Copy the code

[%X{TRACE_ID}] is the unique identifier that we need to use in our system, which will be printed in the log after configuration.

If it does not exist, it is an empty string and it does not matter.

For existing systems

The phenomenon of

What would you do if you had an existing project, the original print log, that passed the order number all the way down from the top?

Same thing, pass an id from the very beginning all the way down to the very bottom?

Of course not.

You can do better than that.

The principle of

We know that MDC works by placing a property in the current thread that is unique and shared within the same thread.

So the different threads don’t interfere with each other.

So for older systems, we can take the simplest approach:

Provides a utility class that gets the order number of the current thread. Of course, you need to set this value to the current thread in one place, usually the method entry.

A better way

You can provide a log printing utility class that duplicates common log printing methods.

Hide log traceId information from development.

Implementation ThreadLocal

Without further ado, see ThreadLocal

Basic utility classes

import org.slf4j.MDC;

/** * log tools *@author binbin.hou
 */
public final class LogUtil {

    private LogUtil(a){}

    /** * trace id */
    private static final String TRACE_ID = "TRACE_ID";

    /** * Set traceId *@param traceId traceId
     */
    public static void setTraceId(final String traceId) {
        MDC.put(TRACE_ID, traceId);
    }

    /** * Remove traceId */
    public static void removeTraceId(a) {
        MDC.remove(TRACE_ID);
    }

    /** * Get batch number *@returnBatch number * /
    public static String getTraceId(a) {
        returnMDC.get(TRACE_ID); }}Copy the code

For asynchronous processing

Spring asynchronous

See async async

Asynchronous traceId processing

In asynchrony, another thread is created.

You are advised to pass the traceId of the parent thread as a parameter and set this parameter to the traceId of the child thread.

Do not rely on the MDC

The MDC restrictions

MDC, while easier to use, is a tool that SLF4J implements for us.

The idea is to store thread-isolated identials based on ThreadLocal.

With this in mind, we can actually implement an MDC-like feature for different application scenarios.

Implementation approach

(1) Generate a unique log identifier

(2) Store a unique thread identifier based on ThreadLocal

(3) Annotation based +AOP

@Around("@annotation(trace)")
public Object trace(ProceedingJoinPoint joinPoint, Trace trace) {

    / / generated id
    // Set the id to the current thread

    Object result = joinPoint.proceed();    
    / / remove the id
    return result;
}
Copy the code

(4) How to use ID

The simplest way to do this is to create a utility class called LogUtil.

Override the common method, and log output calls this method uniformly.

Disadvantages: The output class in the log will not be visible, of course, can be solved through the fetch method

Advantages: simple implementation, easy to expand and replace later.

Github.com/houbb/auto-…

Open source tools

Auto-log is an automatic log monitoring framework designed for Java.

Writing purpose

Tools are often written, sometimes adding logs manually is cumbersome, and introducing Spring is overkill.

So I hope to achieve a tool from simple to complex, easy to use at ordinary times.

features

  • Flexible configuration based on annotations + bytecode

  • Automatically ADAPTS to common logging frameworks

  • Support for programmatic calls

  • Annotated support for perfect spring integration

  • Support for spring-boot integration

  • Supports slow log threshold, time, input and output parameters, exception information and other common attributes

  • Supports the traceId feature

Change log

Quick start

Maven is introduced into

<dependency>
    <group>com.github.houbb</group>
    <artifact>auto-log-core</artifact>
    <version>0.0.8</version>
</dependency>
Copy the code

An introduction to case

UserService userService = AutoLogHelper.proxy(new UserServiceImpl());
userService.queryLog("1");
Copy the code
  • Log as follows
[the INFO] [the 2020-05-29 16:24:06. 227] [the main] [C.G.H.A.L.C.S.I.A utoLogMethodInterceptor. Invoke] - public Java. Lang. String com.github.houbb.auto.log.test.service.impl.UserServiceImpl.queryLog(java.lang.String) param is [1] [INFO] [2020-05-29 16:24:06. 228] [the main] [C.G.H.A.L.C.S.I.A utoLogMethodInterceptor. Invoke] - public Java. Lang. String com.github.houbb.auto.log.test.service.impl.UserServiceImpl.queryLog(java.lang.String) result is result-1Copy the code

code

The method is as follows:

  • UserService.java
public interface UserService {

    String queryLog(final String id);

}
Copy the code
  • UserServiceImpl.java

Use the @autolog annotation to specify which logging method is required.

public class UserServiceImpl implements UserService {

    @Override
    @AutoLog
    public String queryLog(String id) {
        return "result-"+id; }}Copy the code

An example of TraceId

code

UserService service =  AutoLogProxy.getProxy(new UserServiceImpl());
service.traceId("1");
Copy the code

The traceId methods are as follows:

@AutoLog
@TraceId
public String traceId(String id) {
    return id+"1";
}
Copy the code

The test results

Information: [ba7ddaded5a644e5a58fbd276b6657af] < traceId > into arguments: [1]. Information: [ba7ddaded5a644e5a58fbd276b6657af] < traceId > reference: 1-1.Copy the code

One of ba7ddaded5a644e5a58fbd276b6657af is corresponding traceId, throughout the thread cycle, can facilitate our log viewer.

Note that

@AutoLog

The attributes of the core annotation @Autolog are described as follows:

attribute type The default value instructions
param boolean true Whether to print input parameters
result boolean true Whether to print the parameter
costTime boolean false Print time
exception boolean true Check whether printing is abnormal.
slowThresholdMills long – 1 If the value is greater than or equal to 0 and the time consumed exceeds the configured value, slow logs are generated
description string “” Method description, method name is selected by default

@TraceId

@traceid is placed on methods that need to set TraceId, such as the Controller layer, the consumer of MQ, the recipient of RPC requests, etc.

attribute type The default value instructions
id Class The default is uuid Implementation policy of traceId
putIfAbsent boolean false Whether to set a value when the current thread has no value

Spring integration

See SpringServiceTest for a complete example

Annotation statement

Use @enableautolog to enable automatic log output

@Configurable
@ComponentScan(basePackages = "com.github.houbb.auto.log.test.service")
@EnableAutoLog
public class SpringConfig {}Copy the code

The test code

@ContextConfiguration(classes = SpringConfig.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class SpringServiceTest {

    @Autowired
    private UserService userService;

    @Test
    public void queryLogTest(a) {
        userService.queryLog("1"); }}Copy the code
  • The output
Information: public java.lang.String com.github.houbb.auto.log.test.service.impl.UserServiceImpl.queryLog(java.lang.String) param is [1] on May 30, 2020 com 12:17:51 afternoon. Making. Houbb. Auto. The core. Support. The interceptor. AutoLogMethodInterceptor info information: public java.lang.String com.github.houbb.auto.log.test.service.impl.UserServiceImpl.queryLog(java.lang.String) result is Result - 1 May 30, 2020 12:17:51 afternoon org. Springframework. Context. Support. GenericApplicationContext doCloseCopy the code

Integrated use of Springboot

Maven is introduced into

<dependency>
    <groupId>com.github.houbb</groupId>
    <artifactId>auto-log-springboot-starter</artifactId>
    <version>0.0.8</version>
</dependency>
Copy the code

Just import the JAR and configure nothing else.

Use the same way as Spring.

test

@Autowired
private UserService userService;

@Test
public void queryLogTest(a) {
    userService.query("spring-boot");
}
Copy the code

Open source address

Making: github.com/houbb/auto-…

Gitee: gitee.com/houbinbin/a…

Develop reading

Distributed ID generation

Automatic Log Output

The resources

SLF4j traceID

SLF4J MDC mechanism is used to realize log link tracing