简体   繁体   English

自定义SLF4J记录器

[英]Customize SLF4J Logger

I'm trying to find a nice way to add a prefix to my logs without passing it on every calls, without instanciate Logger again. 我正在尝试找到一种很好的方法来为我的日志添加前缀,而不是在每次调用时都传递它,而不再使用instanciate Logger。

The purpose is to trace Rest calls individually. 目的是单独跟踪Rest调用。 (The prefix would be re-generated on each call using UUID) (使用UUID在每次调用时重新生成前缀)

This would be like 这就像

@RestController
class MyClass {
    //Here the prefix is initialise once
    //default value is X
    Logger LOG = LoggerFactory.getLogger(MyClass.class);

    @RequestMapping("/a")
    void methodA() {
        LOG.debug("foo");
    }   

    @RequestMapping("/b")
    void methodB() {    
        LOG.setPrefix("B");

        LOG.debug("bar");
}

with this output 用这个输出

[...] [prefix X] foo
[...] [prefix B] bar

As you've said you're using Logback, here's a couple options to do the kind of thing you're trying to do: 正如你所说的那样你正在使用Logback,这里有几个选项可以做你想要做的事情:

Markers 标记

Each log entry can have a "marker" established for it. 每个日志条目都可以为其建立“标记”。 (The best documentation I've seen for it is in the SLF4J FAQ .) Something like: (我见过的最好的文档是在SLF4J FAQ中 。)类似于:

class MyClass {
    Marker methodBMarker = MarkerFactory.getMarker("B");
    Logger logger = LoggerFactory.getLogger(MyClass.class);
    …
    void methodB() {    
        logger.debug(methodBMarker, "bar");
    }
}

You would need to update all log entries in each method to use the appropriate marker. 您需要更新每个方法中的所有日志条目以使用适当的标记。 You can then put %marker in your layout to put the log entry's marker into the log. 然后,您可以在布局中放置%marker ,将日志条目的标记放入日志中。

MDC MDC

The other option is to use the " Mapped Diagnostic Context " functionality to specify the current "context" for each log entry. 另一种选择是使用“ 映射的诊断上下文 ”功能为每个日志条目指定当前的“上下文”。

class MyClass {
    Logger logger = LoggerFactory.getLogger(MyClass.class);
    …
    void methodB() {
        MDC.put("method", "b");
        try {
            …
            logger.debug("bar");
            …
        } finally {
            MDC.clear();
        }
    }
}

You would then use %mdc{method} in your layout to output that particular MDC value. 然后,您将在布局中使用%mdc{method}来输出该特定的MDC值。 Note that MDC is really intended to be used for per-thread values like something web-connection-specific, so it's important to ensure that it's cleared out of what you don't want when you're leaving the context you want the value logged in. 请注意,MDC实际上是用于每个线程的值,例如特定于Web连接的内容,因此,当您离开上下文时,确保将其从您想要的值记录中清除出来是非常重要的。在。

Please see http://www.slf4j.org/extensions.html#event_logger for an example of how to use the MDC. 有关如何使用MDC的示例,请参阅http://www.slf4j.org/extensions.html#event_logger You do not have to use the EventLogger. 您不必使用EventLogger。 Once you set things in the MDC they are present in every log record. 一旦你在MDC中设置了它们,它们就会出现在每个日志记录中。

A Marker does not meet your criteria since it has to be specified on every call. 标记不符合您的标准,因为必须在每次调用时指定。

Here's my MDC implementation explained to share my experiments with MDC. 这是我的MDC实现解释,与MDC分享我的实验。

//In this abstract class i'm defining initLogData methods to set MDC context
//It would be inherited by Controller and other classes who needs logging with traced transactions
public abstract class AbstractService {
    protected LogData initLogData() {
        return LogData.init();
    }

    protected LogData initLogData(String tName) {
        return LogData.init(tName);
    }
}

//LogData holds the MDC logic
public class LogData {
    private final static int nRandom = 8;
    //this keys are defined in logback pattern (see below)
    private final static String tIdKey = "TID";
    private final static String tNameKey = "TNAME";

    //Transaction id
    private String tId;
    //Transaction name
    private String tName;

    public String getTId() {
        return tId;
    }

    public void setTId(String tId) {
        this.tId = tId;
    }

    public String gettName() {
        return tName;
    }

    public void settName(String tName) {
        this.tName = tName;
    }

    //random transaction id
    //I'm not using uuid since its too longs and perfect unicity is not critical here
    public String createTId(){
        Random r = new Random();
        StringBuilder sb = new StringBuilder();
        while(sb.length() < nRandom){
            sb.append(Integer.toHexString(r.nextInt()));
        }
        return sb.toString().substring(0, nRandom);
    }

    //private constructors (use init() methods to set LogData)
    private LogData(String tId, String tName) {
        this.tId = tId;
        this.tName = tName;
    }

    private LogData(String tName) {
        this.tId = createTId();
        this.tName = tName;
    }

    private LogData() {
        this.tId = createTId();
    }

    //init MDC with cascading calls processing (using same id/name within same context
    //even if init() is called again)
    public static LogData init(String tName) {
        String previousTId = MDC.get(tIdKey);
        String previousTName = MDC.get(tNameKey);

        MDC.clear();
        LogData logData = null;
        if(previousTId != null) {
            logData = new LogData(previousTId, previousTName);
        } else {
            logData = new LogData(tName);
        }
        MDC.put(tIdKey, logData.getTId());
        MDC.put(tNameKey, logData.gettName());
        return logData;
    }

    //init MDC without cascading calls management (new keys are generated for each init() call)
    public static LogData init() {
        MDC.clear();
        LogData logData = new LogData();

        MDC.put(tIdKey, logData.getTId());
        return logData;
    }

}

//logback.xml : values to include in log pattern
[%X{TID}] [%X{TNAME}]

@RestController
@RequestMapping("/test")
public class RestControllerTest extends AbstractRestService {
    private final Logger LOG = LoggerFactory.getLogger(ServiceRestEntrypointStatus.class);

    @RequestMapping(value="/testA")
    public void testA() {
        initLogData("testA");
        LOG.debug("This is A");
    }

    @RequestMapping(value="/testB")
    public void testB() {
        initLogData("testA");
        LOG.debug("This is B");
    }

    @RequestMapping(value="/testC")
    public void testC() {
        initLogData("testC");
        LOG.debug("This is C");
        testA();
        testB();
    }
}

Calling RestControllerTest mapped /test/testA produces : 调用RestControllerTest映射/ test / testA会产生:

[fdb5d310] [testA] This is A

Calling /test/testC produces (id and name are kept even if initLogData is called in sub methods): 调用/ test / testC生成(即使在子方法中调用initLogData,也会保留id和name):

[c7b0af53] [testC] This is C
[c7b0af53] [testC] This is A
[c7b0af53] [testC] This is B

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM