How to configure SmtpAppender programmatically in log4j2 - java

Following is the code, I am working on :
Purpose is to configure SmtpAppender programmatically. Along with the SmtpAppender, I also need to add RollingFileAppender as well as Console appender programmatically.
package vish;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.core.appender.SmtpAppender;
import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilder;
import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilderFactory;
import org.apache.logging.log4j.core.config.builder.api.LayoutComponentBuilder;
import org.apache.logging.log4j.core.config.builder.api.RootLoggerComponentBuilder;
import org.apache.logging.log4j.core.config.builder.impl.BuiltConfiguration;
public class SmtpAppenderBuilder {
public static void main(String[] args) {
String pattern = "%d{MM-dd#HH\\:mm\\:ss}%-4r %-5p [%t] %37c %3x - %m%n";
ConfigurationBuilder<BuiltConfiguration> builder = ConfigurationBuilderFactory.newConfigurationBuilder();
LayoutComponentBuilder layoutBuilder = builder.newLayout("PatternLayout").addAttribute("pattern", pattern);
RootLoggerComponentBuilder rootLogger = builder.newRootLogger(Level.DEBUG);
builder.setStatusLevel(Level.DEBUG);
org.apache.logging.log4j.core.appender.SmtpAppender.Builder smtpBuilder = SmtpAppender.newBuilder();
smtpBuilder.setName("emailAppender");
smtpBuilder.setSmtpUsername("test1#gmail.com");
smtpBuilder.setSmtpPassword("###YpSv1925");
smtpBuilder.setSmtpProtocol("https");
smtpBuilder.setSmtpHost("smtp.gmail.com");
smtpBuilder.setBufferSize(512);
smtpBuilder.setTo("test2#gmail.com");
smtpBuilder.setSubject("testing");
}
}
How should I add the smtpAppender to the configutation or the rootLogger ?

You are mixing up two APIs:
the ConfigurationBuilder API, which is the closest code equivalent to the configuration files. It only creates definitions of the actual logging components, the real ones are created when Configuration#initialize() is called on the configuration object. You can create the definition of an SMTPAppender like this:
private Configuration createConfig() {
ConfigurationBuilder<BuiltConfiguration> builder = ConfigurationBuilderFactory.newConfigurationBuilder()//
.setStatusLevel(Level.DEBUG);
LayoutComponentBuilder layoutBuilder = builder.newLayout("PatternLayout")//
.addAttribute("pattern", "%d{MM-dd#HH\\:mm\\:ss}%-4r %-5p [%t] %37c %3x - %m%n");
AppenderComponentBuilder appenderBuilder = builder.newAppender("emailAppender", "SMTP")//
.addAttribute("smtpUsername", "test1#gmail.com")
.addAttribute("smtpPassword", "###YpSv1925")
.addAttribute("smtpProtocol", "smtps")
.addAttribute("smtpHost", "smtp.gmail.com")
.addAttribute("to", "test2#gmail.com")
.addAttribute("subject", "testing")
.add(layoutBuilder);
AppenderRefComponentBuilder appenderRefBuilder = builder.newAppenderRef("emailAppender");
RootLoggerComponentBuilder rootLoggerBuilder = builder.newRootLogger(Level.DEBUG)//
.add(appenderRefBuilder);
return builder.add(appenderBuilder)//
.add(rootLoggerBuilder)
.build();
}
the actual builders of Log4j 2.x components, which are called by reflection by Configuration#initialize using the definitions above. You can also use them directly:
private static Configuration createConfig2() {
return new AbstractConfiguration(null, ConfigurationSource.NULL_SOURCE) {
#Override
protected void doConfigure() {
Layout<String> layout = PatternLayout.newBuilder()//
.withPattern("%d{MM-dd#HH\\:mm\\:ss}%-4r %-5p [%t] %37c %3x - %m%n")
.withConfiguration(this)
.build();
Appender appender = SmtpAppender.newBuilder()//
.setName("emailAppender")
.setSmtpUsername("test1#gmail.com")
.setSmtpPassword("###YpSv1925")
.setSmtpProtocol("smtps")
.setTo("test2#gmail.com")
.setSubject("testing")
.setLayout(layout)
.setConfiguration(this)
.build();
LoggerConfig rootLogger = getRootLogger();
rootLogger.setLevel(Level.DEBUG);
rootLogger.addAppender(appender, null, null);
}
};
}
Both Configurations are equivalent and you can apply them on the current context with:
Configurator.reconfigure(config);
However they will be lost upon Configurator.reconfigure(), unless you define your own ConfigurationFactory.

Related

create a custom log4j2 rolling file appender

I want to create a custom log4j2 rolling file appender. I need to create this custom appender because I want to wrap the file name with current thread name. We are trying to migrate log4j 1.x to recent log4j2 version and previously we had used DailyRollingFileAppender to log all activities of our application.
please find the below code.
Here we are trying to append the log to a file on daily basis with help of DailyRollingFileAppender based on threadName.
Since DailyRollingFileAppender is deprecated in recent version -so, how to create custom rolling file appender with incorporating our thread based logic.?
Find the below log4j.properties file
log4j.logger.***=INFO, FileLogger
# log4j.appender.FileLogger=org.apache.log4j.DailyRollingFileAppender
# Custom Appendar which will redirect the logs based on thread names configured using
# log4j.appender.FileLogger.threadNameMapping property below
log4j.appender.FileLogger=********.framework.log4j.appender.ThreadNamePatternAppender
log4j.appender.FileLogger.DatePattern='.'yyyy-MM-dd
log4j.appender.FileLogger.file=/logs/fileName.log
log4j.appender.FileLogger.layout=org.apache.log4j.PatternLayout
log4j.appender.FileLogger.layout.ConversionPattern=%d [%-5p] [%t] [%c{1}] [%M] - %m%n
# Custom property to hold mapping between thread names and log file for plug-in
# Beware - ThreadNamePatternAppender class inherits DailyRollingFileAppender hence it will not work for any other type of appender
# This can be distuingished using - ThreadName1>ThreadName1.log|ThreadName2>ThreadName2.log|.....|ThreadNameN>ThreadNameN.log
# Note - If there is no mapping for a particular thread then logs will be written to default log file
log4j.appender.FileLogger.threadNameMapping=********/logs/fileName-fileName.log
Thanks!
import java.util.HashMap;
import java.util.Map;
import org.apache.logging.log4j.core.appender.RollingFileAppender;
import org.apache.log4j.helpers.LogLog;
import org.apache.log4j.spi.LoggingEvent;
public class ThreadNamePatternAppender extends DailyRollingFileAppender {
private Map<String, DailyRollingFileAppender> threadBasedSubAppenders = new HashMap<String, DailyRollingFileAppender>();
private String threadNameMapping;
public String getThreadNameMapping() {
return threadNameMapping;
}
public void setThreadNameMapping(String threadNameMapping) {
this.threadNameMapping = threadNameMapping;
}
#Override
public void activateOptions() {
super.activateOptions();
if (threadNameMapping != null && threadNameMapping.trim().length() > 0) {
DailyRollingFileAppender tempAppender;
String[] threadNames = threadNameMapping.split("\\|");
for (String threadName : threadNames) {
if (threadName != null && threadName.length() > 0) {
try {
LogLog.debug(String.format("Creating new appender for thread %s", threadName));
tempAppender = new DailyRollingFileAppender(getLayout(), threadName.split(">")[1],
getDatePattern());
threadBasedSubAppenders.put(threadName.split(">")[0], tempAppender);
} catch (Exception ex) {
LogLog.error("Failed to create appender", ex);
}
}
}
}
}
#Override
public void append(LoggingEvent event) {
String threadName = event.getThreadName().split(" ")[0];
if (threadBasedSubAppenders.containsKey(threadName)) {
threadBasedSubAppenders.get(threadName).append(event);
} else {
super.append(event);
}
}
#Override
public synchronized void close() {
LogLog.debug("Calling Close on ThreadNamePatternAppender" + getName());
for (DailyRollingFileAppender appender : threadBasedSubAppenders.values()) {
appender.close();
}
this.closed = true;
}
}
The RollingFileAppender in Log4j 2.x is final, so you can not extend it. However you can obtain the functionality of your custom Log4j 1.x appender using:
A RoutingAppender, which can create appenders on demand,
Multiple RollingFileAppender that will write and rotate your files,
The EventLookup to retrieve the current thread name.
For a simple logfile-per-thread appender you can use:
<Routing name="Routing">
<Routes pattern="$${event:ThreadName}">
<Route>
<RollingFile name="Rolling-${event:ThreadName}"
fileName="logs/thread-${event:ThreadName}.log"
filePattern="logs/thread-${event:ThreadName}.log.%d{yyyy-MM-dd}">
<PatternLayout pattern="%d [%-5p] [%t] [%c{1}] [%M] - %m%n" />
<TimeBasedTriggeringPolicy />
</RollingFile>
</Route>
</Routes>
</Routing>
For a more complex configuration both the <Routing> appender and the <Routes> can contain a <Script> (cf. documentation):
the script in the <Routing> appender can initialize the staticVariables map and return a default route,
the script in the <Routes> component chooses the appropriate route based on staticVariables and the logging event.

All logs are not going to file when file logging enabled

I am trying to configure log4j2 programmatically and i am facing issue with logs coming from my cluster.
Here is the case, when user enable file logging (by passing log path as parameter in this case) all logs should go to File, no console logs. When use disable file logging, all logging should go to console. So I am trying in this:
public static void initializeLogging(
boolean vNewVerboseDebugLogging, String vLoggingPath, String vNewWarehouseQueriesLogPath,
boolean vNoConsoleLogging, boolean vIsEmbeddedHive, boolean isTestSubmission, boolean YarnLogs, boolean debugLogs) throws IOException
{
String[] packageGrp = {"org.apache","hive.ql","com.cdcb.cstone"};// this i am using to turn off logging from other packages/classes
if(vLoggingPath!=null) {
ConfigurationBuilder<BuiltConfiguration> builder = ConfigurationBuilderFactory.newConfigurationBuilder();
RootLoggerComponentBuilder rootLogger = builder.newRootLogger(debugLogs?Level.DEBUG:Level.INFO).addAttribute("additivity", true);
LayoutComponentBuilder layoutComponentBuilder = builder.newLayout("PatternLayout").addAttribute("pattern",
(isTestSubmission)?"%-5p [%t]: %m%n":"%d %-5p [%t]: %m%n");
AppenderComponentBuilder fileAppenderBuilder;
fileAppenderBuilder = builder.newAppender("LogToRollingFile", "RollingFile")
.addAttribute("fileName", vLoggingPath)
.addAttribute("filePattern", vLoggingPath + "-%d{MM-dd-yy-HH}.log")
.add(layoutComponentBuilder)
.addComponent(builder.newComponent("Policies")
.addComponent(builder.newComponent("TimeBasedTriggeringPolicy").addAttribute("interval", "1")));
/*builder.newLogger("com.cdcb.idn.HadoopCluster", (vVerboseDebugLogging)?Level.DEBUG:Level.INFO)
.add(builder.newAppenderRef("LogToRollingFile"))
.addAttribute("additivity", false);*/
for(String packNm: packageGrp)
addNewLoggerComponent(builder,packNm,builder.newAppenderRef("LogToRollingFile"),Level.OFF);
builder.add(fileAppenderBuilder);
rootLogger.add(builder.newAppenderRef("LogToRollingFile"));
builder.add(rootLogger);
Configurator.initialize(builder.build()).updateLoggers();
} else {
ConfigurationBuilder<BuiltConfiguration> builder = ConfigurationBuilderFactory.newConfigurationBuilder();
RootLoggerComponentBuilder rootLogger = builder.newRootLogger(debugLogs?Level.DEBUG:Level.INFO);
LayoutComponentBuilder layoutComponentBuilder = builder.newLayout("PatternLayout").addAttribute("pattern",
(isTestSubmission)?"%-5p [%t]: %m%n":"%d %-5p [%t]: %m%n");
AppenderComponentBuilder vConsoleInfo;
AppenderComponentBuilder vConsoleProblems;
if (!vIsEmbeddedHive){
vConsoleProblems = builder.newAppender("consoleProb", "CONSOLE")
.addAttribute("target", ConsoleAppender.Target.SYSTEM_ERR)
.add(layoutComponentBuilder);
builder.newLogger("com.cdcb.idn.HadoopCluster", (vVerboseDebugLogging)?Level.DEBUG:Level.INFO)
.add(builder.newAppenderRef("consoleProb"))
.addAttribute("additivity", false);
for(String packNm: packageGrp)
addNewLoggerComponent(builder,packNm,builder.newAppenderRef("consoleProb"),Level.OFF);
builder.add(vConsoleProblems);
rootLogger.add(builder.newAppenderRef("consoleProb"));
builder.add(rootLogger);
}
vConsoleInfo = builder.newAppender("consoleInfo", "CONSOLE")
.addAttribute("target", ConsoleAppender.Target.SYSTEM_OUT)
.add(layoutComponentBuilder);
/*builder.newLogger("com.cdcb.idn.HadoopCluster", (vVerboseDebugLogging)?Level.DEBUG:Level.INFO)
.add(builder.newAppenderRef("consoleInfo"))
.addAttribute("additivity", false);*/
for(String packNm: packageGrp)
addNewLoggerComponent(builder,packNm,builder.newAppenderRef("consoleInfo"),Level.OFF);
builder.add(vConsoleInfo);
rootLogger.add(builder.newAppenderRef("consoleInfo"));
builder.add(rootLogger);
Configurator.initialize(builder.build()).updateLoggers();
}
LOGGER = LogManager.getLogger(HadoopClusterLogging.class);
}
public static ConfigurationBuilder addNewLoggerComponent(ConfigurationBuilder builder, String name, AppenderRefComponentBuilder appenderReferences, Level level) {
return builder.add(builder.newLogger(name, level)
.add(appenderReferences)
.addAttribute("additivity", false));
}
but the issue here is, when file logging enabled, not all logs going to File, some going to console(for example, logs coming from hadoop cluster). What am i doing wrong here and how can i capture all logs to file when file logging enabled? can come one please help?
One more thing is, how can i disable transitive dependencies logging. Meaning, my logger should log my application logs only, not from from my dependencies.
Thank you!

How to read property from application.properties when using ConfigurationFactory (Log4j's Programmatic Configuration)?

I am using programmatic configuration for log4j as shown in the following link: http://logging.apache.org/log4j/2.x/manual/customconfig.html.
#Plugin(name = "CustomConfigurationFactory", category = ConfigurationFactory.CATEGORY)
#Order(50)
public class CustomConfigurationFactory extends ConfigurationFactory {
static Configuration createConfiguration(final String name, ConfigurationBuilder<BuiltConfiguration> builder) {
builder.setConfigurationName(name);
builder.setStatusLevel(Level.ERROR);
builder.add(builder.newFilter("ThresholdFilter", Filter.Result.ACCEPT, Filter.Result.NEUTRAL).
addAttribute("level", Level.DEBUG));
AppenderComponentBuilder appenderBuilder = builder.newAppender("Stdout", "CONSOLE").
addAttribute("target", ConsoleAppender.Target.SYSTEM_OUT);
appenderBuilder.add(builder.newLayout("PatternLayout").
addAttribute("pattern", "%d [%t] %-5level: %msg%n%throwable"));
appenderBuilder.add(builder.newFilter("MarkerFilter", Filter.Result.DENY,
Filter.Result.NEUTRAL).addAttribute("marker", "FLOW"));
builder.add(appenderBuilder);
builder.add(builder.newLogger("org.apache.logging.log4j", Level.DEBUG).
add(builder.newAppenderRef("Stdout")).
addAttribute("additivity", false));
builder.add(builder.newRootLogger(Level.ERROR).add(builder.newAppenderRef("Stdout")));
return builder.build();
}
#Override
public Configuration getConfiguration(final LoggerContext loggerContext, final ConfigurationSource source) {
return getConfiguration(loggerContext, source.toString(), null);
}
#Override
public Configuration getConfiguration(final LoggerContext loggerContext, final String name, final URI configLocation) {
ConfigurationBuilder<BuiltConfiguration> builder = newConfigurationBuilder();
return createConfiguration(name, builder);
}
#Override
protected String[] getSupportedTypes() {
return new String[] {"*"};
}
}
I wish to choose the appender based on application.properties
I have tried all answers shared on How to access a value defined in the application.properties file in Spring Boot. But none of them work, giving errors like Spring Boot - Environment #Autowired throws NullPointerException. The solutions mentioned in the above link also fail to work.

Log4j2 logs are not directing to correct log file

I am facing an issue with configuring log4j2 logs programmatically.
Please see the below code, I am creating 2 objects of the class App3, and I want to create 2 debug log files(a log file per App3 object), thereafter each object should be able to log to corresponding log file.
But my code is logging all the logs to the second log file after 2nd log is created. Can someone help on this.
Output of the program
file Name: app3_logger1.log_debug.log
2020-06-16 16:19:31,100 DEBUG app3_logger1.log [main] Hello from Log4j 2app3_logger1
file Name: app3_logger2.log_debug.log
2020-06-16 16:19:31,211 DEBUG app3_logger2.log [main] Hello from Log4j 2app3_logger2
2020-06-16 16:19:31,216 DEBUG app3_logger2.log [main] Hello from Log4j 2app3_logger2
2020-06-16 16:19:31,216 DEBUG app3_logger1.log [main] Hello from Log4j 2app3_logger1
2020-06-16 16:19:31,216 DEBUG app3_logger1.log [main] Hello from Log4j 2app3_logger1
2020-06-16 16:19:31,217 DEBUG app3_logger2.log [main] Hello from Log4j 2app3_logger2
Java Class - you just need to add log4j2 dependencies to compile
public class App3 {
public Logger logger;
public static void main(String[] args) {
App3 app1 = new App3();
app1.initializeYourLogger("app3_logger1.log", "%d %p %c [%t] %m%n");
app1.testProg("app3_logger1");
App3 app2 = new App3();
app2.initializeYourLogger("app3_logger2.log", "%d %p %c [%t] %m%n");
app2.testProg("app3_logger2");
app2.testProg("app3_logger2");
app1.testProg("app3_logger1");
app1.testProg("app3_logger1");
app2.testProg("app3_logger2");
}
public void testProg(String s) {
logger.debug("Hello from Log4j 2" + s);
}
public void initializeYourLogger(String fileName, String pattern) {
this.logger = LogManager.getLogger(fileName);
ConfigurationBuilder<BuiltConfiguration> builder = ConfigurationBuilderFactory.newConfigurationBuilder();
builder.setStatusLevel(Level.DEBUG);
builder.setConfigurationName(fileName);
AppenderComponentBuilder componentBuilder = builder.newAppender("log", "File");
componentBuilder.add(builder.newLayout("PatternLayout").addAttribute("pattern", pattern));
RootLoggerComponentBuilder rootLogger = builder.newRootLogger(Level.DEBUG);
LayoutComponentBuilder layoutBuilder = builder.newLayout("PatternLayout").addAttribute("pattern", pattern);
ComponentBuilder triggeringPolicy = builder.newComponent("Policies")
.addComponent(builder.newComponent("SizeBasedTriggeringPolicy").addAttribute("size", "10MB"));
componentBuilder = builder.newAppender("LogToRollingErrorFile", "RollingFile")
.add(builder.newFilter("ThresholdFilter", Filter.Result.ACCEPT, Filter.Result.DENY)
.addAttribute("level", Level.ERROR))
.addAttribute("fileName", fileName + "_error.log")
.addAttribute("filePattern", fileName + "-%d{MM-dd-yy-HH-mm-ss}_error.log").add(layoutBuilder)
.addComponent(triggeringPolicy);
builder.add(componentBuilder);
componentBuilder = builder.newAppender("LogToRollingDebugFile", "RollingFile")
.add(builder.newFilter("ThresholdFilter", Filter.Result.ACCEPT, Filter.Result.DENY)
.addAttribute("level", Level.DEBUG))
.addAttribute("fileName", fileName + "_debug.log")
.addAttribute("filePattern", fileName + "-%d{MM-dd-yy-HH-mm-ss}_debug.log").add(layoutBuilder)
.addComponent(triggeringPolicy);
builder.add(componentBuilder);
AppenderRefComponentBuilder rollingError = rootLogger.getBuilder().newAppenderRef("LogToRollingErrorFile");
AppenderRefComponentBuilder rollingDebug = rootLogger.getBuilder().newAppenderRef("LogToRollingDebugFile");
rootLogger.add(rollingError);
rootLogger.add(rollingDebug);
builder.add(rootLogger);
Configurator.reconfigure(builder.build());
}
}
This is exactly what I want to do in log4j older version, I am still struggling with log4j2,
private void initLogger(String serviceName, String instance) throws IOException {
String loggerName = serviceName+"_"+instance;
this.logger = Logger.getLogger(loggerName);
this.logger.removeAllAppenders();
PatternLayout layout = new PatternLayout();
layout.setConversionPattern("%d: %m%n");
String loggingFolder = this.properties.getLoggingFolder();
String debugFileName = loggingFolder+"/"+loggerName+"_debug.log";
String errorFileName = loggingFolder+"/"+loggerName+"_error.log";
RollingFileAppender debugAppender = new RollingFileAppender(layout, debugFileName, true);
debugAppender.setThreshold(Level.DEBUG);
debugAppender.setMaxFileSize("10000000");
debugAppender.setMaxBackupIndex(49);
logger.addAppender(debugAppender);
RollingFileAppender errorAppender = new RollingFileAppender(layout, errorFileName, true);
errorAppender.setThreshold(Level.ERROR);
errorAppender.setMaxFileSize("10000000");
errorAppender.setMaxBackupIndex(49);
logger.addAppender(debugAppender);
}
Actually, I am quite sure your Logger is being updated correctly. The problem is that both application instances are going to use the same Logging configuration.
If you look at Log4j's architecture you will see that the Loggers and the configuration are anchored in the LoggerContext. By default, Log4j uses the ClassLoaderContextSelector, which means that every ClassLoader can have its own LoggerContext. Your sample application only has a single ClassLoader and so will only have a single LoggerContext and, therefore, only a single Configuration.
So when you reconfigured the second time you simply replaced the prior configuration with the new one. Since the root logger is handling all log events it will route the events from both Loggers you have created to the file created in the second configuration.
If you want logs to end up in two different files then create a configuration with both files and figure out a way to route them to the correct file, either via logger names or filters.
As per observation, your logger is updated with the last initialization so after app2.initializeYourLogger() your object-level logger object updated with App2 configuration.
You need to return the logger object from initializeYourLogger() and also create separate Logger object.

How to change root logging level programmatically for logback

I have the following logback.xml file:
<configuration debug="true">
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="debug">
<appender-ref ref="STDOUT" />
</root>
</configuration>
Now, upon the occurrence of a specific event, I want to programmatically change the level of the root logger from debug to error. I can't use variable substitution, it is mandatory that I do this within the code.
How can it be done ? Thanks.
Try this:
import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
Logger root = (Logger)LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
root.setLevel(Level.INFO);
Note that you can also tell logback to periodically scan your config file like this:
<configuration scan="true" scanPeriod="30 seconds" >
...
</configuration>
using logback 1.1.3 I had to do the following (Scala code):
import ch.qos.logback.classic.Logger
import org.slf4j.LoggerFactory
...
val root: Logger = LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME).asInstanceOf[Logger]
I assume you are using logback (from the configuration file).
From logback manual, I see
Logger rootLogger = LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
Perhaps this can help you change the value?
As pointed out by others, you simply create mockAppender and then create a LoggingEvent instance which essentially listens to the logging event registered/happens inside mockAppender.
Here is how it looks like in test:
import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.classic.spi.LoggingEvent;
import ch.qos.logback.core.Appender;
#RunWith(MockitoJUnitRunner.class)
public class TestLogEvent {
// your Logger
private Logger log = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
// here we mock the appender
#Mock
private Appender<ILoggingEvent> mockAppender;
// Captor is generic-ised with ch.qos.logback.classic.spi.LoggingEvent
#Captor
private ArgumentCaptor<LoggingEvent> captorLoggingEvent;
/**
* set up the test, runs before each test
*/
#Before
public void setUp() {
log.addAppender(mockAppender);
}
/**
* Always have this teardown otherwise we can stuff up our expectations.
* Besides, it's good coding practise
*/
#After
public void teardown() {
log.detachAppender(mockAppender);
}
// Assuming this is your method
public void yourMethod() {
log.info("hello world");
}
#Test
public void testYourLoggingEvent() {
//invoke your method
yourMethod();
// now verify our logging interaction
// essentially appending the event to mockAppender
verify(mockAppender, times(1)).doAppend(captorLoggingEvent.capture());
// Having a generic captor means we don't need to cast
final LoggingEvent loggingEvent = captorLoggingEvent.getValue();
// verify that info log level is called
assertThat(loggingEvent.getLevel(), is(Level.INFO));
// Check the message being logged is correct
assertThat(loggingEvent.getFormattedMessage(), containsString("hello world"));
}
}
I think you can use MDC to change logging level programmatically. The code below is an example to change logging level on current thread. This approach does not create dependency to logback implementation (SLF4J API contains MDC).
<configuration>
<turboFilter class="ch.qos.logback.classic.turbo.DynamicThresholdFilter">
<Key>LOG_LEVEL</Key>
<DefaultThreshold>DEBUG</DefaultThreshold>
<MDCValueLevelPair>
<value>TRACE</value>
<level>TRACE</level>
</MDCValueLevelPair>
<MDCValueLevelPair>
<value>DEBUG</value>
<level>DEBUG</level>
</MDCValueLevelPair>
<MDCValueLevelPair>
<value>INFO</value>
<level>INFO</level>
</MDCValueLevelPair>
<MDCValueLevelPair>
<value>WARN</value>
<level>WARN</level>
</MDCValueLevelPair>
<MDCValueLevelPair>
<value>ERROR</value>
<level>ERROR</level>
</MDCValueLevelPair>
</turboFilter>
......
</configuration>
MDC.put("LOG_LEVEL", "INFO");
You may intercept logging configuration via the LogManager.getLogManager().updateConfiguration() method. Just check for configuration properties which contains .level suffix, and replace default value with Level.ALL value.
public class Application {
// Static initializer is executed when class is loaded by a class loader
static {
try {
java.util.logging.LogManager.getLogManager().updateConfiguration(propertyName -> {
// Check for the log level property
if (propertyName.contains(".level")) {
// Level = ALL => logs all messages
return (oldValue, newValue) -> java.util.logging.Level.ALL.getName();
} else {
// Identity mapper for other propeties
return (oldValue, newValue) -> newValue;
}
});
} catch (IOException e) {
throw new RuntimeException(e);
}
}
So I mostly agree with the top answer but found it to be slightly different in 2023. I found that the following works
import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
final LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
final Logger logger = loggerContext.exists(org.slf4j.Logger.ROOT_LOGGER_NAME); // give it your logger name
final Level newLevel = Level.toLevel("ERROR", null); // give it your log level
logger.setLevel(newLevel);
The primary difference of note is instead of getLogger I had to use getILoggerFactory. To see additional related posts to this see Programmatically change log level in Log4j2 or if you want to be able to this per request see Change priority level in log4j per request
I seem to be having success doing
org.jboss.logmanager.Logger logger = org.jboss.logmanager.Logger.getLogger("");
logger.setLevel(java.util.logging.Level.ALL);
Then to get detailed logging from netty, the following has done it
org.slf4j.impl.SimpleLogger.setLevel(org.slf4j.impl.SimpleLogger.TRACE);
Here's a controller
#RestController
#RequestMapping("/loggers")
public class LoggerConfigController {
private final static org.slf4j.Logger LOGGER = LoggerFactory.getLogger(PetController.class);
#GetMapping()
public List<LoggerDto> getAllLoggers() throws CoreException {
LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
List<Logger> loggers = loggerContext.getLoggerList();
List<LoggerDto> loggerDtos = new ArrayList<>();
for (Logger logger : loggers) {
if (Objects.isNull(logger.getLevel())) {
continue;
}
LoggerDto dto = new LoggerDto(logger.getName(), logger.getLevel().levelStr);
loggerDtos.add(dto);
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("All loggers retrieved. Total of {} loggers found", loggerDtos.size());
}
return loggerDtos;
}
#PutMapping
public boolean updateLoggerLevel(
#RequestParam String name,
#RequestParam String level
)throws CoreException {
LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
Logger logger = loggerContext.getLogger(name);
if (Objects.nonNull(logger) && StringUtils.isNotBlank(level)) {
switch (level) {
case "INFO":
logger.setLevel(Level.INFO);
LOGGER.info("Logger [{}] updated to [{}]", name, level);
break;
case "DEBUG":
logger.setLevel(Level.DEBUG);
LOGGER.info("Logger [{}] updated to [{}]", name, level);
break;
case "ALL":
logger.setLevel(Level.ALL);
LOGGER.info("Logger [{}] updated to [{}]", name, level);
break;
case "OFF":
default:
logger.setLevel(Level.OFF);
LOGGER.info("Logger [{}] updated to [{}]", name, level);
}
}
return true;
}
}

Categories

Resources