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