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