Add a Constraint-based log filter
authorChuck Hagenbuch <chuck@horde.org>
Sat, 26 Sep 2009 03:39:17 +0000 (23:39 -0400)
committerChuck Hagenbuch <chuck@horde.org>
Sat, 26 Sep 2009 03:40:40 +0000 (23:40 -0400)
framework/Log/lib/Horde/Log/Filter/Constraint.php [new file with mode: 0644]
framework/Log/package.xml
framework/Log/test/Horde/Log/Filter/ConstraintTest.php [new file with mode: 0644]

diff --git a/framework/Log/lib/Horde/Log/Filter/Constraint.php b/framework/Log/lib/Horde/Log/Filter/Constraint.php
new file mode 100644 (file)
index 0000000..06fb66f
--- /dev/null
@@ -0,0 +1,96 @@
+<?php
+/**
+ * @category Horde
+ * @package  Horde_Log
+ * @subpackage Filters
+ * @author James Pepin <james@jamespepin.com>
+ * @license  http://opensource.org/licenses/bsd-license.php BSD
+ */
+
+/**
+ * Filters log events using defined constraints on one or more fields of the
+ * $event array
+ *
+ * @category Horde
+ * @package  Horde_Log
+ * @subpackage Filters
+ * @author James Pepin <james@jamespepin.com>
+ * @license  http://opensource.org/licenses/bsd-license.php BSD
+ *
+ * @todo Implement constraint objects for the different types of filtering ie
+ * regex,required,type..etc..  so we can add different constaints ad infinitum.
+ */
+class Horde_Log_Filter_Constraint implements Horde_Log_Filter_Interface
+{
+    private $_constraints = array();
+
+    /**
+     * Add a constraint to the filter
+     *
+     * @param string $field  The field to apply the constraint to
+     * @param Horde_Constraint $constraint  The constraint to apply
+     */
+    public function addConstraint($field, Horde_Constraint $constraint)
+    {
+        if ($this->_constraints[$field] instanceof Horde_Constraint) {
+            $this->_constraints[$field] = new Horde_Constraint_And($this->_constraints[$field], $constraint);
+        } else {
+            $this->_constraints[$field] = $constraint;
+        }
+    }
+
+    /**
+     * Add a regular expression to filter by
+     *
+     * Takes a field name and a regex, if the regex does not match then the
+     * event is filtered.
+     *
+     * @param string $field The name of the field that should be part of the event
+     * @param string $regex The regular expression to filter by
+     */
+    public function addRegex($field, $regex)
+    {
+        $constraint = new Horde_Constraint_PregMatch($regex);
+        $this->addConstraint($field, $constraint);
+    }
+
+    /**
+     * Add a required field to the filter
+     *
+     * If the field does not exist on the event, then it is filtered.
+     *
+     * @param string $field The name of the field that should be part of the event
+     */
+    public function addRequiredField($field)
+    {
+        $notNull = new Horde_Constraint_Not(new Horde_Constraint_Null());
+        $this->addConstraint($field, $notNull);
+    }
+
+    /**
+     * Adds all arguments passed as required fields
+     */
+    public function addRequiredFields()
+    {
+        $fields = func_get_args();
+        foreach ($fields as $f) {
+            $this->addRequiredField($f);
+        }
+    }
+
+    /**
+     * Returns TRUE to accept the message, FALSE to block it.
+     *
+     * @param  array    $event    Log event
+     * @return boolean            accepted?
+     */
+    public function accept($event)
+    {
+        foreach ($this->_constraints as $field => $constraint) {
+            if (!$constraint->evaluate($event[$field])) {
+                return false;
+            }
+        }
+        return true;
+    }
+}
index 7855111..65609b8 100644 (file)
@@ -20,10 +20,10 @@ http://pear.php.net/dtd/package-2.0.xsd">
   <email>mike@maintainable.com</email>
   <active>yes</active>
  </lead>
- <date>2009-02-09</date>
- <time>23:26:00</time>
+ <date>2009-09-25</date>
+ <time>23:00:00</time>
  <version>
-  <release>0.2.0</release>
+  <release>0.3.0</release>
   <api>0.2.0</api>
  </version>
  <stability>
@@ -31,7 +31,7 @@ http://pear.php.net/dtd/package-2.0.xsd">
   <api>beta</api>
  </stability>
  <license uri="http://opensource.org/licenses/bsd-license.php">BSD</license>
- <notes>* Added Horde_Log_Filter_Suppress.
+ <notes>* Added Horde_Log_Filter_Constraint, for flexible filtering per-field by the Horde_Constraint package
  </notes>
  <contents>
   <dir name="/">
@@ -39,6 +39,7 @@ http://pear.php.net/dtd/package-2.0.xsd">
     <dir name="Horde">
      <dir name="Log">
       <dir name="Filter">
+       <file name="Constraint.php" role="php" />
        <file name="Interface.php" role="php" />
        <file name="Level.php" role="php" />
        <file name="Message.php" role="php" />
@@ -72,13 +73,18 @@ http://pear.php.net/dtd/package-2.0.xsd">
     <min>5.2</min>
    </php>
    <pearinstaller>
-    <min>1.5.0</min>
+    <min>1.7.0</min>
    </pearinstaller>
+   <package>
+    <name>Constraint</name>
+    <channel>pear.horde.org</channel>
+   </package>
   </required>
  </dependencies>
  <phprelease>
   <filelist>
    <install name="lib/Horde/Log/Exception.php" as="Horde/Log/Exception.php" />
+   <install name="lib/Horde/Log/Filter/Constraint.php" as="Horde/Log/Filter/Constraint.php" />
    <install name="lib/Horde/Log/Filter/Interface.php" as="Horde/Log/Filter/Interface.php" />
    <install name="lib/Horde/Log/Filter/Level.php" as="Horde/Log/Filter/Level.php" />
    <install name="lib/Horde/Log/Filter/Message.php" as="Horde/Log/Filter/Message.php" />
diff --git a/framework/Log/test/Horde/Log/Filter/ConstraintTest.php b/framework/Log/test/Horde/Log/Filter/ConstraintTest.php
new file mode 100644 (file)
index 0000000..0e51483
--- /dev/null
@@ -0,0 +1,111 @@
+<?php
+/**
+ * Horde Log package
+ *
+ * @category Horde
+ * @package  Horde_Log
+ * @subpackage UnitTests
+ * @author   James Pepin <james@jamespepin.com>
+ * @license  http://opensource.org/licenses/bsd-license.php BSD
+ */
+
+/**
+ * @category Horde
+ * @package  Horde_Log
+ * @subpackage UnitTests
+ * @author   James Pepin <james@jamespepin.com>
+ * @license  http://opensource.org/licenses/bsd-license.php BSD
+ */
+class Horde_Log_Filter_ConstraintTest extends Horde_Test_Case
+{
+    public function testFilterDoesNotAcceptWhenRequiredFieldIsMissing()
+    {
+        $event = array(
+            'someotherfield' => 'other value',
+        );
+        $filterator = new Horde_Log_Filter_Constraint();
+        $filterator->addRequiredField('required_field');
+
+        $this->assertFalse($filterator->accept($event));
+    }
+
+    public function testFilterAcceptsWhenRequiredFieldisPresent()
+    {
+        $event = array(
+            'required_field' => 'somevalue',
+            'someotherfield' => 'other value',
+        );
+        $filterator = new Horde_Log_Filter_Constraint();
+        $filterator->addRequiredField('required_field');
+
+        $this->assertTrue($filterator->accept($event));
+    }
+
+    public function testFilterAcceptsWhenRegexMatchesField()
+    {
+        $event = array(
+            'regex_field'    => 'somevalue',
+            'someotherfield' => 'other value',
+        );
+        $filterator = new Horde_Log_Filter_Constraint();
+        $filterator->addRegex('regex_field', '/somevalue/');
+
+        $this->assertTrue($filterator->accept($event));
+    }
+
+    public function testFilterAcceptsWhenRegex_DOESNOT_MatcheField()
+    {
+        $event = array(
+            'regex_field'    => 'somevalue',
+            'someotherfield' => 'other value',
+        );
+        $filterator = new Horde_Log_Filter_Constraint();
+        $filterator->addRegex('regex_field', '/someothervalue/');
+
+        $this->assertFalse($filterator->accept($event));
+    }
+
+    private function getConstraintMock($returnVal)
+    {
+        $const = $this->getMock('Horde_Constraint', array('evaluate'));
+        $const->expects($this->once())
+            ->method('evaluate')
+            ->will($this->returnValue($returnVal));
+        return $const;
+    }
+
+    public function testFilterCallsEvalOnAllConstraintsWhenTheyAreAllTrue()
+    {
+        $filterator = new Horde_Log_Filter_Constraint();
+        $filterator->addConstraint('fieldname', $this->getConstraintMock(true));
+        $filterator->addConstraint('fieldname', $this->getConstraintMock(true));
+
+        $filterator->accept(array('fieldname' => 'foo'));
+    }
+
+    public function testFilterStopsWhenItFindsAFalseCondition()
+    {
+        $filterator = new Horde_Log_Filter_Constraint();
+        $filterator->addConstraint('fieldname', $this->getConstraintMock(true));
+        $filterator->addConstraint('fieldname', $this->getConstraintMock(true));
+        $filterator->addConstraint('fieldname', new Horde_Constraint_AlwaysFalse());
+
+        $const = $this->getMock('Horde_Constraint', array('evaluate'));
+        $const->expects($this->never())
+            ->method('evaluate');
+        $filterator->addConstraint('fieldname', $const);
+        $filterator->accept(array('fieldname' => 'foo'));
+
+    }
+
+    public function testFilterAcceptCallsConstraintOnNullWhenFieldDoesnotExist()
+    {
+        $filterator = new Horde_Log_Filter_Constraint();
+        $const = $this->getMock('Horde_Constraint', array('evaluate'));
+        $const->expects($this->once())
+            ->method('evaluate')
+            ->with(null);
+        $filterator->addConstraint('fieldname', $const);
+        $filterator->accept(array('someotherfield' => 'foo'));
+    }
+}