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.
Related
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!
My log4j2 properties file:
status = warn
name= properties_configuration
#Directory path where log files will be stored
property.basePath = ./log/
#File logger
appender.rolling.type = RollingFile
appender.rolling.name = fileLogger
appender.rolling.fileName= ${basePath}app.log
appender.rolling.filePattern= ${basePath}app_%d{yyyyMMdd}.log.gz
appender.rolling.layout.type = PatternLayout
appender.rolling.layout.pattern = %d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %msg%n
appender.rolling.policies.type = Policies
appender.rolling.policies.time.type = TimeBasedTriggeringPolicy
appender.rolling.policies.time.interval = 1
appender.rolling.policies.time.modulate = true
appender.rolling.strategy.type = DefaultRolloverStrategy
appender.rolling.strategy.delete.type = Delete
appender.rolling.strategy.delete.basePath = ${basePath}
appender.rolling.strategy.delete.maxDepth = 1
appender.rolling.strategy.delete.ifLastModified.type = IfLastModified
appender.rolling.strategy.delete.ifLastModified.age = 30d
#Root logger configuration
rootLogger.level = info
rootLogger.additivity = false
rootLogger.appenderRef.rolling.ref = fileLogger
I'm using Lombok #Log4j2 annotation:
#Log4j2
public class BotApplication {
public static void main(String[] args) {
ApiContextInitializer.init();
TelegramBotsApi telegram = new TelegramBotsApi();
Bot bot = new Bot();
try {
telegram.registerBot(bot);
log.info("Bot successfully connected.");
} catch (TelegramApiRequestException e) {
log.error("Can't start Bot. Error: {}", e.getMessage());
}
}
}
Application writes logs to file correctly, but always when i run my app i got errors in my console:
log4j:WARN No appenders could be found for logger (org.apache.http.client.protocol.RequestAddCookies).
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
Seems that these errors don't affect to the logger, but I would like to remove them somehow.
Error messages that being with "log4j:WARN" are coming from log4j 1.x. This error means you have the log4j 1.x jar in your classpath but do not have a Log4j 1.x configuration present.
If you do not want to use log4j 1 (and you shouldn't) then add the log4j-1.2-api jar from Log4j 2 to your project.
I have created a Logger over the log4j logger with ConsoleAppender.
The code is as follows,
public class AppLogger{
static {
ConsoleAppender ca = new ConsoleAppender(new PatternLayout(PatternLayout.TTCC_CONVERSION_PATTERN));
org.apache.log4j.Logger.getRootLogger().addAppender(ca);
}
public AppLogger(Class classname) {
logger = LoggerFactory.getLogger(classname);
}
public void debug(String message){
logger.debug(message);
}
...
...
}
I have not used the log4j.properties file because I am not sure where to put it. The above code is working fine and logging properly on the console. Now I want to change it to use it with a FileAppender.
I simply updated the static constructors as follows,
FileAppender fa = new FileAppender();
fa.setFile("d:/log.txt");
fa.setLayout(new PatternLayout(PatternLayout.DEFAULT_CONVERSION_PATTERN));
org.apache.log4j.Logger.getRootLogger().addAppender(fa);
And added fa insted of ca. But now I am getting this error,
log4j:ERROR No output stream or file set for the appender named [null].
I am not sure which property I am missing to define fro the File Appender.
You forgot to activate the options:
FileAppender fa = new FileAppender();
fa.setFile("d:/log.txt");
fa.setLayout(new PatternLayout(PatternLayout.DEFAULT_CONVERSION_PATTERN));
fa.activateOptions(); \\ ← activate the options
org.apache.log4j.Logger.getRootLogger().addAppender(fa);
The method OptionHandler#activateOptions() activate the options that were previously set with calls to option setters.
I have successfully created a log at each process run time. The issue I am having now is any packages that are called that are not children of the current running process does not write its log to my file. For example I create a new log file called running-.log. The process that is running is com.me.foo inside of this class there is a call to a method in com.you and another one in com.zee . I would like to have com.you and com.zee logs write to the running-.log and not to the console log. It isn't as simple as just changing the getLogger() method to be a child of com.me.foo. Some of the logs are written out from third party jars. I am at a loss. If you need to see more code or some additional info, please let me know. There has to be another way to handle this.
Thanks
Code to create the log file dynamically
public void createLogInstance(String packaging,String appenderName, String logFileName){
Logger logger = Logger.getLogger(packaging);
Appender fileAppender = logger.getAppender(appenderName);
if(fileAppender != null){
logger.removeAppender(fileAppender);
}
//Create the root appender
ConsoleAppender console = new ConsoleAppender();
String pattern = ....;
console.setLayout(new PatternLayout(pattern));
console.setThreshold(Level.FATAL);
console.activateOptions();
logger.addAppender(console);
FileAppender fa = new FileAppender();
fa.setName(appenderName);
fa.setFile(logFileName);
fa.setLayout(new PatternLayout(..));
fa.setThreshold(Level.DEBUG);
fa.setAppend(true);
fa.activateOptions();
logger.setAdditivity(false);
logger.addAppender(fa);
}
com.zee log
private static Logger logger = LoggerFactory.getLogger(Zee.class);
com.you log
private static Logger logger = LoggerFactory.getLogger(You.class);
I was missing the rootLogger. Changed this line
Logger logger = Logger.getLogger(packaging);
to
Logger logger = Logger.getRootLogger();
If someone has a better way please let me know.
logger.setLevel() method is not available in log4j2 API. So how to set log level at run time.
I'm not sure if this is the best way, but you set the level on org.apache.logging.log4j.core.config.LoggerConfig which you can get from the LoggerContext via the LogManager.
Once set, you can update the loggers with the new configuration.
As an example:
public static void main(String[] args) {
Logger log = LogManager.getLogger(LogManager.ROOT_LOGGER_NAME);
log.error("An error");
log.debug("A debug");
LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
Configuration conf = ctx.getConfiguration();
conf.getLoggerConfig(LogManager.ROOT_LOGGER_NAME).setLevel(Level.DEBUG);
ctx.updateLoggers(conf);
log.error("Another error");
log.debug("Another debug");
}
Yields:
14:03:41.346 [main] ERROR - An error
14:03:41.348 [main] ERROR - Another error
14:03:41.348 [main] DEBUG - Another debug
Credit to amcintosh, I wrapped their answer in a function:
/** Override the logging level of a given logger, return the previous level */
public static Level setLevel(Logger log, Level level) {
LoggerContext ctx = (LoggerContext)LogManager.getContext(false);
Configuration conf = ctx.getConfiguration();
LoggerConfig lconf = conf.getLoggerConfig(log.getName());
Level oldLevel = lconf.getLevel();
lconf.setLevel(level);
ctx.updateLoggers(conf);
return oldLevel;
}
Despite amoe's comment, this seems to be working correctly for me using Log4J 2.5.
Gary Gregory is correct.
Also the answer to this question is right there on the FAQ page in log4j2's site
https://logging.apache.org/log4j/2.x/faq.html#reconfig_level_from_code
Sample Code below:
Configurator.setLevel(logger.getName(), Level.INFO);
On my side, i had to use this code in order to have this working fine (based on previous answers).
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.config.AbstractConfiguration;
...
public static void changeLoggerLevel(final String module, final Level level) {
String moduleRenamed = module.replaceAll("/", ".");
LoggerContext ctx = (LoggerContext)LogManager.getContext(false);
AbstractConfiguration configuration = (AbstractConfiguration) ctx
.getConfiguration();
if (configuration.getLogger(moduleRenamed) != null) {
LoggerConfig loggerConfig = configuration.getLoggerConfig(moduleRenamed);
loggerConfig.setLevel(level);
} else {
LoggerConfig loggerConfig = new LoggerConfig(moduleRenamed, level, true);
configuration.addLogger(moduleRenamed, loggerConfig);
}
ctx.updateLoggers(configuration);
}
The problem was with the getLoggerConfig() call; if the module you are trying to give a new level is not yet registered, this method returns the root logger (or any intermediate sub path registered), and thus instead of altering the level for com.mycompany you will alter root or com level. That's why you have to add a new LoggerConfig in case the module to alter is not yet registered.
The following APIs in the class org.apache.logging.log4j.core.config.Configurator allow you to change Levels:
setAllLevels(String, Level)
setLevel(Map)
setLevel(String, Level)
setRootLevel(Level)