Search code examples
javamavenlogginglog4j

Log4J log file remains empty when configuring programmatically


I'm trying to configure Log4J programmatically to write the logs to both the console and to a log file. While the console output looks fine, the log file remains empty (but is created though). I'm using OpenJDK 22 on Zorin 17.1 Core.

Why is that and how to fix it?

Here's a minimal working example that resembles the issue in my actual project:

import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.appender.ConsoleAppender;
import org.apache.logging.log4j.core.config.Configurator;
import org.apache.logging.log4j.core.config.LoggerConfig;
import org.apache.logging.log4j.core.config.builder.api.AppenderComponentBuilder;
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.impl.BuiltConfiguration;

public class Main {
    public static void main(String[] args) {
        Level level = Level.WARN;

        ConfigurationBuilder<BuiltConfiguration> builder = ConfigurationBuilderFactory.newConfigurationBuilder();
        builder.setStatusLevel(level);
        builder.setConfigurationName("BuilderTest");

        AppenderComponentBuilder appenderBuilder =
            builder.newAppender("Stdout", "CONSOLE")
                .addAttribute("target", ConsoleAppender.Target.SYSTEM_OUT)
                .add(builder.newLayout("PatternLayout")
                .addAttribute("pattern", "xxxxx %d [%t] %-5level: %msg%n%throwable"))
                .add(builder.newFilter("MarkerFilter", Filter.Result.DENY, Filter.Result.NEUTRAL)
                .addAttribute("marker", "FLOW"));

        builder.add(appenderBuilder);

        appenderBuilder =
            builder.newAppender("File", "File")
            .addAttribute("fileName", "logs/main.log")
            .addAttribute("append", true)
            .addAttribute("locking", false)
            .addAttribute("immediateFlush", true)
            .add(builder.newLayout("PatternLayout")
            .addAttribute("pattern", "xxxxxx %d [%t] %-5level: %msg%n%throwable"));

        builder.add(appenderBuilder);

        builder
            .add(builder.newLogger("org.apache.logging.log4j", level)
            .add(builder.newAppenderRef("Stdout"))
            .addAttribute("additivity", false));

        builder.add(
            builder
                .newRootLogger(level)
                .add(builder.newAppenderRef("Stdout"))
        );

        Configurator.reconfigure(builder.build());
        LoggerConfig loggerConfig = new LoggerConfig(Main.class.getPackageName(), level, false);
        Configurator.setLevel(Main.class.getPackageName(), level);

        LogManager.getRootLogger().error("setLoggingLevel(): Logging level set to %s.".formatted(level));
    }
}

Console output:

xxxxx 2024-08-30 01:43:22,830 [main] ERROR: setLoggingLevel(): Logging level set to WARN.

Here's my pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example.log4j-test</groupId>
    <artifactId>log4j-test</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>22</maven.compiler.source>
        <maven.compiler.target>22</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.3.4</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-api</artifactId>
            <version>3.0.0-beta2</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>3.0.0-beta2</version>
        </dependency>
    </dependencies>
</project>

Solution

  • Project Tree

    ├── pom.xml
    └── src
        └── main
            └── java
                └── com
                    └── example
                        └── Main.java
    

    pom.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>com.example.log4j-test</groupId>
        <artifactId>log4j-test</artifactId>
        <version>1.0-SNAPSHOT</version>
    
        <properties>
            <maven.compiler.source>17</maven.compiler.source>
            <maven.compiler.target>17</maven.compiler.target>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
            <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
            <!--
            <log4j2.version>2.19.0</log4j2.version>
            -->
            <log4j2.version>3.0.0-beta2</log4j2.version>
        </properties>
    
        <dependencies>
    
            <!-- Log4j2 core -->
            <dependency>
                <groupId>org.apache.logging.log4j</groupId>
                <artifactId>log4j-core</artifactId>
                <version>${log4j2.version}</version>
            </dependency>
    
            <!-- Log4j2 API -->
            <dependency>
                <groupId>org.apache.logging.log4j</groupId>
                <artifactId>log4j-api</artifactId>
                <version>${log4j2.version}</version>
            </dependency>
    
        </dependencies>
    
    </project>
    

    Main.java

    package com.example;
    
    import org.apache.logging.log4j.Level;
    import org.apache.logging.log4j.LogManager;
    import org.apache.logging.log4j.Logger;
    import org.apache.logging.log4j.core.config.Configurator;
    import org.apache.logging.log4j.core.config.builder.api.*;
    import org.apache.logging.log4j.core.config.builder.impl.BuiltConfiguration;
    
    public class Main {
    
        //private static final Logger logger = LogManager.getLogger(Main.class);
        //private static final Logger appLoggerInstance = LogManager.getLogger("com.example");
        
        public static void main(String[] args) {
        
    
        
            // Create a ConfigurationBuilder instance
            ConfigurationBuilder<BuiltConfiguration> builder = ConfigurationBuilderFactory.newConfigurationBuilder();
            builder.setStatusLevel(Level.WARN);
        
            // Create a pattern layout
            
            String pattern1 = "XXXX %d [%t] %-5level: %msg%n%throwable";
            String pattern2 = "%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n";
            
            LayoutComponentBuilder layout = builder.newLayout("PatternLayout")
                    .addAttribute("pattern", pattern1 );
    
            // Create Console Appender
            AppenderComponentBuilder consoleAppender = builder.newAppender("Stdout", "Console")
                    .addAttribute("target", "SYSTEM_OUT")
                    .add(layout);
    
            // Create File Appender
            AppenderComponentBuilder fileAppender = builder.newAppender("File", "File")
                    .addAttribute("fileName", "logs/main.log")
                    .addAttribute("append", true)
                    .addAttribute("locking", false)
                    .addAttribute("immediateFlush", true)                
                    .add(layout);
    
            // Add appenders to the configuration
            builder.add(consoleAppender);
            builder.add(fileAppender);
    
            // Root Logger configuration
            RootLoggerComponentBuilder rootLogger = builder.newRootLogger("info");
            rootLogger.add(builder.newAppenderRef("Stdout"));
            rootLogger.add(builder.newAppenderRef("File"));
            builder.add(rootLogger);
    
            // Application-specific Logger configuration
            LoggerComponentBuilder appLogger = builder.newLogger("com.example", "debug")
                    .add(builder.newAppenderRef("Stdout"))
                    .add(builder.newAppenderRef("File"))
                    .addAttribute("additivity", false);
    
            // Add logger to the configuration
            builder.add(appLogger);
    
            // Apply the configuration
            Configurator.initialize(builder.build());
    
            // Test logging
            Logger logger = LogManager.getLogger(Main.class);
            logger.info("This is an info message from the root logger.");
            logger.debug( "This is a debug message from the root logger.");
            logger.error( "This is an error message from the root logger.");
            
            Logger appLoggerInstance = LogManager.getLogger("com.example");
            appLoggerInstance.debug("ZZZ This is a debug message from com.example logger.");
            
            LogManager.getRootLogger().error("setLoggingLevel(): Logging level set to %s.".formatted(Level.WARN));
        }
    }
    

    log4j2.xml

    The program does not use log4j2.xml, but our program code can use log4j2.xml as a baseline conversion mapping.

    <?xml version="1.0" encoding="UTF-8"?>
    <Configuration status="WARN">
        <Appenders>
            <!-- Console Appender Configuration -->
            <Console name="Stdout" target="SYSTEM_OUT">
                <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n"/>
            </Console>
    
            <!-- File Appender Configuration -->
            <File name="File" fileName="logs/app-log4j-xml.log" append="true">
                <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n"/>
            </File>
        </Appenders>
    
        <Loggers>
            <!-- Root Logger Configuration -->
            <Root level="info">
                <AppenderRef ref="Stdout"/>
                <AppenderRef ref="File"/>
            </Root>
    
            <!-- Application Specific Logger Configuration -->
            <Logger name="com.example" level="debug" additivity="false">
                <AppenderRef ref="Stdout"/>
                <AppenderRef ref="File"/>
            </Logger>
        </Loggers>
    </Configuration>
    

    Build And Run

    mvn clean package
    
    mvn dependency:copy-dependencies -DoutputDirectory=target/libs
    
    java -cp "target/libs/*:target/log4j-test-1.0-SNAPSHOT.jar" com.example.Main
    

    Result:

    XXXX 2024-08-30 09:20:01,980 [main] INFO : This is an info message from the root logger.
    XXXX 2024-08-30 09:20:02,002 [main] DEBUG: This is a debug message from the root logger.
    XXXX 2024-08-30 09:20:02,002 [main] ERROR: This is an error message from the root logger.
    XXXX 2024-08-30 09:20:02,003 [main] DEBUG: ZZZ This is a debug message from com.example logger.
    XXXX 2024-08-30 09:20:02,003 [main] ERROR: setLoggingLevel(): Logging level set to WARN.
    

    Mapping log4j2.xml to code

    Configuration status

    <Configuration status="WARN">
    
    builder.setStatusLevel(Level.WARN);
    

    PatternLayout

    <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n"/>
    
    // Create a pattern layout
    
    String pattern1 = "XXXX %d [%t] %-5level: %msg%n%throwable";
    String pattern2 = "%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n";
    
    LayoutComponentBuilder layout = builder.newLayout("PatternLayout")
            .addAttribute("pattern", pattern1 );
    

    Console Appender

    <!-- Console Appender Configuration -->
    <Console name="Stdout" target="SYSTEM_OUT">
        <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n"/>
    </Console>
    
    // Create Console Appender
    AppenderComponentBuilder consoleAppender = builder.newAppender("Stdout", "Console")
            .addAttribute("target", "SYSTEM_OUT")
            .add(layout);
    

    File Appender

    The code is inconsistent with xml. Adjust the code to be consistent with the program described in the problem.

    <!-- File Appender Configuration -->
    <File name="File" fileName="logs/app-log4j-xml.log" append="true">
        <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n"/>
    </File>
    
    // Create File Appender
    AppenderComponentBuilder fileAppender = builder.newAppender("File", "File")
            .addAttribute("fileName", "logs/main.log")
            .addAttribute("append", true)
            .addAttribute("locking", false)
            .addAttribute("immediateFlush", true)                
            .add(layout);
    

    Add Appenders

    // Add appenders to the configuration
    builder.add(consoleAppender);
    builder.add(fileAppender);
    

    Root Logger

    <!-- Root Logger Configuration -->
    <Root level="info">
        <AppenderRef ref="Stdout"/>
        <AppenderRef ref="File"/>
    </Root>
    
    // Root Logger configuration
    RootLoggerComponentBuilder rootLogger = builder.newRootLogger("info");
    rootLogger.add(builder.newAppenderRef("Stdout"));
    rootLogger.add(builder.newAppenderRef("File"));
    builder.add(rootLogger);
    

    App Logger

    <!-- Application Specific Logger Configuration -->
    <Logger name="com.example" level="debug" additivity="false">
        <AppenderRef ref="Stdout"/>
        <AppenderRef ref="File"/>
    </Logger>
    
    // Application-specific Logger configuration
    LoggerComponentBuilder appLogger = builder.newLogger("com.example", "debug")
            .add(builder.newAppenderRef("Stdout"))
            .add(builder.newAppenderRef("File"))
            .addAttribute("additivity", false);
            
    // Add logger to the configuration
    builder.add(appLogger);