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}