I am building an admin UI for managing logback loggers level and changing appenders. I know I can find all appenders that are added to some logger with the following code:
private Map<String, Appender<ILoggingEvent>> getAppendersMap() {
LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
Map<String, Appender<ILoggingEvent>> appendersMap = new HashMap<>();
for (Logger logger : loggerContext.getLoggerList()) {
Iterator<Appender<ILoggingEvent>> appenderIterator = logger.iteratorForAppenders();
while (appenderIterator.hasNext()) {
Appender<ILoggingEvent> appender = appenderIterator.next();
if (!appendersMap.containsKey(appender.getName())) {
appendersMap.put(appender.getName(), appender);
}
}
}
return appendersMap;
}
The problem is if I have this logback.xml as example:
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false" scan="true" scanPeriod="10 minutes">
<appender name="writeToConsole" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>
%date{yyyy-MM-dd;HH:mm:ss.SSS} %-11p: %40.40c: %X{tablist}%m %n
</pattern>
</encoder>
</appender>
<appender name="NOPAppender" class="ch.qos.logback.core.helpers.NOPAppender" />
<root>
<level value="INFO"/>
<appender-ref ref="writeToConsole"/>
</root>
</configuration>
the NOPAppender is not a attached to any logger and therefor the method getAppendersMap() doesn't find it.
When configuring itself from an external file (logback.xml, logback-test.xml, logback.groovy etc) Logback walks through the configuration like so (this is an abridged version of what it does):
APPENDER_BAG in InterpretationContext.objectMapappender-ref (which exists within a logger definition) look for the referenced appender (by name) in this APPENDER_BAG and if present then add that appender to the current loggerYou can define explicit loggers with appender associations: <logger name="com.x.y" level="INFO"><appender-ref ref="STDOUT"/></logger> or use the <root/> element to associate appenders with all logger instances: <root level="INFO"><appender-ref ref="STDOUT"/></root>.
So, appenders which are not associated with any loggers are only known about at configuration time, once the LoggerContext has been created the transient state (the APPENDER_BAG) is discarded. This explains why your getAppendersMap() doesn't find your NOOPAppender.
I think your requirement here is to 'know about' an appender even if it is not wanted at configuration time. Presumably, an appender could be unwanted initially but someone could choose to enable later. It's like you are trying to use logback.xml as a complete statement of all the appenders which someone might want to use at some point in your application's runtime. In order to do this you will have to include the optional appender in your LoggerContext initially but with a no-op implementation. This is sort of what you are already doing in your question but you just need to add your NOPAppender to the root context.
<root level="INFO">
<appender-ref ref="STDOUT"/>
<appender-ref ref="NOPAppender"/>
</root>
This will have no effect on the running system since the appender is a no-op but since it is referenced in the LoggerContext it is available for you to manage though of course if you want to enable this then the choice of appender class would have to be changed programmatically to something other than a no-op.
Another alternative might be to programmatically create an appender and add it to the LoggerContext as/when you need it.
After 4 plus years, I found something that can be used with Logback XML configuration:
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.joran.JoranConfigurator;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.classic.util.ContextInitializer;
import ch.qos.logback.core.Appender;
import ch.qos.logback.core.joran.action.ActionConst;
import ch.qos.logback.core.joran.spi.InterpretationContext;
import ch.qos.logback.core.joran.spi.JoranException;
/**
* Utility class for finding all configured logback appenders.
*/
public class LogbackAppenderFinder {
private static final Logger LOGGER = LoggerFactory.getLogger(LogbackAppenderFinder.class);
private LogbackAppenderFinder() {
}
/**
* Tries to find all appenders from an xml config. The method works by trying to find the default logback config file
* names, can be further enhanced to include others, e.g. files supported by Spring Boot or custom files.
*
* @return Map of all the appenders configured with the XML file where the key is the appender name configured in
* the xml file and the value is the appender itself.
*/
public static Map<String, Appender<ILoggingEvent>> findAppendersFromXmlConfig() {
try {
LoggerContext dummyLoggerContext = new LoggerContext();
ContextInitializer contextInitializer = new ContextInitializer(dummyLoggerContext);
URL configurationFileUrl = contextInitializer.findURLOfDefaultConfigurationFile(true);
if (configurationFileUrl != null && configurationFileUrl.toString().endsWith(".xml")) {
JoranConfigurator configurator = new JoranConfigurator();
configurator.setContext(dummyLoggerContext);
configurator.doConfigure(configurationFileUrl);
InterpretationContext interpretationContext = configurator.getInterpretationContext();
Map<String, Object> objectMap = interpretationContext.getObjectMap();
return (Map<String, Appender<ILoggingEvent>>) objectMap.get(ActionConst.APPENDER_BAG);
} else {
LOGGER.warn("Unable to find xml configuration file.");
}
} catch (JoranException e) {
LOGGER.warn("Failed to parse logback configuration, check config file.");
}
return new HashMap<>();
}
}
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