001/* 002 * Copyright 2011-2016 UnboundID Corp. 003 * 004 * This program is free software; you can redistribute it and/or modify 005 * it under the terms of the GNU General Public License (GPLv2 only) 006 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 007 * as published by the Free Software Foundation. 008 * 009 * This program is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 012 * GNU General Public License for more details. 013 * 014 * You should have received a copy of the GNU General Public License 015 * along with this program; if not, see <http://www.gnu.org/licenses>. 016 */ 017 018package com.unboundid.scim.sdk; 019 020import java.io.Serializable; 021import java.util.EnumSet; 022import java.util.Properties; 023import java.util.Set; 024import java.util.StringTokenizer; 025import java.util.logging.Level; 026import java.util.logging.Logger; 027 028import static com.unboundid.scim.sdk.StaticUtils.*; 029 030 031 032/** 033 * This class provides a means of enabling and configuring debugging in the 034 * SCIM SDK. 035 * <BR><BR> 036 * Access to debug information can be enabled through applications that use the 037 * SDK by calling the {@link Debug#setEnabled} methods, or it can also be 038 * enabled without any code changes through the use of system properties. In 039 * particular, the {@link Debug#PROPERTY_DEBUG_ENABLED}, 040 * {@link Debug#PROPERTY_DEBUG_LEVEL}, and {@link Debug#PROPERTY_DEBUG_TYPE} 041 * properties may be used to control debugging without the need to alter any 042 * code within the application that uses the SDK. 043 * <BR><BR> 044 * The SCIM SDK debugging subsystem uses the Java logging framework available 045 * through the {@code java.util.logging} package with a logger name of 046 * "{@code com.unboundid.scim.sdk}". The {@link Debug#getLogger} method may 047 * be used to access the logger instance used by the SCIM SDK. 048 * <BR><BR> 049 * <H2>Example</H2> 050 * The following example demonstrates the process that may be used to enable 051 * debugging within the SCIM SDK and write information about all messages with 052 * a {@code WARNING} level or higher to a file named "/tmp/test.log": 053 * <PRE> 054 * Debug.setEnabled(true); 055 * Logger logger = Debug.getLogger(); 056 * 057 * FileHandler fileHandler = new FileHandler("/tmp/test.log"); 058 * fileHandler.setLevel(Level.WARNING); 059 * logger.addHandler(fileHandler); 060 * </PRE> 061 */ 062public final class Debug 063 implements Serializable 064{ 065 /** 066 * The name of the system property that will be used to enable debugging in 067 * the UnboundID SCIM SDK for Java. The fully-qualified name for this 068 * property is "{@code com.unboundid.scim.sdk.debug.enabled}". If it is set, 069 * then it should have a value of either "true" or "false". 070 */ 071 public static final String PROPERTY_DEBUG_ENABLED = 072 "com.unboundid.scim.sdk.debug.enabled"; 073 074 075 076 /** 077 * The name of the system property that may be used to indicate whether stack 078 * trace information for the thread calling the debug method should be 079 * included in debug log messages. The fully-qualified name for this property 080 * is "{@code com.unboundid.scim.sdk.debug.includeStackTrace}". If it is set, 081 * then it should have a value of either "true" or "false". 082 */ 083 public static final String PROPERTY_INCLUDE_STACK_TRACE = 084 "com.unboundid.scim.sdk.debug.includeStackTrace"; 085 086 087 088 /** 089 * The name of the system property that will be used to set the initial level 090 * for the debug logger. The fully-qualified name for this property is 091 * "{@code com.unboundid.scim.sdk.debug.level}". If it is set, then it should 092 * be one of the strings "{@code SEVERE}", "{@code WARNING}", "{@code INFO}", 093 * "{@code CONFIG}", "{@code FINE}", "{@code FINER}", or "{@code FINEST}". 094 */ 095 public static final String PROPERTY_DEBUG_LEVEL = 096 "com.unboundid.scim.sdk.debug.level"; 097 098 099 100 /** 101 * The name of the system property that will be used to indicate that 102 * debugging should be enabled for specific types of messages. The 103 * fully-qualified name for this property is 104 * "{@code com.unboundid.scim.sdk.debug.type}". If it is set, then it should 105 * be a comma-delimited list of the names of the desired debug types. See the 106 * {@link DebugType} enum for the available debug types. 107 */ 108 public static final String PROPERTY_DEBUG_TYPE = 109 "com.unboundid.scim.sdk.debug.type"; 110 111 112 113 /** 114 * The name that will be used for the Java logger that will actually handle 115 * the debug messages if debugging is enabled. 116 */ 117 public static final String LOGGER_NAME = "com.unboundid.scim.sdk"; 118 119 120 121 /** 122 * The logger that will be used to handle the debug messages if debugging is 123 * enabled. 124 */ 125 private static final Logger logger = Logger.getLogger(LOGGER_NAME); 126 127 128 129 /** 130 * The serial version UID for this serializable class. 131 */ 132 private static final long serialVersionUID = 1481879450652966238L; 133 134 135 136 // Indicates whether any debugging is currently enabled for the SDK. 137 private static boolean debugEnabled; 138 139 // Indicates whether to capture a thread stack trace whenever a debug message 140 // is logged. 141 private static boolean includeStackTrace; 142 143 // The set of debug types for which debugging is enabled. 144 private static EnumSet<DebugType> debugTypes; 145 146 147 148 static 149 { 150 initialize(System.getProperties()); 151 } 152 153 154 155 /** 156 * Prevent this class from being instantiated. 157 */ 158 private Debug() 159 { 160 // No implementation is required. 161 } 162 163 164 165 /** 166 * Initializes this debugger with the default settings. Debugging will be 167 * disabled, the set of debug types will include all types, and the debug 168 * level will be "ALL". 169 */ 170 public static void initialize() 171 { 172 includeStackTrace = false; 173 debugEnabled = false; 174 debugTypes = EnumSet.allOf(DebugType.class); 175 176 logger.setLevel(Level.ALL); 177 } 178 179 180 181 /** 182 * Initializes this debugger with settings from the provided set of 183 * properties. Any debug setting that isn't configured in the provided 184 * properties will be initialized with its default value. 185 * 186 * @param properties The set of properties to use to initialize this 187 * debugger. 188 */ 189 public static void initialize(final Properties properties) 190 { 191 // First, apply the default values for the properties. 192 initialize(); 193 if ((properties == null) || properties.isEmpty()) 194 { 195 // No properties were provided, so we don't need to do anything. 196 return; 197 } 198 199 final String enabledProp = properties.getProperty(PROPERTY_DEBUG_ENABLED); 200 if ((enabledProp != null) && (enabledProp.length() > 0)) 201 { 202 if (enabledProp.equalsIgnoreCase("true")) 203 { 204 debugEnabled = true; 205 } 206 else if (enabledProp.equalsIgnoreCase("false")) 207 { 208 debugEnabled = false; 209 } 210 else 211 { 212 throw new IllegalArgumentException("Invalid value '" + enabledProp + 213 "' for property " + 214 PROPERTY_DEBUG_ENABLED + 215 ". The value must be either " + 216 "'true' or 'false'."); 217 } 218 } 219 220 final String stackProp = 221 properties.getProperty(PROPERTY_INCLUDE_STACK_TRACE); 222 if ((stackProp != null) && (stackProp.length() > 0)) 223 { 224 if (stackProp.equalsIgnoreCase("true")) 225 { 226 includeStackTrace = true; 227 } 228 else if (stackProp.equalsIgnoreCase("false")) 229 { 230 includeStackTrace = false; 231 } 232 else 233 { 234 throw new IllegalArgumentException("Invalid value '" + stackProp + 235 "' for property " + 236 PROPERTY_INCLUDE_STACK_TRACE + 237 ". The value must be either " + 238 "'true' or 'false'."); 239 } 240 } 241 242 final String typesProp = properties.getProperty(PROPERTY_DEBUG_TYPE); 243 if ((typesProp != null) && (typesProp.length() > 0)) 244 { 245 debugTypes = EnumSet.noneOf(DebugType.class); 246 final StringTokenizer t = new StringTokenizer(typesProp, ", "); 247 while (t.hasMoreTokens()) 248 { 249 final String debugTypeName = t.nextToken(); 250 final DebugType debugType = DebugType.forName(debugTypeName); 251 if (debugType == null) 252 { 253 // Throw a runtime exception to indicate that the debug type is 254 // invalid. 255 throw new IllegalArgumentException("Invalid value '" + debugTypeName + 256 "' for property " + PROPERTY_DEBUG_TYPE + 257 ". Allowed values include: " + 258 DebugType.getTypeNameList() + '.'); 259 } 260 else 261 { 262 debugTypes.add(debugType); 263 } 264 } 265 } 266 267 final String levelProp = properties.getProperty(PROPERTY_DEBUG_LEVEL); 268 if ((levelProp != null) && (levelProp.length() > 0)) 269 { 270 logger.setLevel(Level.parse(levelProp)); 271 } 272 } 273 274 275 276 /** 277 * Retrieves the logger that will be used to write the debug messages. 278 * 279 * @return The logger that will be used to write the debug messages. 280 */ 281 public static Logger getLogger() 282 { 283 return logger; 284 } 285 286 287 288 /** 289 * Indicates whether any form of debugging is enabled. 290 * 291 * @return {@code true} if debugging is enabled, or {@code false} if not. 292 */ 293 public static boolean debugEnabled() 294 { 295 return debugEnabled; 296 } 297 298 299 300 /** 301 * Indicates whether debugging is enabled for messages of the specified debug 302 * type. 303 * 304 * @param debugType The debug type for which to make the determination. 305 * 306 * @return {@code true} if debugging is enabled for messages of the specified 307 * debug type, or {@code false} if not. 308 */ 309 public static boolean debugEnabled(final DebugType debugType) 310 { 311 return (debugEnabled && debugTypes.contains(debugType)); 312 } 313 314 315 316 /** 317 * Specifies whether debugging should be enabled. If it should be, then it 318 * will be enabled for all debug types. 319 * 320 * @param enabled Specifies whether debugging should be enabled. 321 */ 322 public static void setEnabled(final boolean enabled) 323 { 324 debugTypes = EnumSet.allOf(DebugType.class); 325 debugEnabled = enabled; 326 } 327 328 329 330 /** 331 * Specifies whether debugging should be enabled. If it should be, then it 332 * will be enabled for all debug types in the provided set. 333 * 334 * @param enabled Specifies whether debugging should be enabled. 335 * @param types The set of debug types that should be enabled. It may be 336 * {@code null} or empty to indicate that it should be for 337 * all debug types. 338 */ 339 public static void setEnabled(final boolean enabled, 340 final Set<DebugType> types) 341 { 342 if ((types == null) || types.isEmpty()) 343 { 344 debugTypes = EnumSet.allOf(DebugType.class); 345 } 346 else 347 { 348 debugTypes = EnumSet.copyOf(types); 349 } 350 351 debugEnabled = enabled; 352 } 353 354 355 356 /** 357 * Indicates whether log messages should include a stack trace of the thread 358 * that invoked the debug method. 359 * 360 * @return {@code true} if log messages should include a stack trace of the 361 * thread that invoked the debug method, or {@code false} if not. 362 */ 363 public static boolean includeStackTrace() 364 { 365 return includeStackTrace; 366 } 367 368 369 370 /** 371 * Specifies whether log messages should include a stack trace of the thread 372 * that invoked the debug method. 373 * 374 * @param includeStackTrace Indicates whether log messages should include a 375 * stack trace of the thread that invoked the debug 376 * method. 377 */ 378 public static void setIncludeStackTrace(final boolean includeStackTrace) 379 { 380 Debug.includeStackTrace = includeStackTrace; 381 } 382 383 384 385 /** 386 * Retrieves the set of debug types that will be used if debugging is enabled. 387 * 388 * @return The set of debug types that will be used if debugging is enabled. 389 */ 390 public static EnumSet<DebugType> getDebugTypes() 391 { 392 return debugTypes; 393 } 394 395 396 397 /** 398 * Writes debug information about the provided exception, if appropriate. If 399 * it is to be logged, then it will be sent to the underlying logger using the 400 * {@code WARNING} level. 401 * 402 * @param t The exception for which debug information should be written. 403 */ 404 public static void debugException(final Throwable t) 405 { 406 if (debugEnabled && debugTypes.contains(DebugType.EXCEPTION)) 407 { 408 debugException(Level.WARNING, t); 409 } 410 } 411 412 413 414 /** 415 * Writes debug information about the provided exception, if appropriate. 416 * 417 * @param l The log level that should be used for the debug information. 418 * @param t The exception for which debug information should be written. 419 */ 420 public static void debugException(final Level l, final Throwable t) 421 { 422 if (debugEnabled && debugTypes.contains(DebugType.EXCEPTION)) 423 { 424 final StringBuilder buffer = new StringBuilder(); 425 addCommonHeader(buffer, l); 426 buffer.append("caughtException=\""); 427 getStackTrace(t, buffer); 428 buffer.append('"'); 429 430 logger.log(l, buffer.toString(), t); 431 } 432 } 433 434 435 436 /** 437 * Writes debug information about a coding error detected in the use of the 438 * SCIM SDK. If it is to be logged, then it will be sent to the underlying 439 * logger using the {@code SEVERE} level. 440 * 441 * @param t The {@code Throwable} object that was created and will be thrown 442 * as a result of the coding error. 443 */ 444 public static void debugCodingError(final Throwable t) 445 { 446 if (debugEnabled && debugTypes.contains(DebugType.CODING_ERROR)) 447 { 448 final StringBuilder buffer = new StringBuilder(); 449 addCommonHeader(buffer, Level.SEVERE); 450 buffer.append("codingError=\""); 451 getStackTrace(t, buffer); 452 buffer.append('"'); 453 454 logger.log(Level.SEVERE, buffer.toString()); 455 } 456 } 457 458 459 460 /** 461 * Writes a generic debug message, if appropriate. 462 * 463 * @param l The log level that should be used for the debug information. 464 * @param t The debug type to use to determine whether to write the message. 465 * @param m The message to be written. 466 */ 467 public static void debug(final Level l, final DebugType t, final String m) 468 { 469 if (debugEnabled && debugTypes.contains(t)) 470 { 471 final StringBuilder buffer = new StringBuilder(); 472 addCommonHeader(buffer, l); 473 buffer.append("message=\""); 474 buffer.append(m); 475 buffer.append('"'); 476 477 logger.log(l, buffer.toString()); 478 } 479 } 480 481 482 483 /** 484 * Writes a generic debug message, if appropriate. 485 * 486 * @param l The log level that should be used for the debug information. 487 * @param t The debug type to use to determine whether to write the message. 488 * @param m The message to be written. 489 * @param e An exception to include with the log message. 490 */ 491 public static void debug(final Level l, final DebugType t, final String m, 492 final Throwable e) 493 { 494 if (debugEnabled && debugTypes.contains(t)) 495 { 496 final StringBuilder buffer = new StringBuilder(); 497 addCommonHeader(buffer, l); 498 buffer.append("message=\""); 499 buffer.append(m); 500 buffer.append('"'); 501 buffer.append(" exception=\""); 502 getStackTrace(e, buffer); 503 buffer.append('"'); 504 505 logger.log(l, buffer.toString(), e); 506 } 507 } 508 509 510 511 /** 512 * Writes common header information to the provided buffer. It will include 513 * the thread ID, name, and caller stack trace (optional), and it will be 514 * followed by a trailing space. 515 * 516 * @param buffer The buffer to which the information should be appended. 517 * @param level The log level for the message that will be written. 518 */ 519 private static void addCommonHeader(final StringBuilder buffer, 520 final Level level) 521 { 522 buffer.append("level=\""); 523 buffer.append(level.getName()); 524 buffer.append("\" threadID="); 525 buffer.append(Thread.currentThread().getId()); 526 buffer.append(" threadName=\""); 527 buffer.append(Thread.currentThread().getName()); 528 529 if (includeStackTrace) 530 { 531 buffer.append("\" calledFrom=\""); 532 533 boolean appended = false; 534 boolean foundDebug = false; 535 for (final StackTraceElement e : Thread.currentThread().getStackTrace()) 536 { 537 final String className = e.getClassName(); 538 if (className.equals(Debug.class.getName())) 539 { 540 foundDebug = true; 541 } 542 else if (foundDebug) 543 { 544 if (appended) 545 { 546 buffer.append(" / "); 547 } 548 appended = true; 549 550 buffer.append(e.getMethodName()); 551 buffer.append('('); 552 buffer.append(e.getFileName()); 553 554 final int lineNumber = e.getLineNumber(); 555 if (lineNumber > 0) 556 { 557 buffer.append(':'); 558 buffer.append(lineNumber); 559 } 560 else if (e.isNativeMethod()) 561 { 562 buffer.append(":native"); 563 } 564 565 buffer.append(')'); 566 } 567 } 568 } 569 570 buffer.append("\" revision="); 571 buffer.append(Version.REVISION_ID); 572 buffer.append(' '); 573 } 574}