2
0
Эх сурвалжийг харах

Added a new ResettableInterface and implemented it where possible.

When one use Monolog in a long process like an AMQP worker with a
`FingersCrossedHandler` or `BufferHandler` there is a drawback: as soon as there
is an AMQP message that generate a log >= error (for example), all next AMQP
messages will output logs, even if theses messages don't generate log where
level >= error.

In the same context there is a drawback for processor that add an UUID to the
logs. The UUID should change for each AMQP messages.

---

This patch address this issue with a new interface: `ResettableInterface` interface.
Side note: `reset()`, `flush()`, `clear()`,  are already used in Monolog. So
basically, one can use the `reset()` on the `Logger` and on some
`Handler`s / `Processor`s.

It's especially useful for

* the `FingersCrossedHandler`: it `close()` the buffer, then it `clear()` the buffer.
* the `BufferHandler`: it `flush()` the buffer, then it `clear()` the buffer.
* the `UidProcessor`: it renew the `uid`.
Grégoire Pineau 8 жил өмнө
parent
commit
0625068bf0

+ 2 - 0
CHANGELOG.md

@@ -19,6 +19,8 @@
   * Fixed table row styling issues in HtmlFormatter
   * Fixed RavenHandler dropping the message when logging exception
   * Fixed WhatFailureGroupHandler skipping processors when using handleBatch
+  * Added a `ResettableInterface` in order to reset/reset/clear/flush handlers and processors
+    and implement it where possible
 
 ### 1.23.0 (2017-06-19)
 

+ 14 - 2
src/Monolog/Handler/AbstractHandler.php

@@ -11,16 +11,17 @@
 
 namespace Monolog\Handler;
 
-use Monolog\Logger;
 use Monolog\Formatter\FormatterInterface;
 use Monolog\Formatter\LineFormatter;
+use Monolog\Logger;
+use Monolog\ResettableInterface;
 
 /**
  * Base Handler class providing the Handler structure
  *
  * @author Jordi Boggiano <j.boggiano@seld.be>
  */
-abstract class AbstractHandler implements HandlerInterface
+abstract class AbstractHandler implements HandlerInterface, ResettableInterface
 {
     protected $level = Logger::DEBUG;
     protected $bubble = true;
@@ -174,6 +175,17 @@ abstract class AbstractHandler implements HandlerInterface
         }
     }
 
+    public function reset()
+    {
+        $this->close();
+
+        foreach ($this->processors as $processor) {
+            if ($processor instanceof ResettableInterface) {
+                $processor->reset();
+            }
+        }
+    }
+
     /**
      * Gets the default formatter.
      *

+ 2 - 0
src/Monolog/Handler/AbstractProcessingHandler.php

@@ -11,6 +11,8 @@
 
 namespace Monolog\Handler;
 
+use Monolog\ResettableInterface;
+
 /**
  * Base Handler class providing the Handler structure
  *

+ 7 - 2
src/Monolog/Handler/BrowserConsoleHandler.php

@@ -69,14 +69,19 @@ class BrowserConsoleHandler extends AbstractProcessingHandler
             } elseif ($format === 'js') {
                 static::writeOutput(static::generateScript());
             }
-            static::reset();
+            static::resetStatic();
         }
     }
 
+    public function reset()
+    {
+        self::resetStatic();
+    }
+
     /**
      * Forget all logged records
      */
-    public static function reset()
+    public static function resetStatic()
     {
         static::$records = array();
     }

+ 10 - 0
src/Monolog/Handler/BufferHandler.php

@@ -12,6 +12,7 @@
 namespace Monolog\Handler;
 
 use Monolog\Logger;
+use Monolog\ResettableInterface;
 
 /**
  * Buffers all records until closing the handler and then pass them as batch.
@@ -114,4 +115,13 @@ class BufferHandler extends AbstractHandler
         $this->bufferSize = 0;
         $this->buffer = array();
     }
+
+    public function reset()
+    {
+        parent::reset();
+
+        if ($this->handler instanceof ResettableInterface) {
+            $this->handler->reset();
+        }
+    }
 }

+ 8 - 0
src/Monolog/Handler/FingersCrossedHandler.php

@@ -14,6 +14,7 @@ namespace Monolog\Handler;
 use Monolog\Handler\FingersCrossed\ErrorLevelActivationStrategy;
 use Monolog\Handler\FingersCrossed\ActivationStrategyInterface;
 use Monolog\Logger;
+use Monolog\ResettableInterface;
 
 /**
  * Buffers all records until a certain level is reached
@@ -147,7 +148,14 @@ class FingersCrossedHandler extends AbstractHandler
      */
     public function reset()
     {
+        parent::reset();
+
+        $this->buffer = array();
         $this->buffering = true;
+
+        if ($this->handler instanceof ResettableInterface) {
+            $this->handler->reset();
+        }
     }
 
     /**

+ 12 - 0
src/Monolog/Handler/GroupHandler.php

@@ -12,6 +12,7 @@
 namespace Monolog\Handler;
 
 use Monolog\Formatter\FormatterInterface;
+use Monolog\ResettableInterface;
 
 /**
  * Forwards records to multiple handlers
@@ -90,6 +91,17 @@ class GroupHandler extends AbstractHandler
         }
     }
 
+    public function reset()
+    {
+        parent::reset();
+
+        foreach ($this->handlers as $handler) {
+            if ($handler instanceof ResettableInterface) {
+                $handler->reset();
+            }
+        }
+    }
+
     /**
      * {@inheritdoc}
      */

+ 9 - 1
src/Monolog/Handler/HandlerWrapper.php

@@ -11,6 +11,7 @@
 
 namespace Monolog\Handler;
 
+use Monolog\ResettableInterface;
 use Monolog\Formatter\FormatterInterface;
 
 /**
@@ -30,7 +31,7 @@ use Monolog\Formatter\FormatterInterface;
  *
  * @author Alexey Karapetov <alexey@karapetov.com>
  */
-class HandlerWrapper implements HandlerInterface
+class HandlerWrapper implements HandlerInterface, ResettableInterface
 {
     /**
      * @var HandlerInterface
@@ -105,4 +106,11 @@ class HandlerWrapper implements HandlerInterface
     {
         return $this->handler->getFormatter();
     }
+
+    public function reset()
+    {
+        if ($this->handler instanceof ResettableInterface) {
+            return $this->handler->reset();
+        }
+    }
 }

+ 16 - 1
src/Monolog/Logger.php

@@ -25,7 +25,7 @@ use Exception;
  *
  * @author Jordi Boggiano <j.boggiano@seld.be>
  */
-class Logger implements LoggerInterface
+class Logger implements LoggerInterface, ResettableInterface
 {
     /**
      * Detailed debug information
@@ -354,6 +354,21 @@ class Logger implements LoggerInterface
         return true;
     }
 
+    public function reset()
+    {
+        foreach ($this->handlers as $handler) {
+            if ($handler instanceof ResettableInterface) {
+                $handler->reset();
+            }
+        }
+
+        foreach ($this->processors as $processor) {
+            if ($processor instanceof ResettableInterface) {
+                $processor->reset();
+            }
+        }
+    }
+
     /**
      * Adds a log record at the DEBUG level.
      *

+ 15 - 2
src/Monolog/Processor/UidProcessor.php

@@ -11,12 +11,14 @@
 
 namespace Monolog\Processor;
 
+use Monolog\ResettableInterface;
+
 /**
  * Adds a unique identifier into records
  *
  * @author Simon Mönch <sm@webfactory.de>
  */
-class UidProcessor
+class UidProcessor implements ResettableInterface
 {
     private $uid;
 
@@ -26,7 +28,8 @@ class UidProcessor
             throw new \InvalidArgumentException('The uid length must be an integer between 1 and 32');
         }
 
-        $this->uid = substr(hash('md5', uniqid('', true)), 0, $length);
+
+        $this->uid = $this->generateUid($length);
     }
 
     public function __invoke(array $record)
@@ -43,4 +46,14 @@ class UidProcessor
     {
         return $this->uid;
     }
+
+    public function reset()
+    {
+        $this->uid = $this->generateUid(strlen($this->uid));
+    }
+
+    private function generateUid($length)
+    {
+        return substr(hash('md5', uniqid('', true)), 0, $length);
+    }
 }

+ 25 - 0
src/Monolog/ResettableInterface.php

@@ -0,0 +1,25 @@
+<?php
+
+/*
+ * This file is part of the Monolog package.
+ *
+ * (c) Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog;
+
+/**
+ * Handler or Processor implementing this interface will be reset when Logger::reset() is called.
+ *
+ * Resetting an Handler or a Processor usually means cleaning all buffers or
+ * resetting in its internal state.
+ *
+ * @author Grégoire Pineau <lyrixx@lyrixx.info>
+ */
+interface ResettableInterface
+{
+    public function reset();
+}

+ 1 - 1
tests/Monolog/Handler/BrowserConsoleHandlerTest.php

@@ -21,7 +21,7 @@ class BrowserConsoleHandlerTest extends TestCase
 {
     protected function setUp()
     {
-        BrowserConsoleHandler::reset();
+        BrowserConsoleHandler::resetStatic();
     }
 
     protected function generateScript()

+ 2 - 2
tests/Monolog/Handler/ChromePHPHandlerTest.php

@@ -21,7 +21,7 @@ class ChromePHPHandlerTest extends TestCase
 {
     protected function setUp()
     {
-        TestChromePHPHandler::reset();
+        TestChromePHPHandler::resetStatic();
         $_SERVER['HTTP_USER_AGENT'] = 'Monolog Test; Chrome/1.0';
     }
 
@@ -136,7 +136,7 @@ class TestChromePHPHandler extends ChromePHPHandler
 {
     protected $headers = array();
 
-    public static function reset()
+    public static function resetStatic()
     {
         self::$initialized = false;
         self::$overflowed = false;

+ 2 - 2
tests/Monolog/Handler/FingersCrossedHandlerTest.php

@@ -58,7 +58,7 @@ class FingersCrossedHandlerTest extends TestCase
      * @covers Monolog\Handler\FingersCrossedHandler::activate
      * @covers Monolog\Handler\FingersCrossedHandler::reset
      */
-    public function testHandleRestartBufferingAfterReset()
+    public function testHandleResetBufferingAfterReset()
     {
         $test = new TestHandler();
         $handler = new FingersCrossedHandler($test);
@@ -76,7 +76,7 @@ class FingersCrossedHandlerTest extends TestCase
      * @covers Monolog\Handler\FingersCrossedHandler::handle
      * @covers Monolog\Handler\FingersCrossedHandler::activate
      */
-    public function testHandleRestartBufferingAfterBeingTriggeredWhenStopBufferingIsDisabled()
+    public function testHandleResetBufferingAfterBeingTriggeredWhenStopBufferingIsDisabled()
     {
         $test = new TestHandler();
         $handler = new FingersCrossedHandler($test, Logger::WARNING, 0, false, false);

+ 2 - 2
tests/Monolog/Handler/FirePHPHandlerTest.php

@@ -21,7 +21,7 @@ class FirePHPHandlerTest extends TestCase
 {
     public function setUp()
     {
-        TestFirePHPHandler::reset();
+        TestFirePHPHandler::resetStatic();
         $_SERVER['HTTP_USER_AGENT'] = 'Monolog Test; FirePHP/1.0';
     }
 
@@ -77,7 +77,7 @@ class TestFirePHPHandler extends FirePHPHandler
 {
     protected $headers = array();
 
-    public static function reset()
+    public static function resetStatic()
     {
         self::$initialized = false;
         self::$sendHeaders = true;

+ 73 - 0
tests/Monolog/LoggerTest.php

@@ -614,4 +614,77 @@ class LoggerTest extends \PHPUnit_Framework_TestCase
         $logger->pushHandler($handler);
         $logger->info('test');
     }
+
+    public function testReset()
+    {
+        $logger = new Logger('app');
+
+        $testHandler = new Handler\TestHandler();
+        $bufferHandler = new Handler\BufferHandler($testHandler);
+        $groupHandler = new Handler\GroupHandler(array($bufferHandler));
+        $fingersCrossedHandler = new Handler\FingersCrossedHandler($groupHandler);
+
+        $logger->pushHandler($fingersCrossedHandler);
+
+        $processorUid1 = new Processor\UidProcessor(10);
+        $uid1 = $processorUid1->getUid();
+        $groupHandler->pushProcessor($processorUid1);
+
+        $processorUid2 = new Processor\UidProcessor(5);
+        $uid2 = $processorUid2->getUid();
+        $logger->pushProcessor($processorUid2);
+
+        $getProperty = function ($object, $property) {
+            $reflectionProperty = new \ReflectionProperty(get_class($object), $property);
+            $reflectionProperty->setAccessible(true);
+
+            return $reflectionProperty->getValue($object);
+        };
+        $that = $this;
+        $assertBufferOfBufferHandlerEmpty = function () use ($getProperty, $bufferHandler, $that) {
+            $that->assertEmpty($getProperty($bufferHandler, 'buffer'));
+        };
+        $assertBuffersEmpty = function() use ($assertBufferOfBufferHandlerEmpty, $getProperty, $fingersCrossedHandler, $that) {
+            $assertBufferOfBufferHandlerEmpty();
+            $that->assertEmpty($getProperty($fingersCrossedHandler, 'buffer'));
+        };
+
+        $logger->debug('debug');
+        $logger->reset();
+        $assertBuffersEmpty();
+        $this->assertFalse($testHandler->hasDebugRecords());
+        $this->assertFalse($testHandler->hasErrorRecords());
+        $this->assertNotSame($uid1, $uid1 = $processorUid1->getUid());
+        $this->assertNotSame($uid2, $uid2 = $processorUid2->getUid());
+
+        $logger->debug('debug');
+        $logger->error('error');
+        $logger->reset();
+        $assertBuffersEmpty();
+        $this->assertTrue($testHandler->hasDebugRecords());
+        $this->assertTrue($testHandler->hasErrorRecords());
+        $this->assertNotSame($uid1, $uid1 = $processorUid1->getUid());
+        $this->assertNotSame($uid2, $uid2 = $processorUid2->getUid());
+
+        $logger->info('info');
+        $this->assertNotEmpty($getProperty($fingersCrossedHandler, 'buffer'));
+        $assertBufferOfBufferHandlerEmpty();
+        $this->assertFalse($testHandler->hasInfoRecords());
+
+        $logger->reset();
+        $assertBuffersEmpty();
+        $this->assertFalse($testHandler->hasInfoRecords());
+        $this->assertNotSame($uid1, $uid1 = $processorUid1->getUid());
+        $this->assertNotSame($uid2, $uid2 = $processorUid2->getUid());
+
+        $logger->notice('notice');
+        $logger->emergency('emergency');
+        $logger->reset();
+        $assertBuffersEmpty();
+        $this->assertFalse($testHandler->hasInfoRecords());
+        $this->assertTrue($testHandler->hasNoticeRecords());
+        $this->assertTrue($testHandler->hasEmergencyRecords());
+        $this->assertNotSame($uid1, $processorUid1->getUid());
+        $this->assertNotSame($uid2, $processorUid2->getUid());
+    }
 }