      1: <?php
      2: /**
      3:  * Licensed to the Apache Software Foundation (ASF) under one or more
      4:  * contributor license agreements. See the NOTICE file distributed with
      5:  * this work for additional information regarding copyright ownership.
      6:  * The ASF licenses this file to You under the Apache License, Version 2.0
      7:  * (the "License"); you may not use this file except in compliance with
      8:  * the License. You may obtain a copy of the License at
      9:  *
     10:  *     http://www.apache.org/licenses/LICENSE-2.0
     11:  *
     12:  * Unless required by applicable law or agreed to in writing, software
     13:  * distributed under the License is distributed on an "AS IS" BASIS,
     14:  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     15:  * See the License for the specific language governing permissions and
     16:  * limitations under the License.
     17:  *
     18:  * @package log4php
     19:  */
     20:
     21: /**
     22:  * The NDC class implements <i>nested diagnostic contexts</i>.
     23:  *
     24:  * NDC was defined by Neil Harrison in the article "Patterns for Logging
     25:  * Diagnostic Messages" part of the book <i>"Pattern Languages of
     26:  * Program Design 3"</i> edited by Martin et al.
     27:  *
     28:  * A Nested Diagnostic Context, or NDC in short, is an instrument
     29:  * to distinguish interleaved log output from different sources. Log
     30:  * output is typically interleaved when a server handles multiple
     31:  * clients near-simultaneously.
     32:  *
     33:  * This class is similar to the {@link LoggerMDC} class except that it is
     34:  * based on a stack instead of a map.
     35:  *
     36:  * Interleaved log output can still be meaningful if each log entry
     37:  * from different contexts had a distinctive stamp. This is where NDCs
     38:  * come into play.
     39:  *
     40:  * <b>Note that NDCs are managed on a per thread basis</b>.
     41:  *
     42:  * NDC operations such as {@link push()}, {@link pop()},
     43:  * {@link clear()}, {@link getDepth()} and {@link setMaxDepth()}
     44:  * affect the NDC of the <i>current</i> thread only. NDCs of other
     45:  * threads remain unaffected.
     46:  *
     47:  * For example, a servlet can build a per client request NDC
     48:  * consisting the clients host name and other information contained in
     49:  * the the request. <i>Cookies</i> are another source of distinctive
     50:  * information. To build an NDC one uses the {@link push()}
     51:  * operation.
     52:  *
     53:  * Simply put,
     54:  *
     55:  * - Contexts can be nested.
     56:  * - When entering a context, call <kbd>LoggerNDC::push()</kbd>
     57:  *   As a side effect, if there is no nested diagnostic context for the
     58:  *   current thread, this method will create it.
     59:  * - When leaving a context, call <kbd>LoggerNDC::pop()</kbd>
     60:  * - <b>When exiting a thread make sure to call {@link remove()}</b>
     61:  *
     62:  * There is no penalty for forgetting to match each
     63:  * <kbd>push</kbd> operation with a corresponding <kbd>pop</kbd>,
     64:  * except the obvious mismatch between the real application context
     65:  * and the context set in the NDC.
     66:  *
     67:  * If configured to do so, {@link LoggerPatternLayout} and {@link LoggerLayoutTTCC}
     68:  * instances automatically retrieve the nested diagnostic
     69:  * context for the current thread without any user intervention.
     70:  * Hence, even if a servlet is serving multiple clients
     71:  * simultaneously, the logs emanating from the same code (belonging to
     72:  * the same category) can still be distinguished because each client
     73:  * request will have a different NDC tag.
     74:  *
     75:  * Example:
     76:  *
     77:  * {@example ../../examples/php/ndc.php 19}<br>
     78:  *
     79:  * With the properties file:
     80:  *
     81:  * {@example ../../examples/resources/ndc.properties 18}<br>
     82:  *
     83:  * Will result in the following (notice the conn and client ids):
     84:  *
     85:  * <pre>
     86:  * 2009-09-13 19:04:27 DEBUG root conn=1234: just received a new connection in src/examples/php/ndc.php at 23
     87:  * 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
     88:  * 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
     89:  * 2009-09-13 19:04:27 DEBUG root : back and waiting for new connections in src/examples/php/ndc.php at 29
     90:  * </pre>
     91:  *
     92:  * @version $Revision: 1350602 $
     93:  * @package log4php
     94:  * @since 0.3
     95:  */
     96: class LoggerNDC {
     97:
     98:     /** This is the repository of NDC stack */
     99:     private static $stack = array();
    100:
    101:     /**
    102:      * Clear any nested diagnostic information if any. This method is
    103:      * useful in cases where the same thread can be potentially used
    104:      * over and over in different unrelated contexts.
    105:      *
    106:      * <p>This method is equivalent to calling the {@link setMaxDepth()}
    107:      * method with a zero <var>maxDepth</var> argument.
    108:      */
    109:     public static function clear() {
    110:         self::$stack = array();
    111:     }
    112:
    113:     /**
    114:      * Never use this method directly, use the {@link LoggerLoggingEvent::getNDC()} method instead.
    115:      * @return array
    116:      */
    117:     public static function get() {
    118:         return implode(' ', self::$stack);
    119:     }
    120:
    121:     /**
    122:      * Get the current nesting depth of this diagnostic context.
    123:      *
    124:      * @see setMaxDepth()
    125:      * @return integer
    126:      */
    127:     public static function getDepth() {
    128:         return count(self::$stack);
    129:     }
    130:
    131:     /**
    132:      * Clients should call this method before leaving a diagnostic
    133:      * context.
    134:      *
    135:      * <p>The returned value is the value that was pushed last. If no
    136:      * context is available, then the empty string "" is returned.</p>
    137:      *
    138:      * @return string The innermost diagnostic context.
    139:      */
    140:     public static function pop() {
    141:         if(count(self::$stack) > 0) {
    142:             return array_pop(self::$stack);
    143:         } else {
    144:             return '';
    145:         }
    146:     }
    147:
    148:     /**
    149:      * Looks at the last diagnostic context at the top of this NDC
    150:      * without removing it.
    151:      *
    152:      * <p>The returned value is the value that was pushed last. If no
    153:      * context is available, then the empty string "" is returned.</p>
    154:      * @return string The innermost diagnostic context.
    155:      */
    156:     public static function peek() {
    157:         if(count(self::$stack) > 0) {
    158:             return end(self::$stack);
    159:         } else {
    160:             return '';
    161:         }
    162:     }
    163:
    164:     /**
    165:      * Push new diagnostic context information for the current thread.
    166:      *
    167:      * <p>The contents of the <var>message</var> parameter is
    168:      * determined solely by the client.
    169:      *
    170:      * @param string $message The new diagnostic context information.
    171:      */
    172:     public static function push($message) {
    173:         array_push(self::$stack, (string)$message);
    174:     }
    175:
    176:     /**
    177:      * Remove the diagnostic context for this thread.
    178:      */
    179:     public static function remove() {
    180:         LoggerNDC::clear();
    181:     }
    182:
    183:     /**
    184:      * Set maximum depth of this diagnostic context. If the current
    185:      * depth is smaller or equal to <var>maxDepth</var>, then no
    186:      * action is taken.
    187:      *
    188:      * <p>This method is a convenient alternative to multiple
    189:      * {@link pop()} calls. Moreover, it is often the case that at
    190:      * the end of complex call sequences, the depth of the NDC is
    191:      * unpredictable. The {@link setMaxDepth()} method circumvents
    192:      * this problem.
    193:      *
    194:      * @param integer $maxDepth
    195:      * @see getDepth()
    196:      */
    197:     public static function setMaxDepth($maxDepth) {
    198:         $maxDepth = (int)$maxDepth;
    199:         if(LoggerNDC::getDepth() > $maxDepth) {
    200:             self::$stack = array_slice(self::$stack, 0, $maxDepth);
    201:         }
    202:     }
    203: }
    204: