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