001/*
002 * Copyright 2008-2020 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2008-2020 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-2020 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.Serializable;
041import java.text.SimpleDateFormat;
042import java.util.Date;
043import java.util.EnumSet;
044import java.util.Properties;
045import java.util.Set;
046import java.util.StringTokenizer;
047import java.util.logging.Level;
048import java.util.logging.Logger;
049
050import com.unboundid.asn1.ASN1Buffer;
051import com.unboundid.asn1.ASN1Element;
052import com.unboundid.ldap.protocol.LDAPResponse;
053import com.unboundid.ldap.sdk.AbstractConnectionPool;
054import com.unboundid.ldap.sdk.DisconnectType;
055import com.unboundid.ldap.sdk.Entry;
056import com.unboundid.ldap.sdk.InternalSDKHelper;
057import com.unboundid.ldap.sdk.LDAPConnection;
058import com.unboundid.ldap.sdk.LDAPRequest;
059import com.unboundid.ldap.sdk.Version;
060import com.unboundid.ldif.LDIFRecord;
061import com.unboundid.util.json.JSONBuffer;
062
063
064
065/**
066 * This class provides a means of enabling and configuring debugging in the LDAP
067 * SDK.
068 * <BR><BR>
069 * Access to debug information can be enabled through applications that use the
070 * SDK by calling the {@link Debug#setEnabled} methods, or it can also be
071 * enabled without any code changes through the use of system properties.  In
072 * particular, the {@link Debug#PROPERTY_DEBUG_ENABLED},
073 * {@link Debug#PROPERTY_DEBUG_LEVEL}, and {@link Debug#PROPERTY_DEBUG_TYPE}
074 * properties may be used to control debugging without the need to alter any
075 * code within the application that uses the SDK.
076 * <BR><BR>
077 * The LDAP SDK debugging subsystem uses the Java logging framework available
078 * through the {@code java.util.logging} package with a logger name of
079 * "{@code com.unboundid.ldap.sdk}".  The {@link Debug#getLogger} method may
080 * be used to access the logger instance used by the LDAP SDK.
081 * <BR><BR>
082 * <H2>Example</H2>
083 * The following example demonstrates the process that may be used to enable
084 * debugging within the LDAP SDK and write information about all messages with
085 * a {@code WARNING} level or higher to a specified file:
086 * <PRE>
087 * Debug.setEnabled(true);
088 * Logger logger = Debug.getLogger();
089 *
090 * FileHandler fileHandler = new FileHandler(logFilePath);
091 * fileHandler.setLevel(Level.WARNING);
092 * logger.addHandler(fileHandler);
093 * </PRE>
094 */
095@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
096public final class Debug
097       implements Serializable
098{
099  /**
100   * The name of the system property that will be used to enable debugging in
101   * the UnboundID LDAP SDK for Java.  The fully-qualified name for this
102   * property is "{@code com.unboundid.ldap.sdk.debug.enabled}".  If it is set,
103   * then it should have a value of either "true" or "false".
104   */
105  public static final String PROPERTY_DEBUG_ENABLED =
106       "com.unboundid.ldap.sdk.debug.enabled";
107
108
109
110  /**
111   * The name of the system property that may be used to indicate whether stack
112   * trace information for the thread calling the debug method should be
113   * included in debug log messages.  The fully-qualified name for this property
114   * is "{@code com.unboundid.ldap.sdk.debug.includeStackTrace}".  If it is set,
115   * then it should have a value of either "true" or "false".
116   */
117  public static final String PROPERTY_INCLUDE_STACK_TRACE =
118       "com.unboundid.ldap.sdk.debug.includeStackTrace";
119
120
121
122  /**
123   * The name of the system property that will be used to set the initial level
124   * for the debug logger.  The fully-qualified name for this property is
125   * "{@code com.unboundid.ldap.sdk.debug.level}".  If it is set, then it should
126   * be one of the strings "{@code SEVERE}", "{@code WARNING}", "{@code INFO}",
127   * "{@code CONFIG}", "{@code FINE}", "{@code FINER}", or "{@code FINEST}".
128   */
129  public static final String PROPERTY_DEBUG_LEVEL =
130       "com.unboundid.ldap.sdk.debug.level";
131
132
133
134  /**
135   * The name of the system property that will be used to indicate that
136   * debugging should be enabled for specific types of messages.  The
137   * fully-qualified name for this property is
138   * "{@code com.unboundid.ldap.sdk.debug.type}". If it is set, then it should
139   * be a comma-delimited list of the names of the desired debug types.  See the
140   * {@link DebugType} enum for the available debug types.
141   */
142  public static final String PROPERTY_DEBUG_TYPE =
143       "com.unboundid.ldap.sdk.debug.type";
144
145
146
147  /**
148   * The name of the system property that will be used to indicate whether the
149   * LDAP SDK should default to including information about the exception's
150   * cause in an exception message obtained from the
151   * {@link StaticUtils#getExceptionMessage(Throwable)} method.  By default,
152   * the cause will not be included in most messages.
153   */
154  public static final String PROPERTY_INCLUDE_CAUSE_IN_EXCEPTION_MESSAGES =
155       "com.unboundid.ldap.sdk.debug.includeCauseInExceptionMessages";
156
157
158
159  /**
160   * The name of the system property that will be used to indicate whether the
161   * LDAP SDK should default to including a full stack trace (albeit in
162   * condensed form) in an exception message obtained from the
163   * {@link StaticUtils#getExceptionMessage(Throwable)} method.  By default,
164   * stack traces will not be included in most messages.
165   */
166  public static final String
167       PROPERTY_INCLUDE_STACK_TRACE_IN_EXCEPTION_MESSAGES =
168            "com.unboundid.ldap.sdk.debug.includeStackTraceInExceptionMessages";
169
170
171
172  /**
173   * The name that will be used for the Java logger that will actually handle
174   * the debug messages if debugging is enabled.
175   */
176  public static final String LOGGER_NAME = "com.unboundid.ldap.sdk";
177
178
179
180  /**
181   * The logger that will be used to handle the debug messages if debugging is
182   * enabled.
183   */
184  private static final Logger logger = Logger.getLogger(LOGGER_NAME);
185
186
187
188  /**
189   * A set of thread-local formatters that may be used to generate timestamps.
190   */
191  private static final ThreadLocal<SimpleDateFormat> TIMESTAMP_FORMATTERS =
192       new ThreadLocal<>();
193
194
195
196  /**
197   * The serial version UID for this serializable class.
198   */
199  private static final long serialVersionUID = -6079754380415146030L;
200
201
202
203  // Indicates whether any debugging is currently enabled for the SDK.
204  private static boolean debugEnabled;
205
206  // Indicates whether to capture a thread stack trace whenever a debug message
207  // is logged.
208  private static boolean includeStackTrace;
209
210  // The set of debug types for which debugging is enabled.
211  private static EnumSet<DebugType> debugTypes;
212
213
214
215  static
216  {
217    initialize(StaticUtils.getSystemProperties(PROPERTY_DEBUG_ENABLED,
218         PROPERTY_DEBUG_LEVEL, PROPERTY_DEBUG_TYPE,
219         PROPERTY_INCLUDE_STACK_TRACE));
220  }
221
222
223
224  /**
225   * Prevent this class from being instantiated.
226   */
227  private Debug()
228  {
229    // No implementation is required.
230  }
231
232
233
234  /**
235   * Initializes this debugger with the default settings.  Debugging will be
236   * disabled, the set of debug types will include all types, and the debug
237   * level will be "ALL".
238   */
239  public static void initialize()
240  {
241    includeStackTrace = false;
242    debugEnabled      = false;
243    debugTypes        = EnumSet.allOf(DebugType.class);
244
245    StaticUtils.setLoggerLevel(logger, Level.ALL);
246  }
247
248
249
250  /**
251   * Initializes this debugger with settings from the provided set of
252   * properties.  Any debug setting that isn't configured in the provided
253   * properties will be initialized with its default value.
254   *
255   * @param  properties  The set of properties to use to initialize this
256   *                     debugger.
257   */
258  public static void initialize(final Properties properties)
259  {
260    // First, apply the default values for the properties.
261    initialize();
262    if ((properties == null) || properties.isEmpty())
263    {
264      // No properties were provided, so we don't need to do anything.
265      return;
266    }
267
268    final String enabledProp = properties.getProperty(PROPERTY_DEBUG_ENABLED);
269    if ((enabledProp != null) && (! enabledProp.isEmpty()))
270    {
271      if (enabledProp.equalsIgnoreCase("true"))
272      {
273        debugEnabled = true;
274      }
275      else if (enabledProp.equalsIgnoreCase("false"))
276      {
277        debugEnabled = false;
278      }
279      else
280      {
281        throw new IllegalArgumentException("Invalid value '" + enabledProp +
282                                           "' for property " +
283                                           PROPERTY_DEBUG_ENABLED +
284                                           ".  The value must be either " +
285                                           "'true' or 'false'.");
286      }
287    }
288
289    final String stackProp =
290         properties.getProperty(PROPERTY_INCLUDE_STACK_TRACE);
291    if ((stackProp != null) && (! stackProp.isEmpty()))
292    {
293      if (stackProp.equalsIgnoreCase("true"))
294      {
295        includeStackTrace = true;
296      }
297      else if (stackProp.equalsIgnoreCase("false"))
298      {
299        includeStackTrace = false;
300      }
301      else
302      {
303        throw new IllegalArgumentException("Invalid value '" + stackProp +
304                                           "' for property " +
305                                           PROPERTY_INCLUDE_STACK_TRACE +
306                                           ".  The value must be either " +
307                                           "'true' or 'false'.");
308      }
309    }
310
311    final String typesProp = properties.getProperty(PROPERTY_DEBUG_TYPE);
312    if ((typesProp != null) && (! typesProp.isEmpty()))
313    {
314      debugTypes = EnumSet.noneOf(DebugType.class);
315      final StringTokenizer t = new StringTokenizer(typesProp, ", ");
316      while (t.hasMoreTokens())
317      {
318        final String debugTypeName = t.nextToken();
319        final DebugType debugType = DebugType.forName(debugTypeName);
320        if (debugType == null)
321        {
322          // Throw a runtime exception to indicate that the debug type is
323          // invalid.
324          throw new IllegalArgumentException("Invalid value '" + debugTypeName +
325                      "' for property " + PROPERTY_DEBUG_TYPE +
326                      ".  Allowed values include:  " +
327                      DebugType.getTypeNameList() + '.');
328        }
329        else
330        {
331          debugTypes.add(debugType);
332        }
333      }
334    }
335
336    final String levelProp = properties.getProperty(PROPERTY_DEBUG_LEVEL);
337    if ((levelProp != null) && (! levelProp.isEmpty()))
338    {
339      StaticUtils.setLoggerLevel(logger, Level.parse(levelProp));
340    }
341  }
342
343
344
345  /**
346   * Retrieves the logger that will be used to write the debug messages.
347   *
348   * @return  The logger that will be used to write the debug messages.
349   */
350  public static Logger getLogger()
351  {
352    return logger;
353  }
354
355
356
357  /**
358   * Indicates whether any form of debugging is enabled.
359   *
360   * @return  {@code true} if debugging is enabled, or {@code false} if not.
361   */
362  public static boolean debugEnabled()
363  {
364    return debugEnabled;
365  }
366
367
368
369  /**
370   * Indicates whether debugging is enabled for messages of the specified debug
371   * type.
372   *
373   * @param  debugType  The debug type for which to make the determination.
374   *
375   * @return  {@code true} if debugging is enabled for messages of the specified
376   *          debug type, or {@code false} if not.
377   */
378  public static boolean debugEnabled(final DebugType debugType)
379  {
380    return (debugEnabled && debugTypes.contains(debugType));
381  }
382
383
384
385  /**
386   * Specifies whether debugging should be enabled.  If it should be, then it
387   * will be enabled for all debug types.
388   *
389   * @param  enabled  Specifies whether debugging should be enabled.
390   */
391  public static void setEnabled(final boolean enabled)
392  {
393    debugTypes   = EnumSet.allOf(DebugType.class);
394    debugEnabled = enabled;
395  }
396
397
398
399  /**
400   * Specifies whether debugging should be enabled.  If it should be, then it
401   * will be enabled for all debug types in the provided set.
402   *
403   * @param  enabled  Specifies whether debugging should be enabled.
404   * @param  types    The set of debug types that should be enabled.  It may be
405   *                  {@code null} or empty to indicate that it should be for
406   *                  all debug types.
407   */
408  public static void setEnabled(final boolean enabled,
409                                final Set<DebugType> types)
410  {
411    if ((types == null) || types.isEmpty())
412    {
413      debugTypes = EnumSet.allOf(DebugType.class);
414    }
415    else
416    {
417      debugTypes = EnumSet.copyOf(types);
418    }
419
420    debugEnabled = enabled;
421  }
422
423
424
425  /**
426   * Indicates whether log messages should include a stack trace of the thread
427   * that invoked the debug method.
428   *
429   * @return  {@code true} if log messages should include a stack trace of the
430   *          thread that invoked the debug method, or {@code false} if not.
431   */
432  public static boolean includeStackTrace()
433  {
434    return includeStackTrace;
435  }
436
437
438
439  /**
440   * Specifies whether log messages should include a stack trace of the thread
441   * that invoked the debug method.
442   *
443   * @param  includeStackTrace  Indicates whether log messages should include a
444   *                            stack trace of the thread that invoked the debug
445   *                            method.
446   */
447  public static void setIncludeStackTrace(final boolean includeStackTrace)
448  {
449    Debug.includeStackTrace = includeStackTrace;
450  }
451
452
453
454  /**
455   * Retrieves the set of debug types that will be used if debugging is enabled.
456   *
457   * @return  The set of debug types that will be used if debugging is enabled.
458   */
459  public static EnumSet<DebugType> getDebugTypes()
460  {
461    return debugTypes;
462  }
463
464
465
466  /**
467   * Writes debug information about the provided exception, if appropriate.  If
468   * it is to be logged, then it will be sent to the underlying logger using the
469   * {@code WARNING} level.
470   *
471   * @param  t  The exception for which debug information should be written.
472   */
473  public static void debugException(final Throwable t)
474  {
475    if (debugEnabled && debugTypes.contains(DebugType.EXCEPTION))
476    {
477      debugException(Level.WARNING, t);
478    }
479  }
480
481
482
483  /**
484   * Writes debug information about the provided exception, if appropriate.
485   *
486   * @param  l  The log level that should be used for the debug information.
487   * @param  t  The exception for which debug information should be written.
488   */
489  public static void debugException(final Level l, final Throwable t)
490  {
491    if (debugEnabled && debugTypes.contains(DebugType.EXCEPTION))
492    {
493      final JSONBuffer buffer = new JSONBuffer();
494      addCommonHeader(buffer, l, DebugType.EXCEPTION);
495      addCaughtException(buffer, "caught-exception", t);
496      addCommonFooter(buffer);
497
498      log(l, buffer, t);
499    }
500  }
501
502
503
504  /**
505   * Writes debug information to indicate that a connection has been
506   * established, if appropriate.  If it is to be logged, then it will be sent
507   * to the underlying logger using the {@code INFO} level.
508   *
509   * @param  h  The address of the server to which the connection was
510   *            established.
511   * @param  p  The port of the server to which the connection was established.
512   */
513  public static void debugConnect(final String h, final int p)
514  {
515    if (debugEnabled && debugTypes.contains(DebugType.CONNECT))
516    {
517      debugConnect(Level.INFO, h, p, null);
518    }
519  }
520
521
522
523  /**
524   * Writes debug information to indicate that a connection has been
525   * established, if appropriate.
526   *
527   * @param  l  The log level that should be used for the debug information.
528   * @param  h  The address of the server to which the connection was
529   *            established.
530   * @param  p  The port of the server to which the connection was established.
531   */
532  public static void debugConnect(final Level l, final String h, final int p)
533  {
534    if (debugEnabled && debugTypes.contains(DebugType.CONNECT))
535    {
536      debugConnect(l, h, p, null);
537    }
538  }
539
540
541
542  /**
543   * Writes debug information to indicate that a connection has been
544   * established, if appropriate.  If it is to be logged, then it will be sent
545   * to the underlying logger using the {@code INFO} level.
546   *
547   * @param  h  The address of the server to which the connection was
548   *            established.
549   * @param  p  The port of the server to which the connection was established.
550   * @param  c  The connection object for the connection that has been
551   *            established.  It may be {@code null} for historic reasons, but
552   *            should be non-{@code null} in new uses.
553   */
554  public static void debugConnect(final String h, final int p,
555                                  final LDAPConnection c)
556  {
557    if (debugEnabled && debugTypes.contains(DebugType.CONNECT))
558    {
559      debugConnect(Level.INFO, h, p, c);
560    }
561  }
562
563
564
565  /**
566   * Writes debug information to indicate that a connection has been
567   * established, if appropriate.
568   *
569   * @param  l  The log level that should be used for the debug information.
570   * @param  h  The address of the server to which the connection was
571   *            established.
572   * @param  p  The port of the server to which the connection was established.
573   * @param  c  The connection object for the connection that has been
574   *            established.  It may be {@code null} for historic reasons, but
575   *            should be non-{@code null} in new uses.
576   */
577  public static void debugConnect(final Level l, final String h, final int p,
578                                  final LDAPConnection c)
579  {
580    if (debugEnabled && debugTypes.contains(DebugType.CONNECT))
581    {
582      final JSONBuffer buffer = new JSONBuffer();
583      addCommonHeader(buffer, l, DebugType.CONNECT);
584      buffer.appendString("connected-to-address", h);
585      buffer.appendNumber("connected-to-port", p);
586
587      if (c != null)
588      {
589        buffer.appendNumber("connection-id", c.getConnectionID());
590
591        final String connectionName = c.getConnectionName();
592        if (connectionName != null)
593        {
594          buffer.appendString("connection-name", connectionName);
595        }
596
597        final String connectionPoolName = c.getConnectionPoolName();
598        if (connectionPoolName != null)
599        {
600          buffer.appendString("connection-pool-name", connectionPoolName);
601        }
602      }
603
604      addCommonFooter(buffer);
605      log(l, buffer);
606    }
607  }
608
609
610
611  /**
612   * Writes debug information to indicate that a connection has been
613   * terminated, if appropriate.  If it is to be logged, then it will be sent
614   * to the underlying logger using the {@code INFO} level.
615   *
616   * @param  h  The address of the server to which the connection was
617   *            established.
618   * @param  p  The port of the server to which the connection was established.
619   * @param  t  The disconnect type.
620   * @param  m  The disconnect message, if available.
621   * @param  e  The disconnect cause, if available.
622   */
623  public static void debugDisconnect(final String h, final int p,
624                                     final DisconnectType t, final String m,
625                                     final Throwable e)
626  {
627    if (debugEnabled && debugTypes.contains(DebugType.CONNECT))
628    {
629      debugDisconnect(Level.INFO, h, p, null, t, m, e);
630    }
631  }
632
633
634
635  /**
636   * Writes debug information to indicate that a connection has been
637   * terminated, if appropriate.
638   *
639   * @param  l  The log level that should be used for the debug information.
640   * @param  h  The address of the server to which the connection was
641   *            established.
642   * @param  p  The port of the server to which the connection was established.
643   * @param  t  The disconnect type.
644   * @param  m  The disconnect message, if available.
645   * @param  e  The disconnect cause, if available.
646   */
647  public static void debugDisconnect(final Level l, final String h, final int p,
648                                     final DisconnectType t, final String m,
649                                     final Throwable e)
650  {
651    if (debugEnabled && debugTypes.contains(DebugType.CONNECT))
652    {
653      debugDisconnect(l, h, p, null, t, m, e);
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  c  The connection object for the connection that has been closed.
668   *            It may be {@code null} for historic reasons, but should be
669   *            non-{@code null} in new uses.
670   * @param  t  The disconnect type.
671   * @param  m  The disconnect message, if available.
672   * @param  e  The disconnect cause, if available.
673   */
674  public static void debugDisconnect(final String h, final int p,
675                                     final LDAPConnection c,
676                                     final DisconnectType t, final String m,
677                                     final Throwable e)
678  {
679    if (debugEnabled && debugTypes.contains(DebugType.CONNECT))
680    {
681      debugDisconnect(Level.INFO, h, p, c, t, m, e);
682    }
683  }
684
685
686
687  /**
688   * Writes debug information to indicate that a connection has been
689   * terminated, if appropriate.
690   *
691   * @param  l  The log level that should be used for the debug information.
692   * @param  h  The address of the server to which the connection was
693   *            established.
694   * @param  p  The port of the server to which the connection was established.
695   * @param  c  The connection object for the connection that has been closed.
696   *            It may be {@code null} for historic reasons, but should be
697   *            non-{@code null} in new uses.
698   * @param  t  The disconnect type.
699   * @param  m  The disconnect message, if available.
700   * @param  e  The disconnect cause, if available.
701   */
702  public static void debugDisconnect(final Level l, final String h, final int p,
703                                     final LDAPConnection c,
704                                     final DisconnectType t, final String m,
705                                     final Throwable e)
706  {
707    if (debugEnabled && debugTypes.contains(DebugType.CONNECT))
708    {
709      final JSONBuffer buffer = new JSONBuffer();
710      addCommonHeader(buffer, l, DebugType.CONNECT);
711
712      if (c != null)
713      {
714        buffer.appendNumber("connection-id", c.getConnectionID());
715
716        final String connectionName = c.getConnectionName();
717        if (connectionName != null)
718        {
719          buffer.appendString("connection-name", connectionName);
720        }
721
722        final String connectionPoolName = c.getConnectionPoolName();
723        if (connectionPoolName != null)
724        {
725          buffer.appendString("connection-pool-name", connectionPoolName);
726        }
727
728        buffer.appendString("disconnected-from-address", h);
729        buffer.appendNumber("disconnected-from-port", p);
730        buffer.appendString("disconnect-type", t.name());
731
732        if (m != null)
733        {
734          buffer.appendString("disconnect-message", m);
735        }
736
737      }
738
739      if (e != null)
740      {
741        addCaughtException(buffer, "disconnect-cause", e);
742      }
743
744      addCommonFooter(buffer);
745      log(l, buffer, e);
746    }
747  }
748
749
750
751  /**
752   * Writes debug information about the provided request, if appropriate.  If
753   * it is to be logged, then it will be sent to the underlying logger using the
754   * {@code INFO} level.
755   *
756   * @param  r  The LDAP request for which debug information should be written.
757   */
758  public static void debugLDAPRequest(final LDAPRequest r)
759  {
760    if (debugEnabled && debugTypes.contains(DebugType.LDAP))
761    {
762      debugLDAPRequest(Level.INFO, r, -1, null);
763    }
764  }
765
766
767
768  /**
769   * Writes debug information about the provided request, if appropriate.
770   *
771   * @param  l  The log level that should be used for the debug information.
772   * @param  r  The LDAP request for which debug information should be written.
773   */
774  public static void debugLDAPRequest(final Level l, final LDAPRequest r)
775  {
776    if (debugEnabled && debugTypes.contains(DebugType.LDAP))
777    {
778      debugLDAPRequest(l, r, -1, null);
779    }
780  }
781
782
783
784  /**
785   * Writes debug information about the provided request, if appropriate.  If
786   * it is to be logged, then it will be sent to the underlying logger using the
787   * {@code INFO} level.
788   *
789   * @param  r  The LDAP request for which debug information should be written.
790   * @param  i  The message ID for the request that will be sent.  It may be
791   *            negative if no message ID is available.
792   * @param  c  The connection on which the request will be sent.  It may be
793   *            {@code null} for historic reasons, but should be
794   *            non-{@code null} in new uses.
795   */
796  public static void debugLDAPRequest(final LDAPRequest r, final int i,
797                                      final LDAPConnection c)
798  {
799    if (debugEnabled && debugTypes.contains(DebugType.LDAP))
800    {
801      debugLDAPRequest(Level.INFO, r, i, c);
802    }
803  }
804
805
806
807  /**
808   * Writes debug information about the provided request, if appropriate.
809   *
810   * @param  l  The log level that should be used for the debug information.
811   * @param  r  The LDAP request for which debug information should be written.
812   * @param  i  The message ID for the request that will be sent.  It may be
813   *            negative if no message ID is available.
814   * @param  c  The connection on which the request will be sent.  It may be
815   *            {@code null} for historic reasons, but should be
816   *            non-{@code null} in new uses.
817   */
818  public static void debugLDAPRequest(final Level l, final LDAPRequest r,
819                                      final int i, final LDAPConnection c)
820  {
821    if (debugEnabled && debugTypes.contains(DebugType.LDAP))
822    {
823      debugLDAPRequest(Level.INFO, String.valueOf(r), i, c);
824    }
825  }
826
827
828
829  /**
830   * Writes debug information about the provided request, if appropriate.
831   *
832   * @param  l  The log level that should be used for the debug information.
833   * @param  s  A string representation of the LDAP request for which debug
834   *            information should be written.
835   * @param  i  The message ID for the request that will be sent.  It may be
836   *            negative if no message ID is available.
837   * @param  c  The connection on which the request will be sent.  It may be
838   *            {@code null} for historic reasons, but should be
839   *            non-{@code null} in new uses.
840   */
841  public static void debugLDAPRequest(final Level l, final String s,
842                                      final int i, final LDAPConnection c)
843  {
844    if (debugEnabled && debugTypes.contains(DebugType.LDAP))
845    {
846      final JSONBuffer buffer = new JSONBuffer();
847      addCommonHeader(buffer, l, DebugType.LDAP);
848
849      if (c != null)
850      {
851        buffer.appendNumber("connection-id", c.getConnectionID());
852
853        final String connectionName = c.getConnectionName();
854        if (connectionName != null)
855        {
856          buffer.appendString("connection-name", connectionName);
857        }
858
859        final String connectionPoolName = c.getConnectionPoolName();
860        if (connectionPoolName != null)
861        {
862          buffer.appendString("connection-pool-name", connectionPoolName);
863        }
864
865        final String connectedAddress = c.getConnectedAddress();
866        if (connectedAddress != null)
867        {
868          buffer.appendString("connected-to-address", connectedAddress);
869          buffer.appendNumber("connected-to-port", c.getConnectedPort());
870        }
871
872        try
873        {
874          final int soTimeout = InternalSDKHelper.getSoTimeout(c);
875          buffer.appendNumber("socket-timeout-millis", soTimeout);
876        } catch (final Exception e) {}
877      }
878
879      if (i >= 0)
880      {
881        buffer.appendNumber("message-id", i);
882      }
883
884      buffer.appendString("sending-ldap-request", s);
885
886      addCommonFooter(buffer);
887      log(l,  buffer);
888    }
889  }
890
891
892
893  /**
894   * Writes debug information about the provided result, if appropriate.  If
895   * it is to be logged, then it will be sent to the underlying logger using the
896   * {@code INFO} level.
897   *
898   * @param  r  The result for which debug information should be written.
899   */
900  public static void debugLDAPResult(final LDAPResponse r)
901  {
902    if (debugEnabled && debugTypes.contains(DebugType.LDAP))
903    {
904      debugLDAPResult(Level.INFO, r, null);
905    }
906  }
907
908
909
910  /**
911   * Writes debug information about the provided result, if appropriate.
912   *
913   * @param  l  The log level that should be used for the debug information.
914   * @param  r  The result for which debug information should be written.
915   */
916  public static void debugLDAPResult(final Level l, final LDAPResponse r)
917  {
918    if (debugEnabled && debugTypes.contains(DebugType.LDAP))
919    {
920      debugLDAPResult(l, r, null);
921    }
922  }
923
924
925
926  /**
927   * Writes debug information about the provided result, if appropriate.  If
928   * it is to be logged, then it will be sent to the underlying logger using the
929   * {@code INFO} level.
930   *
931   * @param  r  The result for which debug information should be written.
932   * @param  c  The connection on which the response was received.  It may be
933   *            {@code null} for historic reasons, but should be
934   *            non-{@code null} in new uses.
935   */
936  public static void debugLDAPResult(final LDAPResponse r,
937                                     final LDAPConnection c)
938  {
939    if (debugEnabled && debugTypes.contains(DebugType.LDAP))
940    {
941      debugLDAPResult(Level.INFO, r, c);
942    }
943  }
944
945
946
947  /**
948   * Writes debug information about the provided result, if appropriate.
949   *
950   * @param  l  The log level that should be used for the debug information.
951   * @param  r  The result for which debug information should be written.
952   * @param  c  The connection on which the response was received.  It may be
953   *            {@code null} for historic reasons, but should be
954   *            non-{@code null} in new uses.
955   */
956  public static void debugLDAPResult(final Level l, final LDAPResponse r,
957                                     final LDAPConnection c)
958  {
959    if (debugEnabled && debugTypes.contains(DebugType.LDAP))
960    {
961      final JSONBuffer buffer = new JSONBuffer();
962      addCommonHeader(buffer, l, DebugType.LDAP);
963
964      if (c != null)
965      {
966        buffer.appendNumber("connection-id", c.getConnectionID());
967
968        final String connectionName = c.getConnectionName();
969        if (connectionName != null)
970        {
971          buffer.appendString("connection-name", connectionName);
972        }
973
974        final String connectionPoolName = c.getConnectionPoolName();
975        if (connectionPoolName != null)
976        {
977          buffer.appendString("connection-pool-name", connectionPoolName);
978        }
979
980        final String connectedAddress = c.getConnectedAddress();
981        if (connectedAddress != null)
982        {
983          buffer.appendString("connected-to-address", connectedAddress);
984          buffer.appendNumber("connected-to-port", c.getConnectedPort());
985        }
986      }
987
988      buffer.appendString("read-ldap-result", r.toString());
989
990      addCommonFooter(buffer);
991      log(l, buffer);
992    }
993  }
994
995
996
997  /**
998   * Writes debug information about the provided ASN.1 element to be written,
999   * if appropriate.  If it is to be logged, then it will be sent to the
1000   * underlying logger using the {@code INFO} level.
1001   *
1002   * @param  e  The ASN.1 element for which debug information should be written.
1003   */
1004  public static void debugASN1Write(final ASN1Element e)
1005  {
1006    if (debugEnabled && debugTypes.contains(DebugType.ASN1))
1007    {
1008      debugASN1Write(Level.INFO, e);
1009    }
1010  }
1011
1012
1013
1014  /**
1015   * Writes debug information about the provided ASN.1 element to be written,
1016   * if appropriate.
1017   *
1018   * @param  l  The log level that should be used for the debug information.
1019   * @param  e  The ASN.1 element for which debug information should be written.
1020   */
1021  public static void debugASN1Write(final Level l, final ASN1Element e)
1022  {
1023    if (debugEnabled && debugTypes.contains(DebugType.ASN1))
1024    {
1025      final JSONBuffer buffer = new JSONBuffer();
1026      addCommonHeader(buffer, l, DebugType.ASN1);
1027      buffer.appendString("writing-asn1-element", e.toString());
1028
1029      addCommonFooter(buffer);
1030      log(l, buffer);
1031    }
1032  }
1033
1034
1035
1036  /**
1037   * Writes debug information about the provided ASN.1 element to be written,
1038   * if appropriate.  If it is to be logged, then it will be sent to the
1039   * underlying logger using the {@code INFO} level.
1040   *
1041   * @param  b  The ASN.1 buffer with the information to be written.
1042   */
1043  public static void debugASN1Write(final ASN1Buffer b)
1044  {
1045    if (debugEnabled && debugTypes.contains(DebugType.ASN1))
1046    {
1047      debugASN1Write(Level.INFO, b);
1048    }
1049  }
1050
1051
1052
1053  /**
1054   * Writes debug information about the provided ASN.1 element to be written,
1055   * if appropriate.
1056   *
1057   * @param  l  The log level that should be used for the debug information.
1058   * @param  b  The ASN1Buffer with the information to be written.
1059   */
1060  public static void debugASN1Write(final Level l, final ASN1Buffer b)
1061  {
1062    if (debugEnabled && debugTypes.contains(DebugType.ASN1))
1063    {
1064      final JSONBuffer buffer = new JSONBuffer();
1065      addCommonHeader(buffer, l, DebugType.ASN1);
1066      buffer.appendString("writing-asn1-element",
1067           StaticUtils.toHex(b.toByteArray()));
1068
1069      addCommonFooter(buffer);
1070      log(l, buffer);
1071    }
1072  }
1073
1074
1075
1076  /**
1077   * Writes debug information about the provided ASN.1 element that was read, if
1078   * appropriate.  If it is to be logged, then it will be sent to the underlying
1079   * logger using the {@code INFO} level.
1080   *
1081   * @param  e  The ASN.1 element for which debug information should be written.
1082   */
1083  public static void debugASN1Read(final ASN1Element e)
1084  {
1085    if (debugEnabled && debugTypes.contains(DebugType.ASN1))
1086    {
1087      debugASN1Read(Level.INFO, e);
1088    }
1089  }
1090
1091
1092
1093  /**
1094   * Writes debug information about the provided ASN.1 element that was read, if
1095   * appropriate.
1096   *
1097   * @param  l  The log level that should be used for the debug information.
1098   * @param  e  The ASN.1 element for which debug information should be written.
1099   */
1100  public static void debugASN1Read(final Level l, final ASN1Element e)
1101  {
1102    if (debugEnabled && debugTypes.contains(DebugType.ASN1))
1103    {
1104      final JSONBuffer buffer = new JSONBuffer();
1105      addCommonHeader(buffer, l, DebugType.ASN1);
1106      buffer.appendString("read-asn1-element", e.toString());
1107
1108      addCommonFooter(buffer);
1109      log(l, buffer);
1110    }
1111  }
1112
1113
1114
1115  /**
1116   * Writes debug information about the provided ASN.1 element that was read, if
1117   * appropriate.
1118   *
1119   * @param  l         The log level that should be used for the debug
1120   *                   information.
1121   * @param  dataType  A string representation of the data type for the data
1122   *                   that was read.
1123   * @param  berType   The BER type for the element that was read.
1124   * @param  length    The number of bytes in the value of the element that was
1125   *                   read.
1126   * @param  value     A representation of the value that was read.  The debug
1127   *                   message will include the string representation of this
1128   *                   value, unless the value is a byte array in which it will
1129   *                   be a hex representation of the bytes that it contains.
1130   *                   It may be {@code null} for an ASN.1 null element.
1131   */
1132  public static void debugASN1Read(final Level l, final String dataType,
1133                                   final int berType, final int length,
1134                                   final Object value)
1135  {
1136    if (debugEnabled && debugTypes.contains(DebugType.ASN1))
1137    {
1138      final JSONBuffer buffer = new JSONBuffer();
1139      addCommonHeader(buffer, l, DebugType.ASN1);
1140
1141      buffer.beginObject("read-asn1-element");
1142      buffer.appendString("data-type", dataType);
1143      buffer.appendString("ber-type",
1144           StaticUtils.toHex((byte) (berType & 0xFF)));
1145      buffer.appendNumber("value-length", length);
1146
1147      if (value != null)
1148      {
1149        if (value instanceof byte[])
1150        {
1151          buffer.appendString("value-bytes",
1152               StaticUtils.toHex((byte[]) value));
1153        }
1154        else
1155        {
1156          buffer.appendString("value-string", value.toString());
1157        }
1158      }
1159
1160      buffer.endObject();
1161
1162      addCommonFooter(buffer);
1163      log(l, buffer);
1164    }
1165  }
1166
1167
1168
1169  /**
1170   * Writes debug information about interaction with a connection pool.
1171   *
1172   * @param  l  The log level that should be used for the debug information.
1173   * @param  p  The associated connection pool.
1174   * @param  c  The associated LDAP connection, if appropriate.
1175   * @param  m  A message with information about the pool interaction.
1176   * @param  e  An exception to include with the log message, if appropriate.
1177   */
1178  public static void debugConnectionPool(final Level l,
1179                                         final AbstractConnectionPool p,
1180                                         final LDAPConnection c, final String m,
1181                                         final Throwable e)
1182  {
1183    if (debugEnabled && debugTypes.contains(DebugType.CONNECTION_POOL))
1184    {
1185      final JSONBuffer buffer = new JSONBuffer();
1186      addCommonHeader(buffer, l, DebugType.CONNECTION_POOL);
1187
1188      final String poolName = p.getConnectionPoolName();
1189      if (poolName == null)
1190      {
1191        buffer.appendNull("connection-pool-name");
1192      }
1193      else
1194      {
1195        buffer.appendString("connection-pool-name", poolName);
1196      }
1197
1198      if (c != null)
1199      {
1200        buffer.appendNumber("connection-id", c.getConnectionID());
1201
1202        final String connectedAddress = c.getConnectedAddress();
1203        if (connectedAddress != null)
1204        {
1205          buffer.appendString("connected-to-address", connectedAddress);
1206          buffer.appendNumber("connected-to-port", c.getConnectedPort());
1207        }
1208      }
1209
1210      final long currentAvailable = p.getCurrentAvailableConnections();
1211      if (currentAvailable >= 0)
1212      {
1213        buffer.appendNumber("current-available-connections", currentAvailable);
1214      }
1215
1216      final long maxAvailable = p.getMaximumAvailableConnections();
1217      if (maxAvailable >= 0)
1218      {
1219        buffer.appendNumber("maximum-available-connections", maxAvailable);
1220      }
1221
1222      if (m != null)
1223      {
1224        buffer.appendString("message", m);
1225      }
1226
1227      if (e != null)
1228      {
1229        addCaughtException(buffer, "caught-exception", e);
1230      }
1231
1232      addCommonFooter(buffer);
1233      log(l, buffer, e);
1234    }
1235  }
1236
1237
1238
1239  /**
1240   * Writes debug information about the provided LDIF record to be written, if
1241   * if appropriate.  If it is to be logged, then it will be sent to the
1242   * underlying logger using the {@code INFO} level.
1243   *
1244   * @param  r  The LDIF record for which debug information should be written.
1245   */
1246  public static void debugLDIFWrite(final LDIFRecord r)
1247  {
1248    if (debugEnabled && debugTypes.contains(DebugType.LDIF))
1249    {
1250      debugLDIFWrite(Level.INFO, r);
1251    }
1252  }
1253
1254
1255
1256  /**
1257   * Writes debug information about the provided LDIF record to be written, if
1258   * appropriate.
1259   *
1260   * @param  l  The log level that should be used for the debug information.
1261   * @param  r  The LDIF record for which debug information should be written.
1262   */
1263  public static void debugLDIFWrite(final Level l, final LDIFRecord r)
1264  {
1265    if (debugEnabled && debugTypes.contains(DebugType.LDIF))
1266    {
1267      final JSONBuffer buffer = new JSONBuffer();
1268      addCommonHeader(buffer, l, DebugType.LDIF);
1269      buffer.appendString("writing-ldif-record", r.toString());
1270
1271      addCommonFooter(buffer);
1272      log(l, buffer);
1273    }
1274  }
1275
1276
1277
1278  /**
1279   * Writes debug information about the provided record read from LDIF, if
1280   * appropriate.  If it is to be logged, then it will be sent to the underlying
1281   * logger using the {@code INFO} level.
1282   *
1283   * @param  r  The LDIF record for which debug information should be written.
1284   */
1285  public static void debugLDIFRead(final LDIFRecord r)
1286  {
1287    if (debugEnabled && debugTypes.contains(DebugType.LDIF))
1288    {
1289      debugLDIFRead(Level.INFO, r);
1290    }
1291  }
1292
1293
1294
1295  /**
1296   * Writes debug information about the provided record read from LDIF, if
1297   * appropriate.
1298   *
1299   * @param  l  The log level that should be used for the debug information.
1300   * @param  r  The LDIF record for which debug information should be written.
1301   */
1302  public static void debugLDIFRead(final Level l, final LDIFRecord r)
1303  {
1304    if (debugEnabled && debugTypes.contains(DebugType.LDIF))
1305    {
1306      final JSONBuffer buffer = new JSONBuffer();
1307      addCommonHeader(buffer, l, DebugType.LDIF);
1308      buffer.appendString("read-ldif-record", r.toString());
1309
1310      addCommonFooter(buffer);
1311      log(l, buffer);
1312    }
1313  }
1314
1315
1316
1317  /**
1318   * Writes debug information about monitor entry parsing.  If it is to be
1319   * logged, then it will be sent to the underlying logger using the
1320   * {@code FINE} level.
1321   *
1322   * @param  e  The entry containing the monitor information being parsed.
1323   * @param  m  The message to be written to the debug logger.
1324   */
1325  public static void debugMonitor(final Entry e, final String m)
1326  {
1327    if (debugEnabled && debugTypes.contains(DebugType.MONITOR))
1328    {
1329      debugMonitor(Level.FINE, e, m);
1330    }
1331  }
1332
1333
1334
1335  /**
1336   * Writes debug information about monitor entry parsing, if appropriate.
1337   *
1338   * @param  l  The log level that should be used for the debug information.
1339   * @param  e  The entry containing the monitor information being parsed.
1340   * @param  m  The message to be written to the debug logger.
1341   */
1342  public static void debugMonitor(final Level l, final Entry e, final String m)
1343  {
1344    if (debugEnabled && debugTypes.contains(DebugType.MONITOR))
1345    {
1346      final JSONBuffer buffer = new JSONBuffer();
1347      addCommonHeader(buffer, l, DebugType.MONITOR);
1348
1349      if (e != null)
1350      {
1351        buffer.appendString("monitor-entry-dn", e.getDN());
1352      }
1353
1354      if (m != null)
1355      {
1356        buffer.appendString("message", m);
1357      }
1358
1359      addCommonFooter(buffer);
1360      log(l, buffer);
1361    }
1362  }
1363
1364
1365
1366  /**
1367   * Writes debug information about a coding error detected in the use of the
1368   * LDAP SDK.  If it is to be logged, then it will be sent to the underlying
1369   * logger using the {@code SEVERE} level.
1370   *
1371   * @param  t  The {@code Throwable} object that was created and will be thrown
1372   *            as a result of the coding error.
1373   */
1374  public static void debugCodingError(final Throwable t)
1375  {
1376    if (debugEnabled && debugTypes.contains(DebugType.CODING_ERROR))
1377    {
1378      final JSONBuffer buffer = new JSONBuffer();
1379      addCommonHeader(buffer, Level.SEVERE, DebugType.CODING_ERROR);
1380      addCaughtException(buffer, "coding-error", t);
1381
1382      addCommonFooter(buffer);
1383      log(Level.SEVERE, buffer, t);
1384    }
1385  }
1386
1387
1388
1389  /**
1390   * Writes a generic debug message, if appropriate.
1391   *
1392   * @param  l  The log level that should be used for the debug information.
1393   * @param  t  The debug type to use to determine whether to write the message.
1394   * @param  m  The message to be written.
1395   */
1396  public static void debug(final Level l, final DebugType t, final String m)
1397  {
1398    if (debugEnabled && debugTypes.contains(t))
1399    {
1400      final JSONBuffer buffer = new JSONBuffer();
1401      addCommonHeader(buffer, l, t);
1402
1403      if (m != null)
1404      {
1405        buffer.appendString("message", m);
1406      }
1407
1408      addCommonFooter(buffer);
1409      log(l, buffer);
1410    }
1411  }
1412
1413
1414
1415  /**
1416   * Writes a generic debug message, if appropriate.
1417   *
1418   * @param  l  The log level that should be used for the debug information.
1419   * @param  t  The debug type to use to determine whether to write the message.
1420   * @param  m  The message to be written.
1421   * @param  e  An exception to include with the log message.
1422   */
1423  public static void debug(final Level l, final DebugType t, final String m,
1424                           final Throwable e)
1425  {
1426    if (debugEnabled && debugTypes.contains(t))
1427    {
1428      final JSONBuffer buffer = new JSONBuffer();
1429      addCommonHeader(buffer, l, t);
1430
1431      if (m != null)
1432      {
1433        buffer.appendString("message", m);
1434      }
1435
1436      if (e != null)
1437      {
1438        addCaughtException(buffer, "caught-exception", e);
1439      }
1440
1441      addCommonFooter(buffer);
1442      log(l, buffer, e);
1443    }
1444  }
1445
1446
1447
1448  /**
1449   * Adds common header information to the provided JSON buffer.  It will begin
1450   * a JSON object for the log message, then add a timestamp, debug type, log
1451   * level, thread ID, and thread name.
1452   *
1453   * @param  buffer  The JSON buffer to which the content should be added.
1454   * @param  level   The log level for the message that will be written.
1455   * @param  type    The debug type for the message that will be written.
1456   */
1457  private static void addCommonHeader(final JSONBuffer buffer,
1458                                      final Level level, final DebugType type)
1459  {
1460    buffer.beginObject();
1461    buffer.appendString("timestamp", getTimestamp());
1462    buffer.appendString("debug-type", type.getName());
1463    buffer.appendString("level", level.getName());
1464
1465    final Thread t = Thread.currentThread();
1466    buffer.appendNumber("thread-id", t.getId());
1467    buffer.appendString("thread-name", t.getName());
1468  }
1469
1470
1471
1472  /**
1473   * Retrieves a timestamp that represents the current time.
1474   *
1475   * @return  A timestamp that represents the current time.
1476   */
1477  private static String getTimestamp()
1478  {
1479    SimpleDateFormat timestampFormatter = TIMESTAMP_FORMATTERS.get();
1480    if (timestampFormatter == null)
1481    {
1482      timestampFormatter =
1483           new SimpleDateFormat("yyyy'-'MM'-'dd'T'HH':'mm':'ss.SSS'Z'");
1484      timestampFormatter.setTimeZone(StaticUtils.getUTCTimeZone());
1485      TIMESTAMP_FORMATTERS.set(timestampFormatter);
1486    }
1487
1488    return timestampFormatter.format(new Date());
1489  }
1490
1491
1492
1493  /**
1494   * Creates a formatted string representation of the provided stack trace
1495   * frame.
1496   *
1497   * @param  e  The stack trace element to be formatted.
1498   *
1499   * @return  The formatted string representation of the provided stack trace
1500   *          frame.
1501   */
1502  private static String formatStackTraceFrame(final StackTraceElement e)
1503  {
1504    final StringBuilder buffer = new StringBuilder();
1505    buffer.append(e.getMethodName());
1506    buffer.append('(');
1507    buffer.append(e.getFileName());
1508
1509    final int lineNumber = e.getLineNumber();
1510    if (lineNumber > 0)
1511    {
1512      buffer.append(':');
1513      buffer.append(lineNumber);
1514    }
1515    else if (e.isNativeMethod())
1516    {
1517      buffer.append(":native");
1518    }
1519
1520    buffer.append(')');
1521    return buffer.toString();
1522  }
1523
1524
1525
1526  /**
1527   * Adds information about a caught exception to the provided JSON buffer.
1528   *
1529   * @param  buffer     The JSON buffer to which the information should be
1530   *                    appended.
1531   * @param  fieldName  The name to use for the new field to be added with the
1532   *                    exception information.
1533   * @param  t          The exception to be included.
1534   */
1535  private static void addCaughtException(final JSONBuffer buffer,
1536                                         final String fieldName,
1537                                         final Throwable t)
1538  {
1539    if (t == null)
1540    {
1541      return;
1542    }
1543
1544    buffer.beginObject(fieldName);
1545
1546    String message = t.getMessage();
1547    if (message != null)
1548    {
1549      buffer.appendString("message", t.getMessage());
1550    }
1551
1552    buffer.beginArray("stack-trace");
1553    for (final StackTraceElement e : t.getStackTrace())
1554    {
1555      buffer.appendString(formatStackTraceFrame(e));
1556    }
1557    buffer.endArray();
1558
1559    Throwable cause = t.getCause();
1560    while (cause != null)
1561    {
1562      buffer.beginObject("cause");
1563
1564      message = cause.getMessage();
1565      if (message != null)
1566      {
1567        buffer.appendString("message", cause.getMessage());
1568      }
1569
1570      buffer.beginArray("stack-trace");
1571      for (final StackTraceElement e : cause.getStackTrace())
1572      {
1573        buffer.appendString(formatStackTraceFrame(e));
1574      }
1575      buffer.endArray();
1576
1577      cause = cause.getCause();
1578    }
1579
1580    buffer.endObject();
1581  }
1582
1583
1584
1585  /**
1586   * Adds common footer information to the provided JSON buffer.  It will
1587   * include an optional caller stack trace, along with the LDAP SDK version
1588   * and revision.  It will also end the object that encapsulates the log
1589   * message.
1590   *
1591   * @param  buffer  The JSON buffer to which the content should be added.
1592   */
1593  private static void addCommonFooter(final JSONBuffer buffer)
1594  {
1595    if (includeStackTrace)
1596    {
1597      buffer.beginArray("caller-stack-trace");
1598
1599      boolean foundDebug = false;
1600      for (final StackTraceElement e : Thread.currentThread().getStackTrace())
1601      {
1602        final String className = e.getClassName();
1603        if (className.equals(Debug.class.getName()))
1604        {
1605          foundDebug = true;
1606        }
1607        else if (foundDebug)
1608        {
1609          buffer.appendString(formatStackTraceFrame(e));
1610        }
1611      }
1612
1613      buffer.endArray();
1614    }
1615
1616    buffer.appendString("ldap-sdk-version", Version.NUMERIC_VERSION_STRING);
1617    buffer.appendString("ldap-sdk-revision", Version.REVISION_ID);
1618    buffer.endObject();
1619  }
1620
1621
1622
1623  /**
1624   * Logs a JSON-formatted debug message with the given level and fields.
1625   *
1626   * @param  level   The log level to use for the message.
1627   * @param  buffer  The JSON buffer containing the message to be written.
1628   */
1629  private static void log(final Level level, final JSONBuffer buffer)
1630  {
1631    logger.log(level, buffer.toString());
1632  }
1633
1634
1635
1636  /**
1637   * Logs a JSON-formatted debug message with the given level and fields.
1638   *
1639   * @param  level   The log level to use for the message.
1640   * @param  buffer  The JSON buffer containing the message to be written.
1641   * @param  thrown  An exception to be included with the debug message.
1642   */
1643  private static void log(final Level level, final JSONBuffer buffer,
1644                          final Throwable thrown)
1645  {
1646    logger.log(level, buffer.toString(), thrown);
1647  }
1648}