This is an automated email from the ASF dual-hosted git repository. vy pushed a commit to branch 2.25.x in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git
commit 41e9ca55429ed7a4aef316e03985f49151d9e666 Author: Ramanathan <[email protected]> AuthorDate: Fri May 22 01:03:29 2026 +0530 Fix `createOnDemand` for Rolling File Appender (#4072) Co-authored-by: Piotr P. Karwasz <[email protected]> Co-authored-by: Volkan Yazıcı <[email protected]> --- .../RollingFileManagerCreateOnDemandTest.java | 72 +++++++ .../core/appender/rolling/RollingFileManager.java | 230 ++++++--------------- ...2006_Fix_RollingFileAppender_createOnDemand.xml | 14 ++ 3 files changed, 150 insertions(+), 166 deletions(-) diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingFileManagerCreateOnDemandTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingFileManagerCreateOnDemandTest.java new file mode 100644 index 0000000000..6a2e16f457 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingFileManagerCreateOnDemandTest.java @@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.logging.log4j.core.appender.rolling; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; +import org.apache.logging.log4j.core.config.NullConfiguration; +import org.apache.logging.log4j.core.layout.PatternLayout; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +class RollingFileManagerCreateOnDemandTest { + @Test + void testCreateOnDemandDoesNotCreateDirectoryOrFile(@TempDir Path tempDir) throws Exception { + Path logDir = tempDir.resolve("onDemand"); + String logFile = logDir.resolve("test.log").toString(); + File dir = logDir.toFile(); + File file = new File(logFile); + assertFalse(dir.exists(), "Directory should not exist before logging"); + assertFalse(file.exists(), "File should not exist before logging"); + + RollingFileManager manager = RollingFileManager.getFileManager( + logFile, + logFile + ".%d{yyyy-MM-dd}", + true, + false, + NoOpTriggeringPolicy.INSTANCE, + DefaultRolloverStrategy.newBuilder().build(), + null, + PatternLayout.createDefaultLayout(), + 0, + true, + true, + null, + null, + null, + new NullConfiguration()); + assertNotNull(manager); + // Directory and file should still not exist + assertFalse(dir.exists(), "Directory should not exist after manager creation"); + assertFalse(file.exists(), "File should not exist after manager creation"); + + // Log a message + manager.writeToDestination("Hello Log4j2".getBytes(), 0, "Hello Log4j2".length()); + manager.close(); + + // Now directory and file should exist + assertTrue(dir.exists(), "Directory should exist after first log event"); + assertTrue(file.exists(), "File should exist after first log event"); + String content = new String(Files.readAllBytes(file.toPath())); + assertTrue(content.contains("Hello Log4j2")); + } +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/RollingFileManager.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/RollingFileManager.java index 59db7fb8c2..7ec33c9f73 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/RollingFileManager.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/RollingFileManager.java @@ -42,7 +42,6 @@ import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.appender.ConfigurationFactoryData; import org.apache.logging.log4j.core.appender.FileManager; -import org.apache.logging.log4j.core.appender.ManagerFactory; import org.apache.logging.log4j.core.appender.rolling.action.AbstractAction; import org.apache.logging.log4j.core.appender.rolling.action.Action; import org.apache.logging.log4j.core.config.Configuration; @@ -56,7 +55,6 @@ import org.apache.logging.log4j.core.util.Log4jThreadFactory; */ public class RollingFileManager extends FileManager { - private static RollingFileManagerFactory factory = new RollingFileManagerFactory(); private static final int MAX_TRIES = 3; private static final int MIN_DURATION = 100; private static final FileTime EPOCH = FileTime.fromMillis(0); @@ -296,33 +294,74 @@ public class RollingFileManager extends FileManager { final String fileOwner, final String fileGroup, final Configuration configuration) { - if (strategy instanceof DirectWriteRolloverStrategy && fileName != null) { LOGGER.error("The fileName attribute must not be specified with the DirectWriteRolloverStrategy"); return null; } - final String name = fileName == null ? pattern : fileName; + String actualName = fileName == null ? pattern : fileName; + int actualBufferSize = bufferedIO ? bufferSize : Constants.ENCODER_BYTE_BUFFER_SIZE; return narrow( RollingFileManager.class, getManager( - name, - new FactoryData( - fileName, - pattern, - append, - bufferedIO, - policy, - strategy, - advertiseURI, - layout, - bufferSize, - immediateFlush, - createOnDemand, - filePermissions, - fileOwner, - fileGroup, - configuration), - factory)); + actualName, + (name, data) -> { + long size = 0; + File file = null; + if (fileName != null) { + file = new File(fileName); + + if (!createOnDemand) { + try { + FileUtils.makeParentDirs(file); + final boolean created = file.createNewFile(); + LOGGER.trace("New file '{}' created = {}", name, created); + } catch (final IOException ioe) { + LOGGER.error("Unable to create file {}", name, ioe); + return null; + } + } + + size = append ? file.length() : 0; + } + + try { + final ByteBuffer buffer = ByteBuffer.allocate(actualBufferSize); + final OutputStream os = createOnDemand || fileName == null + ? null + : new FileOutputStream(fileName, append); + // LOG4J2-531 create file first so time has valid value. + final long initialTime = file == null || !file.exists() ? 0 : initialFileTime(file); + final boolean writeHeader = file != null && file.exists() && file.length() == 0; + + final RollingFileManager rm = new RollingFileManager( + data.getLoggerContext(), + fileName, + data.getPattern(), + os, + append, + createOnDemand, + size, + initialTime, + data.getTriggeringPolicy(), + data.getRolloverStrategy(), + advertiseURI, + layout, + filePermissions, + fileOwner, + fileGroup, + writeHeader, + buffer); + if (os != null && rm.isAttributeViewEnabled()) { + rm.defineAttributeView(file.toPath()); + } + + return rm; + } catch (final IOException ex) { + LOGGER.error("RollingFileManager ({}): {}", name, ex.getMessage(), ex); + } + return null; + }, + new FactoryData(pattern, policy, strategy, configuration))); } /** @@ -718,69 +757,28 @@ public class RollingFileManager extends FileManager { /** * Factory data. + * + * <p>This is also used by {@link RollingRandomAccessFileManager}.</p> */ - private static class FactoryData extends ConfigurationFactoryData { - private final String fileName; + static class FactoryData extends ConfigurationFactoryData { private final String pattern; - private final boolean append; - private final boolean bufferedIO; - private final int bufferSize; - private final boolean immediateFlush; - private final boolean createOnDemand; private final TriggeringPolicy policy; private final RolloverStrategy strategy; - private final String advertiseURI; - private final Layout<? extends Serializable> layout; - private final String filePermissions; - private final String fileOwner; - private final String fileGroup; /** * Creates the data for the factory. * @param pattern The pattern. - * @param append The append flag. - * @param bufferedIO The bufferedIO flag. - * @param advertiseURI - * @param layout The Layout. - * @param bufferSize the buffer size - * @param immediateFlush flush on every write or not - * @param createOnDemand true if you want to lazy-create the file (a.k.a. on-demand.) - * @param filePermissions File permissions - * @param fileOwner File owner - * @param fileGroup File group * @param configuration The configuration */ public FactoryData( - final String fileName, final String pattern, - final boolean append, - final boolean bufferedIO, final TriggeringPolicy policy, final RolloverStrategy strategy, - final String advertiseURI, - final Layout<? extends Serializable> layout, - final int bufferSize, - final boolean immediateFlush, - final boolean createOnDemand, - final String filePermissions, - final String fileOwner, - final String fileGroup, final Configuration configuration) { super(configuration); - this.fileName = fileName; this.pattern = pattern; - this.append = append; - this.bufferedIO = bufferedIO; - this.bufferSize = bufferSize; this.policy = policy; this.strategy = strategy; - this.advertiseURI = advertiseURI; - this.layout = layout; - this.immediateFlush = immediateFlush; - this.createOnDemand = createOnDemand; - this.filePermissions = filePermissions; - this.fileOwner = fileOwner; - this.fileGroup = fileGroup; } public TriggeringPolicy getTriggeringPolicy() { @@ -794,34 +792,6 @@ public class RollingFileManager extends FileManager { public String getPattern() { return pattern; } - - @Override - public String toString() { - final StringBuilder builder = new StringBuilder(); - builder.append(super.toString()); - builder.append("[pattern="); - builder.append(pattern); - builder.append(", append="); - builder.append(append); - builder.append(", bufferedIO="); - builder.append(bufferedIO); - builder.append(", bufferSize="); - builder.append(bufferSize); - builder.append(", policy="); - builder.append(policy); - builder.append(", strategy="); - builder.append(strategy); - builder.append(", advertiseURI="); - builder.append(advertiseURI); - builder.append(", layout="); - builder.append(layout); - builder.append(", filePermissions="); - builder.append(filePermissions); - builder.append(", fileOwner="); - builder.append(fileOwner); - builder.append("]"); - return builder.toString(); - } } /** @@ -838,78 +808,6 @@ public class RollingFileManager extends FileManager { setTriggeringPolicy(factoryData.getTriggeringPolicy()); } - /** - * Factory to create a RollingFileManager. - */ - private static class RollingFileManagerFactory implements ManagerFactory<RollingFileManager, FactoryData> { - - /** - * Creates a RollingFileManager. - * @param name The name of the entity to manage. - * @param data The data required to create the entity. - * @return a RollingFileManager. - */ - @Override - @SuppressFBWarnings( - value = {"PATH_TRAVERSAL_IN", "PATH_TRAVERSAL_OUT"}, - justification = "The destination file should be specified in the configuration file.") - public RollingFileManager createManager(final String name, final FactoryData data) { - long size = 0; - File file = null; - if (data.fileName != null) { - file = new File(data.fileName); - - try { - FileUtils.makeParentDirs(file); - final boolean created = data.createOnDemand ? false : file.createNewFile(); - LOGGER.trace("New file '{}' created = {}", name, created); - } catch (final IOException ioe) { - LOGGER.error("Unable to create file " + name, ioe); - return null; - } - size = data.append ? file.length() : 0; - } - - try { - final int actualSize = data.bufferedIO ? data.bufferSize : Constants.ENCODER_BYTE_BUFFER_SIZE; - final ByteBuffer buffer = ByteBuffer.wrap(new byte[actualSize]); - final OutputStream os = data.createOnDemand || data.fileName == null - ? null - : new FileOutputStream(data.fileName, data.append); - // LOG4J2-531 create file first so time has valid value. - final long initialTime = file == null || !file.exists() ? 0 : initialFileTime(file); - final boolean writeHeader = file != null && file.exists() && file.length() == 0; - - final RollingFileManager rm = new RollingFileManager( - data.getLoggerContext(), - data.fileName, - data.pattern, - os, - data.append, - data.createOnDemand, - size, - initialTime, - data.policy, - data.strategy, - data.advertiseURI, - data.layout, - data.filePermissions, - data.fileOwner, - data.fileGroup, - writeHeader, - buffer); - if (os != null && rm.isAttributeViewEnabled()) { - rm.defineAttributeView(file.toPath()); - } - - return rm; - } catch (final IOException ex) { - LOGGER.error("RollingFileManager (" + name + ") " + ex, ex); - } - return null; - } - } - static long initialFileTime(final File file) { final Path path = file.toPath(); if (Files.exists(path)) { diff --git a/src/changelog/2.25.5/LOG4J2-2006_Fix_RollingFileAppender_createOnDemand.xml b/src/changelog/2.25.5/LOG4J2-2006_Fix_RollingFileAppender_createOnDemand.xml new file mode 100644 index 0000000000..33d6f9e9fe --- /dev/null +++ b/src/changelog/2.25.5/LOG4J2-2006_Fix_RollingFileAppender_createOnDemand.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<entry xmlns="https://logging.apache.org/xml/ns" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation=" + https://logging.apache.org/xml/ns + https://logging.apache.org/xml/ns/log4j-changelog-0.xsd" + type="fixed"> + <issue id="2006" link="https://github.com/apache/logging-log4j2/issues/2006"/> + <issue id="4072" link="https://github.com/apache/logging-log4j2/pull/4072"/> + <description format="asciidoc"> + Fix the `createOnDemand` behavior of `RollingFileAppender` to correctly defer file and directory creation + until the first log event, while preserving eager creation when disabled. + </description> +</entry> \ No newline at end of file
