Index: src/main/php/Logger.php
===================================================================
--- src/main/php/Logger.php (revision 944773)
+++ src/main/php/Logger.php (working copy)
@@ -100,6 +100,7 @@
'LoggerClassNamePatternConverter' => '/helpers/LoggerClassNamePatternConverter.php',
'LoggerCategoryPatternConverter' => '/helpers/LoggerCategoryPatternConverter.php',
'LoggerPatternParser' => '/helpers/LoggerPatternParser.php',
+ 'LoggerLog' => '/helpers/LoggerLog.php',
'LoggerLayoutHtml' => '/layouts/LoggerLayoutHtml.php',
'LoggerLayoutSimple' => '/layouts/LoggerLayoutSimple.php',
'LoggerLayoutTTCC' => '/layouts/LoggerLayoutTTCC.php',
Index: src/main/php/LoggerLevel.php
===================================================================
--- src/main/php/LoggerLevel.php (revision 944773)
+++ src/main/php/LoggerLevel.php (working copy)
@@ -220,6 +220,14 @@
public function toString() {
return $this->levelStr;
}
+
+ /**
+ * Implementation of the __toString magic method.
+ * @return string
+ */
+ public function __toString() {
+ return $this->toString();
+ }
/**
* Returns the integer representation of this level.
Index: src/main/php/configurators/LoggerConfiguratorXml.php
===================================================================
--- src/main/php/configurators/LoggerConfiguratorXml.php (revision 944773)
+++ src/main/php/configurators/LoggerConfiguratorXml.php (working copy)
@@ -44,398 +44,647 @@
* @since 0.4
*/
class LoggerConfiguratorXml implements LoggerConfigurator {
- const APPENDER_STATE = 1000;
- const LAYOUT_STATE = 1010;
- const ROOT_STATE = 1020;
- const LOGGER_STATE = 1030;
- const FILTER_STATE = 1040;
- const DEFAULT_FILENAME = './log4php.xml';
+ /** Default value for internal debugging. */
+ const DEFAULT_DEBUG_MODE = false;
- /**
- * @var string the default configuration document
- */
+ /** Default value for logger additivity. */
+ const DEFAULT_ADDITIVITY = true;
+
+ /** Default XML configuration. */
const DEFAULT_CONFIGURATION =
'
-
-
-
-
-
-
-
+
+
+
+
+
+
+
';
+ /** An array which acts as a context stack for logging, similar to LoggerNDC. */
+ private $context = array();
+
/**
- * @var string the elements namespace
+ * Appenders are kept here after being parsed, and before they are added
+ * to loggers which use them.
*/
- const XMLNS = 'HTTP://LOGGING.APACHE.ORG/LOG4PHP/';
+ private $appenders = array();
- /**
- * @var LoggerHierarchy
- */
- private $repository;
-
- /**
- * @var array state stack
- */
- private $state;
+ /** The hierarchy is kept here for easy access. @var LoggerHierarchy */
+ private $hierarchy;
+
+ /* (non-PHPdoc) Implementation of the LoggerConfigurator interface. */
+ public function configure(LoggerHierarchy $hierarchy, $url = '') {
+ $this->pushContext('XML configurator');
+
+ $msg = empty($url) ?
+ " with default configuration." :
+ " with configuration file [$url]";
+ $this->debug('Starting XML configurator' . $msg);
+
+ $this->hierarchy = $hierarchy;
+
+ if (empty($url)) {
+ $this->doConfigureDefault();
+ } else if (!file_exists($url)) {
+ $this->warn("Configuration file not found at [$url]. Reverting to default configuration.");
+ $this->doConfigureDefault();
+ } else {
+ $this->doConfigure($url);
+ }
+
+ $this->popContext();
+ return $this->hierarchy;
+ }
+
+ /**
+ * Starts the configuration using the specified config file.
+ * @param string $url path to the config file
+ */
+ private function doConfigure($url) {
+ $xmlData = file_get_contents($url);
+ if ($xmlData !== false) {
+ $this->parse($xmlData);
+ } else {
+ $error = error_get_last();
+ $this->warn("Unable to load configuration file from [$url]: {$error['message']}.");
+ $this->warn('Reverting to default configuration.');
+ $this->doConfigureDefault();
+ }
+ }
+
+ /** Starts the configuration using the default config file. */
+ private function doConfigureDefault()
+ {
+ $this->parse(self::DEFAULT_CONFIGURATION);
+ }
+
+ /**
+ * Parses the XML configuration.
+ *
+ * @param string $xmlData the XML configuration
+ */
+ private function parse($xmlData)
+ {
+ // Supress parser warnings, they will be displayed later
+ libxml_use_internal_errors(true);
+
+ try {
+ $xml = new SimpleXMLElement($xmlData);
+
+ // Parse the root node
+ $this->parseConfigurationNode($xml);
+
+ // Process renderers
+ foreach($xml->renderer as $rendererNode) {
+ $this->parseRendererNode($rendererNode);
+ }
+
+ // Process appenders
+ foreach($xml->appender as $appenderNode) {
+ $this->parseAppenderNode($appenderNode);
+ }
+
+ // Parse root logger
+ if (isset($xml->root)) {
+ $this->parseRootLoggerNode($xml->root);
+ }
+
+ // Parse loggers
+ foreach($xml->logger as $loggerNode) {
+ $this->parseLoggerNode($loggerNode);
+ }
+
+ } catch (LoggerException $e) {
+ $this->error("Parsing of configuration file failed: " . $e->getMessage());
+ $this->hierarchy->resetConfiguration();
+ } catch (Exception $e) {
+ $this->error("Parsing of configuration file failed.");
+ $this->hierarchy->resetConfiguration();
+ }
+
+ // Log any errors and warnings
+ $this->logXMLErrors();
+ }
+
+ /** Logs errors and warnings reported by LibXML to the internal logger. */
+ private function logXMLErrors()
+ {
+ $errors = libxml_get_errors();
+
+ foreach($errors as $error) {
+ $message = trim($error->message);
+ $entry = "LibXML: \"{$message}\" on line {$error->line}, column {$error->column}";
- /**
- * @var Logger parsed Logger
- */
- private $logger;
-
- /**
- * @var LoggerAppender parsed LoggerAppender
- */
- private $appender;
-
- /**
- * @var LoggerFilter parsed LoggerFilter
- */
- private $filter;
-
- /**
- * @var LoggerLayout parsed LoggerLayout
- */
- private $layout;
-
- /**
- * Constructor
- */
- public function __construct() {
- $this->state = array();
- $this->logger = null;
- $this->appender = null;
- $this->filter = null;
- $this->layout = null;
- }
-
- /**
- * Configure the default repository using the resource pointed by url.
- * Url is any valid resource as defined in {@link PHP_MANUAL#file} function.
- * Note that the resource will be search with use_include_path parameter
- * set to "1".
- *
- * @param string $url
- * @static
- */
- public function configure(LoggerHierarchy $hierarchy, $url = '') {
- return $this->doConfigure($url, $hierarchy);
- }
-
- /**
- * Configure the given repository using the resource pointed by url.
- * Url is any valid resurce as defined in {@link PHP_MANUAL#file} function.
- * Note that the resource will be search with use_include_path parameter
- * set to "1".
- *
- * @param string $url
- * @param LoggerHierarchy $repository
- */
- private function doConfigure($url = '', LoggerHierarchy $repository)
- {
- $xmlData = '';
- if (!empty($url))
- $xmlData = implode('', file($url, 1));
- return $this->doConfigureByString($xmlData, $repository);
- }
-
- /**
- * Configure the given repository using the configuration written in xmlData.
- * Do not call this method directly. Use {@link doConfigure()} instead.
- * @param string $xmlData
- * @param LoggerHierarchy $repository
- */
- private function doConfigureByString($xmlData, LoggerHierarchy $repository)
- {
- return $this->parse($xmlData, $repository);
- }
-
- /**
- * @param LoggerHierarchy $repository
- */
- private function doConfigureDefault(LoggerHierarchy $repository)
- {
- return $this->doConfigureByString(self::DEFAULT_CONFIGURATION, $repository);
- }
-
- /**
- * @param string $xmlData
- */
- private function parse($xmlData, LoggerHierarchy $repository)
- {
- // Logger::resetConfiguration();
- $this->repository = $repository;
+ // Log level determined by message level
+ switch($error->level) {
+ case LIBXML_ERR_WARNING:
+ LoggerLog::warn($entry);
+ break;
+ case LIBXML_ERR_ERROR:
+ case LIBXML_ERR_FATAL:
+ LoggerLog::error($entry);
+ break;
+ }
+ }
+ }
+
+ /**
+ * Parses a node and, if it passes validation, adds the
+ * renderer to the hierarchy. If validation fails, issues a warning
+ * and does nothing.
+ *
+ * @param SimpleXMLElement $rendererNode the renderer node
+ */
+ private function parseRendererNode(SimpleXMLElement $rendererNode) {
+ $attrs = $this->getAttributes($rendererNode);
+
+ // Verify required attributes exist and are not empty
+ if (!isset($attrs['renderedclass']) || empty($attrs['renderedclass'])) {
+ $this->warn("Invalid renderer definition. Attribute 'renderedclass' is missing or empty. Skipping renderer.");
+ return null;
+ }
+
+ if (!isset($attrs['renderingclass']) || empty($attrs['renderingclass'])) {
+ $this->warn("Invalid renderer definition. Attribute 'renderingclass' is missing or empty. Skipping renderer.");
+ return null;
+ }
+
+ $renderedClass = $attrs['renderedclass'];
+ $renderingClass = $attrs['renderingclass'];
+
+ // Verify specified renderer classe exist and implements LoggerRendererObject
+ if (!class_exists($renderingClass)) {
+ $this->warn("Invalid rendering class specified. Class [$renderingClass] does not exist. Skipping renderer.");
+ return null;
+ }
+
+ $renderer = new $renderingClass();
+ if (!($renderer instanceof LoggerRendererObject)) {
+ $this->warn("Invalid rendering class specified. Class [$renderingClass] is not a valid renderer. Skipping renderer.");
+ return null;
+ }
+
+ $this->hierarchy->getRendererMap()->addRenderer($renderedClass, $renderingClass);
+ }
+
+
+ /**
+ * Parses the node.
+ *
+ * Processes the 'debug' and 'threshold' attributes and applies them to
+ * the logger hierarchy.
+ *
+ * @param SimpleXMLElement $configurationNode
+ */
+ private function parseConfigurationNode(SimpleXMLElement $configurationNode) {
+ $attrs = $this->getAttributes($configurationNode);
+
+ if (isset($attrs['debug'])) {
+ $value = $attrs['debug'];
+ $debug = LoggerOptionConverter::toBoolean($attrs['debug'], false);
- $parser = xml_parser_create_ns();
-
- xml_set_object($parser, $this);
- xml_set_element_handler($parser, "tagOpen", "tagClose");
-
- $result = xml_parse($parser, $xmlData, true);
- if (!$result) {
- $errorCode = xml_get_error_code($parser);
- $errorStr = xml_error_string($errorCode);
- $errorLine = xml_get_current_line_number($parser);
- $this->repository->resetConfiguration();
- } else {
- xml_parser_free($parser);
- }
- return $result;
- }
-
- /**
- * @param mixed $parser
- * @param string $tag
- * @param array $attribs
- *
- * @todo In 'LOGGER' case find a better way to detect 'getLogger()' method
- */
- private function tagOpen($parser, $tag, $attribs)
- {
- switch ($tag) {
-
- case 'CONFIGURATION' :
- case self::XMLNS.':CONFIGURATION':
-
- if (isset($attribs['THRESHOLD'])) {
-
- $this->repository->setThreshold(
- LoggerOptionConverter::toLevel(
- $this->subst($attribs['THRESHOLD']),
- $this->repository->getThreshold()
- )
- );
- }
- break;
-
- case 'APPENDER' :
- case self::XMLNS.':APPENDER':
-
- unset($this->appender);
- $this->appender = null;
-
- $name = $this->subst(@$attribs['NAME']);
- $class = $this->subst(@$attribs['CLASS']);
-
- $this->appender = LoggerAppenderPool::getAppenderFromPool($name, $class);
-
- if (isset($attribs['THRESHOLD'])) {
- $this->appender->setThreshold(
- LoggerOptionConverter::toLevel(
- $this->subst($attribs['THRESHOLD']), $this->appender->getThreshold()));
- }
-
- $this->state[] = self::APPENDER_STATE;
- break;
-
- case 'APPENDER_REF' :
- case 'APPENDER-REF' :
- case self::XMLNS.':APPENDER_REF':
- case self::XMLNS.':APPENDER-REF':
- if (isset($attribs['REF']) and !empty($attribs['REF'])) {
- $appenderName = $this->subst($attribs['REF']);
-
- $appender = LoggerAppenderPool::getAppenderFromPool($appenderName);
- if ($appender !== null) {
- switch (end($this->state)) {
- case self::LOGGER_STATE:
- case self::ROOT_STATE:
- $this->logger->addAppender($appender);
- break;
- }
- }
- }
- break;
-
- case 'FILTER' :
- case self::XMLNS.':FILTER':
- unset($this->filter);
- $this->filter = null;
+ if ($debug) {
+ $this->setInternalDebugging(true);
+ $this->debug("Internal logging enabled by XML configuration.");
+ }
+ }
+
+ // Set the threshold level if any is specified
+ if (isset($attrs['threshold'])) {
+ $value = $attrs['threshold'];
+ $default = $this->hierarchy->getThreshold();
+ $level = LoggerOptionConverter::toLevel($value, $default);
- $filterName = basename($this->subst(@$attribs['CLASS']));
- if (!empty($filterName)) {
- $this->filter = new $filterName();
- $this->state[] = self::FILTER_STATE;
- }
- break;
-
- case 'LAYOUT':
- case self::XMLNS.':LAYOUT':
- $class = @$attribs['CLASS'];
- $this->layout = LoggerReflectionUtils::createObject($this->subst($class));
- $this->state[] = self::LAYOUT_STATE;
- break;
-
- case 'LOGGER':
- case self::XMLNS.':LOGGER':
- // $this->logger is assigned by reference.
- // Only '$this->logger=null;' destroys referenced object
- unset($this->logger);
- $this->logger = null;
-
- $loggerName = $this->subst(@$attribs['NAME']);
- if (!empty($loggerName)) {
- $this->logger = $this->repository->getLogger($loggerName);
- if ($this->logger !== null and isset($attribs['ADDITIVITY'])) {
- $additivity = LoggerOptionConverter::toBoolean($this->subst($attribs['ADDITIVITY']), true);
- $this->logger->setAdditivity($additivity);
- }
- }
- $this->state[] = self::LOGGER_STATE;;
- break;
-
- case 'LEVEL':
- case self::XMLNS.':LEVEL':
- case 'PRIORITY':
- case self::XMLNS.':PRIORITY':
-
- if (!isset($attribs['VALUE'])) {
- // LoggerDOMConfigurator::tagOpen() LEVEL value not set
- break;
- }
-
- if ($this->logger === null) {
- // LoggerDOMConfigurator::tagOpen() LEVEL. parent logger is null
- break;
- }
-
- switch (end($this->state)) {
- case self::ROOT_STATE:
- $this->logger->setLevel(
- LoggerOptionConverter::toLevel(
- $this->subst($attribs['VALUE']),
- $this->logger->getLevel()
- )
- );
- break;
- case self::LOGGER_STATE:
- $this->logger->setLevel(
- LoggerOptionConverter::toLevel(
- $this->subst($attribs['VALUE']),
- $this->logger->getLevel()
- )
- );
- break;
- default:
- //LoggerLog::warn("LoggerDOMConfigurator::tagOpen() LEVEL state '{$this->state}' not allowed here");
- }
- break;
-
- case 'PARAM':
- case self::XMLNS.':PARAM':
- if (!isset($attribs['NAME'])) {
- // LoggerDOMConfigurator::tagOpen() PARAM attribute 'name' not defined.
- break;
- }
- if (!isset($attribs['VALUE'])) {
- // LoggerDOMConfigurator::tagOpen() PARAM. attribute 'value' not defined.
- break;
- }
-
- switch (end($this->state)) {
- case self::APPENDER_STATE:
- if ($this->appender !== null) {
- LoggerReflectionUtils::setter($this->appender, $this->subst($attribs['NAME']), $this->subst($attribs['VALUE']));
- }
- break;
- case self::LAYOUT_STATE:
- if ($this->layout !== null) {
- LoggerReflectionUtils::setter($this->layout, $this->subst($attribs['NAME']), $this->subst($attribs['VALUE']));
- }
- break;
- case self::FILTER_STATE:
- if ($this->filter !== null) {
- LoggerReflectionUtils::setter($this->filter, $this->subst($attribs['NAME']), $this->subst($attribs['VALUE']));
- }
- break;
- default:
- //LoggerLog::warn("LoggerDOMConfigurator::tagOpen() PARAM state '{$this->state}' not allowed here");
- }
- break;
-
- case 'RENDERER':
- case self::XMLNS.':RENDERER':
+ if(!is_null($level)) {
+ $this->debug("Setting hierarchy threshold to [$level].");
+ $this->hierarchy->setThreshold($level);
+ }
+ }
+ }
+
+ /**
+ * Parses a node and adds it to the logger hierarchy.
+ * @param SimpleXMLElement $loggerNode the logger node.
+ */
+ private function parseLoggerNode(SimpleXMLElement $loggerNode) {
+ $attrs = $this->getAttributes($loggerNode);
+
+ if (!isset($attrs['name']) || empty($attrs['name'])) {
+ $this->warn("Invalid logger configuration. Attribute 'name' is missing or empty. Skipping logger.");
+ return null;
+ }
+ $name = $attrs['name'];
+
+ $this->pushContext("logger [$name]");
+ $this->debug("Processing");
+
+ $logger = $this->hierarchy->getLogger($name);
+
+ if(isset($attrs['additivity'])) {
+ $value = $attrs['additivity'];
+ $default = self::DEFAULT_ADDITIVITY;
+ $additivity = LoggerOptionConverter::toBoolean($value, $default);
+ $this->debug("Setting additivity to [" . ($additivity ? 'true' : 'false') . "]");
+ $logger->setAdditivity($additivity);
+ }
+
+ if (isset($loggerNode->level)) {
+ $default = $logger->getLevel();
+ $level = $this->parseLevelNode($loggerNode->level, $default);
+ $this->debug("Setting level to [$level].");
+ $logger->setLevel($level);
+ }
+
+ $this->linkAppendersToLogger($logger, $loggerNode);
+
+ $this->popContext();
+ return $logger;
+ }
+
+ /**
+ * Parses a or node for child nodes. For each
+ * of these, reads the ref attributes and adds the appender with the
+ * corresponding name to the logger.
+ *
+ * @param Logger $logger
+ * @param SimpleXMLElement $loggerNode
+ */
+ private function linkAppendersToLogger(Logger $logger, SimpleXMLElement $loggerNode) {
+ $references = $this->getAppenderReferences($loggerNode);
+
+ $this->debug("Processing appender references.");
+ foreach ($references as $name) {
+ if (isset($this->appenders[$name])) {
+ $this->debug("Adding appender [$name] to logger.");
+ $logger->addAppender($this->appenders[$name]);
+ } else {
+ $this->warn("Appender named [$name] not defined in configuration. Skipping.");
+ }
+ $this->popContext();
+ }
+ }
+
+ /**
+ * Parses a node for child nodes. Extracts the
+ * 'ref' attributes from each one and returns them in an array.
+ */
+ private function getAppenderReferences(SimpleXMLElement $loggerNode) {
+ $refs = array();
+ foreach($loggerNode->appender_ref as $ref) {
+ $attrs = $this->getAttributes($ref);
+ if (!isset($attrs['ref']) || empty($attrs['ref'])) {
+ $this->warn("Invalid appender_ref element. Required attribute 'ref' is missing or empty.");
+ continue;
+ }
+ $refs[] = (string) $attrs['ref'];
+ }
+ return $refs;
+ }
+
+ /**
+ * Parses the node. Links appender defined in child
+ * nodes and sets the root logger level from the child node if it
+ * exists.
+ *
+ * @param SimpleXMLElement $loggerNode
+ */
+ private function parseRootLoggerNode(SimpleXMLElement $loggerNode) {
+ $attrs = $this->getAttributes($loggerNode);
+
+ $this->pushContext("root");
+ $this->debug("Processing");
+
+ $logger = $this->hierarchy->getRootLogger();
- $renderedClass = $this->subst(@$attribs['RENDEREDCLASS']);
- $renderingClass = $this->subst(@$attribs['RENDERINGCLASS']);
-
- if (!empty($renderedClass) and !empty($renderingClass)) {
- $this->repository->getRendererMap()->addRenderer($renderedClass, $renderingClass);
- }
- break;
-
- case 'ROOT':
- case self::XMLNS.':ROOT':
- $this->logger = Logger::getRootLogger();
- $this->state[] = self::ROOT_STATE;
- break;
- }
- }
+ if (isset($loggerNode->level)) {
+ $default = $logger->getLevel();
+ $level = $this->parseLevelNode($loggerNode->level, $default);
+
+ $this->debug("Setting level to [$level].");
+ $logger->setLevel($level);
+ }
+
+ $this->linkAppendersToLogger($logger, $loggerNode);
+
+ $this->popContext();
+ }
+
+ /**
+ * Parses a node and returns the corresponding logger level.
+ * @param SimpleXMLElement $levelNode the level node to parse
+ * @param LoggerLevel $default the default logger level
+ * @returns LoggerLevel the level corresponding to given level node, or
+ * $default if the level node is invalid.
+ */
+ private function parseLevelNode(SimpleXMLElement $levelNode, $default) {
+ $attrs = $this->getAttributes($levelNode);
+
+ if (isset($attrs['value'])) {
+ $value = $attrs['value'];
+ return LoggerOptionConverter::toLevel($value, $default);
+ }
+
+ return $default;
+ }
+
+ /**
+ * Parses an node. Creates a corresponding LoggerAppender
+ * object and stores it in the 'appenders' member variable. If the
+ * node is invalid, issues a warning and does nothing.
+ * @param SimpleXMLElement $appenderNode the node to parse
+ */
+ private function parseAppenderNode(SimpleXMLElement $appenderNode) {
+ $attrs = $this->getAttributes($appenderNode);
+
+ if (!isset($attrs['name']) || empty($attrs['name'])) {
+ $this->warn("Invalid appender configuration. Required attribute 'name' is missing or empty. Skipping appender.");
+ return null;
+ }
+
+ if (!isset($attrs['class']) || empty($attrs['class'])) {
+ $this->warn("Invalid appender configuration. Required attribute 'class' is missing or empty. Skipping appender.");
+ return null;
+ }
+
+ $name = $attrs['name'];
+ $class = $attrs['class'];
+
+ $this->pushContext("appender [$name]");
+ $this->debug("Processing.");
+
+ if (!class_exists($class)) {
+ $this->warn("[$class] is not a valid appender class. Skipping appender.");
+ $this->popContext();
+ return null;
+ }
+
+ $appender = new $class();
+
+ if (!is_subclass_of($class, 'LoggerAppender')) {
+ $this->warn("[$class] is not a valid appender class. Skipping appender.");
+ $this->popContext();
+ return null;
+ }
+ $appender->setName($name);
+
+ if (isset($attrs['threshold'])) {
+ $value = $attrs['threshold'];
+ $threshold = LoggerOptionConverter::toLevel($value, $appender->getThreshold());
+ $this->debug("Setting threshold to [$threshold].");
+ $appender->setThreshold($threshold);
+ }
+
+ // Parse the appender's layout, if any
+ if (isset($appenderNode->layout)) {
+ $layout = $this->parseLayoutNode($appenderNode->layout);
+ if (!is_null($layout)) {
+ $appender->setLayout($layout);
+ $this->debug("Setting layout to [" . get_class($layout) . "]");
+ }
+ }
+ // Revert to default layout if needed
+ if ($appender->requiresLayout() and is_null($appender->getLayout())) {
+ $this->warn("No layout specified. This appender class requires a layout. Using default (LoggerLayoutSimple).");
+ $layout = new LoggerLayoutSimple();
+ $appender->setLayout($layout);
+ }
+
+ // Parse filters (if any exist)
+ foreach($appenderNode->filter as $filterNode) {
+ $filter = $this->parseFilterNode($filterNode);
+ if (!is_null($filter)) {
+ $this->debug("Adding filter [" . get_class($filter) . "]");
+ $appender->addFilter($filter);
+ }
+ }
+
+ /*
+ * TODO: Maybe it would be smarter to activate the options later,
+ * once the appender is linked to a logger.
+ */
+ $this->debug("Activating.");
+ $appender->activateOptions();
+
+ // Store appender
+ $this->appenders[$name] = $appender;
+
+ $this->popContext();
+ }
+
+ /**
+ * Parses a node. Creates and returns the corresponding
+ * LoggerFilter object. Issues a warning on error.
+ *
+ * @param SimpleXMLElement $filterNode the node to parse
+ * @returns LoggerFilter the filter object corresponding to the
+ * given configuration node, or NULL if an error happened
+ */
+ private function parseFilterNode(SimpleXMLElement $filterNode) {
+ $attrs = $this->getAttributes($filterNode);
+
+ if (!isset($attrs['class']) || empty($attrs['class'])) {
+ $this->warn("Invalid filter configuration. Mandatory attribute 'class' is missing. Skipping filter.");
+ return null;
+ }
+
+ $class = $attrs['class'];
+ $this->pushContext("filter [$class]");
+ $this->debug("Processing");
+
+ if (!class_exists($class)) {
+ $this->warn("Invalid filter class specified [$class] (class does not exist). Skipping filter.");
+ $this->popContext();
+ return null;
+ }
+
+ $filter = new $class();
+
+ if (!is_subclass_of($class, 'LoggerFilter')) {
+ $this->warn("Invalid filter class specified [$class] (not an instance of LoggerFilter). Skipping filter.");
+ $this->popContext();
+ return null;
+ }
+
+ foreach($this->fetchParams($filterNode) as $name => $value) {
+ $success = LoggerReflectionUtils::setter($filter, $name, $value);
+ if ($success === false) {
+ $this->error("Invalid parameter [$name].");
+ } else {
+ $this->debug("Setting parameter [$name = $value].");
+ }
+ }
+
+ $this->popContext();
+ return $filter;
+ }
+
+ /**
+ * Parses a node. Creates and returns the corresponding
+ * LoggerLayout object. Issues a warning on error.
+ * @param SimpleXMLElement $layoutNode the node to parse
+ *
+ * @returns LoggerLayout the layout object corresponding to the
+ * given configuration node, or NULL if an error happened
+ */
+ private function parseLayoutNode(SimpleXMLElement $layoutNode) {
+ $attrs = $this->getAttributes($layoutNode);
+
+ if (!isset($attrs['class']) || empty($attrs['class'])) {
+ $this->warn("Invalid layout configuration. Mandatory attribute 'class' is missing.");
+ return null;
+ }
+
+ $class = $attrs['class'];
+ $this->pushContext("layout [$class]");
+ $this->debug("Processing");
+
+ if (!class_exists($class)) {
+ $this->error("Invalid layout class specified [$class] (does not exist).");
+ $this->popContext();
+ return null;
+ }
+
+ $layout = new $class();
+
+ if (!is_subclass_of($class, 'LoggerLayout')) {
+ $this->error("Invalid layout class specified [$class] (not an instance of LoggerLayout).");
+ $this->popContext();
+ return null;
+ }
+
+ foreach($this->fetchParams($layoutNode) as $name => $value) {
+ $success = LoggerReflectionUtils::setter($layout, $name, $value);
+ if ($success === false) {
+ $this->error("Invalid parameter [$name].");
+ } else {
+ $this->debug("Setting parameter [$name = $value].");
+ }
+ }
+
+ $this->popContext();
+ return $layout;
+ }
+
+ /**
+ * Parses all nodes which are children of the given node and
+ * returns them as an associative array.
+ *
+ * @param SimpleXMLElement $xmlNode Node to parse.
+ * @return array params as name/value pairs
+ */
+ private function fetchParams(SimpleXMLElement $xmlNode) {
+ $params = array();
+
+ foreach($xmlNode->param as $paramNode) {
+ $result = $this->parseParamNode($paramNode);
+ if (is_null($result)) {
+ continue;
+ }
+
+ list($name, $value) = $result;
+ $params[$name] = $value;
+ }
+
+ return $params;
+ }
+
+ /**
+ * Extracts param name and value from a XML node.
+ *
+ * @param SimpleXMLElement $paramNode Param node to parse.
+ * @return array an array with two elements, the first is param name,
+ * the second is param value; or null on failure.
+ */
+ private function parseParamNode(SimpleXMLElement $paramNode) {
+ $attrs = $this->getAttributes($paramNode);
+
+ // Each param node must have a name and value attribute
+ if (!isset($attrs['name']) || empty($attrs['name'])) {
+ $this->warn("Invalid param node. Attribute 'name' is missing or empty. Skipping param.");
+ return null;
+ }
+
+ // Value can be empty
+ if (!isset($attrs['value'])) {
+ $this->warn("Invalid param node. Attribute 'value' is missing. Skipping param.");
+ return null;
+ }
- /**
- * @param mixed $parser
- * @param string $tag
- */
- private function tagClose($parser, $tag)
- {
- switch ($tag) {
-
- case 'CONFIGURATION' :
- case self::XMLNS.':CONFIGURATION':
- break;
-
- case 'APPENDER' :
- case self::XMLNS.':APPENDER':
- if ($this->appender !== null) {
- if ($this->appender->requiresLayout() and $this->appender->getLayout() === null) {
- $appenderName = $this->appender->getName();
- $this->appender->setLayout(LoggerReflectionUtils::createObject('LoggerLayoutSimple'));
- }
- $this->appender->activateOptions();
- }
- array_pop($this->state);
- break;
-
- case 'FILTER' :
- case self::XMLNS.':FILTER':
- if ($this->filter !== null) {
- $this->filter->activateOptions();
- $this->appender->addFilter($this->filter);
- $this->filter = null;
- }
- array_pop($this->state);
- break;
-
- case 'LAYOUT':
- case self::XMLNS.':LAYOUT':
- if ($this->appender !== null and $this->layout !== null and $this->appender->requiresLayout()) {
- $this->layout->activateOptions();
- $this->appender->setLayout($this->layout);
- $this->layout = null;
- }
- array_pop($this->state);
- break;
-
- case 'LOGGER':
- case self::XMLNS.':LOGGER':
- array_pop($this->state);
- break;
-
- case 'ROOT':
- case self::XMLNS.':ROOT':
- array_pop($this->state);
- break;
- }
- }
-
- private function subst($value)
- {
- return LoggerOptionConverter::substVars($value);
- }
+ return array(
+ $attrs['name'],
+ $attrs['value']
+ );
+ }
+
+ /**
+ * Reads attributes from an XML node and returns them as an associative
+ * array. Attribute names changed to lower case to make them case
+ * insensitive.
+ *
+ * @param $xmlNode SimpleXMLElement the XML node
+ * @return array attributes as name/value pairs
+ */
+ private function getAttributes(SimpleXMLElement $xmlNode) {
+ $attrs = $xmlNode->attributes();
+ if (count($attrs) > 0) {
+ $attrs = (array) $attrs;
+ return array_change_key_case($attrs['@attributes'], CASE_LOWER);
+ }
+ return array();
+ }
+
+ /**
+ * Logs a debug message to internal logger. Prepends the context
+ * to the message before logging it.
+ * @param string $msg message to log
+ */
+ private function debug($msg) {
+ $context = $this->formatContext();
+ LoggerLog::debug("$context$msg");
+ }
+ /**
+ * Logs a warning message to internal logger. Prepends the context
+ * to the message before logging it.
+ * @param string $msg message to log
+ */
+ private function warn($msg) {
+ $context = $this->formatContext();
+ LoggerLog::warn("$context$msg");
+ }
+
+ /**
+ * Logs an error message to internal logger. Prepends the context
+ * to the message before logging it.
+ * @param string $msg message to log
+ */
+ private function error($msg) {
+ $context = $this->formatContext();
+ LoggerLog::error("$context$msg");
+ }
+
+ /** Returns the current context as a string. */
+ private function formatContext() {
+ if (empty($this->context)) {
+ return '';
+ }
+ return implode(' > ', $this->context) . ': ';
+ }
+
+ /** Pushes a new string onto the context stack */
+ private function pushContext($text) {
+ $this->context[] = $text;
+ }
+
+ /** Pops the top string off the context stack. */
+ private function popContext() {
+ if(count($this->context) > 0) {
+ return array_pop($this->context);
+ }
+ }
}
Index: src/main/php/helpers/LoggerLog.php
===================================================================
--- src/main/php/helpers/LoggerLog.php (revision 0)
+++ src/main/php/helpers/LoggerLog.php (revision 0)
@@ -0,0 +1,144 @@
+
\ No newline at end of file
Index: src/main/php/helpers/LoggerOptionConverter.php
===================================================================
--- src/main/php/helpers/LoggerOptionConverter.php (revision 944773)
+++ src/main/php/helpers/LoggerOptionConverter.php (working copy)
@@ -56,6 +56,8 @@
return (string)$_SERVER[$key];
} else if(isset($_ENV[$key])) {
return (string)$_ENV[$key];
+ } else if(getenv($key) !== false) {
+ return getenv($key);
} else {
return $def;
}