199

I am trying to use SLF4J (with log4j binding) for the first time.

I would like to configure 3 different named Loggers that can be returned by a LoggerFactory which will log different levels and push the messages to different appenders:

  • Logger 1 "FileLogger" logs DEBUG and appends to DailyRollingFileAppender
  • Logger 2 "TracingLogger" logs TRACE+ and appends to a JmsAppender
  • Logger 3 "ErrorLogger" logs ERROR+ and appends to a different JmsAppender

Furthermore I want them configured programmatically (in Java, as opposed to XML or a log4j.properties file).

I imagine that, normally, I would define these Loggers somewhere in some bootstrapping code, like an init() method. However, because I want to use slf4j-log4j, I'm confused about where I could define loggers and make them available to the classpath.

I don't believe this is a violation of SLF4J's underlying purpose (as a facade), because my code using the SLF4J API won't ever know that these loggers exist. My code just makes normal calls to the SLF4J API, which then forwards them on to the log4j Loggers it finds on the classpath.

But how do I configure those log4j Loggers on the classpath...in Java?!

ROMANIA_engineer
  • 54,432
  • 29
  • 203
  • 199
IAmYourFaja
  • 55,468
  • 181
  • 466
  • 756
  • http://stackoverflow.com/questions/1666121/programmatically-creating-different-log-files-using-log4j – skaffman Jan 25 '12 at 15:49
  • 4
    For log4j 1.x use the accepted answer below for 2.x see https://logging.apache.org/log4j/2.x/manual/customconfig.html – earcam Dec 20 '15 at 03:29

4 Answers4

293

You can add/remove Appender programmatically to Log4j:

  ConsoleAppender console = new ConsoleAppender(); //create appender
  //configure the appender
  String PATTERN = "%d [%p|%c|%C{1}] %m%n";
  console.setLayout(new PatternLayout(PATTERN)); 
  console.setThreshold(Level.FATAL);
  console.activateOptions();
  //add appender to any Logger (here is root)
  Logger.getRootLogger().addAppender(console);

  FileAppender fa = new FileAppender();
  fa.setName("FileLogger");
  fa.setFile("mylog.log");
  fa.setLayout(new PatternLayout("%d %-5p [%c{1}] %m%n"));
  fa.setThreshold(Level.DEBUG);
  fa.setAppend(true);
  fa.activateOptions();

  //add appender to any Logger (here is root)
  Logger.getRootLogger().addAppender(fa);
  //repeat with all other desired appenders

I'd suggest you put it into an init() somewhere, where you are sure, that this will be executed before anything else. You can then remove all existing appenders on the root logger with

 Logger.getRootLogger().getLoggerRepository().resetConfiguration();

and start with adding your own. You need log4j in the classpath of course for this to work.

Remark:
You can take any Logger.getLogger(...) you like to add appenders. I just took the root logger because it is at the bottom of all things and will handle everything that is passed through other appenders in other categories (unless configured otherwise by setting the additivity flag).

If you need to know how logging works and how is decided where logs are written read this manual for more infos about that.
In Short:

  Logger fizz = LoggerFactory.getLogger("com.fizz")

will give you a logger for the category "com.fizz".
For the above example this means that everything logged with it will be referred to the console and file appender on the root logger.
If you add an appender to Logger.getLogger("com.fizz").addAppender(newAppender) then logging from fizz will be handled by alle the appenders from the root logger and the newAppender.
You don't create Loggers with the configuration, you just provide handlers for all possible categories in your system.

Aamir
  • 16,329
  • 10
  • 59
  • 65
oers
  • 18,436
  • 13
  • 66
  • 75
  • 2
    Thanks oers! Quick question - I noticed you're adding the appenders to the root Logger. Is there a reason for this? – IAmYourFaja Jan 25 '12 at 15:38
  • And, more importantly, I'll need to specify which Logger to retrieve from SLF4J's LoggerFactory. Is it possible to ask SLF4J for log4j's root logger? – IAmYourFaja Jan 25 '12 at 15:43
  • 3
    @AdamTannon You can take any Logger.getLogger(...) you like. I just took the root logger because it is at the bottom of all things and will handle everything that is passed through other appenders in other categories (unless configured otherwise). [See logger hierarchy](https://logging.apache.org/log4j/1.2/manual.html) – oers Jan 25 '12 at 15:44
  • @AdamTannon you can't use the sl4j factory to get the log4j root logger. SL4j is a logging facade. You won't get anything log4j specific from it. – oers Jan 25 '12 at 15:53
  • 2
    oers - I appreciate your wonderful feedback, but am just not connecting all the dots here. Can you modify your example to show the addition of a new Logger (not the root logger) that, once added to the system, will be available to any other class that asks for it? For example, a Logger that would normally be accessed by, say, `Logger fizz = LoggerFactory.getLogger("com.fizz");` Thanks! – IAmYourFaja Jan 25 '12 at 16:14
  • @AdamTannon the configuration does not make logger available. You can create as manny loggers as you like and name them whatever you like. The configuration just defines where events from loggers are outputted. Normally(!) every logger logs to the appenders that where added to root logger. But you can specify special appenders for each category. This is basic logging functionality. I'll try to make it clear with an example. – oers Jan 25 '12 at 17:35
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/7048/discussion-between-oers-and-adam-tannon) – oers Jan 25 '12 at 18:25
  • I would like to note that in my case the level of the log was inherited from the root logger. And I spent some time to understand why I did not get any messages. So was able to post only messages with level of the root logger and higher. – Andrew Gans May 16 '16 at 09:58
48

It sounds like you're trying to use log4j from "both ends" (the consumer end and the configuration end).

If you want to code against the slf4j api but determine ahead of time (and programmatically) the configuration of the log4j Loggers that the classpath will return, you absolutely have to have some sort of logging adaptation which makes use of lazy construction.

public class YourLoggingWrapper {
    private static boolean loggingIsInitialized = false;

    public YourLoggingWrapper() {
        // ...blah
    }

    public static void debug(String debugMsg) {
        log(LogLevel.Debug, debugMsg);
    }

    // Same for all other log levels your want to handle.
    // You mentioned TRACE and ERROR.

    private static void log(LogLevel level, String logMsg) {
        if(!loggingIsInitialized)
            initLogging();

        org.slf4j.Logger slf4jLogger = org.slf4j.LoggerFactory.getLogger("DebugLogger");

        switch(level) {
        case: Debug:
            logger.debug(logMsg);
            break;
        default:
            // whatever
        }
    }

    // log4j logging is lazily constructed; it gets initialized
    // the first time the invoking app calls a log method
    private static void initLogging() {
        loggingIsInitialized = true;

        org.apache.log4j.Logger debugLogger = org.apache.log4j.LoggerFactory.getLogger("DebugLogger");

        // Now all the same configuration code that @oers suggested applies...
        // configure the logger, configure and add its appenders, etc.
        debugLogger.addAppender(someConfiguredFileAppender);
    }

With this approach, you don't need to worry about where/when your log4j loggers get configured. The first time the classpath asks for them, they get lazily constructed, passed back and made available via slf4j. Hope this helped!

Christophe Roussy
  • 16,299
  • 4
  • 85
  • 85
IAmYourFaja
  • 55,468
  • 181
  • 466
  • 756
  • 2
    Nailed it! Thank you so much for a helpful example! @Oers - thank you for trying to steer me in the right direction - I'm going to give you the green check for your dedication but have to give zharvey the bounty because it was exactly what I was looking for. Thanks again everyone! – IAmYourFaja Jan 28 '12 at 00:50
7

If someone comes looking for configuring log4j2 programmatically in Java, then this link could help: (https://www.studytonight.com/post/log4j2-programmatic-configuration-in-java-class)

Here is the basic code for configuring a Console Appender:

ConfigurationBuilder<BuiltConfiguration> builder = ConfigurationBuilderFactory.newConfigurationBuilder();

builder.setStatusLevel(Level.DEBUG);
// naming the logger configuration
builder.setConfigurationName("DefaultLogger");

// create a console appender
AppenderComponentBuilder appenderBuilder = builder.newAppender("Console", "CONSOLE")
                .addAttribute("target", ConsoleAppender.Target.SYSTEM_OUT);
// add a layout like pattern, json etc
appenderBuilder.add(builder.newLayout("PatternLayout")
                .addAttribute("pattern", "%d %p %c [%t] %m%n"));
RootLoggerComponentBuilder rootLogger = builder.newRootLogger(Level.DEBUG);
rootLogger.add(builder.newAppenderRef("Console"));

builder.add(appenderBuilder);
builder.add(rootLogger);
Configurator.reconfigure(builder.build());

This will reconfigure the default rootLogger and will also create a new appender.

iamabhishek
  • 437
  • 6
  • 17
4

In the case that you have defined an appender in log4j properties and would like to update it programmatically, set the name in the log4j properties and get it by name.

Here's an example log4j.properties entry:

log4j.appender.stdout.Name=console
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.Threshold=INFO

To update it, do the following:

((ConsoleAppender) Logger.getRootLogger().getAppender("console")).setThreshold(Level.DEBUG);
Kyle Shrader
  • 912
  • 11
  • 16