From ccd189c61f4f2fa30a3bef3bed05f6e12fbb17ff Mon Sep 17 00:00:00 2001 From: "Neil A. Wilson" Date: Thu, 23 Sep 2021 18:22:39 -0500 Subject: [PATCH] Add a method for debugging to a file Added debug methods that can be useful for appending messages to a specified file, optionally including timestamp and/or stack trace. These methods are safe to call concurrently, even across multiple processes. --- messages/unboundid-ldapsdk-util.properties | 2 + src/com/unboundid/util/Debug.java | 113 ++++++++++++++++++ .../sdk/ProhibitedMethodCallsTestCase.java | 2 +- .../src/com/unboundid/util/DebugTestCase.java | 54 +++++++++ 4 files changed, 170 insertions(+), 1 deletion(-) diff --git a/messages/unboundid-ldapsdk-util.properties b/messages/unboundid-ldapsdk-util.properties index 0590bf2fd..e3da40545 100644 --- a/messages/unboundid-ldapsdk-util.properties +++ b/messages/unboundid-ldapsdk-util.properties @@ -1216,3 +1216,5 @@ ERR_BC_FIPS_HELPER_CANNOT_LOAD_JSSE_PROVIDER_CLASS=Unable to load the Bouncy \ ERR_BC_FIPS_PROVIDER_CANNOT_INSTANTIATE_JSSE_PROVIDER=An unexpected error \ occurred while attempting to create an instance of the Bouncy Castle JSSE \ provider class ''{0}'': {1} +ERR_DEBUG_CANNOT_WRITE_TO_FILE=An unexpected error occurred while attempting \ + to write a debug message to file ''{0}'': {1}. The debug message was: {2} diff --git a/src/com/unboundid/util/Debug.java b/src/com/unboundid/util/Debug.java index e6877903e..094329c87 100644 --- a/src/com/unboundid/util/Debug.java +++ b/src/com/unboundid/util/Debug.java @@ -37,7 +37,12 @@ +import java.io.File; import java.io.Serializable; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; +import java.nio.file.StandardOpenOption; import java.text.SimpleDateFormat; import java.util.Date; import java.util.EnumSet; @@ -61,6 +66,8 @@ import com.unboundid.ldif.LDIFRecord; import com.unboundid.util.json.JSONBuffer; +import static com.unboundid.util.UtilityMessages.*; + /** @@ -1702,4 +1709,110 @@ private static void log(@NotNull final Level level, { logger.log(level, buffer.toString(), thrown); } + + + + /** + * Appends the provided debug message to the specified file. This method + * should be safe to call concurrently, even across multiple processes. + * + * @param path The path to the file to which the message should be + * appended. It must not be {@code null}. + * @param message The debug message to be appended to the file. It must not + * be {@code null}. + */ + public static void debugToFile(@NotNull final String path, + @NotNull final String message) + { + debugToFile(new File(path), true, true, message); + } + + + + /** + * Appends the provided debug message to the specified file. This method + * should be safe to call concurrently, even across multiple processes. + * + * @param file The file to which the message should be appended. It must + * not be {@code null}. + * @param message The debug message to be appended to the file. It must not + * be {@code null}. + */ + public static void debugToFile(@NotNull final File file, + @NotNull final String message) + { + debugToFile(file, true, true, message); + } + + + + /** + * Appends the provided debug message to the specified file. This method + * should be safe to call concurrently, even across multiple processes. + * + * @param file The file to which the message should be + * appended. It must not be {@code null}. + * @param includeTimestamp Indicates whether to include a timestamp along + * with the debug message. + * @param includeStackTrace Indicates whether to include a stack trace along + * with the debug message. + * @param message The debug message to be appended to the file. + * It must not be {@code null}. + */ + public static synchronized void debugToFile(@NotNull final File file, + final boolean includeTimestamp, + final boolean includeStackTrace, + @NotNull final String message) + { + try + { + try (FileChannel fileChannel = FileChannel.open( + file.toPath(), + StaticUtils.setOf( + StandardOpenOption.WRITE, + StandardOpenOption.CREATE, + StandardOpenOption.APPEND, + StandardOpenOption.SYNC))) + { + try (FileLock fileLock = fileChannel.lock()) + { + final ByteStringBuffer messageBuffer = new ByteStringBuffer(); + + if (fileChannel.size() > 0L) + { + messageBuffer.append(StaticUtils.EOL_BYTES); + } + + if (includeTimestamp) + { + messageBuffer.append( + StaticUtils.encodeRFC3339Time(System.currentTimeMillis())); + messageBuffer.append(StaticUtils.EOL_BYTES); + } + + messageBuffer.append(message); + messageBuffer.append(StaticUtils.EOL_BYTES); + + if (includeStackTrace) + { + messageBuffer.append(StaticUtils.getStackTrace( + Thread.currentThread().getStackTrace())); + messageBuffer.append(StaticUtils.EOL_BYTES); + } + + fileChannel.write(ByteBuffer.wrap( + messageBuffer.getBackingArray(), 0, messageBuffer.length())); + } + } + } + catch (final Exception e) + { + // An error occurred while attempting to write to the file. As a + // fallback, print a message about it to standard error. + Debug.debugException(e); + System.err.println(ERR_DEBUG_CANNOT_WRITE_TO_FILE.get( + file.getAbsolutePath(), StaticUtils.getExceptionMessage(e), + message)); + } + } } diff --git a/tests/unit/src/com/unboundid/ldap/sdk/ProhibitedMethodCallsTestCase.java b/tests/unit/src/com/unboundid/ldap/sdk/ProhibitedMethodCallsTestCase.java index 57d6b5a79..c63a745f0 100644 --- a/tests/unit/src/com/unboundid/ldap/sdk/ProhibitedMethodCallsTestCase.java +++ b/tests/unit/src/com/unboundid/ldap/sdk/ProhibitedMethodCallsTestCase.java @@ -298,7 +298,7 @@ public void testLoggerLogLevel(final File f) throws Exception { final Map> allowedExceptions = StaticUtils.mapOf( - "Debug.java", StaticUtils.setOf(92), + "Debug.java", StaticUtils.setOf(99), "StaticUtils.java", StaticUtils.setOf(563, 586)); final Map unwrappedLines = diff --git a/tests/unit/src/com/unboundid/util/DebugTestCase.java b/tests/unit/src/com/unboundid/util/DebugTestCase.java index b7f8ca627..9094abd82 100644 --- a/tests/unit/src/com/unboundid/util/DebugTestCase.java +++ b/tests/unit/src/com/unboundid/util/DebugTestCase.java @@ -38,10 +38,14 @@ import java.io.ByteArrayInputStream; +import java.io.File; +import java.util.ArrayList; import java.util.EnumSet; import java.util.HashSet; +import java.util.List; import java.util.Properties; import java.util.Set; +import java.util.UUID; import java.util.logging.Level; import java.util.logging.Logger; @@ -4823,4 +4827,54 @@ private static Set getNames(final String... baseNames) return nameSet; } + + + + /** + * Tests the behavior of the methods that can be used to write debug + * messages to a specified file. + * + * @throws Exception If an unexpected problem occurs. + */ + @Test() + public void testDebugToFile() + throws Exception + { + final List messages = new ArrayList<>(); + + final File debugFile = createTempFile(); + assertTrue(debugFile.delete()); + + String uuid = UUID.randomUUID().toString(); + messages.add(uuid); + Debug.debugToFile(debugFile.getAbsolutePath(), uuid); + + uuid = UUID.randomUUID().toString(); + messages.add(uuid); + Debug.debugToFile(debugFile, uuid); + + uuid = UUID.randomUUID().toString(); + messages.add(uuid); + Debug.debugToFile(debugFile, true, true, uuid); + + uuid = UUID.randomUUID().toString(); + messages.add(uuid); + Debug.debugToFile(debugFile, false, true, uuid); + + uuid = UUID.randomUUID().toString(); + messages.add(uuid); + Debug.debugToFile(debugFile, true, false, uuid); + + uuid = UUID.randomUUID().toString(); + messages.add(uuid); + Debug.debugToFile(debugFile, false, false, uuid); + + final List lines = StaticUtils.readFileLines(debugFile); + for (final String message : messages) + { + assertTrue(lines.contains(message)); + } + + assertTrue(lines.contains("")); + } }