LOG4PHP-118: Additivity cannot be disabled. Thanks for the patch to Craig Marvelley
[logging-log4php.git] / src / main / php / configurators / LoggerConfiguratorIni.php
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 * Allows the configuration of log4php from an external file.
23 *
24 * <p>This is the most commonly used method of configuring log4php.</p>
25 *
26 * <p>It is sometimes useful to see how log4php is reading configuration
27 * files. You can enable log4php internal logging by defining the
28 * <b>log4php.debug</b> variable.</p>
29 *
30 * <p>The <i>LoggerConfiguratorIni</i> does not handle the
31 * advanced configuration features supported by the {@link LoggerConfiguratorXml}
32 * such as support for {@link LoggerFilter},
33 * custom {@link LoggerErrorHandlers}, nested appenders such as the {@link Logger AsyncAppender},
34 * etc.</p>
35 *
36 * <p>All option <i>values</i> admit variable substitution. The
37 * syntax of variable substitution is similar to that of Unix
38 * shells. The string between an opening <b>"${"</b> and
39 * closing <b>"}"</b> is interpreted as a key. The value of
40 * the substituted variable can be defined as a system property or in
41 * the configuration file itself. The value of the key is first
42 * searched in the defined constants, in the enviroments variables
43 * and if not found there, it is
44 * then searched in the configuration file being parsed. The
45 * corresponding value replaces the ${variableName} sequence.</p>
46 * <p>For example, if <b>$_ENV['home']</b> env var is set to
47 * <b>/home/xyz</b>, then every occurrence of the sequence
48 * <b>${home}</b> will be interpreted as
49 * <b>/home/xyz</b>. See {@link LoggerOptionConverter::getSystemProperty()}
50 * for details.</p>
51 *
52 * <p>An example how to use this appender:</p>
53 *
54 * {@example ../../examples/php/appender_dailyfile.php 19}
55 *
56 * <p>And the corresponding ini file:</p>
57 *
58 * {@example ../../examples/resources/appender_dailyfile.properties 18}
59 *
60 * @version $Revision$
61 * @package log4php
62 * @subpackage configurators
63 * @since 0.5
64 */
65 class LoggerConfiguratorIni implements LoggerConfigurator {
66 const CATEGORY_PREFIX = "log4php.category.";
67 const LOGGER_PREFIX = "log4php.logger.";
68 const FACTORY_PREFIX = "log4php.factory";
69 const ADDITIVITY_PREFIX = "log4php.additivity.";
70 const ROOT_CATEGORY_PREFIX = "log4php.rootCategory";
71 const ROOT_LOGGER_PREFIX = "log4php.rootLogger";
72 const APPENDER_PREFIX = "log4php.appender.";
73 const RENDERER_PREFIX = "log4php.renderer.";
74 const THRESHOLD_PREFIX = "log4php.threshold";
75
76 /**
77 * Key for specifying the {@link LoggerFactory}.
78 */
79 const LOGGER_FACTORY_KEY = "log4php.loggerFactory";
80 const LOGGER_DEBUG_KEY = "log4php.debug";
81 const INTERNAL_ROOT_NAME = "root";
82
83 /**
84 * Constructor
85 */
86 public function __construct() {
87 }
88
89 /**
90 * Read configuration from a file.
91 *
92 * <p>The function {@link PHP_MANUAL#parse_ini_file} is used to read the
93 * file.</p>
94 *
95 * <b>The existing configuration is not cleared nor reset.</b>
96 * If you require a different behavior, then call
97 * {@link Logger::resetConfiguration()}
98 * method before calling {@link doConfigure()}.
99 *
100 * <p>The configuration file consists of statements in the format
101 * <b>key=value</b>. The syntax of different configuration
102 * elements are discussed below.
103 *
104 * <p><b>Repository-wide threshold</b></p>
105 *
106 * <p>The repository-wide threshold filters logging requests by level
107 * regardless of logger. The syntax is:
108 *
109 * <pre>
110 * log4php.threshold=[level]
111 * </pre>
112 *
113 * <p>The level value can consist of the string values OFF, FATAL,
114 * ERROR, WARN, INFO, DEBUG, ALL or a <i>custom level</i> value. A
115 * custom level value can be specified in the form
116 * <samp>level#classname</samp>. By default the repository-wide threshold is set
117 * to the lowest possible value, namely the level <b>ALL</b>.
118 * </p>
119 *
120 *
121 * <p><b>Appender configuration</b></p>
122 *
123 * <p>Appender configuration syntax is:</p>
124 * <pre>
125 * ; For appender named <i>appenderName</i>, set its class.
126 * ; Note: The appender name can contain dots.
127 * log4php.appender.appenderName=name_of_appender_class
128 *
129 * ; Set appender specific options.
130 *
131 * log4php.appender.appenderName.option1=value1
132 * log4php.appender.appenderName.optionN=valueN
133 * </pre>
134 *
135 * For each named appender you can configure its {@link LoggerLayout}. The
136 * syntax for configuring an appender's layout is:
137 * <pre>
138 * log4php.appender.appenderName.layout=name_of_layout_class
139 * log4php.appender.appenderName.layout.option1=value1
140 * ....
141 * log4php.appender.appenderName.layout.optionN=valueN
142 * </pre>
143 *
144 * <p><b>Configuring loggers</b></p>
145 *
146 * <p>The syntax for configuring the root logger is:
147 * <pre>
148 * log4php.rootLogger=[level], appenderName, appenderName, ...
149 * </pre>
150 *
151 * <p>This syntax means that an optional <i>level</i> can be
152 * supplied followed by appender names separated by commas.
153 *
154 * <p>The level value can consist of the string values OFF, FATAL,
155 * ERROR, WARN, INFO, DEBUG, ALL or a <i>custom level</i> value. A
156 * custom level value can be specified in the form</p>
157 *
158 * <pre>level#classname</pre>
159 *
160 * <p>If a level value is specified, then the root level is set
161 * to the corresponding level. If no level value is specified,
162 * then the root level remains untouched.
163 *
164 * <p>The root logger can be assigned multiple appenders.
165 *
166 * <p>Each <i>appenderName</i> (separated by commas) will be added to
167 * the root logger. The named appender is defined using the
168 * appender syntax defined above.
169 *
170 * <p>For non-root categories the syntax is almost the same:
171 * <pre>
172 * log4php.logger.logger_name=[level|INHERITED|NULL], appenderName, appenderName, ...
173 * </pre>
174 *
175 * <p>The meaning of the optional level value is discussed above
176 * in relation to the root logger. In addition however, the value
177 * INHERITED can be specified meaning that the named logger should
178 * inherit its level from the logger hierarchy.</p>
179 *
180 * <p>If no level value is supplied, then the level of the
181 * named logger remains untouched.</p>
182 *
183 * <p>By default categories inherit their level from the
184 * hierarchy. However, if you set the level of a logger and later
185 * decide that that logger should inherit its level, then you should
186 * specify INHERITED as the value for the level value. NULL is a
187 * synonym for INHERITED.</p>
188 *
189 * <p>Similar to the root logger syntax, each <i>appenderName</i>
190 * (separated by commas) will be attached to the named logger.</p>
191 *
192 * <p>See the <i>appender additivity rule</i> in the user manual for
193 * the meaning of the <b>additivity</b> flag.
194 *
195 * <p><b>ObjectRenderers</b></p>
196 *
197 * <p>You can customize the way message objects of a given type are
198 * converted to String before being logged. This is done by
199 * specifying a {@link LoggerRendererObject}
200 * for the object type would like to customize.</p>
201 *
202 * <p>The syntax is:
203 *
204 * <pre>
205 * log4php.renderer.name_of_rendered_class=name_of_rendering.class
206 * </pre>
207 *
208 * As in,
209 * <pre>
210 * log4php.renderer.myFruit=myFruitRenderer
211 * </pre>
212 *
213 * <p><b>Logger Factories</b></p>
214 *
215 * The usage of custom logger factories is discouraged and no longer
216 * documented.
217 *
218 * <p><b>Example</b></p>
219 *
220 * <p>An example configuration is given below. Other configuration
221 * file examples are given in the <b>tests</b> folder.
222 *
223 * <pre>
224 * ; Set options for appender named "A1".
225 * ; Appender "A1" will be a LoggerAppenderSyslog
226 * log4php.appender.A1=LoggerAppenderSyslog
227 *
228 * ; The syslog daemon resides on www.abc.net
229 * log4php.appender.A1.ident=log4php-test
230 *
231 * ; A1's layout is a LoggerPatternLayout, using the conversion pattern
232 * ; <b>%r %-5p %c{2} %M.%L %x - %m%n</b>. Thus, the log output will
233 * ; include the relative time since the start of the application in
234 * ; milliseconds, followed by the level of the log request,
235 * ; followed by the two rightmost components of the logger name,
236 * ; followed by the callers method name, followed by the line number,
237 * ; the nested disgnostic context and finally the message itself.
238 * ; Refer to the documentation of LoggerPatternLayout} for further information
239 * ; on the syntax of the ConversionPattern key.
240 * log4php.appender.A1.layout=LoggerPatternLayout
241 * log4php.appender.A1.layout.ConversionPattern="%-4r %-5p %c{2} %M.%L %x - %m%n"
242 *
243 * ; Set options for appender named "A2"
244 * ; A2 should be a LoggerAppenderRollingFile, with maximum file size of 10 MB
245 * ; using at most one backup file. A2's layout is TTCC, using the
246 * ; ISO8061 date format with context printing enabled.
247 * log4php.appender.A2=LoggerAppenderRollingFile
248 * log4php.appender.A2.MaxFileSize=10MB
249 * log4php.appender.A2.MaxBackupIndex=1
250 * log4php.appender.A2.layout=LoggerLayoutTTCC
251 * log4php.appender.A2.layout.ContextPrinting="true"
252 * log4php.appender.A2.layout.DateFormat="%c"
253 *
254 * ; Root logger set to DEBUG using the A2 appender defined above.
255 * log4php.rootLogger=DEBUG, A2
256 *
257 * ; Logger definitions:
258 * ; The SECURITY logger inherits is level from root. However, it's output
259 * ; will go to A1 appender defined above. It's additivity is non-cumulative.
260 * log4php.logger.SECURITY=INHERIT, A1
261 * log4php.additivity.SECURITY=false
262 *
263 * ; Only warnings or above will be logged for the logger "SECURITY.access".
264 * ; Output will go to A1.
265 * log4php.logger.SECURITY.access=WARN
266 *
267 *
268 * ; The logger "class.of.the.day" inherits its level from the
269 * ; logger hierarchy. Output will go to the appender's of the root
270 * ; logger, A2 in this case.
271 * log4php.logger.class.of.the.day=INHERIT
272 * </pre>
273 *
274 * <p>Refer to the <b>setOption</b> method in each Appender and
275 * Layout for class specific options.</p>
276 *
277 * <p>Use the <b>&quot;;&quot;</b> character at the
278 * beginning of a line for comments.</p>
279 *
280 * @param string $url The name of the configuration file where the
281 * configuration information is stored.
282 * @param LoggerHierarchy $repository the repository to apply the configuration
283 */
284 public function configure(LoggerHierarchy $hierarchy, $url = '') {
285 $properties = @parse_ini_file($url);
286 if ($properties === false) {
287 $error = error_get_last();
288 throw new LoggerException("LoggerConfiguratorIni: Error parsing configuration file: ".$error['message']);
289 }
290 if (count($properties) == 0) {
291 trigger_error("LoggerConfiguratorIni: Configuration file is empty.", E_USER_WARNING);
292 }
293
294 return $this->doConfigureProperties($properties, $hierarchy);
295 }
296
297 /**
298 * Read configuration options from <b>properties</b>.
299 *
300 * @see doConfigure().
301 * @param array $properties
302 * @param LoggerHierarchy $hierarchy
303 */
304 private function doConfigureProperties($properties, LoggerHierarchy $hierarchy) {
305 $thresholdStr = @$properties[self::THRESHOLD_PREFIX];
306 $hierarchy->setThreshold(LoggerOptionConverter::toLevel($thresholdStr, LoggerLevel::getLevelAll()));
307 $this->configureRootCategory($properties, $hierarchy);
308 $this->parseCatsAndRenderers($properties, $hierarchy);
309 return true;
310 }
311
312 /**
313 * @param array $props array of properties
314 * @param LoggerHierarchy $hierarchy
315 */
316 private function configureRootCategory($props, LoggerHierarchy $hierarchy) {
317 $effectivePrefix = self::ROOT_LOGGER_PREFIX;
318 $value = @$props[self::ROOT_LOGGER_PREFIX];
319
320 if(empty($value)) {
321 $value = @$props[self::ROOT_CATEGORY_PREFIX];
322 $effectivePrefix = self::ROOT_CATEGORY_PREFIX;
323 }
324
325 if(empty($value)) {
326 // TODO "Could not find root logger information. Is this OK?"
327 } else {
328 $root = $hierarchy->getRootLogger();
329 $this->parseCategory($props, $root, $effectivePrefix, self::INTERNAL_ROOT_NAME, $value);
330 }
331 }
332
333 /**
334 * Parse non-root elements, such non-root categories and renderers.
335 *
336 * @param array $props array of properties
337 * @param LoggerHierarchy $hierarchy
338 */
339 private function parseCatsAndRenderers($props, LoggerHierarchy $hierarchy) {
340 while(list($key,$value) = each($props)) {
341 if(strpos($key, self::CATEGORY_PREFIX) === 0 or
342 strpos($key, self::LOGGER_PREFIX) === 0) {
343 if(strpos($key, self::CATEGORY_PREFIX) === 0) {
344 $loggerName = substr($key, strlen(self::CATEGORY_PREFIX));
345 } else if(strpos($key, self::LOGGER_PREFIX) === 0) {
346 $loggerName = substr($key, strlen(self::LOGGER_PREFIX));
347 }
348
349 $logger = $hierarchy->getLogger($loggerName);
350 $this->parseCategory($props, $logger, $key, $loggerName, $value);
351 $this->parseAdditivityForLogger($props, $logger, $loggerName);
352 } else if(strpos($key, self::RENDERER_PREFIX) === 0) {
353 $renderedClass = substr($key, strlen(self::RENDERER_PREFIX));
354 $renderingClass = $value;
355 $hierarchy->getRendererMap()->addRenderer($renderedClass, $renderingClass);
356 }
357 }
358 }
359
360 /**
361 * Parse the additivity option for a non-root category.
362 *
363 * @param array $props array of properties
364 * @param Logger $cat
365 * @param string $loggerName
366 */
367 private function parseAdditivityForLogger($props, Logger $cat, $loggerName) {
368 $value = LoggerOptionConverter::findAndSubst(self::ADDITIVITY_PREFIX . $loggerName, $props);
369 $additivity = LoggerOptionConverter::toBoolean($value, true);
370 $cat->setAdditivity($additivity);
371 }
372
373 /**
374 * This method must work for the root category as well.
375 *
376 * @param array $props array of properties
377 * @param Logger $logger
378 * @param string $optionKey
379 * @param string $loggerName
380 * @param string $value
381 * @return Logger
382 */
383 private function parseCategory($props, Logger $logger, $optionKey, $loggerName, $value) {
384 // We must skip over ',' but not white space
385 $st = explode(',', $value);
386
387 // If value is not in the form ", appender.." or "", then we should set
388 // the level of the loggeregory.
389
390 if(!(empty($value) || @$value[0] == ',')) {
391 // just to be on the safe side...
392 if(count($st) == 0) {
393 return;
394 }
395 $levelStr = current($st);
396
397 // If the level value is inherited, set category level value to
398 // null. We also check that the user has not specified inherited for the
399 // root category.
400 if('INHERITED' == strtoupper($levelStr) || 'NULL' == strtoupper($levelStr)) {
401 if($loggerName == self::INTERNAL_ROOT_NAME) {
402 // TODO: throw exception? "The root logger cannot be set to null."
403 } else {
404 $logger->setLevel(null);
405 }
406 } else {
407 $logger->setLevel(LoggerOptionConverter::toLevel($levelStr, LoggerLevel::getLevelDebug()));
408 }
409 }
410
411 // TODO: removing should be done by the logger, if necessary and wanted
412 // $logger->removeAllAppenders();
413 while($appenderName = next($st)) {
414 $appenderName = trim($appenderName);
415 if(empty($appenderName)) {
416 continue;
417 }
418
419 $appender = $this->parseAppender($props, $appenderName);
420 if($appender !== null) {
421 $logger->addAppender($appender);
422 }
423 }
424 }
425
426 /**
427 * @param array $props array of properties
428 * @param string $appenderName
429 * @return LoggerAppender
430 */
431 private function parseAppender($props, $appenderName) {
432 $appender = LoggerAppenderPool::getAppenderFromPool($appenderName);
433 $prefix = self::APPENDER_PREFIX . $appenderName;
434 if($appender === null) {
435 // Appender was not previously initialized.
436 $appenderClass = @$props[$prefix];
437 $appender = LoggerAppenderPool::getAppenderFromPool($appenderName, $appenderClass);
438 if($appender === null) {
439 return null;
440 }
441 }
442
443 if($appender->requiresLayout() ) {
444 $layoutPrefix = $prefix . ".layout";
445 $layoutClass = @$props[$layoutPrefix];
446 $layoutClass = LoggerOptionConverter::substVars($layoutClass, $props);
447 if(empty($layoutClass)) {
448 $layout = LoggerReflectionUtils::createObject('LoggerLayoutSimple');
449 } else {
450 $layout = LoggerReflectionUtils::createObject($layoutClass);
451 if($layout === null) {
452 $layout = LoggerReflectionUtils::createObject('LoggerLayoutSimple');
453 }
454 }
455
456 LoggerReflectionUtils::setPropertiesByObject($layout, $props, $layoutPrefix . ".");
457 $appender->setLayout($layout);
458
459 }
460 LoggerReflectionUtils::setPropertiesByObject($appender, $props, $prefix . ".");
461 return $appender;
462 }
463 }