      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:  * Converts ini configuration files to a PHP array.
     23:  *
     24:  * These used to be called "properties" files (inherited from log4j), and that
     25:  * file extension is still supported.
     26:  *
     27:  * @package log4php
     28:  * @subpackage configurators
     29:  * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
     30:  * @version $Revision: 1343601 $
     31:  * @since 2.2
     32:  */
     33: class LoggerConfigurationAdapterINI implements LoggerConfigurationAdapter {
     34:
     35:     /** Name to assign to the root logger. */
     36:     const ROOT_LOGGER_NAME = "root";
     37:
     38:     /** Prefix used for defining logger additivity. */
     39:     const ADDITIVITY_PREFIX = "log4php.additivity.";
     40:
     41:     /** Prefix used for defining logger threshold. */
     42:     const THRESHOLD_PREFIX = "log4php.threshold";
     43:
     44:     /** Prefix used for defining the root logger. */
     45:     const ROOT_LOGGER_PREFIX = "log4php.rootLogger";
     46:
     47:     /** Prefix used for defining a logger. */
     48:     const LOGGER_PREFIX = "log4php.logger.";
     49:
     50:     /** Prefix used for defining an appender. */
     51:     const APPENDER_PREFIX = "log4php.appender.";
     52:
     53:     /** Prefix used for defining a renderer. */
     54:     const RENDERER_PREFIX = "log4php.renderer.";
     55:
     56:     /** Holds the configuration. */
     57:     private $config = array();
     58:
     59:     /**
     60:      * Loads and parses the INI configuration file.
     61:      *
     62:      * @param string $url Path to the config file.
     63:      * @throws LoggerException
     64:      */
     65:     private function load($url) {
     66:         if (!file_exists($url)) {
     67:             throw new LoggerException("File [$url] does not exist.");
     68:         }
     69:
     70:         $properties = @parse_ini_file($url, true);
     71:         if ($properties === false) {
     72:             $error = error_get_last();
     73:             throw new LoggerException("Error parsing configuration file: {$error['message']}");
     74:         }
     75:
     76:         return $properties;
     77:     }
     78:
     79:     /**
     80:     * Converts the provided INI configuration file to a PHP array config.
     81:     *
     82:     * @param string $path Path to the config file.
     83:     * @throws LoggerException If the file cannot be loaded or parsed.
     84:     */
     85:     public function convert($path) {
     86:         // Load the configuration
     87:         $properties = $this->load($path);
     88:
     89:         // Parse threshold
     90:         if (isset($properties[self::THRESHOLD_PREFIX])) {
     91:             $this->config['threshold'] = $properties[self::THRESHOLD_PREFIX];
     92:         }
     93:
     94:         // Parse root logger
     95:         if (isset($properties[self::ROOT_LOGGER_PREFIX])) {
     96:             $this->parseLogger($properties[self::ROOT_LOGGER_PREFIX], self::ROOT_LOGGER_NAME);
     97:         }
     98:
     99:         $appenders = array();
    100:
    101:         foreach($properties as $key => $value) {
    102:             // Parse loggers
    103:             if ($this->beginsWith($key, self::LOGGER_PREFIX)) {
    104:                 $name = substr($key, strlen(self::LOGGER_PREFIX));
    105:                 $this->parseLogger($value, $name);
    106:             }
    107:
    108:             // Parse additivity
    109:             if ($this->beginsWith($key, self::ADDITIVITY_PREFIX)) {
    110:                 $name = substr($key, strlen(self::ADDITIVITY_PREFIX));
    111:                 $this->config['loggers'][$name]['additivity'] = $value;
    112:             }
    113:
    114:             // Parse appenders
    115:             else if ($this->beginsWith($key, self::APPENDER_PREFIX)) {
    116:                 $this->parseAppender($key, $value);
    117:             }
    118:
    119:             // Parse renderers
    120:             else if ($this->beginsWith($key, self::RENDERER_PREFIX)) {
    121:                 $this->parseRenderer($key, $value);
    122:             }
    123:         }
    124:
    125:         return $this->config;
    126:     }
    127:
    128:
    129:     /**
    130:      * Parses a logger definition.
    131:      *
    132:      * Loggers are defined in the following manner:
    133:      * <pre>
    134:      * log4php.logger.<name> = [<level>], [<appender-ref>, <appender-ref>, ...]
    135:      * </pre>
    136:      *
    137:      * @param string $value The configuration value (level and appender-refs).
    138:      * @param string $name Logger name.
    139:      */
    140:     private function parseLogger($value, $name) {
    141:         // Value is divided by commas
    142:         $parts = explode(',', $value);
    143:         if (empty($value) || empty($parts)) {
    144:             return;
    145:         }
    146:
    147:         // The first value is the logger level
    148:         $level = array_shift($parts);
    149:
    150:         // The remaining values are appender references
    151:         $appenders = array();
    152:         while($appender = array_shift($parts)) {
    153:             $appender = trim($appender);
    154:             if (!empty($appender)) {
    155:                 $appenders[] = trim($appender);
    156:             }
    157:         }
    158:
    159:         // Find the target configuration
    160:         if ($name == self::ROOT_LOGGER_NAME) {
    161:             $this->config['rootLogger']['level'] = trim($level);
    162:             $this->config['rootLogger']['appenders'] = $appenders;
    163:         } else {
    164:             $this->config['loggers'][$name]['level'] = trim($level);
    165:             $this->config['loggers'][$name]['appenders'] = $appenders;
    166:         }
    167:     }
    168:
    169:     /**
    170:      * Parses an configuration line pertaining to an appender.
    171:      *
    172:      * Parses the following patterns:
    173:      *
    174:      * Appender class:
    175:      * <pre>
    176:      * log4php.appender.<name> = <class>
    177:      * </pre>
    178:      *
    179:      * Appender parameter:
    180:      * <pre>
    181:      * log4php.appender.<name>.<param> = <value>
    182:      * </pre>
    183:      *
    184:      * Appender threshold:
    185:      * <pre>
    186:      * log4php.appender.<name>.threshold = <level>
    187:      * </pre>
    188:      *
    189:      * Appender layout:
    190:      * <pre>
    191:      * log4php.appender.<name>.layout = <layoutClass>
    192:      * </pre>
    193:      *
    194:      * Layout parameter:
    195:      * <pre>
    196:      * log4php.appender.<name>.layout.<param> = <value>
    197:      * </pre>
    198:      *
    199:      * For example, a full appender config might look like:
    200:      * <pre>
    201:      * log4php.appender.myAppender = LoggerAppenderConsole
    202:      * log4php.appender.myAppender.threshold = info
    203:      * log4php.appender.myAppender.target = stdout
    204:      * log4php.appender.myAppender.layout = LoggerLayoutPattern
    205:      * log4php.appender.myAppender.layout.conversionPattern = "%d %c: %m%n"
    206:      * </pre>
    207:      *
    208:      * After parsing all these options, the following configuration can be
    209:      * found under $this->config['appenders']['myAppender']:
    210:      * <pre>
    211:      * array(
    212:      *  'class' => LoggerAppenderConsole,
    213:      *  'threshold' => info,
    214:      *  'params' => array(
    215:      *      'target' => 'stdout'
    216:      *  ),
    217:      *  'layout' => array(
    218:      *      'class' => 'LoggerAppenderConsole',
    219:      *      'params' => array(
    220:      *          'conversionPattern' => '%d %c: %m%n'
    221:      *      )
    222:      *  )
    223:      * )
    224:      * </pre>
    225:      *
    226:      * @param string $key
    227:      * @param string $value
    228:      */
    229:     private function parseAppender($key, $value) {
    230:
    231:         // Remove the appender prefix from key
    232:         $subKey = substr($key, strlen(self::APPENDER_PREFIX));
    233:
    234:         // Divide the string by dots
    235:         $parts = explode('.', $subKey);
    236:         $count = count($parts);
    237:
    238:         // The first part is always the appender name
    239:         $name = trim($parts[0]);
    240:
    241:         // Only one part - this line defines the appender class
    242:         if ($count == 1) {
    243:             $this->config['appenders'][$name]['class'] = $value;
    244:             return;
    245:         }
    246:
    247:         // Two parts - either a parameter, a threshold or layout class
    248:         else if ($count == 2) {
    249:
    250:             if ($parts[1] == 'layout') {
    251:                 $this->config['appenders'][$name]['layout']['class'] = $value;
    252:                 return;
    253:             } else if ($parts[1] == 'threshold') {
    254:                 $this->config['appenders'][$name]['threshold'] = $value;
    255:                 return;
    256:             } else {
    257:                 $this->config['appenders'][$name]['params'][$parts[1]] = $value;
    258:                 return;
    259:             }
    260:         }
    261:
    262:         // Three parts - this can only be a layout parameter
    263:         else if ($count == 3) {
    264:             if ($parts[1] == 'layout') {
    265:                 $this->config['appenders'][$name]['layout']['params'][$parts[2]] = $value;
    266:                 return;
    267:             }
    268:         }
    269:
    270:         trigger_error("log4php: Don't know how to parse the following line: \"$key = $value\". Skipping.");
    271:     }
    272:
    273:     /**
    274:      * Parses a renderer definition.
    275:      *
    276:      * Renderers are defined as:
    277:      * <pre>
    278:      * log4php.renderer.<renderedClass> = <renderingClass>
    279:      * </pre>
    280:      *
    281:      * @param string $key log4php.renderer.<renderedClass>
    282:      * @param string $value <renderingClass>
    283:      */
    284:     private function parseRenderer($key, $value) {
    285:         // Remove the appender prefix from key
    286:         $renderedClass = substr($key, strlen(self::APPENDER_PREFIX));
    287:         $renderingClass = $value;
    288:
    289:         $this->config['renderers'][] = compact('renderedClass', 'renderingClass');
    290:     }
    291:
    292:     /** Helper method. Returns true if $str begins with $sub. */
    293:     private function beginsWith($str, $sub) {
    294:         return (strncmp($str, $sub, strlen($sub)) == 0);
    295:     }
    296:
    297:
    298: }
    299:
    300: