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