简体   繁体   中英

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.

The purpose is to trace Rest calls individually. (The prefix would be re-generated on each call using 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:

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:

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.

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. 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.

Please see http://www.slf4j.org/extensions.html#event_logger for an example of how to use the MDC. You do not have to use the EventLogger. Once you set things in the MDC they are present in every log record.

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.

//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 :

[fdb5d310] [testA] This is A

Calling /test/testC produces (id and name are kept even if initLogData is called in sub methods):

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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