I would like to retrieve all the appenders from my log4j.xml by name to my Java class. there is no logger that has all appenders attached Because I cannot change the log4j.xml. Is there any way to retrieve those appenders? Some appenders aren't attached to any Logger. searching the internet it seemed not to be possible.
Not sure but maybe this code will help you:
static Set<Appender> getAllAppenders() {
Set<Appender> allAppenders = new HashSet<>();
LoggerRepository repository = LogManager.getLoggerRepository();
Enumeration<Logger> loggers = repository.getCurrentLoggers();
while (loggers.hasMoreElements()) {
Logger logger = loggers.nextElement();
Enumeration<Appender> appenders = logger.getAllAppenders();
while (appenders.hasMoreElements()) {
allAppenders.add(appenders.nextElement());
}
}
return allAppenders;
}
Related
I've got a program that has a log4j configuration written in XML. I am trying to modify the original application, and attempting to improve upon the previous logger config.
Since I cannot modify the xml file itself, I want to be able to generate a new configuration through the ConfigurationBuilderFactory, and use it alongside the other config. The only issue is that, I am unable to accomplish this. It does not seem to want to work with both.
What can I do?
The following is my code, greatly simplified:
/**
* Internally uses {#link org.apache.logging.log4j.Logger}
*/
public final class MyLogger {
private static final LoggerContext context;
static {
ConfigurationBuilder<BuiltConfiguration> builder = ConfigurationBuilderFactory.newConfigurationBuilder();
{
builder.setStatusLevel(WARN);
AppenderComponentBuilder console = builder.newAppender("SysOut", "Console");
console.addAttribute(...);
console.add(builder.newLayout(...).addAttribute(...));
builder.add(console);
// ... more configuration below
}
context = Configurator.initialize(builder.build()); // only works if no previous config exists, but will not replace an old config
}
}
// later on...
context.getLogger("MyLogger"); // uses the xml config, not the one written above
For creating loggers using the ConfigurationBuilder API programmatically you can refer below code.
It creates a logger in log4j2 environment added with some layout and appenders defined :
public class Log4j2Logger {
int counter = 0;
LoggerContext ctx;
Configuration config;
Logger logger;
String loggerName = "testLogger";
String appenderName = "myAppender";
static String testMessage = "This is a Test Message";
public void log() {
final ConfigurationBuilder<BuiltConfiguration> builder = ConfigurationBuilderFactory.newConfigurationBuilder();
final LoggerComponentBuilder loggerComp = builder.newLogger(loggerName, Level.ALL).addAttribute("additivity",
false);
builder.add(loggerComp);
config = builder.build();
ctx = Configurator.initialize(config);
config = ctx.getConfiguration();
ctx.start(config);
ctx.updateLoggers(config);
// To create/add the logger of the configuration specified above we can use the
// getLogger(..) method
logger = ctx.getLogger(loggerName);
// Now we need to attach an appender to the logger so that our messages could be
// logged
logger.addAppender(addConsoleAppender(ctx.getConfiguration(), appenderName));
while (counter < 10) {
logger.error(testMessage + counter);
counter++;
}
}
private Appender addConsoleAppender(Configuration config, String appenderName) {
Appender consoleAppender = ConsoleAppender.newBuilder().setConfiguration(config).setName(appenderName)
.withImmediateFlush(true).build();
consoleAppender.start();
return consoleAppender;
}
}
And for testing, you can have following in any test class:
Log4j2Logger testLogger = new Log4j2Logger();
testLogger.log();
This API helps you to handle logs in a powerful way.
You can :
Create multiple loggers with your configuration
Add multiple Appenders to it.
Configure them also.
Remove logger when the usage is over.
Create Asynchronous Loggers also.
PS : I have used log4j2 version 2.12.1.
I think you can create your own log4j.xml. You have to ensure that your XML will be loaded in your program. So just define the resource containing your XML in the Java Classpath before the resource containing the other XML.
I'm able to programmatically set the logging level on the application with the following code, but is it also possible to do this on a package level, say com.somepackage.* where I want the level to be only ERROR rather than DEBUG or INFO on said package?
// Sets the logging level to INFO
LoggerContext loggerContext = (LoggerContext)LoggerFactory.getILoggerFactory();
Logger rootLogger = loggerContext.getLogger(Logger.ROOT_LOGGER_NAME);
rootLogger.setLevel(Level.INFO);
But I can't seem to find a way to set it on a package level...
You should set the package name as logger-name
// Sets the package level to INFO
LoggerContext loggerContext = (LoggerContext)LoggerFactory.getILoggerFactory();
Logger rootLogger = loggerContext.getLogger("com.somepackage");
rootLogger.setLevel(Level.INFO);
You should be able to get the package name more elegant, but this is basically it.
This follows the tree like hierarchy for the Logger Context:
Logger Context
You can do it by using logback..
Logger LOG = (Logger) org.slf4j.LoggerFactory.getLogger(ch.qos.logback.classic.Logger.ROOT_LOGGER_NAME);
LOG.setLevel(Level.WARN);
This solved my problem.
You can get the SLF4J logger for the package and cast it to Logback logger. Less code that #dimitri-dewaele's.
((Logger) LoggerFactory.getLogger("com.somepackage")).setLevel(DEBUG)
#nandu-prajapati's approach is similar, except that it sets the root logger level, not the one desired.
In case you don't want to change the logback-classic to a compile-time dependency, here is some code that uses reflection to set this level assuming that logback is used as the slf4j runtime-binding. Here AbcClass is the class whose logger level you want to change to TRACE:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SomeClass {
private static final Logger logger = LoggerFactory.getLogger(SomeClass .class);
public static void beforeEachTest() throws Exception {
final Logger loggerInterface = LoggerFactory.getLogger(AbcClass.class);
String loggerLevelNew = "TRACE";
if (!loggerInterface.isTraceEnabled()) {
try {
Class<?> levelLogBackClass = Class.forName("ch.qos.logback.classic.Level");
Method toLevelMethod = levelLogBackClass.getDeclaredMethod("toLevel", String.class);
Object traceLvel = toLevelMethod.invoke(null, loggerLevelNew);
Method loggerSetLevelMethod= loggerInterface.getClass().getDeclaredMethod("setLevel", levelLogBackClass);
loggerSetLevelMethod.invoke(loggerInterface, traceLvel);
} catch (Exception e) {
logger.warn("Problem setting logger level to:{}, msg: {}", loggerLevelNew, e.getMessage());
throw e;
}
}
}
}
Something similar can be done for log4j and JDK logging to make this (kind-of) library agnostic
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)
Is there any way to specify Log4J 2.x log4j2.xml file location manually (like DOMConfigurator in Log4J 1.x), without messing with classpath and system properties?
You could use the static method #initialize(String contextName, ClassLoader loader, String configLocation) (see source here) in org.apache.logging.log4j.core.config.Configurator.
(You can pass null for the class loader.)
Be aware that this class is not part of the public API so your code may break with any minor release.
For completeness, you can also specify the location of the configuration file with this system property:
-Dlog4j.configurationFile=path/to/log4j2.xml
If you are using log4j2 and properties are in defined in log4j2.properties file then use this.
-Dlog4j2.configurationFile=file:/home/atul/log4j2.properties
For log4j version 2.12.1, you can find how to reconfigure log4j2 here.
Below is an example
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.LoggerContext;
File file = new File("C:\\Path for Windows OS\\yourConfig.xml");
LoggerContext context = (LoggerContext) LogManager.getContext(false);
context.setConfigLocation(file.toURI());
Logger log = LogManager.getLogger(YourClass.class);
It seems to me that way of configuring log4j2 is changing with new releases, so you should be aware of that.
In Windows, be aware that you need to use a URI with the log4j.configurationFile property
-Dlog4j.configurationFile=file://C:\path\to\log4j2.xml
Using the LoggerContext allows to setConfigLocation.
File f = new File(this.logConfigFile);
URI fc = f.toURI();
System.out.println("Loading logging config file: " + fc);
Logger l = (Logger) LogManager.getLogger(LogManager.ROOT_LOGGER_NAME);
l.getContext().setConfigLocation(fc);
or alternatively
LoggerContext.getContext().setConfigLocation(java.net.URI);
You can initialize like below as well
ConfigurationSource source = new ConfigurationSource(new FileInputStream(log4j file Path));
XmlConfiguration xmlConfig = new XmlConfiguration(source);
Logger logger = (Logger) LogManager.getLogger();
logger.getContext().start(xmlConfig);
In each class you can get logger instance as below
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
private final Logger logger = LogManager.getLogger(ABC.class);
Step: 1 - Get ready with your log4J.xml file with the appender details (Mostly under the resource folder)
Step: 2 - Following code should be added to the configuration class (In the previous log4J we had PropertyConfigurator and now we need to go with LoggerContext)
String log4JFilePath = "file path of your log4J.xml file";
LoggerContext loggerContext = (LoggerContext)LoggerManager.getContext(false);
File file = new File(log4JFilePath);
loggerContext.setConfigLocation(file.toURI());
Step: 3 - Add the following line to utilise the logger in any classes
private static final Logger logger = LogManager.getLogger(yourClassName.class);
logger.info("log here");
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.config.Configurator;
public class Foo {
public static void main(String[] args) {
Configurator.initialize(null, "src/main/config/log4j2.xml"); //path specify
Logger logger = LogManager.getLogger(APITestToolMain.class);
logger.info("working");
}
}
resource: https://www.baeldung.com/spring-boot-change-log4j2-location
void initializeLogger()
{
try
{
String basepath=checkpath();
File f = new File(basepath+"\\config\\log4j2.properties");
URI fc = f.toURI();
LoggerContext context = (LoggerContext) LogManager.getContext(false);
context.setConfigLocation(f.toURI());
}
catch (Exception e)
{
errorlog="Unable to load logging property:";
System.out.println(errorlog+": "+e.getMessage());
}
}
This is the way I initialize my log4j2 properties file from a different location, so what I simply do is to call the initializeLogger() method in my main method.
And it works perfectly.
Perhaps you need to see what the checkpath() blocks looks like, I added the function below.
String checkpath()
{
String parentpath="";
try
{
URL url=getClass().getProtectionDomain().getCodeSource().getLocation();
File f=new File(url.toURI());
parentpath=f.getParent();
}
catch(Exception ex)
{
//logger.error("unable to retrieve application parent path");
}
return parentpath;
}