001/*
002 * Copyright 2008-2024 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2008-2024 Ping Identity Corporation
007 *
008 * Licensed under the Apache License, Version 2.0 (the "License");
009 * you may not use this file except in compliance with the License.
010 * You may obtain a copy of the License at
011 *
012 *    http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing, software
015 * distributed under the License is distributed on an "AS IS" BASIS,
016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017 * See the License for the specific language governing permissions and
018 * limitations under the License.
019 */
020/*
021 * Copyright (C) 2008-2024 Ping Identity Corporation
022 *
023 * This program is free software; you can redistribute it and/or modify
024 * it under the terms of the GNU General Public License (GPLv2 only)
025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
026 * as published by the Free Software Foundation.
027 *
028 * This program is distributed in the hope that it will be useful,
029 * but WITHOUT ANY WARRANTY; without even the implied warranty of
030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
031 * GNU General Public License for more details.
032 *
033 * You should have received a copy of the GNU General Public License
034 * along with this program; if not, see <http://www.gnu.org/licenses>.
035 */
036package com.unboundid.util;
037
038
039
040import java.io.File;
041import java.io.Serializable;
042import java.nio.ByteBuffer;
043import java.nio.channels.FileChannel;
044import java.nio.channels.FileLock;
045import java.nio.file.StandardOpenOption;
046import java.text.SimpleDateFormat;
047import java.util.Date;
048import java.util.EnumSet;
049import java.util.Properties;
050import java.util.Set;
051import java.util.StringTokenizer;
052import java.util.logging.FileHandler;
053import java.util.logging.Level;
054import java.util.logging.Logger;
055
056import com.unboundid.asn1.ASN1Buffer;
057import com.unboundid.asn1.ASN1Element;
058import com.unboundid.ldap.protocol.LDAPResponse;
059import com.unboundid.ldap.sdk.AbstractConnectionPool;
060import com.unboundid.ldap.sdk.DisconnectType;
061import com.unboundid.ldap.sdk.Entry;
062import com.unboundid.ldap.sdk.InternalSDKHelper;
063import com.unboundid.ldap.sdk.LDAPConnection;
064import com.unboundid.ldap.sdk.LDAPRequest;
065import com.unboundid.ldap.sdk.Version;
066import com.unboundid.ldif.LDIFRecord;
067import com.unboundid.util.json.JSONBuffer;
068
069import static com.unboundid.util.UtilityMessages.*;
070
071
072
073/**
074 * This class provides a means of enabling and configuring debugging in the LDAP
075 * SDK.
076 * <BR><BR>
077 * Access to debug information can be enabled through applications that use the
078 * SDK by calling the {@link Debug#setEnabled} methods, or it can also be
079 * enabled without any code changes through the use of system properties.  In
080 * particular, the {@link Debug#PROPERTY_DEBUG_ENABLED},
081 * {@link Debug#PROPERTY_DEBUG_LEVEL}, and {@link Debug#PROPERTY_DEBUG_TYPE}
082 * properties may be used to control debugging without the need to alter any
083 * code within the application that uses the SDK.
084 * <BR><BR>
085 * The LDAP SDK debugging subsystem uses the Java logging framework available
086 * through the {@code java.util.logging} package with a logger name of
087 * "{@code com.unboundid.ldap.sdk}".  The {@link Debug#getLogger} method may
088 * be used to access the logger instance used by the LDAP SDK.
089 * <BR><BR>
090 * <H2>Example</H2>
091 * The following example demonstrates the process that may be used to enable
092 * debugging within the LDAP SDK and write information about all messages with
093 * a {@code WARNING} level or higher to a specified file:
094 * <PRE>
095 * Debug.setEnabled(true);
096 * Logger logger = Debug.getLogger();
097 *
098 * FileHandler fileHandler = new FileHandler(logFilePath);
099 * fileHandler.setLevel(Level.WARNING);
100 * logger.addHandler(fileHandler);
101 * </PRE>
102 */
103@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
104public final class Debug
105       implements Serializable
106{
107  /**
108   * The name of the system property that will be used to enable debugging in
109   * the UnboundID LDAP SDK for Java.  The fully-qualified name for this
110   * property is "{@code com.unboundid.ldap.sdk.debug.enabled}".  If it is set,
111   * then it should have a value of either "true" or "false".
112   */
113  @NotNull public static final String PROPERTY_DEBUG_ENABLED =
114       "com.unboundid.ldap.sdk.debug.enabled";
115
116
117
118  /**
119   * The name of the system property that may be used to indicate whether stack
120   * trace information for the thread calling the debug method should be
121   * included in debug log messages.  The fully-qualified name for this property
122   * is "{@code com.unboundid.ldap.sdk.debug.includeStackTrace}".  If it is set,
123   * then it should have a value of either "true" or "false".
124   */
125  @NotNull public static final String PROPERTY_INCLUDE_STACK_TRACE =
126       "com.unboundid.ldap.sdk.debug.includeStackTrace";
127
128
129
130  /**
131   * The name of the system property that will be used to set the initial level
132   * for the debug logger.  The fully-qualified name for this property is
133   * "{@code com.unboundid.ldap.sdk.debug.level}".  If it is set, then it should
134   * be one of the strings "{@code SEVERE}", "{@code WARNING}", "{@code INFO}",
135   * "{@code CONFIG}", "{@code FINE}", "{@code FINER}", or "{@code FINEST}".
136   */
137  @NotNull public static final String PROPERTY_DEBUG_LEVEL =
138       "com.unboundid.ldap.sdk.debug.level";
139
140
141
142  /**
143   * The name of the system property that will be used to indicate that
144   * debugging should be enabled for specific types of messages.  The
145   * fully-qualified name for this property is
146   * "{@code com.unboundid.ldap.sdk.debug.type}". If it is set, then it should
147   * be a comma-delimited list of the names of the desired debug types.  See the
148   * {@link DebugType} enum for the available debug types.
149   */
150  @NotNull public static final String PROPERTY_DEBUG_TYPE =
151       "com.unboundid.ldap.sdk.debug.type";
152
153
154
155  /**
156   * The name of the system property that will be used to indicate whether the
157   * LDAP SDK should default to including information about the exception's
158   * cause in an exception message obtained from the
159   * {@link StaticUtils#getExceptionMessage(Throwable)} method.  By default,
160   * the cause will not be included in most messages.
161   */
162  @NotNull public static final String
163       PROPERTY_INCLUDE_CAUSE_IN_EXCEPTION_MESSAGES =
164            "com.unboundid.ldap.sdk.debug.includeCauseInExceptionMessages";
165
166
167
168  /**
169   * The name of the system property that will be used to indicate whether the
170   * LDAP SDK should default to including a full stack trace (albeit in
171   * condensed form) in an exception message obtained from the
172   * {@link StaticUtils#getExceptionMessage(Throwable)} method.  By default,
173   * stack traces will not be included in most messages.
174   */
175  @NotNull public static final String
176       PROPERTY_INCLUDE_STACK_TRACE_IN_EXCEPTION_MESSAGES =
177            "com.unboundid.ldap.sdk.debug.includeStackTraceInExceptionMessages";
178
179
180
181  /**
182   * The name of the system property that will be used ot indicate whether
183   * debug messages (which will be formatted as JSON objects) should be
184   * generated using a multi-line string representation.  By default, debug
185   * message will use a single-line string representation.
186   */
187  @NotNull public static final String PROPERTY_USE_MULTI_LINE_DEBUG_MESSAGES =
188       "com.unboundid.ldap.sdk.debug.useMultiLineDebugMessages";
189
190
191
192  /**
193   * The name of the system property that will be used to indicate that debug
194   * log messages should be written to the specified file.  The fully-qualified
195   * name for this property is "{@code com.unboundid.ldap.sdk.debug.file}".  If
196   * it is set, then its value should be a pattern that describes the path to
197   * the log file to create as described in the Javadoc documentation for the
198   * {@code java.util.logging.FileHandler} class.
199   */
200  @NotNull public static final String PROPERTY_DEBUG_FILE =
201       "com.unboundid.ldap.sdk.debug.file";
202
203
204
205  /**
206   * The name that will be used for the Java logger that will actually handle
207   * the debug messages if debugging is enabled.
208   */
209  @NotNull public static final String LOGGER_NAME = "com.unboundid.ldap.sdk";
210
211
212
213  /**
214   * The logger that will be used to handle the debug messages if debugging is
215   * enabled.
216   */
217  @NotNull private static final Logger logger = Logger.getLogger(LOGGER_NAME);
218
219
220
221  /**
222   * A set of thread-local formatters that may be used to generate timestamps.
223   */
224  @NotNull private static final ThreadLocal<SimpleDateFormat>
225       TIMESTAMP_FORMATTERS = new ThreadLocal<>();
226
227
228
229  /**
230   * The serial version UID for this serializable class.
231   */
232  private static final long serialVersionUID = -6079754380415146030L;
233
234
235
236  // Indicates whether any debugging is currently enabled for the SDK.
237  private static boolean debugEnabled;
238
239  // Indicates whether to capture a thread stack trace whenever a debug message
240  // is logged.
241  private static boolean includeStackTrace;
242
243  // Indiates whether debug messages should be formatted using multiple lines.
244  private static boolean useMultiLineDebugMessages;
245
246  // The set of debug types for which debugging is enabled.
247  @NotNull private static EnumSet<DebugType> debugTypes=
248       EnumSet.allOf(DebugType.class);
249
250
251
252  static
253  {
254    initialize(StaticUtils.getSystemProperties(PROPERTY_DEBUG_ENABLED,
255         PROPERTY_DEBUG_LEVEL, PROPERTY_DEBUG_TYPE,
256         PROPERTY_INCLUDE_STACK_TRACE));
257
258    final String logFilePropertyValue =
259         StaticUtils.getSystemProperty(PROPERTY_DEBUG_FILE);
260    if (logFilePropertyValue != null)
261    {
262      try
263      {
264        logger.setUseParentHandlers(false);
265
266        final FileHandler fileHandler =
267             new FileHandler(logFilePropertyValue, true);
268        fileHandler.setFormatter(
269             new MinimalLogFormatter(null, false, false, true));
270        logger.addHandler(fileHandler);
271      }
272      catch (final Exception e)
273      {
274        throw new RuntimeException(e);
275      }
276    }
277  }
278
279
280
281  /**
282   * Prevent this class from being instantiated.
283   */
284  private Debug()
285  {
286    // No implementation is required.
287  }
288
289
290
291  /**
292   * Initializes this debugger with the default settings.  Debugging will be
293   * disabled, the set of debug types will include all types, and the debug
294   * level will be "ALL".
295   */
296  public static void initialize()
297  {
298    includeStackTrace = false;
299    useMultiLineDebugMessages = false;
300    debugEnabled = false;
301    debugTypes = EnumSet.allOf(DebugType.class);
302
303    StaticUtils.setLoggerLevel(logger, Level.ALL);
304  }
305
306
307
308  /**
309   * Initializes this debugger with settings from the provided set of
310   * properties.  Any debug setting that isn't configured in the provided
311   * properties will be initialized with its default value.
312   *
313   * @param  properties  The set of properties to use to initialize this
314   *                     debugger.
315   */
316  public static void initialize(@Nullable final Properties properties)
317  {
318    // First, apply the default values for the properties.
319    initialize();
320    if ((properties == null) || properties.isEmpty())
321    {
322      // No properties were provided, so we don't need to do anything.
323      return;
324    }
325
326    final String enabledProp = properties.getProperty(PROPERTY_DEBUG_ENABLED);
327    if ((enabledProp != null) && (! enabledProp.isEmpty()))
328    {
329      if (enabledProp.equalsIgnoreCase("true"))
330      {
331        debugEnabled = true;
332      }
333      else if (enabledProp.equalsIgnoreCase("false"))
334      {
335        debugEnabled = false;
336      }
337      else
338      {
339        throw new IllegalArgumentException("Invalid value '" + enabledProp +
340             "' for property " + PROPERTY_DEBUG_ENABLED +
341             ".  The value must be either 'true' or 'false'.");
342      }
343    }
344
345    final String stackProp =
346         properties.getProperty(PROPERTY_INCLUDE_STACK_TRACE);
347    if ((stackProp != null) && (! stackProp.isEmpty()))
348    {
349      if (stackProp.equalsIgnoreCase("true"))
350      {
351        includeStackTrace = true;
352      }
353      else if (stackProp.equalsIgnoreCase("false"))
354      {
355        includeStackTrace = false;
356      }
357      else
358      {
359        throw new IllegalArgumentException("Invalid value '" + stackProp +
360             "' for property " + PROPERTY_INCLUDE_STACK_TRACE +
361             ".  The value must be either 'true' or 'false'.");
362      }
363    }
364
365    final String multiLineProp =
366         properties.getProperty(PROPERTY_USE_MULTI_LINE_DEBUG_MESSAGES);
367    if ((multiLineProp != null) && (! multiLineProp.isEmpty()))
368    {
369      if (multiLineProp.equalsIgnoreCase("true"))
370      {
371        useMultiLineDebugMessages = true;
372      }
373      else if (multiLineProp.equalsIgnoreCase("false"))
374      {
375        useMultiLineDebugMessages = false;
376      }
377      else
378      {
379        throw new IllegalArgumentException("Invalid value '" + multiLineProp +
380             "' for property " + PROPERTY_USE_MULTI_LINE_DEBUG_MESSAGES +
381             ".  The value must be either 'true' or 'false'.");
382      }
383    }
384
385    final String typesProp = properties.getProperty(PROPERTY_DEBUG_TYPE);
386    if ((typesProp != null) && (! typesProp.isEmpty()))
387    {
388      debugTypes = EnumSet.noneOf(DebugType.class);
389      final StringTokenizer t = new StringTokenizer(typesProp, ", ");
390      while (t.hasMoreTokens())
391      {
392        final String debugTypeName = t.nextToken();
393        final DebugType debugType = DebugType.forName(debugTypeName);
394        if (debugType == null)
395        {
396          // Throw a runtime exception to indicate that the debug type is
397          // invalid.
398          throw new IllegalArgumentException("Invalid value '" + debugTypeName +
399               "' for property " + PROPERTY_DEBUG_TYPE +
400               ".  Allowed values include:  " + DebugType.getTypeNameList() +
401               '.');
402        }
403        else
404        {
405          debugTypes.add(debugType);
406        }
407      }
408    }
409
410    final String levelProp = properties.getProperty(PROPERTY_DEBUG_LEVEL);
411    if ((levelProp != null) && (! levelProp.isEmpty()))
412    {
413      try
414      {
415        StaticUtils.setLoggerLevel(logger,  parseDebugLogLevel(levelProp));
416      }
417      catch (final IllegalArgumentException e)
418      {
419        throw new IllegalArgumentException(
420             "Invalid value '" + levelProp + "' for property " +
421                  PROPERTY_DEBUG_LEVEL + '.',
422             e);
423      }
424    }
425  }
426
427
428
429  /**
430   * Retrieves the logger that will be used to write the debug messages.
431   *
432   * @return  The logger that will be used to write the debug messages.
433   */
434  @NotNull()
435  public static Logger getLogger()
436  {
437    return logger;
438  }
439
440
441
442  /**
443   * Indicates whether any form of debugging is enabled.
444   *
445   * @return  {@code true} if debugging is enabled, or {@code false} if not.
446   */
447  public static boolean debugEnabled()
448  {
449    return debugEnabled;
450  }
451
452
453
454  /**
455   * Indicates whether debugging is enabled for messages of the specified debug
456   * type.
457   *
458   * @param  debugType  The debug type for which to make the determination.
459   *
460   * @return  {@code true} if debugging is enabled for messages of the specified
461   *          debug type, or {@code false} if not.
462   */
463  public static boolean debugEnabled(@NotNull final DebugType debugType)
464  {
465    return (debugEnabled && debugTypes.contains(debugType));
466  }
467
468
469
470  /**
471   * Specifies whether debugging should be enabled.  If it should be, then it
472   * will be enabled for all debug types.
473   *
474   * @param  enabled  Specifies whether debugging should be enabled.
475   */
476  public static void setEnabled(final boolean enabled)
477  {
478    debugTypes   = EnumSet.allOf(DebugType.class);
479    debugEnabled = enabled;
480  }
481
482
483
484  /**
485   * Specifies whether debugging should be enabled.  If it should be, then it
486   * will be enabled for all debug types in the provided set.
487   *
488   * @param  enabled  Specifies whether debugging should be enabled.
489   * @param  types    The set of debug types that should be enabled.  It may be
490   *                  {@code null} or empty to indicate that it should be for
491   *                  all debug types.
492   */
493  public static void setEnabled(final boolean enabled,
494                                @Nullable final Set<DebugType> types)
495  {
496    if ((types == null) || types.isEmpty())
497    {
498      debugTypes = EnumSet.allOf(DebugType.class);
499    }
500    else
501    {
502      debugTypes = EnumSet.copyOf(types);
503    }
504
505    debugEnabled = enabled;
506  }
507
508
509
510  /**
511   * Indicates whether log messages should include a stack trace of the thread
512   * that invoked the debug method.
513   *
514   * @return  {@code true} if log messages should include a stack trace of the
515   *          thread that invoked the debug method, or {@code false} if not.
516   */
517  public static boolean includeStackTrace()
518  {
519    return includeStackTrace;
520  }
521
522
523
524  /**
525   * Specifies whether log messages should include a stack trace of the thread
526   * that invoked the debug method.
527   *
528   * @param  includeStackTrace  Indicates whether log messages should include a
529   *                            stack trace of the thread that invoked the debug
530   *                            method.
531   */
532  public static void setIncludeStackTrace(final boolean includeStackTrace)
533  {
534    Debug.includeStackTrace = includeStackTrace;
535  }
536
537
538
539  /**
540   * Indicates whether debug messages (which will be formatted as JSON objects)
541   * should use a multi-line or single-line string representation.
542   *
543   * @return  {@code true} if debug messages should be formatted as multi-line
544   *          strings, or {@code false} if debug messages should be formatted as
545   *          single-line strings.
546   */
547  public static boolean useMultiLineDebugMessages()
548  {
549    return useMultiLineDebugMessages;
550  }
551
552
553
554  /**
555   * Specifies whether debug messages (which will be formatted as JSON objects)
556   * should use a multi-line or single-line string representation.
557   *
558   * @param  useMultiLineDebugMessages  Indicates whether debug messages
559   *                                    should be formatted as multi-line
560   *                                    strings (if {@code true}) or single-line
561   *                                    strings (if {@code false}).
562   */
563  public static void setUseMultiLineDebugMessages(
564              final boolean useMultiLineDebugMessages)
565  {
566    Debug.useMultiLineDebugMessages = useMultiLineDebugMessages;
567  }
568
569
570
571  /**
572   * Retrieves the set of debug types that will be used if debugging is enabled.
573   *
574   * @return  The set of debug types that will be used if debugging is enabled.
575   */
576  @NotNull()
577  public static EnumSet<DebugType> getDebugTypes()
578  {
579    return debugTypes;
580  }
581
582
583
584  /**
585   * Writes debug information about the provided exception, if appropriate.  If
586   * it is to be logged, then it will be sent to the underlying logger using the
587   * {@code WARNING} level.
588   *
589   * @param  t  The exception for which debug information should be written.
590   */
591  public static void debugException(@NotNull final Throwable t)
592  {
593    if (debugEnabled && debugTypes.contains(DebugType.EXCEPTION))
594    {
595      debugException(Level.WARNING, t);
596    }
597  }
598
599
600
601  /**
602   * Writes debug information about the provided exception, if appropriate.
603   *
604   * @param  l  The log level that should be used for the debug information.
605   * @param  t  The exception for which debug information should be written.
606   */
607  public static void debugException(@NotNull final Level l,
608                                    @NotNull final Throwable t)
609  {
610    if (debugEnabled && debugTypes.contains(DebugType.EXCEPTION))
611    {
612      final JSONBuffer buffer = new JSONBuffer(
613           null, JSONBuffer.DEFAULT_MAX_BUFFER_SIZE, useMultiLineDebugMessages);
614      addCommonHeader(buffer, l, DebugType.EXCEPTION);
615      addCaughtException(buffer, "caught-exception", t);
616      addCommonFooter(buffer);
617
618      log(l, buffer, t);
619    }
620  }
621
622
623
624  /**
625   * Writes debug information to indicate that a connection has been
626   * established, if appropriate.  If it is to be logged, then it will be sent
627   * to the underlying logger using the {@code INFO} level.
628   *
629   * @param  h  The address of the server to which the connection was
630   *            established.
631   * @param  p  The port of the server to which the connection was established.
632   */
633  public static void debugConnect(@NotNull final String h, final int p)
634  {
635    if (debugEnabled && debugTypes.contains(DebugType.CONNECT))
636    {
637      debugConnect(Level.INFO, h, p, null);
638    }
639  }
640
641
642
643  /**
644   * Writes debug information to indicate that a connection has been
645   * established, if appropriate.
646   *
647   * @param  l  The log level that should be used for the debug information.
648   * @param  h  The address of the server to which the connection was
649   *            established.
650   * @param  p  The port of the server to which the connection was established.
651   */
652  public static void debugConnect(@NotNull final Level l,
653                                  @NotNull final String h, final int p)
654  {
655    if (debugEnabled && debugTypes.contains(DebugType.CONNECT))
656    {
657      debugConnect(l, h, p, null);
658    }
659  }
660
661
662
663  /**
664   * Writes debug information to indicate that a connection has been
665   * established, if appropriate.  If it is to be logged, then it will be sent
666   * to the underlying logger using the {@code INFO} level.
667   *
668   * @param  h  The address of the server to which the connection was
669   *            established.
670   * @param  p  The port of the server to which the connection was established.
671   * @param  c  The connection object for the connection that has been
672   *            established.  It may be {@code null} for historic reasons, but
673   *            should be non-{@code null} in new uses.
674   */
675  public static void debugConnect(@NotNull final String h, final int p,
676                                  @Nullable final LDAPConnection c)
677  {
678    if (debugEnabled && debugTypes.contains(DebugType.CONNECT))
679    {
680      debugConnect(Level.INFO, h, p, c);
681    }
682  }
683
684
685
686  /**
687   * Writes debug information to indicate that a connection has been
688   * established, if appropriate.
689   *
690   * @param  l  The log level that should be used for the debug information.
691   * @param  h  The address of the server to which the connection was
692   *            established.
693   * @param  p  The port of the server to which the connection was established.
694   * @param  c  The connection object for the connection that has been
695   *            established.  It may be {@code null} for historic reasons, but
696   *            should be non-{@code null} in new uses.
697   */
698  public static void debugConnect(@NotNull final Level l,
699                                  @NotNull final String h, final int p,
700                                  @Nullable final LDAPConnection c)
701  {
702    if (debugEnabled && debugTypes.contains(DebugType.CONNECT))
703    {
704      final JSONBuffer buffer = new JSONBuffer(
705           null, JSONBuffer.DEFAULT_MAX_BUFFER_SIZE, useMultiLineDebugMessages);
706      addCommonHeader(buffer, l, DebugType.CONNECT);
707      buffer.appendString("connected-to-address", h);
708      buffer.appendNumber("connected-to-port", p);
709
710      if (c != null)
711      {
712        buffer.appendNumber("connection-id", c.getConnectionID());
713
714        final String connectionName = c.getConnectionName();
715        if (connectionName != null)
716        {
717          buffer.appendString("connection-name", connectionName);
718        }
719
720        final String connectionPoolName = c.getConnectionPoolName();
721        if (connectionPoolName != null)
722        {
723          buffer.appendString("connection-pool-name", connectionPoolName);
724        }
725      }
726
727      addCommonFooter(buffer);
728      log(l, buffer);
729    }
730  }
731
732
733
734  /**
735   * Writes debug information to indicate that a connection has been
736   * terminated, if appropriate.  If it is to be logged, then it will be sent
737   * to the underlying logger using the {@code INFO} level.
738   *
739   * @param  h  The address of the server to which the connection was
740   *            established.
741   * @param  p  The port of the server to which the connection was established.
742   * @param  t  The disconnect type.
743   * @param  m  The disconnect message, if available.
744   * @param  e  The disconnect cause, if available.
745   */
746  public static void debugDisconnect(@NotNull final String h,
747                                     final int p,
748                                     @NotNull final DisconnectType t,
749                                     @Nullable final String m,
750                                     @Nullable final Throwable e)
751  {
752    if (debugEnabled && debugTypes.contains(DebugType.CONNECT))
753    {
754      debugDisconnect(Level.INFO, h, p, null, t, m, e);
755    }
756  }
757
758
759
760  /**
761   * Writes debug information to indicate that a connection has been
762   * terminated, if appropriate.
763   *
764   * @param  l  The log level that should be used for the debug information.
765   * @param  h  The address of the server to which the connection was
766   *            established.
767   * @param  p  The port of the server to which the connection was established.
768   * @param  t  The disconnect type.
769   * @param  m  The disconnect message, if available.
770   * @param  e  The disconnect cause, if available.
771   */
772  public static void debugDisconnect(@NotNull final Level l,
773                                     @NotNull final String h, final int p,
774                                     @NotNull final DisconnectType t,
775                                     @Nullable final String m,
776                                     @Nullable final Throwable e)
777  {
778    if (debugEnabled && debugTypes.contains(DebugType.CONNECT))
779    {
780      debugDisconnect(l, h, p, null, t, m, e);
781    }
782  }
783
784
785
786  /**
787   * Writes debug information to indicate that a connection has been
788   * terminated, if appropriate.  If it is to be logged, then it will be sent
789   * to the underlying logger using the {@code INFO} level.
790   *
791   * @param  h  The address of the server to which the connection was
792   *            established.
793   * @param  p  The port of the server to which the connection was established.
794   * @param  c  The connection object for the connection that has been closed.
795   *            It may be {@code null} for historic reasons, but should be
796   *            non-{@code null} in new uses.
797   * @param  t  The disconnect type.
798   * @param  m  The disconnect message, if available.
799   * @param  e  The disconnect cause, if available.
800   */
801  public static void debugDisconnect(@NotNull final String h, final int p,
802                                     @Nullable final LDAPConnection c,
803                                     @NotNull final DisconnectType t,
804                                     @Nullable final String m,
805                                     @Nullable final Throwable e)
806  {
807    if (debugEnabled && debugTypes.contains(DebugType.CONNECT))
808    {
809      debugDisconnect(Level.INFO, h, p, c, t, m, e);
810    }
811  }
812
813
814
815  /**
816   * Writes debug information to indicate that a connection has been
817   * terminated, if appropriate.
818   *
819   * @param  l  The log level that should be used for the debug information.
820   * @param  h  The address of the server to which the connection was
821   *            established.
822   * @param  p  The port of the server to which the connection was established.
823   * @param  c  The connection object for the connection that has been closed.
824   *            It may be {@code null} for historic reasons, but should be
825   *            non-{@code null} in new uses.
826   * @param  t  The disconnect type.
827   * @param  m  The disconnect message, if available.
828   * @param  e  The disconnect cause, if available.
829   */
830  public static void debugDisconnect(@NotNull final Level l,
831                                     @NotNull final String h, final int p,
832                                     @Nullable final LDAPConnection c,
833                                     @NotNull final DisconnectType t,
834                                     @Nullable final String m,
835                                     @Nullable final Throwable e)
836  {
837    if (debugEnabled && debugTypes.contains(DebugType.CONNECT))
838    {
839      final JSONBuffer buffer = new JSONBuffer(
840           null, JSONBuffer.DEFAULT_MAX_BUFFER_SIZE, useMultiLineDebugMessages);
841      addCommonHeader(buffer, l, DebugType.CONNECT);
842
843      if (c != null)
844      {
845        buffer.appendNumber("connection-id", c.getConnectionID());
846
847        final String connectionName = c.getConnectionName();
848        if (connectionName != null)
849        {
850          buffer.appendString("connection-name", connectionName);
851        }
852
853        final String connectionPoolName = c.getConnectionPoolName();
854        if (connectionPoolName != null)
855        {
856          buffer.appendString("connection-pool-name", connectionPoolName);
857        }
858
859        buffer.appendString("disconnected-from-address", h);
860        buffer.appendNumber("disconnected-from-port", p);
861        buffer.appendString("disconnect-type", t.name());
862
863        if (m != null)
864        {
865          buffer.appendString("disconnect-message", m);
866        }
867
868      }
869
870      if (e != null)
871      {
872        addCaughtException(buffer, "disconnect-cause", e);
873      }
874
875      addCommonFooter(buffer);
876      log(l, buffer, e);
877    }
878  }
879
880
881
882  /**
883   * Writes debug information about the provided request, if appropriate.  If
884   * it is to be logged, then it will be sent to the underlying logger using the
885   * {@code INFO} level.
886   *
887   * @param  r  The LDAP request for which debug information should be written.
888   */
889  public static void debugLDAPRequest(@NotNull final LDAPRequest r)
890  {
891    if (debugEnabled && debugTypes.contains(DebugType.LDAP))
892    {
893      debugLDAPRequest(Level.INFO, r, -1, null);
894    }
895  }
896
897
898
899  /**
900   * Writes debug information about the provided request, if appropriate.
901   *
902   * @param  l  The log level that should be used for the debug information.
903   * @param  r  The LDAP request for which debug information should be written.
904   */
905  public static void debugLDAPRequest(@NotNull final Level l,
906                                      @NotNull final LDAPRequest r)
907  {
908    if (debugEnabled && debugTypes.contains(DebugType.LDAP))
909    {
910      debugLDAPRequest(l, r, -1, null);
911    }
912  }
913
914
915
916  /**
917   * Writes debug information about the provided request, if appropriate.  If
918   * it is to be logged, then it will be sent to the underlying logger using the
919   * {@code INFO} level.
920   *
921   * @param  r  The LDAP request for which debug information should be written.
922   * @param  i  The message ID for the request that will be sent.  It may be
923   *            negative if no message ID is available.
924   * @param  c  The connection on which the request will be sent.  It may be
925   *            {@code null} for historic reasons, but should be
926   *            non-{@code null} in new uses.
927   */
928  public static void debugLDAPRequest(@NotNull final LDAPRequest r, final int i,
929                                      @Nullable final LDAPConnection c)
930  {
931    if (debugEnabled && debugTypes.contains(DebugType.LDAP))
932    {
933      debugLDAPRequest(Level.INFO, r, i, c);
934    }
935  }
936
937
938
939  /**
940   * Writes debug information about the provided request, if appropriate.
941   *
942   * @param  l  The log level that should be used for the debug information.
943   * @param  r  The LDAP request for which debug information should be written.
944   * @param  i  The message ID for the request that will be sent.  It may be
945   *            negative if no message ID is available.
946   * @param  c  The connection on which the request will be sent.  It may be
947   *            {@code null} for historic reasons, but should be
948   *            non-{@code null} in new uses.
949   */
950  public static void debugLDAPRequest(@NotNull final Level l,
951                                      @NotNull final LDAPRequest r,
952                                      final int i,
953                                      @Nullable final LDAPConnection c)
954  {
955    if (debugEnabled && debugTypes.contains(DebugType.LDAP))
956    {
957      debugLDAPRequest(l, String.valueOf(r), i, c);
958    }
959  }
960
961
962
963  /**
964   * Writes debug information about the provided request, if appropriate.
965   *
966   * @param  l  The log level that should be used for the debug information.
967   * @param  s  A string representation of the LDAP request for which debug
968   *            information should be written.
969   * @param  i  The message ID for the request that will be sent.  It may be
970   *            negative if no message ID is available.
971   * @param  c  The connection on which the request will be sent.  It may be
972   *            {@code null} for historic reasons, but should be
973   *            non-{@code null} in new uses.
974   */
975  public static void debugLDAPRequest(@NotNull final Level l,
976                                      @NotNull final String s,
977                                      final int i,
978                                      @Nullable final LDAPConnection c)
979  {
980    if (debugEnabled && debugTypes.contains(DebugType.LDAP))
981    {
982      final JSONBuffer buffer = new JSONBuffer(
983           null, JSONBuffer.DEFAULT_MAX_BUFFER_SIZE, useMultiLineDebugMessages);
984      addCommonHeader(buffer, l, DebugType.LDAP);
985
986      if (c != null)
987      {
988        buffer.appendNumber("connection-id", c.getConnectionID());
989
990        final String connectionName = c.getConnectionName();
991        if (connectionName != null)
992        {
993          buffer.appendString("connection-name", connectionName);
994        }
995
996        final String connectionPoolName = c.getConnectionPoolName();
997        if (connectionPoolName != null)
998        {
999          buffer.appendString("connection-pool-name", connectionPoolName);
1000        }
1001
1002        final String connectedAddress = c.getConnectedAddress();
1003        if (connectedAddress != null)
1004        {
1005          buffer.appendString("connected-to-address", connectedAddress);
1006          buffer.appendNumber("connected-to-port", c.getConnectedPort());
1007        }
1008
1009        try
1010        {
1011          final int soTimeout = InternalSDKHelper.getSoTimeout(c);
1012          buffer.appendNumber("socket-timeout-millis", soTimeout);
1013        } catch (final Exception e) {}
1014      }
1015
1016      if (i >= 0)
1017      {
1018        buffer.appendNumber("message-id", i);
1019      }
1020
1021      buffer.appendString("sending-ldap-request", s);
1022
1023      addCommonFooter(buffer);
1024      log(l,  buffer);
1025    }
1026  }
1027
1028
1029
1030  /**
1031   * Writes debug information about the provided result, if appropriate.  If
1032   * it is to be logged, then it will be sent to the underlying logger using the
1033   * {@code INFO} level.
1034   *
1035   * @param  r  The result for which debug information should be written.
1036   */
1037  public static void debugLDAPResult(@NotNull final LDAPResponse r)
1038  {
1039    if (debugEnabled && debugTypes.contains(DebugType.LDAP))
1040    {
1041      debugLDAPResult(Level.INFO, r, null);
1042    }
1043  }
1044
1045
1046
1047  /**
1048   * Writes debug information about the provided result, if appropriate.
1049   *
1050   * @param  l  The log level that should be used for the debug information.
1051   * @param  r  The result for which debug information should be written.
1052   */
1053  public static void debugLDAPResult(@NotNull final Level l,
1054                                     @NotNull final LDAPResponse r)
1055  {
1056    if (debugEnabled && debugTypes.contains(DebugType.LDAP))
1057    {
1058      debugLDAPResult(l, r, null);
1059    }
1060  }
1061
1062
1063
1064  /**
1065   * Writes debug information about the provided result, if appropriate.  If
1066   * it is to be logged, then it will be sent to the underlying logger using the
1067   * {@code INFO} level.
1068   *
1069   * @param  r  The result for which debug information should be written.
1070   * @param  c  The connection on which the response was received.  It may be
1071   *            {@code null} for historic reasons, but should be
1072   *            non-{@code null} in new uses.
1073   */
1074  public static void debugLDAPResult(@NotNull final LDAPResponse r,
1075                                     @Nullable final LDAPConnection c)
1076  {
1077    if (debugEnabled && debugTypes.contains(DebugType.LDAP))
1078    {
1079      debugLDAPResult(Level.INFO, r, c);
1080    }
1081  }
1082
1083
1084
1085  /**
1086   * Writes debug information about the provided result, if appropriate.
1087   *
1088   * @param  l  The log level that should be used for the debug information.
1089   * @param  r  The result for which debug information should be written.
1090   * @param  c  The connection on which the response was received.  It may be
1091   *            {@code null} for historic reasons, but should be
1092   *            non-{@code null} in new uses.
1093   */
1094  public static void debugLDAPResult(@NotNull final Level l,
1095                                     @NotNull final LDAPResponse r,
1096                                     @Nullable final LDAPConnection c)
1097  {
1098    if (debugEnabled && debugTypes.contains(DebugType.LDAP))
1099    {
1100      final JSONBuffer buffer = new JSONBuffer(
1101           null, JSONBuffer.DEFAULT_MAX_BUFFER_SIZE, useMultiLineDebugMessages);
1102      addCommonHeader(buffer, l, DebugType.LDAP);
1103
1104      if (c != null)
1105      {
1106        buffer.appendNumber("connection-id", c.getConnectionID());
1107
1108        final String connectionName = c.getConnectionName();
1109        if (connectionName != null)
1110        {
1111          buffer.appendString("connection-name", connectionName);
1112        }
1113
1114        final String connectionPoolName = c.getConnectionPoolName();
1115        if (connectionPoolName != null)
1116        {
1117          buffer.appendString("connection-pool-name", connectionPoolName);
1118        }
1119
1120        final String connectedAddress = c.getConnectedAddress();
1121        if (connectedAddress != null)
1122        {
1123          buffer.appendString("connected-to-address", connectedAddress);
1124          buffer.appendNumber("connected-to-port", c.getConnectedPort());
1125        }
1126      }
1127
1128      buffer.appendString("read-ldap-result", r.toString());
1129
1130      addCommonFooter(buffer);
1131      log(l, buffer);
1132    }
1133  }
1134
1135
1136
1137  /**
1138   * Writes debug information about the provided ASN.1 element to be written,
1139   * if appropriate.  If it is to be logged, then it will be sent to the
1140   * underlying logger using the {@code INFO} level.
1141   *
1142   * @param  e  The ASN.1 element for which debug information should be written.
1143   */
1144  public static void debugASN1Write(@NotNull final ASN1Element e)
1145  {
1146    if (debugEnabled && debugTypes.contains(DebugType.ASN1))
1147    {
1148      debugASN1Write(Level.INFO, e);
1149    }
1150  }
1151
1152
1153
1154  /**
1155   * Writes debug information about the provided ASN.1 element to be written,
1156   * if appropriate.
1157   *
1158   * @param  l  The log level that should be used for the debug information.
1159   * @param  e  The ASN.1 element for which debug information should be written.
1160   */
1161  public static void debugASN1Write(@NotNull final Level l,
1162                                    @NotNull final ASN1Element e)
1163  {
1164    if (debugEnabled && debugTypes.contains(DebugType.ASN1))
1165    {
1166      final JSONBuffer buffer = new JSONBuffer(
1167           null, JSONBuffer.DEFAULT_MAX_BUFFER_SIZE, useMultiLineDebugMessages);
1168      addCommonHeader(buffer, l, DebugType.ASN1);
1169      buffer.appendString("writing-asn1-element", e.toString());
1170
1171      addCommonFooter(buffer);
1172      log(l, buffer);
1173    }
1174  }
1175
1176
1177
1178  /**
1179   * Writes debug information about the provided ASN.1 element to be written,
1180   * if appropriate.  If it is to be logged, then it will be sent to the
1181   * underlying logger using the {@code INFO} level.
1182   *
1183   * @param  b  The ASN.1 buffer with the information to be written.
1184   */
1185  public static void debugASN1Write(@NotNull final ASN1Buffer b)
1186  {
1187    if (debugEnabled && debugTypes.contains(DebugType.ASN1))
1188    {
1189      debugASN1Write(Level.INFO, b);
1190    }
1191  }
1192
1193
1194
1195  /**
1196   * Writes debug information about the provided ASN.1 element to be written,
1197   * if appropriate.
1198   *
1199   * @param  l  The log level that should be used for the debug information.
1200   * @param  b  The ASN1Buffer with the information to be written.
1201   */
1202  public static void debugASN1Write(@NotNull final Level l,
1203                                    @NotNull final ASN1Buffer b)
1204  {
1205    if (debugEnabled && debugTypes.contains(DebugType.ASN1))
1206    {
1207      final JSONBuffer buffer = new JSONBuffer(
1208           null, JSONBuffer.DEFAULT_MAX_BUFFER_SIZE, useMultiLineDebugMessages);
1209      addCommonHeader(buffer, l, DebugType.ASN1);
1210      buffer.appendString("writing-asn1-element",
1211           StaticUtils.toHex(b.toByteArray()));
1212
1213      addCommonFooter(buffer);
1214      log(l, buffer);
1215    }
1216  }
1217
1218
1219
1220  /**
1221   * Writes debug information about the provided ASN.1 element that was read, if
1222   * appropriate.  If it is to be logged, then it will be sent to the underlying
1223   * logger using the {@code INFO} level.
1224   *
1225   * @param  e  The ASN.1 element for which debug information should be written.
1226   */
1227  public static void debugASN1Read(@NotNull final ASN1Element e)
1228  {
1229    if (debugEnabled && debugTypes.contains(DebugType.ASN1))
1230    {
1231      debugASN1Read(Level.INFO, e);
1232    }
1233  }
1234
1235
1236
1237  /**
1238   * Writes debug information about the provided ASN.1 element that was read, if
1239   * appropriate.
1240   *
1241   * @param  l  The log level that should be used for the debug information.
1242   * @param  e  The ASN.1 element for which debug information should be written.
1243   */
1244  public static void debugASN1Read(@NotNull final Level l,
1245                                   @NotNull final ASN1Element e)
1246  {
1247    if (debugEnabled && debugTypes.contains(DebugType.ASN1))
1248    {
1249      final JSONBuffer buffer = new JSONBuffer(
1250           null, JSONBuffer.DEFAULT_MAX_BUFFER_SIZE, useMultiLineDebugMessages);
1251      addCommonHeader(buffer, l, DebugType.ASN1);
1252      buffer.appendString("read-asn1-element", e.toString());
1253
1254      addCommonFooter(buffer);
1255      log(l, buffer);
1256    }
1257  }
1258
1259
1260
1261  /**
1262   * Writes debug information about the provided ASN.1 element that was read, if
1263   * appropriate.
1264   *
1265   * @param  l         The log level that should be used for the debug
1266   *                   information.
1267   * @param  dataType  A string representation of the data type for the data
1268   *                   that was read.
1269   * @param  berType   The BER type for the element that was read.
1270   * @param  length    The number of bytes in the value of the element that was
1271   *                   read.
1272   * @param  value     A representation of the value that was read.  The debug
1273   *                   message will include the string representation of this
1274   *                   value, unless the value is a byte array in which it will
1275   *                   be a hex representation of the bytes that it contains.
1276   *                   It may be {@code null} for an ASN.1 null element.
1277   */
1278  public static void debugASN1Read(@NotNull final Level l,
1279                                   @NotNull final String dataType,
1280                                   final int berType, final int length,
1281                                   @Nullable final Object value)
1282  {
1283    if (debugEnabled && debugTypes.contains(DebugType.ASN1))
1284    {
1285      final JSONBuffer buffer = new JSONBuffer(
1286           null, JSONBuffer.DEFAULT_MAX_BUFFER_SIZE, useMultiLineDebugMessages);
1287      addCommonHeader(buffer, l, DebugType.ASN1);
1288
1289      buffer.beginObject("read-asn1-element");
1290      buffer.appendString("data-type", dataType);
1291      buffer.appendString("ber-type",
1292           StaticUtils.toHex((byte) (berType & 0xFF)));
1293      buffer.appendNumber("value-length", length);
1294
1295      if (value != null)
1296      {
1297        if (value instanceof byte[])
1298        {
1299          buffer.appendString("value-bytes",
1300               StaticUtils.toHex((byte[]) value));
1301        }
1302        else
1303        {
1304          buffer.appendString("value-string", value.toString());
1305        }
1306      }
1307
1308      buffer.endObject();
1309
1310      addCommonFooter(buffer);
1311      log(l, buffer);
1312    }
1313  }
1314
1315
1316
1317  /**
1318   * Writes debug information about interaction with a connection pool.
1319   *
1320   * @param  l  The log level that should be used for the debug information.
1321   * @param  p  The associated connection pool.
1322   * @param  c  The associated LDAP connection, if appropriate.
1323   * @param  m  A message with information about the pool interaction.
1324   * @param  e  An exception to include with the log message, if appropriate.
1325   */
1326  public static void debugConnectionPool(@NotNull final Level l,
1327                          @NotNull final AbstractConnectionPool p,
1328                          @Nullable final LDAPConnection c,
1329                          @Nullable final String m, @Nullable final Throwable e)
1330  {
1331    if (debugEnabled && debugTypes.contains(DebugType.CONNECTION_POOL))
1332    {
1333      final JSONBuffer buffer = new JSONBuffer(
1334           null, JSONBuffer.DEFAULT_MAX_BUFFER_SIZE, useMultiLineDebugMessages);
1335      addCommonHeader(buffer, l, DebugType.CONNECTION_POOL);
1336
1337      final String poolName = p.getConnectionPoolName();
1338      if (poolName == null)
1339      {
1340        buffer.appendNull("connection-pool-name");
1341      }
1342      else
1343      {
1344        buffer.appendString("connection-pool-name", poolName);
1345      }
1346
1347      if (c != null)
1348      {
1349        buffer.appendNumber("connection-id", c.getConnectionID());
1350
1351        final String connectedAddress = c.getConnectedAddress();
1352        if (connectedAddress != null)
1353        {
1354          buffer.appendString("connected-to-address", connectedAddress);
1355          buffer.appendNumber("connected-to-port", c.getConnectedPort());
1356        }
1357      }
1358
1359      final long currentAvailable = p.getCurrentAvailableConnections();
1360      if (currentAvailable >= 0)
1361      {
1362        buffer.appendNumber("current-available-connections", currentAvailable);
1363      }
1364
1365      final long maxAvailable = p.getMaximumAvailableConnections();
1366      if (maxAvailable >= 0)
1367      {
1368        buffer.appendNumber("maximum-available-connections", maxAvailable);
1369      }
1370
1371      if (m != null)
1372      {
1373        buffer.appendString("message", m);
1374      }
1375
1376      if (e != null)
1377      {
1378        addCaughtException(buffer, "caught-exception", e);
1379      }
1380
1381      addCommonFooter(buffer);
1382      log(l, buffer, e);
1383    }
1384  }
1385
1386
1387
1388  /**
1389   * Writes debug information about the provided LDIF record to be written, if
1390   * if appropriate.  If it is to be logged, then it will be sent to the
1391   * underlying logger using the {@code INFO} level.
1392   *
1393   * @param  r  The LDIF record for which debug information should be written.
1394   */
1395  public static void debugLDIFWrite(@NotNull final LDIFRecord r)
1396  {
1397    if (debugEnabled && debugTypes.contains(DebugType.LDIF))
1398    {
1399      debugLDIFWrite(Level.INFO, r);
1400    }
1401  }
1402
1403
1404
1405  /**
1406   * Writes debug information about the provided LDIF record to be written, if
1407   * appropriate.
1408   *
1409   * @param  l  The log level that should be used for the debug information.
1410   * @param  r  The LDIF record for which debug information should be written.
1411   */
1412  public static void debugLDIFWrite(@NotNull final Level l,
1413                                    @NotNull final LDIFRecord r)
1414  {
1415    if (debugEnabled && debugTypes.contains(DebugType.LDIF))
1416    {
1417      final JSONBuffer buffer = new JSONBuffer(
1418           null, JSONBuffer.DEFAULT_MAX_BUFFER_SIZE, useMultiLineDebugMessages);
1419      addCommonHeader(buffer, l, DebugType.LDIF);
1420      buffer.appendString("writing-ldif-record", r.toString());
1421
1422      addCommonFooter(buffer);
1423      log(l, buffer);
1424    }
1425  }
1426
1427
1428
1429  /**
1430   * Writes debug information about the provided record read from LDIF, if
1431   * appropriate.  If it is to be logged, then it will be sent to the underlying
1432   * logger using the {@code INFO} level.
1433   *
1434   * @param  r  The LDIF record for which debug information should be written.
1435   */
1436  public static void debugLDIFRead(@NotNull final LDIFRecord r)
1437  {
1438    if (debugEnabled && debugTypes.contains(DebugType.LDIF))
1439    {
1440      debugLDIFRead(Level.INFO, r);
1441    }
1442  }
1443
1444
1445
1446  /**
1447   * Writes debug information about the provided record read from LDIF, if
1448   * appropriate.
1449   *
1450   * @param  l  The log level that should be used for the debug information.
1451   * @param  r  The LDIF record for which debug information should be written.
1452   */
1453  public static void debugLDIFRead(@NotNull final Level l,
1454                                   @NotNull final LDIFRecord r)
1455  {
1456    if (debugEnabled && debugTypes.contains(DebugType.LDIF))
1457    {
1458      final JSONBuffer buffer = new JSONBuffer(
1459           null, JSONBuffer.DEFAULT_MAX_BUFFER_SIZE, useMultiLineDebugMessages);
1460      addCommonHeader(buffer, l, DebugType.LDIF);
1461      buffer.appendString("read-ldif-record", r.toString());
1462
1463      addCommonFooter(buffer);
1464      log(l, buffer);
1465    }
1466  }
1467
1468
1469
1470  /**
1471   * Writes debug information about monitor entry parsing.  If it is to be
1472   * logged, then it will be sent to the underlying logger using the
1473   * {@code FINE} level.
1474   *
1475   * @param  e  The entry containing the monitor information being parsed.
1476   * @param  m  The message to be written to the debug logger.
1477   */
1478  public static void debugMonitor(@Nullable final Entry e,
1479                                  @Nullable final String m)
1480  {
1481    if (debugEnabled && debugTypes.contains(DebugType.MONITOR))
1482    {
1483      debugMonitor(Level.FINE, e, m);
1484    }
1485  }
1486
1487
1488
1489  /**
1490   * Writes debug information about monitor entry parsing, if appropriate.
1491   *
1492   * @param  l  The log level that should be used for the debug information.
1493   * @param  e  The entry containing the monitor information being parsed.
1494   * @param  m  The message to be written to the debug logger.
1495   */
1496  public static void debugMonitor(@NotNull final Level l,
1497                                  @Nullable final Entry e,
1498                                  @Nullable final String m)
1499  {
1500    if (debugEnabled && debugTypes.contains(DebugType.MONITOR))
1501    {
1502      final JSONBuffer buffer = new JSONBuffer(
1503           null, JSONBuffer.DEFAULT_MAX_BUFFER_SIZE, useMultiLineDebugMessages);
1504      addCommonHeader(buffer, l, DebugType.MONITOR);
1505
1506      if (e != null)
1507      {
1508        buffer.appendString("monitor-entry-dn", e.getDN());
1509      }
1510
1511      if (m != null)
1512      {
1513        buffer.appendString("message", m);
1514      }
1515
1516      addCommonFooter(buffer);
1517      log(l, buffer);
1518    }
1519  }
1520
1521
1522
1523  /**
1524   * Writes debug information about a coding error detected in the use of the
1525   * LDAP SDK.  If it is to be logged, then it will be sent to the underlying
1526   * logger using the {@code SEVERE} level.
1527   *
1528   * @param  t  The {@code Throwable} object that was created and will be thrown
1529   *            as a result of the coding error.
1530   */
1531  public static void debugCodingError(@NotNull final Throwable t)
1532  {
1533    if (debugEnabled && debugTypes.contains(DebugType.CODING_ERROR))
1534    {
1535      final JSONBuffer buffer = new JSONBuffer(
1536           null, JSONBuffer.DEFAULT_MAX_BUFFER_SIZE, useMultiLineDebugMessages);
1537      addCommonHeader(buffer, Level.SEVERE, DebugType.CODING_ERROR);
1538      addCaughtException(buffer, "coding-error", t);
1539
1540      addCommonFooter(buffer);
1541      log(Level.SEVERE, buffer, t);
1542    }
1543  }
1544
1545
1546
1547  /**
1548   * Writes a generic debug message, if appropriate.
1549   *
1550   * @param  l  The log level that should be used for the debug information.
1551   * @param  t  The debug type to use to determine whether to write the message.
1552   * @param  m  The message to be written.
1553   */
1554  public static void debug(@NotNull final Level l,
1555                           @NotNull final DebugType t, @Nullable final String m)
1556  {
1557    if (debugEnabled && debugTypes.contains(t))
1558    {
1559      final JSONBuffer buffer = new JSONBuffer(
1560           null, JSONBuffer.DEFAULT_MAX_BUFFER_SIZE, useMultiLineDebugMessages);
1561      addCommonHeader(buffer, l, t);
1562
1563      if (m != null)
1564      {
1565        buffer.appendString("message", m);
1566      }
1567
1568      addCommonFooter(buffer);
1569      log(l, buffer);
1570    }
1571  }
1572
1573
1574
1575  /**
1576   * Writes a generic debug message, if appropriate.
1577   *
1578   * @param  l  The log level that should be used for the debug information.
1579   * @param  t  The debug type to use to determine whether to write the message.
1580   * @param  m  The message to be written.
1581   * @param  e  An exception to include with the log message.
1582   */
1583  public static void debug(@NotNull final Level l, @NotNull final DebugType t,
1584                           @Nullable final String m,
1585                           @Nullable final Throwable e)
1586  {
1587    if (debugEnabled && debugTypes.contains(t))
1588    {
1589      final JSONBuffer buffer = new JSONBuffer(
1590           null, JSONBuffer.DEFAULT_MAX_BUFFER_SIZE, useMultiLineDebugMessages);
1591      addCommonHeader(buffer, l, t);
1592
1593      if (m != null)
1594      {
1595        buffer.appendString("message", m);
1596      }
1597
1598      if (e != null)
1599      {
1600        addCaughtException(buffer, "caught-exception", e);
1601      }
1602
1603      addCommonFooter(buffer);
1604      log(l, buffer, e);
1605    }
1606  }
1607
1608
1609
1610  /**
1611   * Adds common header information to the provided JSON buffer.  It will begin
1612   * a JSON object for the log message, then add a timestamp, debug type, log
1613   * level, thread ID, and thread name.
1614   *
1615   * @param  buffer  The JSON buffer to which the content should be added.
1616   * @param  level   The log level for the message that will be written.
1617   * @param  type    The debug type for the message that will be written.
1618   */
1619  private static void addCommonHeader(@NotNull final JSONBuffer buffer,
1620                                      @NotNull final Level level,
1621                                      @NotNull final DebugType type)
1622  {
1623    buffer.beginObject();
1624    buffer.appendString("timestamp", getTimestamp());
1625    buffer.appendString("debug-type", type.getName());
1626    buffer.appendString("level", level.getName());
1627
1628    final Thread t = Thread.currentThread();
1629    buffer.appendNumber("thread-id", t.getId());
1630    buffer.appendString("thread-name", t.getName());
1631  }
1632
1633
1634
1635  /**
1636   * Retrieves a timestamp that represents the current time.
1637   *
1638   * @return  A timestamp that represents the current time.
1639   */
1640  @NotNull()
1641  private static String getTimestamp()
1642  {
1643    SimpleDateFormat timestampFormatter = TIMESTAMP_FORMATTERS.get();
1644    if (timestampFormatter == null)
1645    {
1646      timestampFormatter =
1647           new SimpleDateFormat("yyyy'-'MM'-'dd'T'HH':'mm':'ss.SSS'Z'");
1648      timestampFormatter.setTimeZone(StaticUtils.getUTCTimeZone());
1649      TIMESTAMP_FORMATTERS.set(timestampFormatter);
1650    }
1651
1652    return timestampFormatter.format(new Date());
1653  }
1654
1655
1656
1657  /**
1658   * Creates a formatted string representation of the provided stack trace
1659   * frame.
1660   *
1661   * @param  e  The stack trace element to be formatted.
1662   *
1663   * @return  The formatted string representation of the provided stack trace
1664   *          frame.
1665   */
1666  @NotNull()
1667  private static String formatStackTraceFrame(
1668                             @NotNull final StackTraceElement e)
1669  {
1670    final StringBuilder buffer = new StringBuilder();
1671    buffer.append(e.getMethodName());
1672    buffer.append('(');
1673    buffer.append(e.getFileName());
1674
1675    final int lineNumber = e.getLineNumber();
1676    if (lineNumber > 0)
1677    {
1678      buffer.append(':');
1679      buffer.append(lineNumber);
1680    }
1681    else if (e.isNativeMethod())
1682    {
1683      buffer.append(":native");
1684    }
1685
1686    buffer.append(')');
1687    return buffer.toString();
1688  }
1689
1690
1691
1692  /**
1693   * Adds information about a caught exception to the provided JSON buffer.
1694   *
1695   * @param  buffer     The JSON buffer to which the information should be
1696   *                    appended.
1697   * @param  fieldName  The name to use for the new field to be added with the
1698   *                    exception information.
1699   * @param  t          The exception to be included.
1700   */
1701  private static void addCaughtException(@NotNull final JSONBuffer buffer,
1702                                         @NotNull final String fieldName,
1703                                         @Nullable final Throwable t)
1704  {
1705    if (t == null)
1706    {
1707      return;
1708    }
1709
1710    buffer.beginObject(fieldName);
1711
1712    final String message = t.getMessage();
1713    if (message != null)
1714    {
1715      buffer.appendString("message", message);
1716    }
1717
1718    buffer.beginArray("stack-trace");
1719    for (final StackTraceElement e : t.getStackTrace())
1720    {
1721      buffer.appendString(formatStackTraceFrame(e));
1722    }
1723    buffer.endArray();
1724
1725    final Throwable cause = t.getCause();
1726    if (cause != null)
1727    {
1728      addCaughtException(buffer, "cause", cause);
1729    }
1730
1731    buffer.endObject();
1732  }
1733
1734
1735
1736  /**
1737   * Adds common footer information to the provided JSON buffer.  It will
1738   * include an optional caller stack trace, along with the LDAP SDK version
1739   * and revision.  It will also end the object that encapsulates the log
1740   * message.
1741   *
1742   * @param  buffer  The JSON buffer to which the content should be added.
1743   */
1744  private static void addCommonFooter(@NotNull final JSONBuffer buffer)
1745  {
1746    if (includeStackTrace)
1747    {
1748      buffer.beginArray("caller-stack-trace");
1749
1750      boolean foundDebug = false;
1751      for (final StackTraceElement e : Thread.currentThread().getStackTrace())
1752      {
1753        final String className = e.getClassName();
1754        if (className.equals(Debug.class.getName()))
1755        {
1756          foundDebug = true;
1757        }
1758        else if (foundDebug)
1759        {
1760          buffer.appendString(formatStackTraceFrame(e));
1761        }
1762      }
1763
1764      buffer.endArray();
1765    }
1766
1767    buffer.appendString("ldap-sdk-version", Version.NUMERIC_VERSION_STRING);
1768    buffer.appendString("ldap-sdk-revision", Version.REVISION_ID);
1769    buffer.endObject();
1770  }
1771
1772
1773
1774  /**
1775   * Logs a JSON-formatted debug message with the given level and fields.
1776   *
1777   * @param  level   The log level to use for the message.
1778   * @param  buffer  The JSON buffer containing the message to be written.
1779   */
1780  private static void log(@NotNull final Level level,
1781                          @NotNull final JSONBuffer buffer)
1782  {
1783    logger.log(level, buffer.toString());
1784  }
1785
1786
1787
1788  /**
1789   * Logs a JSON-formatted debug message with the given level and fields.
1790   *
1791   * @param  level   The log level to use for the message.
1792   * @param  buffer  The JSON buffer containing the message to be written.
1793   * @param  thrown  An exception to be included with the debug message.
1794   */
1795  private static void log(@NotNull final Level level,
1796                          @NotNull final JSONBuffer buffer,
1797                          @Nullable final Throwable thrown)
1798  {
1799    logger.log(level, buffer.toString(), thrown);
1800  }
1801
1802
1803
1804  /**
1805   * Appends the provided debug message to the specified file.  This method
1806   * should be safe to call concurrently, even across multiple processes.
1807   *
1808   * @param  path     The path to the file to which the message should be
1809   *                  appended.  It must not be {@code null}.
1810   * @param  message  The debug message to be appended to the file.  It must not
1811   *                  be {@code null}.
1812   */
1813  public static void debugToFile(@NotNull final String path,
1814                                 @NotNull final String message)
1815  {
1816    debugToFile(new File(path), true, true, message);
1817  }
1818
1819
1820
1821  /**
1822   * Appends the provided debug message to the specified file.  This method
1823   * should be safe to call concurrently, even across multiple processes.
1824   *
1825   * @param  file     The file to which the message should be appended.  It must
1826   *                  not be {@code null}.
1827   * @param  message  The debug message to be appended to the file.  It must not
1828   *                  be {@code null}.
1829   */
1830  public static void debugToFile(@NotNull final File file,
1831                                 @NotNull final String message)
1832  {
1833    debugToFile(file, true, true, message);
1834  }
1835
1836
1837
1838  /**
1839   * Appends the provided debug message to the specified file.  This method
1840   * should be safe to call concurrently, even across multiple processes.
1841   *
1842   * @param  file               The file to which the message should be
1843   *                            appended.  It must not be {@code null}.
1844   * @param  includeTimestamp   Indicates whether to include a timestamp along
1845   *                            with the debug message.
1846   * @param  includeStackTrace  Indicates whether to include a stack trace along
1847   *                            with the debug message.
1848   * @param  message            The debug message to be appended to the file.
1849   *                            It must not be {@code null}.
1850   */
1851  public static synchronized void debugToFile(@NotNull final File file,
1852                                              final boolean includeTimestamp,
1853                                              final boolean includeStackTrace,
1854                                              @NotNull final String message)
1855  {
1856    try
1857    {
1858      try (FileChannel fileChannel = FileChannel.open(
1859           file.toPath(),
1860           StaticUtils.setOf(
1861                StandardOpenOption.WRITE,
1862                StandardOpenOption.CREATE,
1863                StandardOpenOption.APPEND,
1864                StandardOpenOption.SYNC)))
1865      {
1866        try (FileLock fileLock = fileChannel.lock())
1867        {
1868          // We need to reference the fileLock variable inside the try block to
1869          // avoid a compiler warning.
1870          Validator.ensureTrue(fileLock.isValid());
1871
1872          final ByteStringBuffer messageBuffer = new ByteStringBuffer();
1873
1874          if (fileChannel.size() > 0L)
1875          {
1876            messageBuffer.append(StaticUtils.EOL_BYTES);
1877          }
1878
1879          if (includeTimestamp)
1880          {
1881            messageBuffer.append(
1882                 StaticUtils.encodeRFC3339Time(System.currentTimeMillis()));
1883            messageBuffer.append(StaticUtils.EOL_BYTES);
1884          }
1885
1886          messageBuffer.append(message);
1887          messageBuffer.append(StaticUtils.EOL_BYTES);
1888
1889          if (includeStackTrace)
1890          {
1891            messageBuffer.append(StaticUtils.getStackTrace(
1892                 Thread.currentThread().getStackTrace()));
1893            messageBuffer.append(StaticUtils.EOL_BYTES);
1894          }
1895
1896          fileChannel.write(ByteBuffer.wrap(
1897               messageBuffer.getBackingArray(), 0, messageBuffer.length()));
1898        }
1899      }
1900    }
1901    catch (final Exception e)
1902    {
1903      // An error occurred while attempting to write to the file.  As a
1904      // fallback, print a message about it to standard error.
1905      Debug.debugException(e);
1906      System.err.println(ERR_DEBUG_CANNOT_WRITE_TO_FILE.get(
1907           file.getAbsolutePath(), StaticUtils.getExceptionMessage(e),
1908           message));
1909    }
1910  }
1911
1912
1913
1914  /**
1915   * Attempts to parse the provided string as a debug log level.
1916   *
1917   * @param  levelString  The string representation of the level to use.  It
1918   *                      must not be {@code null} or empty.
1919   *
1920   * @return  The log level that was parsed.
1921   *
1922   * @throws  IllegalArgumentException  If the provided string cannot be parsed
1923   *                                    as a valid debug log level.
1924   */
1925  @NotNull()
1926  public static Level parseDebugLogLevel(@NotNull final String levelString)
1927         throws IllegalArgumentException
1928  {
1929    final String lowerLevelString = StaticUtils.toLowerCase(levelString);
1930    switch (lowerLevelString)
1931    {
1932      case "off":
1933      case "none":
1934      case "disabled":
1935        return Level.OFF;
1936      case "severe":
1937      case "error":
1938        return Level.SEVERE;
1939      case "warning":
1940      case "warn":
1941        return Level.WARNING;
1942      case "info":
1943      case "information":
1944      case "informational":
1945        return Level.INFO;
1946      case "config":
1947      case "configuration":
1948        return Level.CONFIG;
1949      case "fine":
1950        return Level.FINE;
1951      case "finer":
1952        return Level.FINER;
1953      case "finest":
1954      case "verbose":
1955        return Level.FINEST;
1956      case "all":
1957        return Level.ALL;
1958      default:
1959        try
1960        {
1961          return Level.parse(levelString);
1962        }
1963        catch (final IllegalArgumentException e)
1964        {
1965          throw new IllegalArgumentException(
1966               ERR_DEBUG_CANNOT_PARSE_LEVEL.get(levelString));
1967        }
1968    }
1969  }
1970}