diff --git a/.gitignore b/.gitignore index 8e63c675..82da0099 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,7 @@ composer.lock # PhpStorm project files .idea + +# Vim swapfiles. +.*.swp +.*.swo diff --git a/src/changes/changes.xml b/src/changes/changes.xml index cb8b3d93..0f13e74d 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -21,6 +21,7 @@ + Added new filter for matching a logger name. Made Logger::isInitialized() public. Improved LoggerAppenderMail to set the Content-type header as defined in layout. diff --git a/src/examples/php/filter_namematch.php b/src/examples/php/filter_namematch.php new file mode 100644 index 00000000..8815748d --- /dev/null +++ b/src/examples/php/filter_namematch.php @@ -0,0 +1,26 @@ +info("Messages from foo are denied due to the second filter"); +$loggerBar->info("Messages from bar are accepted"); diff --git a/src/examples/resources/filter_namematch.xml b/src/examples/resources/filter_namematch.xml new file mode 100644 index 00000000..b219019a --- /dev/null +++ b/src/examples/resources/filter_namematch.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + diff --git a/src/main/php/LoggerAutoloader.php b/src/main/php/LoggerAutoloader.php index 86098c51..c47459a4 100644 --- a/src/main/php/LoggerAutoloader.php +++ b/src/main/php/LoggerAutoloader.php @@ -6,15 +6,15 @@ * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - * + * * @package log4php */ @@ -26,15 +26,15 @@ /** * Class autoloader. - * + * * @package log4php * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0 */ class LoggerAutoloader { - + /** Maps classnames to files containing the class. */ private static $classes = array( - + // Base 'LoggerAppender' => '/LoggerAppender.php', 'LoggerAppenderPool' => '/LoggerAppenderPool.php', @@ -52,7 +52,7 @@ class LoggerAutoloader { 'LoggerReflectionUtils' => '/LoggerReflectionUtils.php', 'LoggerRoot' => '/LoggerRoot.php', 'LoggerThrowableInformation' => '/LoggerThrowableInformation.php', - + // Appenders 'LoggerAppenderConsole' => '/appenders/LoggerAppenderConsole.php', 'LoggerAppenderDailyFile' => '/appenders/LoggerAppenderDailyFile.php', @@ -68,7 +68,7 @@ class LoggerAutoloader { 'LoggerAppenderRollingFile' => '/appenders/LoggerAppenderRollingFile.php', 'LoggerAppenderSocket' => '/appenders/LoggerAppenderSocket.php', 'LoggerAppenderSyslog' => '/appenders/LoggerAppenderSyslog.php', - + // Configurators 'LoggerConfigurationAdapter' => '/configurators/LoggerConfigurationAdapter.php', 'LoggerConfigurationAdapterINI' => '/configurators/LoggerConfigurationAdapterINI.php', @@ -80,6 +80,7 @@ class LoggerAutoloader { 'LoggerFilterDenyAll' => '/filters/LoggerFilterDenyAll.php', 'LoggerFilterLevelMatch' => '/filters/LoggerFilterLevelMatch.php', 'LoggerFilterLevelRange' => '/filters/LoggerFilterLevelRange.php', + 'LoggerFilterNameMatch' => '/filters/LoggerFilterNameMatch.php', 'LoggerFilterStringMatch' => '/filters/LoggerFilterStringMatch.php', // Helpers @@ -87,7 +88,7 @@ class LoggerAutoloader { 'LoggerOptionConverter' => '/helpers/LoggerOptionConverter.php', 'LoggerPatternParser' => '/helpers/LoggerPatternParser.php', 'LoggerUtils' => '/helpers/LoggerUtils.php', - + // Pattern converters 'LoggerPatternConverter' => '/pattern/LoggerPatternConverter.php', 'LoggerPatternConverterClass' => '/pattern/LoggerPatternConverterClass.php', @@ -113,7 +114,7 @@ class LoggerAutoloader { 'LoggerPatternConverterSessionID' => '/pattern/LoggerPatternConverterSessionID.php', 'LoggerPatternConverterSuperglobal' => '/pattern/LoggerPatternConverterSuperglobal.php', 'LoggerPatternConverterThrowable' => '/pattern/LoggerPatternConverterThrowable.php', - + // Layouts 'LoggerLayoutHtml' => '/layouts/LoggerLayoutHtml.php', 'LoggerLayoutPattern' => '/layouts/LoggerLayoutPattern.php', @@ -121,14 +122,14 @@ class LoggerAutoloader { 'LoggerLayoutSimple' => '/layouts/LoggerLayoutSimple.php', 'LoggerLayoutTTCC' => '/layouts/LoggerLayoutTTCC.php', 'LoggerLayoutXml' => '/layouts/LoggerLayoutXml.php', - + // Renderers 'LoggerRendererDefault' => '/renderers/LoggerRendererDefault.php', 'LoggerRendererException' => '/renderers/LoggerRendererException.php', 'LoggerRendererMap' => '/renderers/LoggerRendererMap.php', 'LoggerRenderer' => '/renderers/LoggerRenderer.php', ); - + /** * Loads a class. * @param string $className The name of the class to load. diff --git a/src/main/php/filters/LoggerFilterNameMatch.php b/src/main/php/filters/LoggerFilterNameMatch.php new file mode 100644 index 00000000..50b7daee --- /dev/null +++ b/src/main/php/filters/LoggerFilterNameMatch.php @@ -0,0 +1,130 @@ +The filter admits four options: {@link $stringToMatch}, {@link $caseSensitive}, + * {@link $exactMatch}, and + * {@link $acceptOnMatch}. If the value of the {@link $stringToMatch} option is included + * in name of the {@link LoggerLoggingEvent}, then the {@link decide()} method returns + * {@link LoggerFilter::ACCEPT} if the AcceptOnMatch option value is true and + * {@link LoggerFilter::DENY} if it is false. If there is no match, {@link LoggerFilter::NEUTRAL} + * is returned. Matching is case-sensitive by default. Setting {@link $caseSensitive} + * to false makes matching case insensitive. An exact match can be required by setting + * {@link $exactMatch} to true.

+ * + *

+ * An example for this filter: + * + * {@example ../../examples/php/filter_namematch.php 19} + * + *

+ * The corresponding XML file: + * + * {@example ../../examples/resources/filter_namematch.xml 18} + * + * @package log4php + * @subpackage filters + * @since 2.3.1 + */ +class LoggerFilterNameMatch extends LoggerFilter { + + /** + * @var boolean + */ + protected $acceptOnMatch = true; + + /** + * @var boolean + */ + protected $caseSensitive = true; + + /** + * @var boolean + */ + protected $exactMatch = false; + + /** + * @var string + */ + protected $stringToMatch; + + /** + * @param mixed $acceptOnMatch a boolean or a string ('true' or 'false') + */ + public function setAcceptOnMatch($acceptOnMatch) { + $this->setBoolean('acceptOnMatch', $acceptOnMatch); + } + + /** + * @param mixed $caseSensitive a boolean or a string ('true' or 'false') + */ + public function setCaseSensitive($caseSensitive) { + $this->setBoolean('caseSensitive', $caseSensitive); + } + + /** + * @param mixed $caseSensitive a boolean or a string ('true' or 'false') + */ + public function setExactMatch($exactMatch) { + $this->setBoolean('exactMatch', $exactMatch); + } + + /** + * @param string $s the string to match + */ + public function setStringToMatch($string) { + $this->setString('stringToMatch', $string); + } + + /** + * @return integer a {@link LOGGER_FILTER_NEUTRAL} is there is no string match. + */ + public function decide(LoggerLoggingEvent $event) { + $msg = $event->getLoggerName(); + + if($msg === null or $this->stringToMatch === null) { + return LoggerFilter::NEUTRAL; + } + + if($this->caseSensitive) { + return $this->testString((function_exists('mb_strpos') ? 'mb_strpos' : 'strpos'), $msg); + } else { + return $this->testString((function_exists('mb_stripos') ? 'mb_stripos' : 'stripos'), $msg); + } + } + + + protected function testString($method, $msg) { + if($method($msg, $this->stringToMatch) !== false) { + if($this->exactMatch) { + $lenFunc = function_exists('mb_strlen') ? 'mb_strlen' : 'strlen'; + if($lenFunc($this->stringToMatch) === $lenFunc($msg)) { + // We were looking for an exact match, and we found one. + return ($this->acceptOnMatch) ? LoggerFilter::ACCEPT : LoggerFilter::DENY; + } + } else { // No exact match required. + return ($this->acceptOnMatch) ? LoggerFilter::ACCEPT : LoggerFilter::DENY; + } + } + return LoggerFilter::NEUTRAL; + } +} diff --git a/src/site/xdoc/docs/filters.xml b/src/site/xdoc/docs/filters.xml index 555205d7..000e650d 100644 --- a/src/site/xdoc/docs/filters.xml +++ b/src/site/xdoc/docs/filters.xml @@ -156,6 +156,10 @@ array( LoggerFilterLevelRange Filters based on logging event level range. + + LoggerFilterNameMatch + Filters by searching for a string in the logger name. + LoggerFilterStringMatch Filters by searching for a string in the logging event message. @@ -265,6 +269,79 @@ array( +]]> + + + + +

This filter allows or denies logging events if the logger name contains a given string. If no match + is found the filter remains neutral.

+ + +

Configurable parameters

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterTypeRequiredDefaultDescription
stringToMatchLoggerLevelYes-The string to match.
caseSensitivebooleanNotrueIf true, matches are case sensitive. If false, matches ignore case differences.
exactMatchLoggerLevelNofalseIf true, the stringToMatch must have the same text content as the logger name. This + parameter co-operates with the caseSensitive parameter; that is, if your + match is case insensitive, then "Example" is considered an exact match with + "example".
acceptOnMatchbooleanNotrueIf true and a match is found, the matching log event is accepted. If false and a + match is found, the matching log event is denied.
+ +

Example

+ +

One common pattern is to give each class in your application a logger via + $this->logger = Logger::getLogger(__CLASS__) in the constructor. You could use this + filter to log any events from your Payment class for easier debugging and greater + visibility.

+ +

The following filter configuration only accepts events which contain the string "Payment" in + the logger name.

+ +

+    
+    
+
+
 ]]>
diff --git a/src/test/php/filters/LoggerFilterNameMatchTest.php b/src/test/php/filters/LoggerFilterNameMatchTest.php new file mode 100644 index 00000000..b3316213 --- /dev/null +++ b/src/test/php/filters/LoggerFilterNameMatchTest.php @@ -0,0 +1,106 @@ +setAcceptOnMatch("true"); + $filter->setStringToMatch("AcCePtEd"); + + $eventFromAccepted = new LoggerLoggingEvent("LoggerFilterNameMatchTest", new Logger('AcCePtEd'), LoggerLevel::getLevelInfo(), "Irrelevant"); + $eventFromAccepted2 = new LoggerLoggingEvent("LoggerFilterNameMatchTest", new Logger('Accepted'), LoggerLevel::getLevelInfo(), "Irrelevant"); + $eventFromElsewhere = new LoggerLoggingEvent("LoggerFilterNameMatchTest", new Logger('Elsewhere'), LoggerLevel::getLevelInfo(), "Irrelevant"); + + + // Events are case-sensitive by default. + $this->assertEquals($filter->decide($eventFromAccepted), LoggerFilter::ACCEPT); + $this->assertEquals($filter->decide($eventFromAccepted2), LoggerFilter::NEUTRAL); + $this->assertEquals($filter->decide($eventFromElsewhere), LoggerFilter::NEUTRAL); + + // But we can make them case insensitive. + $filter->setCaseSensitive("false"); + $this->assertEquals($filter->decide($eventFromAccepted), LoggerFilter::ACCEPT); + $this->assertEquals($filter->decide($eventFromAccepted2), LoggerFilter::ACCEPT); + $this->assertEquals($filter->decide($eventFromElsewhere), LoggerFilter::NEUTRAL); + } + + public function testPartialMatch() { + $filter = new LoggerFilterNameMatch(); + $filter->setAcceptOnMatch("true"); + $filter->setStringToMatch("Accept"); + + $eventFromAccept = new LoggerLoggingEvent("LoggerFilterNameMatchTest", new Logger('Accept'), LoggerLevel::getLevelInfo(), "Irrelevant"); + $eventFromAccepted = new LoggerLoggingEvent("LoggerFilterNameMatchTest", new Logger('Accepted'), LoggerLevel::getLevelInfo(), "Irrelevant"); + $eventFromElsewhere = new LoggerLoggingEvent("LoggerFilterNameMatchTest", new Logger('Elsewhere'), LoggerLevel::getLevelInfo(), "Irrelevant"); + + // Partial matches are accepted. + $this->assertEquals($filter->decide($eventFromAccept), LoggerFilter::ACCEPT); + $this->assertEquals($filter->decide($eventFromAccepted), LoggerFilter::ACCEPT); + $this->assertEquals($filter->decide($eventFromElsewhere), LoggerFilter::NEUTRAL); + } + + public function testExactMatch() { + $filter = new LoggerFilterNameMatch(); + $filter->setAcceptOnMatch("true"); + $filter->setStringToMatch("Accept"); + $filter->setExactMatch("true"); + + $eventFromAccept = new LoggerLoggingEvent("LoggerFilterNameMatchTest", new Logger('Accept'), LoggerLevel::getLevelInfo(), "Irrelevant"); + $eventFromAccepted = new LoggerLoggingEvent("LoggerFilterNameMatchTest", new Logger('Accepted'), LoggerLevel::getLevelInfo(), "Irrelevant"); + $eventFromElsewhere = new LoggerLoggingEvent("LoggerFilterNameMatchTest", new Logger('Elsewhere'), LoggerLevel::getLevelInfo(), "Irrelevant"); + + // Partial matches are accepted. + $this->assertEquals($filter->decide($eventFromAccept), LoggerFilter::ACCEPT); + $this->assertEquals($filter->decide($eventFromAccepted), LoggerFilter::NEUTRAL); + $this->assertEquals($filter->decide($eventFromElsewhere), LoggerFilter::NEUTRAL); + } + + public function testAcceptOnMatchTrue() { + $filter = new LoggerFilterNameMatch(); + $filter->setAcceptOnMatch("true"); + $filter->setStringToMatch("Accept"); + + $eventFromAccept = new LoggerLoggingEvent("LoggerFilterNameMatchTest", new Logger('Accept'), LoggerLevel::getLevelInfo(), "Irrelevant"); + $eventFromNeutral = new LoggerLoggingEvent("LoggerFilterNameMatchTest", new Logger('Neutral'), LoggerLevel::getLevelInfo(), "Irrelevant"); + + $this->assertEquals($filter->decide($eventFromAccept), LoggerFilter::ACCEPT); + $this->assertEquals($filter->decide($eventFromNeutral), LoggerFilter::NEUTRAL); + } + + public function testAcceptOnMatchFalse() { + $filter = new LoggerFilterNameMatch(); + $filter->setAcceptOnMatch("false"); + $filter->setStringToMatch("Deny"); + + $eventFromDeny = new LoggerLoggingEvent("LoggerFilterNameMatchTest", new Logger('Deny'), LoggerLevel::getLevelInfo(), "Irrelevant"); + $eventFromNeutral = new LoggerLoggingEvent("LoggerFilterNameMatchTest", new Logger('Neutral'), LoggerLevel::getLevelInfo(), "Irrelevant"); + + $this->assertEquals($filter->decide($eventFromDeny), LoggerFilter::DENY); + $this->assertEquals($filter->decide($eventFromNeutral), LoggerFilter::NEUTRAL); + } +}