All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.unboundid.util.Debug Maven / Gradle / Ivy

/*
 * Copyright 2008-2024 Ping Identity Corporation
 * All Rights Reserved.
 */
/*
 * Copyright 2008-2024 Ping Identity Corporation
 *
 * Licensed 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.
 */
/*
 * Copyright (C) 2008-2024 Ping Identity Corporation
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License (GPLv2 only)
 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see .
 */
package com.unboundid.util;



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;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.logging.FileHandler;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.unboundid.asn1.ASN1Buffer;
import com.unboundid.asn1.ASN1Element;
import com.unboundid.ldap.protocol.LDAPResponse;
import com.unboundid.ldap.sdk.AbstractConnectionPool;
import com.unboundid.ldap.sdk.DisconnectType;
import com.unboundid.ldap.sdk.Entry;
import com.unboundid.ldap.sdk.InternalSDKHelper;
import com.unboundid.ldap.sdk.LDAPConnection;
import com.unboundid.ldap.sdk.LDAPRequest;
import com.unboundid.ldap.sdk.Version;
import com.unboundid.ldif.LDIFRecord;
import com.unboundid.util.json.JSONBuffer;

import static com.unboundid.util.UtilityMessages.*;



/**
 * This class provides a means of enabling and configuring debugging in the LDAP
 * SDK.
 * 

* Access to debug information can be enabled through applications that use the * SDK by calling the {@link Debug#setEnabled} methods, or it can also be * enabled without any code changes through the use of system properties. In * particular, the {@link Debug#PROPERTY_DEBUG_ENABLED}, * {@link Debug#PROPERTY_DEBUG_LEVEL}, and {@link Debug#PROPERTY_DEBUG_TYPE} * properties may be used to control debugging without the need to alter any * code within the application that uses the SDK. *

* The LDAP SDK debugging subsystem uses the Java logging framework available * through the {@code java.util.logging} package with a logger name of * "{@code com.unboundid.ldap.sdk}". The {@link Debug#getLogger} method may * be used to access the logger instance used by the LDAP SDK. *

*

Example

* The following example demonstrates the process that may be used to enable * debugging within the LDAP SDK and write information about all messages with * a {@code WARNING} level or higher to a specified file: *
 * Debug.setEnabled(true);
 * Logger logger = Debug.getLogger();
 *
 * FileHandler fileHandler = new FileHandler(logFilePath);
 * fileHandler.setLevel(Level.WARNING);
 * logger.addHandler(fileHandler);
 * 
*/ @ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) public final class Debug implements Serializable { /** * The name of the system property that will be used to enable debugging in * the UnboundID LDAP SDK for Java. The fully-qualified name for this * property is "{@code com.unboundid.ldap.sdk.debug.enabled}". If it is set, * then it should have a value of either "true" or "false". */ @NotNull public static final String PROPERTY_DEBUG_ENABLED = "com.unboundid.ldap.sdk.debug.enabled"; /** * The name of the system property that may be used to indicate whether stack * trace information for the thread calling the debug method should be * included in debug log messages. The fully-qualified name for this property * is "{@code com.unboundid.ldap.sdk.debug.includeStackTrace}". If it is set, * then it should have a value of either "true" or "false". */ @NotNull public static final String PROPERTY_INCLUDE_STACK_TRACE = "com.unboundid.ldap.sdk.debug.includeStackTrace"; /** * The name of the system property that will be used to set the initial level * for the debug logger. The fully-qualified name for this property is * "{@code com.unboundid.ldap.sdk.debug.level}". If it is set, then it should * be one of the strings "{@code SEVERE}", "{@code WARNING}", "{@code INFO}", * "{@code CONFIG}", "{@code FINE}", "{@code FINER}", or "{@code FINEST}". */ @NotNull public static final String PROPERTY_DEBUG_LEVEL = "com.unboundid.ldap.sdk.debug.level"; /** * The name of the system property that will be used to indicate that * debugging should be enabled for specific types of messages. The * fully-qualified name for this property is * "{@code com.unboundid.ldap.sdk.debug.type}". If it is set, then it should * be a comma-delimited list of the names of the desired debug types. See the * {@link DebugType} enum for the available debug types. */ @NotNull public static final String PROPERTY_DEBUG_TYPE = "com.unboundid.ldap.sdk.debug.type"; /** * The name of the system property that will be used to indicate whether the * LDAP SDK should default to including information about the exception's * cause in an exception message obtained from the * {@link StaticUtils#getExceptionMessage(Throwable)} method. By default, * the cause will not be included in most messages. */ @NotNull public static final String PROPERTY_INCLUDE_CAUSE_IN_EXCEPTION_MESSAGES = "com.unboundid.ldap.sdk.debug.includeCauseInExceptionMessages"; /** * The name of the system property that will be used to indicate whether the * LDAP SDK should default to including a full stack trace (albeit in * condensed form) in an exception message obtained from the * {@link StaticUtils#getExceptionMessage(Throwable)} method. By default, * stack traces will not be included in most messages. */ @NotNull public static final String PROPERTY_INCLUDE_STACK_TRACE_IN_EXCEPTION_MESSAGES = "com.unboundid.ldap.sdk.debug.includeStackTraceInExceptionMessages"; /** * The name of the system property that will be used ot indicate whether * debug messages (which will be formatted as JSON objects) should be * generated using a multi-line string representation. By default, debug * message will use a single-line string representation. */ @NotNull public static final String PROPERTY_USE_MULTI_LINE_DEBUG_MESSAGES = "com.unboundid.ldap.sdk.debug.useMultiLineDebugMessages"; /** * The name of the system property that will be used to indicate that debug * log messages should be written to the specified file. The fully-qualified * name for this property is "{@code com.unboundid.ldap.sdk.debug.file}". If * it is set, then its value should be a pattern that describes the path to * the log file to create as described in the Javadoc documentation for the * {@code java.util.logging.FileHandler} class. */ @NotNull public static final String PROPERTY_DEBUG_FILE = "com.unboundid.ldap.sdk.debug.file"; /** * The name that will be used for the Java logger that will actually handle * the debug messages if debugging is enabled. */ @NotNull public static final String LOGGER_NAME = "com.unboundid.ldap.sdk"; /** * The logger that will be used to handle the debug messages if debugging is * enabled. */ @NotNull private static final Logger logger = Logger.getLogger(LOGGER_NAME); /** * A set of thread-local formatters that may be used to generate timestamps. */ @NotNull private static final ThreadLocal TIMESTAMP_FORMATTERS = new ThreadLocal<>(); /** * The serial version UID for this serializable class. */ private static final long serialVersionUID = -6079754380415146030L; // Indicates whether any debugging is currently enabled for the SDK. private static boolean debugEnabled; // Indicates whether to capture a thread stack trace whenever a debug message // is logged. private static boolean includeStackTrace; // Indiates whether debug messages should be formatted using multiple lines. private static boolean useMultiLineDebugMessages; // The set of debug types for which debugging is enabled. @NotNull private static EnumSet debugTypes= EnumSet.allOf(DebugType.class); static { initialize(StaticUtils.getSystemProperties(PROPERTY_DEBUG_ENABLED, PROPERTY_DEBUG_LEVEL, PROPERTY_DEBUG_TYPE, PROPERTY_INCLUDE_STACK_TRACE)); final String logFilePropertyValue = StaticUtils.getSystemProperty(PROPERTY_DEBUG_FILE); if (logFilePropertyValue != null) { try { logger.setUseParentHandlers(false); final FileHandler fileHandler = new FileHandler(logFilePropertyValue, true); fileHandler.setFormatter( new MinimalLogFormatter(null, false, false, true)); logger.addHandler(fileHandler); } catch (final Exception e) { throw new RuntimeException(e); } } } /** * Prevent this class from being instantiated. */ private Debug() { // No implementation is required. } /** * Initializes this debugger with the default settings. Debugging will be * disabled, the set of debug types will include all types, and the debug * level will be "ALL". */ public static void initialize() { includeStackTrace = false; useMultiLineDebugMessages = false; debugEnabled = false; debugTypes = EnumSet.allOf(DebugType.class); StaticUtils.setLoggerLevel(logger, Level.ALL); } /** * Initializes this debugger with settings from the provided set of * properties. Any debug setting that isn't configured in the provided * properties will be initialized with its default value. * * @param properties The set of properties to use to initialize this * debugger. */ public static void initialize(@Nullable final Properties properties) { // First, apply the default values for the properties. initialize(); if ((properties == null) || properties.isEmpty()) { // No properties were provided, so we don't need to do anything. return; } final String enabledProp = properties.getProperty(PROPERTY_DEBUG_ENABLED); if ((enabledProp != null) && (! enabledProp.isEmpty())) { if (enabledProp.equalsIgnoreCase("true")) { debugEnabled = true; } else if (enabledProp.equalsIgnoreCase("false")) { debugEnabled = false; } else { throw new IllegalArgumentException("Invalid value '" + enabledProp + "' for property " + PROPERTY_DEBUG_ENABLED + ". The value must be either 'true' or 'false'."); } } final String stackProp = properties.getProperty(PROPERTY_INCLUDE_STACK_TRACE); if ((stackProp != null) && (! stackProp.isEmpty())) { if (stackProp.equalsIgnoreCase("true")) { includeStackTrace = true; } else if (stackProp.equalsIgnoreCase("false")) { includeStackTrace = false; } else { throw new IllegalArgumentException("Invalid value '" + stackProp + "' for property " + PROPERTY_INCLUDE_STACK_TRACE + ". The value must be either 'true' or 'false'."); } } final String multiLineProp = properties.getProperty(PROPERTY_USE_MULTI_LINE_DEBUG_MESSAGES); if ((multiLineProp != null) && (! multiLineProp.isEmpty())) { if (multiLineProp.equalsIgnoreCase("true")) { useMultiLineDebugMessages = true; } else if (multiLineProp.equalsIgnoreCase("false")) { useMultiLineDebugMessages = false; } else { throw new IllegalArgumentException("Invalid value '" + multiLineProp + "' for property " + PROPERTY_USE_MULTI_LINE_DEBUG_MESSAGES + ". The value must be either 'true' or 'false'."); } } final String typesProp = properties.getProperty(PROPERTY_DEBUG_TYPE); if ((typesProp != null) && (! typesProp.isEmpty())) { debugTypes = EnumSet.noneOf(DebugType.class); final StringTokenizer t = new StringTokenizer(typesProp, ", "); while (t.hasMoreTokens()) { final String debugTypeName = t.nextToken(); final DebugType debugType = DebugType.forName(debugTypeName); if (debugType == null) { // Throw a runtime exception to indicate that the debug type is // invalid. throw new IllegalArgumentException("Invalid value '" + debugTypeName + "' for property " + PROPERTY_DEBUG_TYPE + ". Allowed values include: " + DebugType.getTypeNameList() + '.'); } else { debugTypes.add(debugType); } } } final String levelProp = properties.getProperty(PROPERTY_DEBUG_LEVEL); if ((levelProp != null) && (! levelProp.isEmpty())) { try { StaticUtils.setLoggerLevel(logger, parseDebugLogLevel(levelProp)); } catch (final IllegalArgumentException e) { throw new IllegalArgumentException( "Invalid value '" + levelProp + "' for property " + PROPERTY_DEBUG_LEVEL + '.', e); } } } /** * Retrieves the logger that will be used to write the debug messages. * * @return The logger that will be used to write the debug messages. */ @NotNull() public static Logger getLogger() { return logger; } /** * Indicates whether any form of debugging is enabled. * * @return {@code true} if debugging is enabled, or {@code false} if not. */ public static boolean debugEnabled() { return debugEnabled; } /** * Indicates whether debugging is enabled for messages of the specified debug * type. * * @param debugType The debug type for which to make the determination. * * @return {@code true} if debugging is enabled for messages of the specified * debug type, or {@code false} if not. */ public static boolean debugEnabled(@NotNull final DebugType debugType) { return (debugEnabled && debugTypes.contains(debugType)); } /** * Specifies whether debugging should be enabled. If it should be, then it * will be enabled for all debug types. * * @param enabled Specifies whether debugging should be enabled. */ public static void setEnabled(final boolean enabled) { debugTypes = EnumSet.allOf(DebugType.class); debugEnabled = enabled; } /** * Specifies whether debugging should be enabled. If it should be, then it * will be enabled for all debug types in the provided set. * * @param enabled Specifies whether debugging should be enabled. * @param types The set of debug types that should be enabled. It may be * {@code null} or empty to indicate that it should be for * all debug types. */ public static void setEnabled(final boolean enabled, @Nullable final Set types) { if ((types == null) || types.isEmpty()) { debugTypes = EnumSet.allOf(DebugType.class); } else { debugTypes = EnumSet.copyOf(types); } debugEnabled = enabled; } /** * Indicates whether log messages should include a stack trace of the thread * that invoked the debug method. * * @return {@code true} if log messages should include a stack trace of the * thread that invoked the debug method, or {@code false} if not. */ public static boolean includeStackTrace() { return includeStackTrace; } /** * Specifies whether log messages should include a stack trace of the thread * that invoked the debug method. * * @param includeStackTrace Indicates whether log messages should include a * stack trace of the thread that invoked the debug * method. */ public static void setIncludeStackTrace(final boolean includeStackTrace) { Debug.includeStackTrace = includeStackTrace; } /** * Indicates whether debug messages (which will be formatted as JSON objects) * should use a multi-line or single-line string representation. * * @return {@code true} if debug messages should be formatted as multi-line * strings, or {@code false} if debug messages should be formatted as * single-line strings. */ public static boolean useMultiLineDebugMessages() { return useMultiLineDebugMessages; } /** * Specifies whether debug messages (which will be formatted as JSON objects) * should use a multi-line or single-line string representation. * * @param useMultiLineDebugMessages Indicates whether debug messages * should be formatted as multi-line * strings (if {@code true}) or single-line * strings (if {@code false}). */ public static void setUseMultiLineDebugMessages( final boolean useMultiLineDebugMessages) { Debug.useMultiLineDebugMessages = useMultiLineDebugMessages; } /** * Retrieves the set of debug types that will be used if debugging is enabled. * * @return The set of debug types that will be used if debugging is enabled. */ @NotNull() public static EnumSet getDebugTypes() { return debugTypes; } /** * Writes debug information about the provided exception, if appropriate. If * it is to be logged, then it will be sent to the underlying logger using the * {@code WARNING} level. * * @param t The exception for which debug information should be written. */ public static void debugException(@NotNull final Throwable t) { if (debugEnabled && debugTypes.contains(DebugType.EXCEPTION)) { debugException(Level.WARNING, t); } } /** * Writes debug information about the provided exception, if appropriate. * * @param l The log level that should be used for the debug information. * @param t The exception for which debug information should be written. */ public static void debugException(@NotNull final Level l, @NotNull final Throwable t) { if (debugEnabled && debugTypes.contains(DebugType.EXCEPTION)) { final JSONBuffer buffer = new JSONBuffer( null, JSONBuffer.DEFAULT_MAX_BUFFER_SIZE, useMultiLineDebugMessages); addCommonHeader(buffer, l, DebugType.EXCEPTION); addCaughtException(buffer, "caught-exception", t); addCommonFooter(buffer); log(l, buffer, t); } } /** * Writes debug information to indicate that a connection has been * established, if appropriate. If it is to be logged, then it will be sent * to the underlying logger using the {@code INFO} level. * * @param h The address of the server to which the connection was * established. * @param p The port of the server to which the connection was established. */ public static void debugConnect(@NotNull final String h, final int p) { if (debugEnabled && debugTypes.contains(DebugType.CONNECT)) { debugConnect(Level.INFO, h, p, null); } } /** * Writes debug information to indicate that a connection has been * established, if appropriate. * * @param l The log level that should be used for the debug information. * @param h The address of the server to which the connection was * established. * @param p The port of the server to which the connection was established. */ public static void debugConnect(@NotNull final Level l, @NotNull final String h, final int p) { if (debugEnabled && debugTypes.contains(DebugType.CONNECT)) { debugConnect(l, h, p, null); } } /** * Writes debug information to indicate that a connection has been * established, if appropriate. If it is to be logged, then it will be sent * to the underlying logger using the {@code INFO} level. * * @param h The address of the server to which the connection was * established. * @param p The port of the server to which the connection was established. * @param c The connection object for the connection that has been * established. It may be {@code null} for historic reasons, but * should be non-{@code null} in new uses. */ public static void debugConnect(@NotNull final String h, final int p, @Nullable final LDAPConnection c) { if (debugEnabled && debugTypes.contains(DebugType.CONNECT)) { debugConnect(Level.INFO, h, p, c); } } /** * Writes debug information to indicate that a connection has been * established, if appropriate. * * @param l The log level that should be used for the debug information. * @param h The address of the server to which the connection was * established. * @param p The port of the server to which the connection was established. * @param c The connection object for the connection that has been * established. It may be {@code null} for historic reasons, but * should be non-{@code null} in new uses. */ public static void debugConnect(@NotNull final Level l, @NotNull final String h, final int p, @Nullable final LDAPConnection c) { if (debugEnabled && debugTypes.contains(DebugType.CONNECT)) { final JSONBuffer buffer = new JSONBuffer( null, JSONBuffer.DEFAULT_MAX_BUFFER_SIZE, useMultiLineDebugMessages); addCommonHeader(buffer, l, DebugType.CONNECT); buffer.appendString("connected-to-address", h); buffer.appendNumber("connected-to-port", p); if (c != null) { buffer.appendNumber("connection-id", c.getConnectionID()); final String connectionName = c.getConnectionName(); if (connectionName != null) { buffer.appendString("connection-name", connectionName); } final String connectionPoolName = c.getConnectionPoolName(); if (connectionPoolName != null) { buffer.appendString("connection-pool-name", connectionPoolName); } } addCommonFooter(buffer); log(l, buffer); } } /** * Writes debug information to indicate that a connection has been * terminated, if appropriate. If it is to be logged, then it will be sent * to the underlying logger using the {@code INFO} level. * * @param h The address of the server to which the connection was * established. * @param p The port of the server to which the connection was established. * @param t The disconnect type. * @param m The disconnect message, if available. * @param e The disconnect cause, if available. */ public static void debugDisconnect(@NotNull final String h, final int p, @NotNull final DisconnectType t, @Nullable final String m, @Nullable final Throwable e) { if (debugEnabled && debugTypes.contains(DebugType.CONNECT)) { debugDisconnect(Level.INFO, h, p, null, t, m, e); } } /** * Writes debug information to indicate that a connection has been * terminated, if appropriate. * * @param l The log level that should be used for the debug information. * @param h The address of the server to which the connection was * established. * @param p The port of the server to which the connection was established. * @param t The disconnect type. * @param m The disconnect message, if available. * @param e The disconnect cause, if available. */ public static void debugDisconnect(@NotNull final Level l, @NotNull final String h, final int p, @NotNull final DisconnectType t, @Nullable final String m, @Nullable final Throwable e) { if (debugEnabled && debugTypes.contains(DebugType.CONNECT)) { debugDisconnect(l, h, p, null, t, m, e); } } /** * Writes debug information to indicate that a connection has been * terminated, if appropriate. If it is to be logged, then it will be sent * to the underlying logger using the {@code INFO} level. * * @param h The address of the server to which the connection was * established. * @param p The port of the server to which the connection was established. * @param c The connection object for the connection that has been closed. * It may be {@code null} for historic reasons, but should be * non-{@code null} in new uses. * @param t The disconnect type. * @param m The disconnect message, if available. * @param e The disconnect cause, if available. */ public static void debugDisconnect(@NotNull final String h, final int p, @Nullable final LDAPConnection c, @NotNull final DisconnectType t, @Nullable final String m, @Nullable final Throwable e) { if (debugEnabled && debugTypes.contains(DebugType.CONNECT)) { debugDisconnect(Level.INFO, h, p, c, t, m, e); } } /** * Writes debug information to indicate that a connection has been * terminated, if appropriate. * * @param l The log level that should be used for the debug information. * @param h The address of the server to which the connection was * established. * @param p The port of the server to which the connection was established. * @param c The connection object for the connection that has been closed. * It may be {@code null} for historic reasons, but should be * non-{@code null} in new uses. * @param t The disconnect type. * @param m The disconnect message, if available. * @param e The disconnect cause, if available. */ public static void debugDisconnect(@NotNull final Level l, @NotNull final String h, final int p, @Nullable final LDAPConnection c, @NotNull final DisconnectType t, @Nullable final String m, @Nullable final Throwable e) { if (debugEnabled && debugTypes.contains(DebugType.CONNECT)) { final JSONBuffer buffer = new JSONBuffer( null, JSONBuffer.DEFAULT_MAX_BUFFER_SIZE, useMultiLineDebugMessages); addCommonHeader(buffer, l, DebugType.CONNECT); if (c != null) { buffer.appendNumber("connection-id", c.getConnectionID()); final String connectionName = c.getConnectionName(); if (connectionName != null) { buffer.appendString("connection-name", connectionName); } final String connectionPoolName = c.getConnectionPoolName(); if (connectionPoolName != null) { buffer.appendString("connection-pool-name", connectionPoolName); } buffer.appendString("disconnected-from-address", h); buffer.appendNumber("disconnected-from-port", p); buffer.appendString("disconnect-type", t.name()); if (m != null) { buffer.appendString("disconnect-message", m); } } if (e != null) { addCaughtException(buffer, "disconnect-cause", e); } addCommonFooter(buffer); log(l, buffer, e); } } /** * Writes debug information about the provided request, if appropriate. If * it is to be logged, then it will be sent to the underlying logger using the * {@code INFO} level. * * @param r The LDAP request for which debug information should be written. */ public static void debugLDAPRequest(@NotNull final LDAPRequest r) { if (debugEnabled && debugTypes.contains(DebugType.LDAP)) { debugLDAPRequest(Level.INFO, r, -1, null); } } /** * Writes debug information about the provided request, if appropriate. * * @param l The log level that should be used for the debug information. * @param r The LDAP request for which debug information should be written. */ public static void debugLDAPRequest(@NotNull final Level l, @NotNull final LDAPRequest r) { if (debugEnabled && debugTypes.contains(DebugType.LDAP)) { debugLDAPRequest(l, r, -1, null); } } /** * Writes debug information about the provided request, if appropriate. If * it is to be logged, then it will be sent to the underlying logger using the * {@code INFO} level. * * @param r The LDAP request for which debug information should be written. * @param i The message ID for the request that will be sent. It may be * negative if no message ID is available. * @param c The connection on which the request will be sent. It may be * {@code null} for historic reasons, but should be * non-{@code null} in new uses. */ public static void debugLDAPRequest(@NotNull final LDAPRequest r, final int i, @Nullable final LDAPConnection c) { if (debugEnabled && debugTypes.contains(DebugType.LDAP)) { debugLDAPRequest(Level.INFO, r, i, c); } } /** * Writes debug information about the provided request, if appropriate. * * @param l The log level that should be used for the debug information. * @param r The LDAP request for which debug information should be written. * @param i The message ID for the request that will be sent. It may be * negative if no message ID is available. * @param c The connection on which the request will be sent. It may be * {@code null} for historic reasons, but should be * non-{@code null} in new uses. */ public static void debugLDAPRequest(@NotNull final Level l, @NotNull final LDAPRequest r, final int i, @Nullable final LDAPConnection c) { if (debugEnabled && debugTypes.contains(DebugType.LDAP)) { debugLDAPRequest(l, String.valueOf(r), i, c); } } /** * Writes debug information about the provided request, if appropriate. * * @param l The log level that should be used for the debug information. * @param s A string representation of the LDAP request for which debug * information should be written. * @param i The message ID for the request that will be sent. It may be * negative if no message ID is available. * @param c The connection on which the request will be sent. It may be * {@code null} for historic reasons, but should be * non-{@code null} in new uses. */ public static void debugLDAPRequest(@NotNull final Level l, @NotNull final String s, final int i, @Nullable final LDAPConnection c) { if (debugEnabled && debugTypes.contains(DebugType.LDAP)) { final JSONBuffer buffer = new JSONBuffer( null, JSONBuffer.DEFAULT_MAX_BUFFER_SIZE, useMultiLineDebugMessages); addCommonHeader(buffer, l, DebugType.LDAP); if (c != null) { buffer.appendNumber("connection-id", c.getConnectionID()); final String connectionName = c.getConnectionName(); if (connectionName != null) { buffer.appendString("connection-name", connectionName); } final String connectionPoolName = c.getConnectionPoolName(); if (connectionPoolName != null) { buffer.appendString("connection-pool-name", connectionPoolName); } final String connectedAddress = c.getConnectedAddress(); if (connectedAddress != null) { buffer.appendString("connected-to-address", connectedAddress); buffer.appendNumber("connected-to-port", c.getConnectedPort()); } try { final int soTimeout = InternalSDKHelper.getSoTimeout(c); buffer.appendNumber("socket-timeout-millis", soTimeout); } catch (final Exception e) {} } if (i >= 0) { buffer.appendNumber("message-id", i); } buffer.appendString("sending-ldap-request", s); addCommonFooter(buffer); log(l, buffer); } } /** * Writes debug information about the provided result, if appropriate. If * it is to be logged, then it will be sent to the underlying logger using the * {@code INFO} level. * * @param r The result for which debug information should be written. */ public static void debugLDAPResult(@NotNull final LDAPResponse r) { if (debugEnabled && debugTypes.contains(DebugType.LDAP)) { debugLDAPResult(Level.INFO, r, null); } } /** * Writes debug information about the provided result, if appropriate. * * @param l The log level that should be used for the debug information. * @param r The result for which debug information should be written. */ public static void debugLDAPResult(@NotNull final Level l, @NotNull final LDAPResponse r) { if (debugEnabled && debugTypes.contains(DebugType.LDAP)) { debugLDAPResult(l, r, null); } } /** * Writes debug information about the provided result, if appropriate. If * it is to be logged, then it will be sent to the underlying logger using the * {@code INFO} level. * * @param r The result for which debug information should be written. * @param c The connection on which the response was received. It may be * {@code null} for historic reasons, but should be * non-{@code null} in new uses. */ public static void debugLDAPResult(@NotNull final LDAPResponse r, @Nullable final LDAPConnection c) { if (debugEnabled && debugTypes.contains(DebugType.LDAP)) { debugLDAPResult(Level.INFO, r, c); } } /** * Writes debug information about the provided result, if appropriate. * * @param l The log level that should be used for the debug information. * @param r The result for which debug information should be written. * @param c The connection on which the response was received. It may be * {@code null} for historic reasons, but should be * non-{@code null} in new uses. */ public static void debugLDAPResult(@NotNull final Level l, @NotNull final LDAPResponse r, @Nullable final LDAPConnection c) { if (debugEnabled && debugTypes.contains(DebugType.LDAP)) { final JSONBuffer buffer = new JSONBuffer( null, JSONBuffer.DEFAULT_MAX_BUFFER_SIZE, useMultiLineDebugMessages); addCommonHeader(buffer, l, DebugType.LDAP); if (c != null) { buffer.appendNumber("connection-id", c.getConnectionID()); final String connectionName = c.getConnectionName(); if (connectionName != null) { buffer.appendString("connection-name", connectionName); } final String connectionPoolName = c.getConnectionPoolName(); if (connectionPoolName != null) { buffer.appendString("connection-pool-name", connectionPoolName); } final String connectedAddress = c.getConnectedAddress(); if (connectedAddress != null) { buffer.appendString("connected-to-address", connectedAddress); buffer.appendNumber("connected-to-port", c.getConnectedPort()); } } buffer.appendString("read-ldap-result", r.toString()); addCommonFooter(buffer); log(l, buffer); } } /** * Writes debug information about the provided ASN.1 element to be written, * if appropriate. If it is to be logged, then it will be sent to the * underlying logger using the {@code INFO} level. * * @param e The ASN.1 element for which debug information should be written. */ public static void debugASN1Write(@NotNull final ASN1Element e) { if (debugEnabled && debugTypes.contains(DebugType.ASN1)) { debugASN1Write(Level.INFO, e); } } /** * Writes debug information about the provided ASN.1 element to be written, * if appropriate. * * @param l The log level that should be used for the debug information. * @param e The ASN.1 element for which debug information should be written. */ public static void debugASN1Write(@NotNull final Level l, @NotNull final ASN1Element e) { if (debugEnabled && debugTypes.contains(DebugType.ASN1)) { final JSONBuffer buffer = new JSONBuffer( null, JSONBuffer.DEFAULT_MAX_BUFFER_SIZE, useMultiLineDebugMessages); addCommonHeader(buffer, l, DebugType.ASN1); buffer.appendString("writing-asn1-element", e.toString()); addCommonFooter(buffer); log(l, buffer); } } /** * Writes debug information about the provided ASN.1 element to be written, * if appropriate. If it is to be logged, then it will be sent to the * underlying logger using the {@code INFO} level. * * @param b The ASN.1 buffer with the information to be written. */ public static void debugASN1Write(@NotNull final ASN1Buffer b) { if (debugEnabled && debugTypes.contains(DebugType.ASN1)) { debugASN1Write(Level.INFO, b); } } /** * Writes debug information about the provided ASN.1 element to be written, * if appropriate. * * @param l The log level that should be used for the debug information. * @param b The ASN1Buffer with the information to be written. */ public static void debugASN1Write(@NotNull final Level l, @NotNull final ASN1Buffer b) { if (debugEnabled && debugTypes.contains(DebugType.ASN1)) { final JSONBuffer buffer = new JSONBuffer( null, JSONBuffer.DEFAULT_MAX_BUFFER_SIZE, useMultiLineDebugMessages); addCommonHeader(buffer, l, DebugType.ASN1); buffer.appendString("writing-asn1-element", StaticUtils.toHex(b.toByteArray())); addCommonFooter(buffer); log(l, buffer); } } /** * Writes debug information about the provided ASN.1 element that was read, if * appropriate. If it is to be logged, then it will be sent to the underlying * logger using the {@code INFO} level. * * @param e The ASN.1 element for which debug information should be written. */ public static void debugASN1Read(@NotNull final ASN1Element e) { if (debugEnabled && debugTypes.contains(DebugType.ASN1)) { debugASN1Read(Level.INFO, e); } } /** * Writes debug information about the provided ASN.1 element that was read, if * appropriate. * * @param l The log level that should be used for the debug information. * @param e The ASN.1 element for which debug information should be written. */ public static void debugASN1Read(@NotNull final Level l, @NotNull final ASN1Element e) { if (debugEnabled && debugTypes.contains(DebugType.ASN1)) { final JSONBuffer buffer = new JSONBuffer( null, JSONBuffer.DEFAULT_MAX_BUFFER_SIZE, useMultiLineDebugMessages); addCommonHeader(buffer, l, DebugType.ASN1); buffer.appendString("read-asn1-element", e.toString()); addCommonFooter(buffer); log(l, buffer); } } /** * Writes debug information about the provided ASN.1 element that was read, if * appropriate. * * @param l The log level that should be used for the debug * information. * @param dataType A string representation of the data type for the data * that was read. * @param berType The BER type for the element that was read. * @param length The number of bytes in the value of the element that was * read. * @param value A representation of the value that was read. The debug * message will include the string representation of this * value, unless the value is a byte array in which it will * be a hex representation of the bytes that it contains. * It may be {@code null} for an ASN.1 null element. */ public static void debugASN1Read(@NotNull final Level l, @NotNull final String dataType, final int berType, final int length, @Nullable final Object value) { if (debugEnabled && debugTypes.contains(DebugType.ASN1)) { final JSONBuffer buffer = new JSONBuffer( null, JSONBuffer.DEFAULT_MAX_BUFFER_SIZE, useMultiLineDebugMessages); addCommonHeader(buffer, l, DebugType.ASN1); buffer.beginObject("read-asn1-element"); buffer.appendString("data-type", dataType); buffer.appendString("ber-type", StaticUtils.toHex((byte) (berType & 0xFF))); buffer.appendNumber("value-length", length); if (value != null) { if (value instanceof byte[]) { buffer.appendString("value-bytes", StaticUtils.toHex((byte[]) value)); } else { buffer.appendString("value-string", value.toString()); } } buffer.endObject(); addCommonFooter(buffer); log(l, buffer); } } /** * Writes debug information about interaction with a connection pool. * * @param l The log level that should be used for the debug information. * @param p The associated connection pool. * @param c The associated LDAP connection, if appropriate. * @param m A message with information about the pool interaction. * @param e An exception to include with the log message, if appropriate. */ public static void debugConnectionPool(@NotNull final Level l, @NotNull final AbstractConnectionPool p, @Nullable final LDAPConnection c, @Nullable final String m, @Nullable final Throwable e) { if (debugEnabled && debugTypes.contains(DebugType.CONNECTION_POOL)) { final JSONBuffer buffer = new JSONBuffer( null, JSONBuffer.DEFAULT_MAX_BUFFER_SIZE, useMultiLineDebugMessages); addCommonHeader(buffer, l, DebugType.CONNECTION_POOL); final String poolName = p.getConnectionPoolName(); if (poolName == null) { buffer.appendNull("connection-pool-name"); } else { buffer.appendString("connection-pool-name", poolName); } if (c != null) { buffer.appendNumber("connection-id", c.getConnectionID()); final String connectedAddress = c.getConnectedAddress(); if (connectedAddress != null) { buffer.appendString("connected-to-address", connectedAddress); buffer.appendNumber("connected-to-port", c.getConnectedPort()); } } final long currentAvailable = p.getCurrentAvailableConnections(); if (currentAvailable >= 0) { buffer.appendNumber("current-available-connections", currentAvailable); } final long maxAvailable = p.getMaximumAvailableConnections(); if (maxAvailable >= 0) { buffer.appendNumber("maximum-available-connections", maxAvailable); } if (m != null) { buffer.appendString("message", m); } if (e != null) { addCaughtException(buffer, "caught-exception", e); } addCommonFooter(buffer); log(l, buffer, e); } } /** * Writes debug information about the provided LDIF record to be written, if * if appropriate. If it is to be logged, then it will be sent to the * underlying logger using the {@code INFO} level. * * @param r The LDIF record for which debug information should be written. */ public static void debugLDIFWrite(@NotNull final LDIFRecord r) { if (debugEnabled && debugTypes.contains(DebugType.LDIF)) { debugLDIFWrite(Level.INFO, r); } } /** * Writes debug information about the provided LDIF record to be written, if * appropriate. * * @param l The log level that should be used for the debug information. * @param r The LDIF record for which debug information should be written. */ public static void debugLDIFWrite(@NotNull final Level l, @NotNull final LDIFRecord r) { if (debugEnabled && debugTypes.contains(DebugType.LDIF)) { final JSONBuffer buffer = new JSONBuffer( null, JSONBuffer.DEFAULT_MAX_BUFFER_SIZE, useMultiLineDebugMessages); addCommonHeader(buffer, l, DebugType.LDIF); buffer.appendString("writing-ldif-record", r.toString()); addCommonFooter(buffer); log(l, buffer); } } /** * Writes debug information about the provided record read from LDIF, if * appropriate. If it is to be logged, then it will be sent to the underlying * logger using the {@code INFO} level. * * @param r The LDIF record for which debug information should be written. */ public static void debugLDIFRead(@NotNull final LDIFRecord r) { if (debugEnabled && debugTypes.contains(DebugType.LDIF)) { debugLDIFRead(Level.INFO, r); } } /** * Writes debug information about the provided record read from LDIF, if * appropriate. * * @param l The log level that should be used for the debug information. * @param r The LDIF record for which debug information should be written. */ public static void debugLDIFRead(@NotNull final Level l, @NotNull final LDIFRecord r) { if (debugEnabled && debugTypes.contains(DebugType.LDIF)) { final JSONBuffer buffer = new JSONBuffer( null, JSONBuffer.DEFAULT_MAX_BUFFER_SIZE, useMultiLineDebugMessages); addCommonHeader(buffer, l, DebugType.LDIF); buffer.appendString("read-ldif-record", r.toString()); addCommonFooter(buffer); log(l, buffer); } } /** * Writes debug information about monitor entry parsing. If it is to be * logged, then it will be sent to the underlying logger using the * {@code FINE} level. * * @param e The entry containing the monitor information being parsed. * @param m The message to be written to the debug logger. */ public static void debugMonitor(@Nullable final Entry e, @Nullable final String m) { if (debugEnabled && debugTypes.contains(DebugType.MONITOR)) { debugMonitor(Level.FINE, e, m); } } /** * Writes debug information about monitor entry parsing, if appropriate. * * @param l The log level that should be used for the debug information. * @param e The entry containing the monitor information being parsed. * @param m The message to be written to the debug logger. */ public static void debugMonitor(@NotNull final Level l, @Nullable final Entry e, @Nullable final String m) { if (debugEnabled && debugTypes.contains(DebugType.MONITOR)) { final JSONBuffer buffer = new JSONBuffer( null, JSONBuffer.DEFAULT_MAX_BUFFER_SIZE, useMultiLineDebugMessages); addCommonHeader(buffer, l, DebugType.MONITOR); if (e != null) { buffer.appendString("monitor-entry-dn", e.getDN()); } if (m != null) { buffer.appendString("message", m); } addCommonFooter(buffer); log(l, buffer); } } /** * Writes debug information about a coding error detected in the use of the * LDAP SDK. If it is to be logged, then it will be sent to the underlying * logger using the {@code SEVERE} level. * * @param t The {@code Throwable} object that was created and will be thrown * as a result of the coding error. */ public static void debugCodingError(@NotNull final Throwable t) { if (debugEnabled && debugTypes.contains(DebugType.CODING_ERROR)) { final JSONBuffer buffer = new JSONBuffer( null, JSONBuffer.DEFAULT_MAX_BUFFER_SIZE, useMultiLineDebugMessages); addCommonHeader(buffer, Level.SEVERE, DebugType.CODING_ERROR); addCaughtException(buffer, "coding-error", t); addCommonFooter(buffer); log(Level.SEVERE, buffer, t); } } /** * Writes a generic debug message, if appropriate. * * @param l The log level that should be used for the debug information. * @param t The debug type to use to determine whether to write the message. * @param m The message to be written. */ public static void debug(@NotNull final Level l, @NotNull final DebugType t, @Nullable final String m) { if (debugEnabled && debugTypes.contains(t)) { final JSONBuffer buffer = new JSONBuffer( null, JSONBuffer.DEFAULT_MAX_BUFFER_SIZE, useMultiLineDebugMessages); addCommonHeader(buffer, l, t); if (m != null) { buffer.appendString("message", m); } addCommonFooter(buffer); log(l, buffer); } } /** * Writes a generic debug message, if appropriate. * * @param l The log level that should be used for the debug information. * @param t The debug type to use to determine whether to write the message. * @param m The message to be written. * @param e An exception to include with the log message. */ public static void debug(@NotNull final Level l, @NotNull final DebugType t, @Nullable final String m, @Nullable final Throwable e) { if (debugEnabled && debugTypes.contains(t)) { final JSONBuffer buffer = new JSONBuffer( null, JSONBuffer.DEFAULT_MAX_BUFFER_SIZE, useMultiLineDebugMessages); addCommonHeader(buffer, l, t); if (m != null) { buffer.appendString("message", m); } if (e != null) { addCaughtException(buffer, "caught-exception", e); } addCommonFooter(buffer); log(l, buffer, e); } } /** * Adds common header information to the provided JSON buffer. It will begin * a JSON object for the log message, then add a timestamp, debug type, log * level, thread ID, and thread name. * * @param buffer The JSON buffer to which the content should be added. * @param level The log level for the message that will be written. * @param type The debug type for the message that will be written. */ private static void addCommonHeader(@NotNull final JSONBuffer buffer, @NotNull final Level level, @NotNull final DebugType type) { buffer.beginObject(); buffer.appendString("timestamp", getTimestamp()); buffer.appendString("debug-type", type.getName()); buffer.appendString("level", level.getName()); final Thread t = Thread.currentThread(); buffer.appendNumber("thread-id", t.getId()); buffer.appendString("thread-name", t.getName()); } /** * Retrieves a timestamp that represents the current time. * * @return A timestamp that represents the current time. */ @NotNull() private static String getTimestamp() { SimpleDateFormat timestampFormatter = TIMESTAMP_FORMATTERS.get(); if (timestampFormatter == null) { timestampFormatter = new SimpleDateFormat("yyyy'-'MM'-'dd'T'HH':'mm':'ss.SSS'Z'"); timestampFormatter.setTimeZone(StaticUtils.getUTCTimeZone()); TIMESTAMP_FORMATTERS.set(timestampFormatter); } return timestampFormatter.format(new Date()); } /** * Creates a formatted string representation of the provided stack trace * frame. * * @param e The stack trace element to be formatted. * * @return The formatted string representation of the provided stack trace * frame. */ @NotNull() private static String formatStackTraceFrame( @NotNull final StackTraceElement e) { final StringBuilder buffer = new StringBuilder(); buffer.append(e.getMethodName()); buffer.append('('); buffer.append(e.getFileName()); final int lineNumber = e.getLineNumber(); if (lineNumber > 0) { buffer.append(':'); buffer.append(lineNumber); } else if (e.isNativeMethod()) { buffer.append(":native"); } buffer.append(')'); return buffer.toString(); } /** * Adds information about a caught exception to the provided JSON buffer. * * @param buffer The JSON buffer to which the information should be * appended. * @param fieldName The name to use for the new field to be added with the * exception information. * @param t The exception to be included. */ private static void addCaughtException(@NotNull final JSONBuffer buffer, @NotNull final String fieldName, @Nullable final Throwable t) { if (t == null) { return; } buffer.beginObject(fieldName); final String message = t.getMessage(); if (message != null) { buffer.appendString("message", message); } buffer.beginArray("stack-trace"); for (final StackTraceElement e : t.getStackTrace()) { buffer.appendString(formatStackTraceFrame(e)); } buffer.endArray(); final Throwable cause = t.getCause(); if (cause != null) { addCaughtException(buffer, "cause", cause); } buffer.endObject(); } /** * Adds common footer information to the provided JSON buffer. It will * include an optional caller stack trace, along with the LDAP SDK version * and revision. It will also end the object that encapsulates the log * message. * * @param buffer The JSON buffer to which the content should be added. */ private static void addCommonFooter(@NotNull final JSONBuffer buffer) { if (includeStackTrace) { buffer.beginArray("caller-stack-trace"); boolean foundDebug = false; for (final StackTraceElement e : Thread.currentThread().getStackTrace()) { final String className = e.getClassName(); if (className.equals(Debug.class.getName())) { foundDebug = true; } else if (foundDebug) { buffer.appendString(formatStackTraceFrame(e)); } } buffer.endArray(); } buffer.appendString("ldap-sdk-version", Version.NUMERIC_VERSION_STRING); buffer.appendString("ldap-sdk-revision", Version.REVISION_ID); buffer.endObject(); } /** * Logs a JSON-formatted debug message with the given level and fields. * * @param level The log level to use for the message. * @param buffer The JSON buffer containing the message to be written. */ private static void log(@NotNull final Level level, @NotNull final JSONBuffer buffer) { logger.log(level, buffer.toString()); } /** * Logs a JSON-formatted debug message with the given level and fields. * * @param level The log level to use for the message. * @param buffer The JSON buffer containing the message to be written. * @param thrown An exception to be included with the debug message. */ private static void log(@NotNull final Level level, @NotNull final JSONBuffer buffer, @Nullable final Throwable thrown) { 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()) { // We need to reference the fileLock variable inside the try block to // avoid a compiler warning. Validator.ensureTrue(fileLock.isValid()); 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)); } } /** * Attempts to parse the provided string as a debug log level. * * @param levelString The string representation of the level to use. It * must not be {@code null} or empty. * * @return The log level that was parsed. * * @throws IllegalArgumentException If the provided string cannot be parsed * as a valid debug log level. */ @NotNull() public static Level parseDebugLogLevel(@NotNull final String levelString) throws IllegalArgumentException { final String lowerLevelString = StaticUtils.toLowerCase(levelString); switch (lowerLevelString) { case "off": case "none": case "disabled": return Level.OFF; case "severe": case "error": return Level.SEVERE; case "warning": case "warn": return Level.WARNING; case "info": case "information": case "informational": return Level.INFO; case "config": case "configuration": return Level.CONFIG; case "fine": return Level.FINE; case "finer": return Level.FINER; case "finest": case "verbose": return Level.FINEST; case "all": return Level.ALL; default: try { return Level.parse(levelString); } catch (final IllegalArgumentException e) { throw new IllegalArgumentException( ERR_DEBUG_CANNOT_PARSE_LEVEL.get(levelString)); } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy