diff --git a/library/log4php/LoggerNDC.php b/library/log4php/LoggerNDC.php new file mode 100644 --- /dev/null +++ b/library/log4php/LoggerNDC.php @@ -0,0 +1,203 @@ +nested diagnostic contexts. + * + * NDC was defined by Neil Harrison in the article "Patterns for Logging + * Diagnostic Messages" part of the book "Pattern Languages of + * Program Design 3" edited by Martin et al. + * + * A Nested Diagnostic Context, or NDC in short, is an instrument + * to distinguish interleaved log output from different sources. Log + * output is typically interleaved when a server handles multiple + * clients near-simultaneously. + * + * This class is similar to the {@link LoggerMDC} class except that it is + * based on a stack instead of a map. + * + * Interleaved log output can still be meaningful if each log entry + * from different contexts had a distinctive stamp. This is where NDCs + * come into play. + * + * Note that NDCs are managed on a per thread basis. + * + * NDC operations such as {@link push()}, {@link pop()}, + * {@link clear()}, {@link getDepth()} and {@link setMaxDepth()} + * affect the NDC of the current thread only. NDCs of other + * threads remain unaffected. + * + * For example, a servlet can build a per client request NDC + * consisting the clients host name and other information contained in + * the the request. Cookies are another source of distinctive + * information. To build an NDC one uses the {@link push()} + * operation. + * + * Simply put, + * + * - Contexts can be nested. + * - When entering a context, call LoggerNDC::push() + * As a side effect, if there is no nested diagnostic context for the + * current thread, this method will create it. + * - When leaving a context, call LoggerNDC::pop() + * - When exiting a thread make sure to call {@link remove()} + * + * There is no penalty for forgetting to match each + * push operation with a corresponding pop, + * except the obvious mismatch between the real application context + * and the context set in the NDC. + * + * If configured to do so, {@link LoggerPatternLayout} and {@link LoggerLayoutTTCC} + * instances automatically retrieve the nested diagnostic + * context for the current thread without any user intervention. + * Hence, even if a servlet is serving multiple clients + * simultaneously, the logs emanating from the same code (belonging to + * the same category) can still be distinguished because each client + * request will have a different NDC tag. + * + * Example: + * + * {@example ../../examples/php/ndc.php 19}
+ * + * With the properties file: + * + * {@example ../../examples/resources/ndc.properties 18}
+ * + * Will result in the following (notice the conn and client ids): + * + *
+ * 2009-09-13 19:04:27 DEBUG root conn=1234: just received a new connection in src/examples/php/ndc.php at 23
+ * 2009-09-13 19:04:27 DEBUG root conn=1234 client=ab23: some more messages that can in src/examples/php/ndc.php at 25
+ * 2009-09-13 19:04:27 DEBUG root conn=1234 client=ab23: now related to a client in src/examples/php/ndc.php at 26
+ * 2009-09-13 19:04:27 DEBUG root : back and waiting for new connections in src/examples/php/ndc.php at 29
+ * 
+ * + * @version $Revision: 1350602 $ + * @package log4php + * @since 0.3 + */ +class LoggerNDC { + + /** This is the repository of NDC stack */ + private static $stack = array(); + + /** + * Clear any nested diagnostic information if any. This method is + * useful in cases where the same thread can be potentially used + * over and over in different unrelated contexts. + * + *

This method is equivalent to calling the {@link setMaxDepth()} + * method with a zero maxDepth argument. + */ + public static function clear() { + self::$stack = array(); + } + + /** + * Never use this method directly, use the {@link LoggerLoggingEvent::getNDC()} method instead. + * @return array + */ + public static function get() { + return implode(' ', self::$stack); + } + + /** + * Get the current nesting depth of this diagnostic context. + * + * @see setMaxDepth() + * @return integer + */ + public static function getDepth() { + return count(self::$stack); + } + + /** + * Clients should call this method before leaving a diagnostic + * context. + * + *

The returned value is the value that was pushed last. If no + * context is available, then the empty string "" is returned.

+ * + * @return string The innermost diagnostic context. + */ + public static function pop() { + if (count(self::$stack) > 0) { + return array_pop(self::$stack); + } else { + return ''; + } + } + + /** + * Looks at the last diagnostic context at the top of this NDC + * without removing it. + * + *

The returned value is the value that was pushed last. If no + * context is available, then the empty string "" is returned.

+ * @return string The innermost diagnostic context. + */ + public static function peek() { + if (count(self::$stack) > 0) { + return end(self::$stack); + } else { + return ''; + } + } + + /** + * Push new diagnostic context information for the current thread. + * + *

The contents of the message parameter is + * determined solely by the client. + * + * @param string $message The new diagnostic context information. + */ + public static function push($message) { + array_push(self::$stack, (string)$message); + } + + /** + * Remove the diagnostic context for this thread. + */ + public static function remove() { + LoggerNDC::clear(); + } + + /** + * Set maximum depth of this diagnostic context. If the current + * depth is smaller or equal to maxDepth, then no + * action is taken. + * + *

This method is a convenient alternative to multiple + * {@link pop()} calls. Moreover, it is often the case that at + * the end of complex call sequences, the depth of the NDC is + * unpredictable. The {@link setMaxDepth()} method circumvents + * this problem. + * + * @param integer $maxDepth + * @see getDepth() + */ + public static function setMaxDepth($maxDepth) { + $maxDepth = (int)$maxDepth; + if (LoggerNDC::getDepth() > $maxDepth) { + self::$stack = array_slice(self::$stack, 0, $maxDepth); + } + } +}