The documentation on Logback logging separation indicates that I can use context selectors to create different logging configurations on the same JVM. Somehow a context selector will allow me to call LoggerFactory.getLogger(Foo.class)
and, based upon the context, I will get a differently configured logger.
Unfortunately the examples only deal with JNDI in the context of a specially configured web server such as Tomcat or Jetty. I want to know how I can practically use a context selector myself, for example in a non-web application.
My goal is to have more than one logging configuration on the same JVM. Here is one scenario:
logback.xml
configuration on the classpath.logback.xml
from a custom directory.I provide these example scenarios just to get an idea of practical use of context selectors---how I would do something useful with them in real life.
LoggerFactory.getLogger(Foo.class)
returns a logger from the correct configuration based on the thread?ILoggerFactory
instance that would give me loggers from a programmatic configuration?Logback provides a mechanism for dealing with multiple contexts, without corruption of data, nor collision between logger context instances.
Logback executes an async appender in a separate thread to decouple the logging overhead from the thread executing your code. Using the async appender is incredibly easy. Refer the appender that should be asynchronously invoked within an <appender> element.
Log4j, Logback, and Log4j2 are good logging frameworks that are broadly used. So which one should you use? I recommend using Log4j2 because it's the fastest and most advanced of the three frameworks. Logback is still a good option, if performance is not your highest priority.
Setting the context name is a simple and straightforward method in order to distinguish between multiple applications logging to the same target. This last example illustrates naming of the logger context. Adding the contextName conversion word in layout's pattern will output the said name.
I had asked this question to prevent my needing to trace through the Logback source code, but as the initial answers were inadequate I wound up having to do so anyway. So let me explain the initialization of the SLF4J+Logback system and how it relates to context selectors.
SLF4J is a logging API that allows various implementations, one of them being Logback.
When the first request is made to org.slf4j.LoggerFactory.getLogger(...)
, the SLF4J framework is initialized by creating a org.slf4j.impl.StaticLoggerBinder
. The trick is that StaticLoggerBinder
is not distributed with SLF4J; it is actually implemented in whichever logging implementation is being used (e.g. Logback). This is a somewhat cumbersome approach to loading a specific implementation (a service loader might have been a better choice), but that's somewhat beside the point here.
Logback's implementation of StaticLoggerBinder
creates a singleton ch.qos.logback.classic.util.ContextSelectorStaticBinder
. This is the class that sets up the context selector. The logic goes something like is outlined below.
a. If the "logback.ContextSelector" system property contains "JNDI", use a ContextJNDISelector
.
b. If the "logback.ContextSelector"
system property contains anything else, assume the value is the name of a context selector class and try to instantiate that.
c. Otherwise if there is no "logback.ContextSelector"
system property, use a DefaultContextSelector
.
If a custom ContextSelector
is used, ContextSelectorStaticBinder
will instantiate it using a constructor that takes a LoggerContext
as a parameter, and will pass it the default LoggerContext
which StaticLoggerBinder
has created and auto-configured. (Changing the default configuration strategy is a separate subject I won't cover here.)
As Pieter points out in another answer, the way to install a custom context selector is to provide the name of the implementing class in the "logback.ContextSelector"
system property. Unfortunately this approach is a bit precarious, and it obviously has to be done 1) manually and 2) before any SLF4J calls are made. (Here again a service loader mechanism would be been much better; I have filed issue LOGBACK-1196 for this improvement.)
If you manage to get your custom Logback context selector installed, you'll probably want to store the LoggerContext
you receive in the constructor so that you can return it in ContextSelector.getDefaultLoggerContext()
. Other than this, the most important method in ContextSelector
is ContextSelector.getLoggerContext()
; from this method you will determine what logger context is appropriate for the current context and return it.
The LoggerContext
which is so important here is ch.qos.logback.classic.LoggerContext
, which implements ILoggerFactory
. When you access the main org.slf4j.LoggerFactory.getLogger(...)
method, it uses the singleton StaticLoggerBinder
(discussed above) to look up the logger factory. For Logback StaticLoggerBinder
will use the singleton ContextSelectorStaticBinder
(also discussed above) which hopefully will return to your now installed custom LoggerContext
.
(The ContextSelector.getLoggerContext(String name)
, ContextSelector.detachLoggerContext(String loggerContextName)
, and ContextSelector.getContextNames()
methods seem to only be used in a situation such as the JNDI context selector in which you wish to keep track of context selectors using names. If you don't need named context selectors, it appears you can safely return null
and an empty list as appropriate for these methods.)
Thus the custom ContextSelector
need simply provide some LoggerContext
appropriately configured for the calling thread; this LoggerContext
will serve as the ILoggerFactory
that creates a logger based upon the LoggerContext
configuration. Configuration is covered in the Logback documentation; perhaps this discussion here makes clearer what the Logback logger context is all about.
As for the actual mechanics of associating a LoggerContext
with a thread, that was never an issue for me. It's simple: I'll be using my own Csar library, which handles such things with ease. And now that I've figured out how to hook into the logger context selection process, I've already implemented this in a logging assistance library named Clogr which uses Csar and which I have now publicly released.
Because I wound up having to do all the research myself, I'll be marking my own answer as the accepted answer unless someone points out something important in one of the other answers which I didn't cover in my own.
Based on a quick look at the logback source code I think you should be able implement all of the scenario's you mentioned by plugging in your own ContextSelector via a java system property. I'm not sure if this feature is documented or not but it's certainly there: ContextSelector initialization
When logback bootstraps itself it will first create the default logger context. The default logger context is typically configured via logback.xml or logback.groovy. More info on configuring the default logger context can be found here: Logback configuration
As you already read logback has the notion of context selectors that decide which logger context to use when creating a logger. The default context selector simply returns the default logger context but by plugging in your own context selector you can do pretty much anything you want.
The following example shows you how to plug in you're own ContextSelector. The selector itself doesn't do much; implementing it so that if fulfills your needs is up to you ;)
import ch.qos.logback.classic.ClassicConstants;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.selector.ContextSelector;
import org.slf4j.LoggerFactory;
import java.util.Arrays;
import java.util.List;
public class LoggingExperiment {
public static void main(String[] args) {
System.getProperties().setProperty(
ClassicConstants.LOGBACK_CONTEXT_SELECTOR,
MyCustomContextSelector.class.getName()
);
LoggerFactory.getLogger(LoggingExperiment.class).info("test");
}
// this is implementation is just a copy of ch.qos.logback.classic.selector.DefaultContextSelector
// but it shows you how to bootstrap you're own context selector
public static class MyCustomContextSelector implements ContextSelector {
private LoggerContext defaultLoggerContext;
public MyCustomContextSelector(LoggerContext context) {
System.out.println("You're custom ContextSelector is being constructed!");
this.defaultLoggerContext = context;
}
public LoggerContext getDefaultLoggerContext() {
return defaultLoggerContext;
}
public LoggerContext getLoggerContext() {
//TODO create and return the LoggerContext that should be used
//if ("A".equals(Thread.currentThread().getName())){
// //return LoggerContext x and create it if necessary
// // Take a look at ch.qos.logback.classic.selector.ContextJNDISelector for an example of how to create & cache LoggerContexts.
// // Also note that when using multiple contexts you'll also have the adjust the other methods of this class appropriately.
//}else {
// return getDefaultLoggerContext();
//}
return getDefaultLoggerContext();
}
public LoggerContext detachLoggerContext(String loggerContextName) {
return defaultLoggerContext;
}
public List<String> getContextNames() {
return Arrays.asList(defaultLoggerContext.getName());
}
public LoggerContext getLoggerContext(String name) {
if (defaultLoggerContext.getName().equals(name)) {
return defaultLoggerContext;
} else {
return null;
}
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With