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>
├── pom.xml
└── src
└── main
└── java
└── com
└── example
└── Main.java
<?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>
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));
}
}
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>
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.
<Configuration status="WARN">
builder.setStatusLevel(Level.WARN);
<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 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);
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 to the configuration
builder.add(consoleAppender);
builder.add(fileAppender);
<!-- 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);
<!-- 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);