diff --git a/nucleus/glassfish-jul-extension/src/main/java/org/glassfish/main/jul/rotation/LogFileManager.java b/nucleus/glassfish-jul-extension/src/main/java/org/glassfish/main/jul/rotation/LogFileManager.java index c57c5fbdcd1..e7ba66de20c 100644 --- a/nucleus/glassfish-jul-extension/src/main/java/org/glassfish/main/jul/rotation/LogFileManager.java +++ b/nucleus/glassfish-jul-extension/src/main/java/org/glassfish/main/jul/rotation/LogFileManager.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2024 Eclipse Foundation and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2025 Eclipse Foundation and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -13,7 +13,6 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 */ - package org.glassfish.main.jul.rotation; import java.io.BufferedOutputStream; @@ -26,6 +25,10 @@ import java.nio.file.StandardCopyOption; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; import java.util.function.Supplier; @@ -35,7 +38,6 @@ import static java.lang.System.Logger.Level.INFO; import static org.glassfish.main.jul.tracing.GlassFishLoggingTracer.trace; - /** * Manages the logging file, it's rotations, packing of rolled log file, etc. *

@@ -49,6 +51,7 @@ * @author David Matejcek */ public class LogFileManager { + private static final Logger LOG = System.getLogger(LogFileManager.class.getName()); private static final DateTimeFormatter SUFFIX_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH-mm-ss"); @@ -61,6 +64,13 @@ public class LogFileManager { private MeteredFileWriter writer; + private final ExecutorService sequentialExecutor = createSequentialExecutor(); + + private static ExecutorService createSequentialExecutor() { + final ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 1, 100, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>()); + executor.allowCoreThreadTimeOut(true); + return executor; + } /** * Creates the manager and initializes it with given parameters. It only creates the manager but @@ -76,7 +86,7 @@ public class LogFileManager { * crosses this value, old files will be permanently deleted. */ public LogFileManager(final File logFile, Charset fileEncoding, // - final long maxFileSize, final boolean compressOldLogFiles, final int maxCountOfOldLogFiles // + final long maxFileSize, final boolean compressOldLogFiles, final int maxCountOfOldLogFiles // ) { this.logFile = logFile; this.fileEncoding = fileEncoding; @@ -84,7 +94,6 @@ public LogFileManager(final File logFile, Charset fileEncoding, // this.archiver = new LogFileArchiver(logFile, compressOldLogFiles, maxCountOfOldLogFiles); } - /** * Writes the text to the log file. * @@ -107,7 +116,6 @@ public void write(String text) throws IllegalStateException { } } - /** * Flushed the file writer and if the file is too large, rolls the file. */ @@ -127,7 +135,6 @@ public void flush() { } } - /** * @return the size of the logFile in bytes. The value is obtained from the outputstream, only * if the output stream is closed, this method will check the file system. @@ -141,7 +148,6 @@ public long getFileSize() { } } - /** * Calls {@link #roll()} if the file is bigger than limit given in constructor. */ @@ -156,7 +162,6 @@ public void rollIfFileTooBig() { } } - /** * Calls {@link #roll()} if the file is not empty. */ @@ -171,7 +176,6 @@ public void rollIfFileNotEmpty() { } } - /** * Rolls the file regardless of it's size and if it is currently used for output. *

@@ -211,7 +215,6 @@ public void roll() { } } - /** * @return true if the handler owning this instance can write to the outputstream. */ @@ -224,7 +227,6 @@ public boolean isOutputEnabled() { } } - /** * Creates the file, initializes the MeteredStream and calls the stream setter given in * constructor. @@ -258,7 +260,6 @@ public void enableOutput() { } } - /** * Calls the close method given in constructor, then closes the output stream. *

@@ -283,7 +284,6 @@ public void disableOutput() { } } - private boolean isRollFileSizeLimitReached() { if (this.maxFileSize <= 0) { return false; @@ -292,7 +292,6 @@ private boolean isRollFileSizeLimitReached() { return fileSize >= this.maxFileSize; } - private File prepareAchivedLogFileTarget() { final String archivedFileNameBase = logFile.getName() + "_" + SUFFIX_FORMATTER.format(LocalDateTime.now()); int counter = 1; @@ -309,7 +308,6 @@ private File prepareAchivedLogFileTarget() { } } - /** * Make sure that server.log contents are flushed out to start from a clean file again after * the rename... @@ -321,7 +319,6 @@ private void forceOSFilesync(final File file) throws IOException { new FileOutputStream(file).close(); } - private void moveFile(final File logFileToArchive, final File target) throws IOException { logInfoAsync(() -> "Archiving file " + logFileToArchive + " to " + target); try { @@ -332,13 +329,12 @@ private void moveFile(final File logFileToArchive, final File target) throws IOE // copy bytes explicitly to a renamed file. // Can happen on some windows file systems - then we try non-atomic version at least. logErrorAsync(String.format( - "File %s could not be renamed to %s atomically, now trying to move it without this request.", - logFileToArchive, target), e); + "File %s could not be renamed to %s atomically, now trying to move it without this request.", + logFileToArchive, target), e); Files.move(logFileToArchive.toPath(), target.toPath()); } } - /** * This logs in a separate thread to avoid deadlocks. The separate thread can be blocked when * the LogRecordBuffer is full while the LogFileManager is still locked and doesn't process @@ -348,10 +344,9 @@ private void moveFile(final File logFileToArchive, final File target) throws IOE */ private void logInfoAsync(final Supplier message) { trace(getClass(), message); - new Thread(() -> LOG.log(INFO, message), "LogFileManager-Async-Info-Logger").start(); + sequentialExecutor.submit(()-> LOG.log(INFO, message), "LogFileManager-Async-Info-Logger"); } - /** * This logs in a separate thread to avoid deadlocks. The separate thread can be blocked when * the LogRecordBuffer is full while the LogFileManager is still locked and doesn't process @@ -364,6 +359,6 @@ private void logInfoAsync(final Supplier message) { */ private void logErrorAsync(final String message, final Exception exception) { GlassFishLoggingTracer.error(getClass(), message, exception); - new Thread(() -> LOG.log(ERROR, message, exception), "LogFileManager-Async-Error-Logger").start(); + sequentialExecutor.submit(() -> LOG.log(ERROR, message, exception), "LogFileManager-Async-Error-Logger"); } } diff --git a/nucleus/glassfish-jul-extension/src/test/java/org/glassfish/main/jul/GlassFishLogManagerLifeCycleTest.java b/nucleus/glassfish-jul-extension/src/test/java/org/glassfish/main/jul/GlassFishLogManagerLifeCycleTest.java index 285682e4c7f..7a0bc596524 100644 --- a/nucleus/glassfish-jul-extension/src/test/java/org/glassfish/main/jul/GlassFishLogManagerLifeCycleTest.java +++ b/nucleus/glassfish-jul-extension/src/test/java/org/glassfish/main/jul/GlassFishLogManagerLifeCycleTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2024 Eclipse Foundation and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2025 Eclipse Foundation and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -18,6 +18,8 @@ import java.util.List; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; @@ -71,6 +73,7 @@ public class GlassFishLogManagerLifeCycleTest { private static GlassFishLogManagerConfiguration originalCfg; private static boolean originalLevelResolution; private static CompletableFuture process; + private ExecutorService sequentialExecutor = Executors.newSingleThreadExecutor(); @BeforeAll public static void backupConfiguration() { @@ -133,6 +136,7 @@ public void startReconfiguration() throws Exception { () -> assertEquals(GlassFishLoggingStatus.CONFIGURING, MANAGER.getLoggingStatus()), () -> assertNull(COLLECTOR.pop(), "COLLECTOR should be empty after reconfiguration started")); doLog(Level.INFO, "Log after reconfiguration started", 0); + Thread.sleep(10L); assertNull(COLLECTOR.pop(), "COLLECTOR should be empty after reconfiguration started even if we log again"); } @@ -269,7 +273,7 @@ private void reconfigureToBlockingHandler() { * @throws Exception */ private void doLog(final Level level, final String message, final int sleepMillis) throws Exception { - new Thread(() -> LOG.log(level, message)).start(); + sequentialExecutor.submit(() -> LOG.log(level, message)); if (sleepMillis > 0) { Thread.sleep(sleepMillis); } diff --git a/nucleus/glassfish-jul-extension/src/test/java/org/glassfish/main/jul/handler/GlassFishLogHandlerTest.java b/nucleus/glassfish-jul-extension/src/test/java/org/glassfish/main/jul/handler/GlassFishLogHandlerTest.java index 2cf9d01ef8d..37dedc8a3e4 100644 --- a/nucleus/glassfish-jul-extension/src/test/java/org/glassfish/main/jul/handler/GlassFishLogHandlerTest.java +++ b/nucleus/glassfish-jul-extension/src/test/java/org/glassfish/main/jul/handler/GlassFishLogHandlerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2024 Contributors to the Eclipse Foundation + * Copyright (c) 2022, 2025 Contributors to the Eclipse Foundation * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -18,6 +18,7 @@ import java.io.File; import java.nio.file.Files; +import java.util.List; import java.util.logging.Level; import java.util.logging.LogManager; import java.util.logging.Logger; @@ -134,12 +135,14 @@ public void enableStandardStreamsLoggers(TestInfo testInfo) throws Exception { public void roll() throws Exception { assertTrue(handler.isReady(), "handler.ready"); handler.publish(new GlassFishLogRecord(Level.SEVERE, "File one, record one", false)); + final File logFile = handler.getConfiguration().getLogFile(); // pump is now to play Thread.sleep(MILLIS_FOR_PUMP); + final List fileLines1 = Files.readAllLines(logFile.toPath()); assertAll( () -> assertTrue(handler.isReady(), "handler.ready"), - () -> assertTrue(handler.getConfiguration().getLogFile().exists(), "file one exists"), - () -> assertThat("file content", Files.readAllLines(handler.getConfiguration().getLogFile().toPath()), + () -> assertTrue(logFile.exists(), "file one exists"), + () -> assertThat("file content, filename=" + logFile + ", content=" + fileLines1 , fileLines1, contains( stringContainsInOrder("INFO", "main", "Tommy, can you hear me?"), stringContainsInOrder("INFO", "main", "Some info message"), @@ -152,10 +155,11 @@ public void roll() throws Exception { handler.roll(); // There will be an asynchronous thread. Thread.sleep(10L); + final List fileLines2 = Files.readAllLines(logFile.toPath()); assertAll( () -> assertTrue(handler.isReady(), "handler.ready"), - () -> assertTrue(handler.getConfiguration().getLogFile().exists(), "file exists"), - () -> assertThat("file content", Files.readAllLines(handler.getConfiguration().getLogFile().toPath()), + () -> assertTrue(logFile.exists(), "file exists"), + () -> assertThat("file content, filename=" + logFile + ", content=" + fileLines2 , fileLines2, contains( stringContainsInOrder("INFO", "Rolling the file ", ".log", "output was originally enabled: true"), stringContainsInOrder("INFO", "Archiving file ", ".log", " to ", ".log_") @@ -164,10 +168,11 @@ public void roll() throws Exception { ); handler.publish(new GlassFishLogRecord(Level.SEVERE, "File two, line two", false)); Thread.sleep(MILLIS_FOR_PUMP); + final List fileLines3 = Files.readAllLines(logFile.toPath()); assertAll( () -> assertTrue(handler.isReady(), "handler.ready"), - () -> assertTrue(handler.getConfiguration().getLogFile().exists(), "file exists"), - () -> assertThat("file content", Files.readAllLines(handler.getConfiguration().getLogFile().toPath()), + () -> assertTrue(logFile.exists(), "file exists"), + () -> assertThat("file content, filename=" + logFile + ", content=" + fileLines3 , fileLines3, contains( stringContainsInOrder("INFO", "Rolling the file ", ".log", "output was originally enabled: true"), stringContainsInOrder("INFO", "Archiving file ", ".log", " to ", ".log_"), @@ -177,7 +182,6 @@ public void roll() throws Exception { ); } - @Test @Order(50) public void disabledlogStandardStreams() throws Exception {