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