001 /*
002 * Copyright 2011-2012 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
018 package com.unboundid.scim.sdk;
019
020 import java.io.Serializable;
021 import java.util.EnumSet;
022 import java.util.Properties;
023 import java.util.Set;
024 import java.util.StringTokenizer;
025 import java.util.logging.Level;
026 import java.util.logging.Logger;
027
028 import 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 */
062 public 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_NUMBER);
572 buffer.append(' ');
573 }
574 }