      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:  * Default implementation of the logger configurator.
     23:  *
     24:  * Configures log4php based on a provided configuration file or array.
     25:  *
     26:  * @package log4php
     27:  * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
     28:  * @version $Revision: 1394956 $
     29:  * @since 2.2
     30:  */
     31: class LoggerConfiguratorDefault implements LoggerConfigurator
     32: {
     33:     /** XML configuration file format. */
     34:     const FORMAT_XML = 'xml';
     35:
     36:     /** PHP configuration file format. */
     37:     const FORMAT_PHP = 'php';
     38:
     39:     /** INI (properties) configuration file format. */
     40:     const FORMAT_INI = 'ini';
     41:
     42:     /** Defines which adapter should be used for parsing which format. */
     43:     private $adapters = array(
     44:         self::FORMAT_XML => 'LoggerConfigurationAdapterXML',
     45:         self::FORMAT_INI => 'LoggerConfigurationAdapterINI',
     46:         self::FORMAT_PHP => 'LoggerConfigurationAdapterPHP',
     47:     );
     48:
     49:     /** Default configuration; used if no configuration file is provided. */
     50:     private static $defaultConfiguration = array(
     51:         'threshold' => 'ALL',
     52:         'rootLogger' => array(
     53:             'level' => 'DEBUG',
     54:             'appenders' => array('default'),
     55:         ),
     56:         'appenders' => array(
     57:             'default' => array(
     58:                 'class' => 'LoggerAppenderEcho'
     59:             ),
     60:         ),
     61:     );
     62:
     63:     /** Holds the appenders before they are linked to loggers. */
     64:     private $appenders = array();
     65:
     66:     /**
     67:      * Configures log4php based on the given configuration. The input can
     68:      * either be a path to the config file, or a PHP array holding the
     69:      * configuration.
     70:      *
     71:      * If no configuration is given, or if the given configuration cannot be
     72:      * parsed for whatever reason, a warning will be issued, and log4php
     73:      * will use the default configuration contained in
     74:      * {@link $defaultConfiguration}.
     75:      *
     76:      * @param LoggerHierarchy $hierarchy The hierarchy on which to perform
     77:      *      the configuration.
     78:      * @param string|array $input Either path to the config file or the
     79:      *      configuration as an array. If not set, default configuration
     80:      *      will be used.
     81:      */
     82:     public function configure(LoggerHierarchy $hierarchy, $input = null) {
     83:         $config = $this->parse($input);
     84:         $this->doConfigure($hierarchy, $config);
     85:     }
     86:
     87:     /**
     88:      * Parses the given configuration and returns the parsed configuration
     89:      * as a PHP array. Does not perform any configuration.
     90:      *
     91:      * If no configuration is given, or if the given configuration cannot be
     92:      * parsed for whatever reason, a warning will be issued, and the default
     93:      * configuration will be returned ({@link $defaultConfiguration}).
     94:      *
     95:      * @param string|array $input Either path to the config file or the
     96:      *      configuration as an array. If not set, default configuration
     97:      *      will be used.
     98:      * @return array The parsed configuration.
     99:      */
    100:     public function parse($input) {
    101:         // No input - use default configuration
    102:         if (!isset($input)) {
    103:             $config = self::$defaultConfiguration;
    104:         }
    105:
    106:         // Array input - contains configuration within the array
    107:         else if (is_array($input)) {
    108:             $config = $input;
    109:         }
    110:
    111:         // String input - contains path to configuration file
    112:         else if (is_string($input)) {
    113:             try {
    114:                 $config = $this->parseFile($input);
    115:             } catch (LoggerException $e) {
    116:                 $this->warn("Configuration failed. " . $e->getMessage() . " Using default configuration.");
    117:                 $config = self::$defaultConfiguration;
    118:             }
    119:         }
    120:
    121:         // Anything else is an error
    122:         else {
    123:             $this->warn("Invalid configuration param given. Reverting to default configuration.");
    124:             $config = self::$defaultConfiguration;
    125:         }
    126:
    127:         return $config;
    128:     }
    129:
    130:     /**
    131:      * Returns the default log4php configuration.
    132:      * @return array
    133:      */
    134:     public static function getDefaultConfiguration() {
    135:         return self::$defaultConfiguration;
    136:     }
    137:
    138:     /**
    139:      * Loads the configuration file from the given URL, determines which
    140:      * adapter to use, converts the configuration to a PHP array and
    141:      * returns it.
    142:      *
    143:      * @param string $url Path to the config file.
    144:      * @return The configuration from the config file, as a PHP array.
    145:      * @throws LoggerException If the configuration file cannot be loaded, or
    146:      *      if the parsing fails.
    147:      */
    148:     private function parseFile($url) {
    149:
    150:         if (!file_exists($url)) {
    151:             throw new LoggerException("File not found at [$url].");
    152:         }
    153:
    154:         $type = $this->getConfigType($url);
    155:         $adapterClass = $this->adapters[$type];
    156:
    157:         $adapter = new $adapterClass();
    158:         return $adapter->convert($url);
    159:     }
    160:
    161:     /** Determines configuration file type based on the file extension. */
    162:     private function getConfigType($url) {
    163:         $info = pathinfo($url);
    164:         $ext = strtolower($info['extension']);
    165:
    166:         switch($ext) {
    167:             case 'xml':
    168:                 return self::FORMAT_XML;
    169:
    170:             case 'ini':
    171:             case 'properties':
    172:                 return self::FORMAT_INI;
    173:
    174:             case 'php':
    175:                 return self::FORMAT_PHP;
    176:
    177:             default:
    178:                 throw new LoggerException("Unsupported configuration file extension: $ext");
    179:         }
    180:     }
    181:
    182:     /**
    183:      * Constructs the logger hierarchy based on configuration.
    184:      *
    185:      * @param LoggerHierarchy $hierarchy
    186:      * @param array $config
    187:      */
    188:     private function doConfigure(LoggerHierarchy $hierarchy, $config) {
    189:         if (isset($config['threshold'])) {
    190:             $threshold = LoggerLevel::toLevel($config['threshold']);
    191:             if (isset($threshold)) {
    192:                 $hierarchy->setThreshold($threshold);
    193:             } else {
    194:                 $this->warn("Invalid threshold value [{$config['threshold']}] specified. Ignoring threshold definition.");
    195:             }
    196:         }
    197:
    198:         // Configure appenders and add them to the appender pool
    199:         if (isset($config['appenders']) && is_array($config['appenders'])) {
    200:             foreach($config['appenders'] as $name => $appenderConfig) {
    201:                 $this->configureAppender($name, $appenderConfig);
    202:             }
    203:         }
    204:
    205:         // Configure root logger
    206:         if (isset($config['rootLogger'])) {
    207:             $this->configureRootLogger($hierarchy, $config['rootLogger']);
    208:         }
    209:
    210:         // Configure loggers
    211:         if (isset($config['loggers']) && is_array($config['loggers'])) {
    212:             foreach($config['loggers'] as $loggerName => $loggerConfig) {
    213:                 $this->configureOtherLogger($hierarchy, $loggerName, $loggerConfig);
    214:             }
    215:         }
    216:
    217:         // Configure renderers
    218:         if (isset($config['renderers']) && is_array($config['renderers'])) {
    219:             foreach($config['renderers'] as $rendererConfig) {
    220:                 $this->configureRenderer($hierarchy, $rendererConfig);
    221:             }
    222:         }
    223:
    224:         if (isset($config['defaultRenderer'])) {
    225:             $this->configureDefaultRenderer($hierarchy, $config['defaultRenderer']);
    226:         }
    227:     }
    228:
    229:     private function configureRenderer(LoggerHierarchy $hierarchy, $config) {
    230:         if (empty($config['renderingClass'])) {
    231:             $this->warn("Rendering class not specified. Skipping renderer definition.");
    232:             return;
    233:         }
    234:
    235:         if (empty($config['renderedClass'])) {
    236:             $this->warn("Rendered class not specified. Skipping renderer definition.");
    237:             return;
    238:         }
    239:
    240:         // Error handling performed by RendererMap
    241:         $hierarchy->getRendererMap()->addRenderer($config['renderedClass'], $config['renderingClass']);
    242:     }
    243:
    244:     private function configureDefaultRenderer(LoggerHierarchy $hierarchy, $class) {
    245:         if (empty($class)) {
    246:             $this->warn("Rendering class not specified. Skipping default renderer definition.");
    247:             return;
    248:         }
    249:
    250:         // Error handling performed by RendererMap
    251:         $hierarchy->getRendererMap()->setDefaultRenderer($class);
    252:     }
    253:
    254:     /**
    255:      * Configures an appender based on given config and saves it to
    256:      * {@link $appenders} array so it can be later linked to loggers.
    257:      * @param string $name Appender name.
    258:      * @param array $config Appender configuration options.
    259:      */
    260:     private function configureAppender($name, $config) {
    261:
    262:         // TODO: add this check to other places where it might be useful
    263:         if (!is_array($config)) {
    264:             $type = gettype($config);
    265:             $this->warn("Invalid configuration provided for appender [$name]. Expected an array, found <$type>. Skipping appender definition.");
    266:             return;
    267:         }
    268:
    269:         // Parse appender class
    270:         $class = $config['class'];
    271:         if (empty($class)) {
    272:             $this->warn("No class given for appender [$name]. Skipping appender definition.");
    273:             return;
    274:         }
    275:         if (!class_exists($class)) {
    276:             $this->warn("Invalid class [$class] given for appender [$name]. Class does not exist. Skipping appender definition.");
    277:             return;
    278:         }
    279:
    280:         // Instantiate the appender
    281:         $appender = new $class($name);
    282:         if (!($appender instanceof LoggerAppender)) {
    283:             $this->warn("Invalid class [$class] given for appender [$name]. Not a valid LoggerAppender class. Skipping appender definition.");
    284:             return;
    285:         }
    286:
    287:         // Parse the appender threshold
    288:         if (isset($config['threshold'])) {
    289:             $threshold = LoggerLevel::toLevel($config['threshold']);
    290:             if ($threshold instanceof LoggerLevel) {
    291:                 $appender->setThreshold($threshold);
    292:             } else {
    293:                 $this->warn("Invalid threshold value [{$config['threshold']}] specified for appender [$name]. Ignoring threshold definition.");
    294:             }
    295:         }
    296:
    297:         // Parse the appender layout
    298:         if ($appender->requiresLayout() && isset($config['layout'])) {
    299:             $this->createAppenderLayout($appender, $config['layout']);
    300:         }
    301:
    302:         // Parse filters
    303:         if (isset($config['filters']) && is_array($config['filters'])) {
    304:             foreach($config['filters'] as $filterConfig) {
    305:                 $this->createAppenderFilter($appender, $filterConfig);
    306:             }
    307:         }
    308:
    309:         // Set options if any
    310:         if (isset($config['params'])) {
    311:             $this->setOptions($appender, $config['params']);
    312:         }
    313:
    314:         // Activate and save for later linking to loggers
    315:         $appender->activateOptions();
    316:         $this->appenders[$name] = $appender;
    317:     }
    318:
    319:     /**
    320:      * Parses layout config, creates the layout and links it to the appender.
    321:      * @param LoggerAppender $appender
    322:      * @param array $config Layout configuration.
    323:      */
    324:     private function createAppenderLayout(LoggerAppender $appender, $config) {
    325:         $name = $appender->getName();
    326:         $class = $config['class'];
    327:         if (empty($class)) {
    328:             $this->warn("Layout class not specified for appender [$name]. Reverting to default layout.");
    329:             return;
    330:         }
    331:         if (!class_exists($class)) {
    332:             $this->warn("Nonexistant layout class [$class] specified for appender [$name]. Reverting to default layout.");
    333:             return;
    334:         }
    335:
    336:         $layout = new $class();
    337:         if (!($layout instanceof LoggerLayout)) {
    338:             $this->warn("Invalid layout class [$class] sepcified for appender [$name]. Reverting to default layout.");
    339:             return;
    340:         }
    341:
    342:         if (isset($config['params'])) {
    343:             $this->setOptions($layout, $config['params']);
    344:         }
    345:
    346:         $layout->activateOptions();
    347:         $appender->setLayout($layout);
    348:     }
    349:
    350:     /**
    351:      * Parses filter config, creates the filter and adds it to the appender's
    352:      * filter chain.
    353:      * @param LoggerAppender $appender
    354:      * @param array $config Filter configuration.
    355:      */
    356:     private function createAppenderFilter(LoggerAppender $appender, $config) {
    357:         $name = $appender->getName();
    358:         $class = $config['class'];
    359:         if (!class_exists($class)) {
    360:             $this->warn("Nonexistant filter class [$class] specified on appender [$name]. Skipping filter definition.");
    361:             return;
    362:         }
    363:
    364:         $filter = new $class();
    365:         if (!($filter instanceof LoggerFilter)) {
    366:             $this->warn("Invalid filter class [$class] sepcified on appender [$name]. Skipping filter definition.");
    367:             return;
    368:         }
    369:
    370:         if (isset($config['params'])) {
    371:             $this->setOptions($filter, $config['params']);
    372:         }
    373:
    374:         $filter->activateOptions();
    375:         $appender->addFilter($filter);
    376:     }
    377:
    378:     /**
    379:      * Configures the root logger
    380:      * @see configureLogger()
    381:      */
    382:     private function configureRootLogger(LoggerHierarchy $hierarchy, $config) {
    383:         $logger = $hierarchy->getRootLogger();
    384:         $this->configureLogger($logger, $config);
    385:     }
    386:
    387:     /**
    388:      * Configures a logger which is not root.
    389:      * @see configureLogger()
    390:      */
    391:     private function configureOtherLogger(LoggerHierarchy $hierarchy, $name, $config) {
    392:         // Get logger from hierarchy (this creates it if it doesn't already exist)
    393:         $logger = $hierarchy->getLogger($name);
    394:         $this->configureLogger($logger, $config);
    395:     }
    396:
    397:     /**
    398:      * Configures a logger.
    399:      *
    400:      * @param Logger $logger The logger to configure
    401:      * @param array $config Logger configuration options.
    402:      */
    403:     private function configureLogger(Logger $logger, $config) {
    404:         $loggerName = $logger->getName();
    405:
    406:         // Set logger level
    407:         if (isset($config['level'])) {
    408:             $level = LoggerLevel::toLevel($config['level']);
    409:             if (isset($level)) {
    410:                 $logger->setLevel($level);
    411:             } else {
    412:                 $this->warn("Invalid level value [{$config['level']}] specified for logger [$loggerName]. Ignoring level definition.");
    413:             }
    414:         }
    415:
    416:         // Link appenders to logger
    417:         if (isset($config['appenders'])) {
    418:             foreach($config['appenders'] as $appenderName) {
    419:                 if (isset($this->appenders[$appenderName])) {
    420:                     $logger->addAppender($this->appenders[$appenderName]);
    421:                 } else {
    422:                     $this->warn("Nonexistnant appender [$appenderName] linked to logger [$loggerName].");
    423:                 }
    424:             }
    425:         }
    426:
    427:         // Set logger additivity
    428:         if (isset($config['additivity'])) {
    429:             try {
    430:                 $additivity = LoggerOptionConverter::toBooleanEx($config['additivity'], null);
    431:                 $logger->setAdditivity($additivity);
    432:             } catch (Exception $ex) {
    433:                 $this->warn("Invalid additivity value [{$config['additivity']}] specified for logger [$loggerName]. Ignoring additivity setting.");
    434:             }
    435:         }
    436:     }
    437:
    438:     /**
    439:      * Helper method which applies given options to an object which has setters
    440:      * for these options (such as appenders, layouts, etc.).
    441:      *
    442:      * For example, if options are:
    443:      * <code>
    444:      * array(
    445:      *  'file' => '/tmp/myfile.log',
    446:      *  'append' => true
    447:      * )
    448:      * </code>
    449:      *
    450:      * This method will call:
    451:      * <code>
    452:      * $object->setFile('/tmp/myfile.log')
    453:      * $object->setAppend(true)
    454:      * </code>
    455:      *
    456:      * If required setters do not exist, it will produce a warning.
    457:      *
    458:      * @param mixed $object The object to configure.
    459:      * @param unknown_type $options
    460:      */
    461:     private function setOptions($object, $options) {
    462:         foreach($options as $name => $value) {
    463:             $setter = "set$name";
    464:             if (method_exists($object, $setter)) {
    465:                 $object->$setter($value);
    466:             } else {
    467:                 $class = get_class($object);
    468:                 $this->warn("Nonexistant option [$name] specified on [$class]. Skipping.");
    469:             }
    470:         }
    471:     }
    472:
    473:     /** Helper method to simplify error reporting. */
    474:     private function warn($message) {
    475:         trigger_error("log4php: $message", E_USER_WARNING);
    476:     }
    477: }
    478: