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; }