From 0138b5924ff14ef709398bf878dbb9bf2a0b7c33 Mon Sep 17 00:00:00 2001
From: Valery Kharseko <vharseko@3a-systems.ru>
Date: Wed, 23 Sep 2026 13:05:17 +0000
Subject: [PATCH] [#1030] Keep the setup log out of the way of start-ds, and say when it is gone (#1032)

---
 opendj-server-legacy/src/main/java/org/opends/quicksetup/TempLogFile.java |  102 +++++++++++++++++++++++++++++++++++++++++++++++++--
 1 files changed, 98 insertions(+), 4 deletions(-)

diff --git a/opendj-server-legacy/src/main/java/org/opends/quicksetup/TempLogFile.java b/opendj-server-legacy/src/main/java/org/opends/quicksetup/TempLogFile.java
index ccb1c9e..6f193b8 100644
--- a/opendj-server-legacy/src/main/java/org/opends/quicksetup/TempLogFile.java
+++ b/opendj-server-legacy/src/main/java/org/opends/quicksetup/TempLogFile.java
@@ -20,6 +20,7 @@
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.nio.charset.Charset;
 import java.nio.file.Files;
 import java.util.Date;
 import java.text.DateFormat;
@@ -51,9 +52,51 @@
    */
   public static TempLogFile newTempLogFile(final String prefix)
   {
+    return newTempLogFile(prefix, null);
+  }
+
+  /**
+   * Creates a new temporary log file in the given directory.
+   * <p>
+   * The directory is created if it does not exist yet. When it is {@code null} or cannot be
+   * used, the log file goes to the OS temporary directory instead, as with
+   * {@link #newTempLogFile(String)}. The name of the file follows the pattern
+   * prefix-[RANDOM_NUMBER_STRING].log either way.
+   *
+   * @param prefix
+   *          log file prefix to which log messages will be written.
+   * @param directory
+   *          the directory to create the log file in, or {@code null} for the OS temporary
+   *          directory.
+   * @return a new temporary log file.
+   */
+  public static TempLogFile newTempLogFile(final String prefix, final File directory)
+  {
+    IOException fallbackReason = null;
+    if (directory != null)
+    {
+      try
+      {
+        Files.createDirectories(directory.toPath());
+        return new TempLogFile(Files.createTempFile(directory.toPath(), prefix, ".log").toFile());
+      }
+      catch (final IOException e)
+      {
+        // Nothing can be logged yet: the first publisher is the one the constructor installs
+        // below, so the warning has to wait until there is a log to write it to.
+        fallbackReason = e;
+      }
+    }
     try
     {
-      return new TempLogFile(Files.createTempFile(prefix, ".log").toFile());
+      final TempLogFile tempLogFile = new TempLogFile(Files.createTempFile(prefix, ".log").toFile());
+      if (fallbackReason != null)
+      {
+        localizedLogger.warn(LocalizableMessage.raw("Unable to create temp log file in " + directory
+            + " because: " + fallbackReason.getMessage() + ", falling back to the temporary directory"),
+            fallbackReason);
+      }
+      return tempLogFile;
     }
     catch (final IOException e)
     {
@@ -68,9 +111,14 @@
   {
     this.logFile = null;
     this.writer=null;
+    this.startupErrorLogPublisher = null;
+    this.startupDebugLogPublisher = null;
   }
 
   final TextWriter writer;
+  /** Kept so that they can be taken off the logger singletons again, see {@link #deleteLogFileAfterSuccess()}. */
+  private final ErrorLogPublisher startupErrorLogPublisher;
+  private final DebugLogPublisher startupDebugLogPublisher;
   
   private TempLogFile(final File file) throws IOException
   {
@@ -83,9 +131,9 @@
     }else {
     	writer=new TextWriter.STREAM(new FileOutputStream(file));
     }
-    ErrorLogPublisher startupErrorLogPublisher = TextErrorLogPublisher.getServerStartupTextErrorPublisher(writer);
+    startupErrorLogPublisher = TextErrorLogPublisher.getServerStartupTextErrorPublisher(writer);
     ErrorLogger.getInstance().addLogPublisher(startupErrorLogPublisher);
-    DebugLogger.getInstance().addPublisherIfRequired(writer);
+    startupDebugLogPublisher = DebugLogger.getInstance().addPublisherIfRequired(writer);
 
     localizedLogger.info(LocalizableMessage.raw("QuickSetup application launched " + DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG).format(new Date()), null));
   }
@@ -100,11 +148,23 @@
     return logFile;
   }
 
-  /** Closes the log file handler and delete the temp log file . */
+  /**
+   * Closes the log file handler and delete the temp log file .
+   * <p>
+   * The publishers installed by the constructor go with it: they are held by the logger
+   * singletons, which outlive this object, and once the writer is shut everything they are
+   * handed is written to a closed stream and swallowed.
+   */
   public void deleteLogFileAfterSuccess()
   {
     if (isEnabled())
     {
+      if (startupErrorLogPublisher != null) {
+        ErrorLogger.getInstance().removeLogPublisher(startupErrorLogPublisher);
+      }
+      if (startupDebugLogPublisher != null) {
+        DebugLogger.getInstance().removeLogPublisher(startupDebugLogPublisher);
+      }
     	if (writer!=null) {
     		writer.shutdown();
     	}
@@ -122,6 +182,40 @@
   }
 
   /**
+   * Return {@code true} if the temp log file is still on disk and can be read.
+   * <p>
+   * Unlike {@link #isEnabled()} this is about the file, not the logger: something else may have
+   * removed the file while the logger still writes to it (see issue #1030), and then there is
+   * nothing to hand over to whoever needs the log.
+   *
+   * @return {@code true} if the temp log file is there and readable.
+   */
+  public boolean isReadable()
+  {
+    return logFile != null && Files.isReadable(logFile.toPath()) && Files.isRegularFile(logFile.toPath());
+  }
+
+  /**
+   * Reads the whole temp log file.
+   * <p>
+   * The file is decoded with the default charset of the JVM, which is the one
+   * {@link TextWriter.STREAM} wrote it with: reader and writer are the same JVM, so a
+   * non-ASCII path or base DN in a report comes back as it was logged.
+   *
+   * @return the contents of the temp log file.
+   * @throws IOException
+   *           if the file cannot be read, for instance because it is no longer there.
+   */
+  public String readContents() throws IOException
+  {
+    if (logFile == null)
+    {
+      throw new IOException("No temp log file");
+    }
+    return new String(Files.readAllBytes(logFile.toPath()), Charset.defaultCharset());
+  }
+
+  /**
    * Return the absolute path of the temp log file.
    * @return the absolute path of the temp log file.
    */

--
Gitblit v1.10.0