Skip to content

Commit

Permalink
Added a new RestartableInterface and implemented it where possible.
Browse files Browse the repository at this point in the history
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 a Uid to the
logs. The UUID should change for each AMQP messages.

---

This patch address this issue with a new interface: `Restartable` interface.
Side note: `reset()`, `flush()`, `clear()`,  are already used in Monolog. So
basically, one can use the new `restart()` on the `Logger` and on some
`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`.
  • Loading branch information
lyrixx committed Apr 5, 2018
1 parent fd8c787 commit eb81195
Show file tree
Hide file tree
Showing 11 changed files with 192 additions and 6 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
### 1.24.0

* Added a `RestartableInterface` in order to restart/clear/flush handler and processor
and implement it where possible

### 1.23.0 (2017-06-19)

* Improved SyslogUdpHandler's support for RFC5424 and added optional `$ident` argument
Expand Down
16 changes: 14 additions & 2 deletions src/Monolog/Handler/AbstractHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,17 @@

namespace Monolog\Handler;

use Monolog\Logger;
use Monolog\Formatter\FormatterInterface;
use Monolog\Formatter\LineFormatter;
use Monolog\Logger;
use Monolog\RestartableInterface;

/**
* Base Handler class providing the Handler structure
*
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
abstract class AbstractHandler implements HandlerInterface
abstract class AbstractHandler implements HandlerInterface, RestartableInterface
{
protected $level = Logger::DEBUG;
protected $bubble = true;
Expand Down Expand Up @@ -174,6 +175,17 @@ public function __destruct()
}
}

public function restart()
{
$this->close();

foreach ($this->processors as $processor) {
if ($processor instanceof RestartableInterface) {
$processor->restart();
}
}
}

/**
* Gets the default formatter.
*
Expand Down
2 changes: 2 additions & 0 deletions src/Monolog/Handler/AbstractProcessingHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@

namespace Monolog\Handler;

use Monolog\RestartableInterface;

/**
* Base Handler class providing the Handler structure
*
Expand Down
10 changes: 10 additions & 0 deletions src/Monolog/Handler/BufferHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
namespace Monolog\Handler;

use Monolog\Logger;
use Monolog\RestartableInterface;

/**
* Buffers all records until closing the handler and then pass them as batch.
Expand Down Expand Up @@ -114,4 +115,13 @@ public function clear()
$this->bufferSize = 0;
$this->buffer = array();
}

public function restart()
{
parent::restart();

if ($this->handler instanceof RestartableInterface) {
$this->handler->restart();
}
}
}
12 changes: 12 additions & 0 deletions src/Monolog/Handler/FingersCrossedHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use Monolog\Handler\FingersCrossed\ErrorLevelActivationStrategy;
use Monolog\Handler\FingersCrossed\ActivationStrategyInterface;
use Monolog\Logger;
use Monolog\RestartableInterface;

/**
* Buffers all records until a certain level is reached
Expand Down Expand Up @@ -160,4 +161,15 @@ public function clear()
$this->buffer = array();
$this->reset();
}

public function restart()
{
parent::restart();

$this->clear();

if ($this->handler instanceof RestartableInterface) {
$this->handler->restart();
}
}
}
12 changes: 12 additions & 0 deletions src/Monolog/Handler/GroupHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
namespace Monolog\Handler;

use Monolog\Formatter\FormatterInterface;
use Monolog\RestartableInterface;

/**
* Forwards records to multiple handlers
Expand Down Expand Up @@ -90,6 +91,17 @@ public function handleBatch(array $records)
}
}

public function restart()
{
parent::restart();

foreach ($this->handlers as $handler) {
if ($handler instanceof RestartableInterface) {
$handler->restart();
}
}
}

/**
* {@inheritdoc}
*/
Expand Down
10 changes: 9 additions & 1 deletion src/Monolog/Handler/HandlerWrapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

namespace Monolog\Handler;

use Monolog\RestartableInterface;
use Monolog\Formatter\FormatterInterface;

/**
Expand All @@ -30,7 +31,7 @@
*
* @author Alexey Karapetov <alexey@karapetov.com>
*/
class HandlerWrapper implements HandlerInterface
class HandlerWrapper implements HandlerInterface, RestartableInterface
{
/**
* @var HandlerInterface
Expand Down Expand Up @@ -105,4 +106,11 @@ public function getFormatter()
{
return $this->handler->getFormatter();
}

public function restart()
{
if ($this->handler instanceof RestartableInterface) {
return $this->handler->restart();
}
}
}
17 changes: 16 additions & 1 deletion src/Monolog/Logger.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
*
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
class Logger implements LoggerInterface
class Logger implements LoggerInterface, RestartableInterface
{
/**
* Detailed debug information
Expand Down Expand Up @@ -344,6 +344,21 @@ public function addRecord($level, $message, array $context = array())
return true;
}

public function restart()
{
foreach ($this->handlers as $handler) {
if ($handler instanceof RestartableInterface) {
$handler->restart();
}
}

foreach ($this->processors as $processor) {
if ($processor instanceof RestartableInterface) {
$processor->restart();
}
}
}

/**
* Adds a log record at the DEBUG level.
*
Expand Down
17 changes: 15 additions & 2 deletions src/Monolog/Processor/UidProcessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,14 @@

namespace Monolog\Processor;

use Monolog\RestartableInterface;

/**
* Adds a unique identifier into records
*
* @author Simon Mönch <sm@webfactory.de>
*/
class UidProcessor
class UidProcessor implements RestartableInterface
{
private $uid;

Expand All @@ -26,7 +28,8 @@ public function __construct($length = 7)
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)
Expand All @@ -43,4 +46,14 @@ public function getUid()
{
return $this->uid;
}

public function restart()
{
$this->uid = $this->generateUid(strlen($this->uid));
}

private function generateUid($length)
{
return substr(hash('md5', uniqid('', true)), 0, $length);
}
}
25 changes: 25 additions & 0 deletions src/Monolog/RestartableInterface.php
Original file line number Diff line number Diff line change
@@ -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 restarted when Logger::restart is called.
*
* Restarting an Handler or a Processor usually means cleaning all buffers or
* reseting in its internal state.
*
* @author Grégoire Pineau <lyrixx@lyrixx.info>
*/
interface RestartableInterface
{
public function restart();
}
72 changes: 72 additions & 0 deletions tests/Monolog/LoggerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -545,4 +545,76 @@ public function useMicrosecondTimestampsProvider()
'without microseconds' => array(false, PHP_VERSION_ID >= 70100 ? 'assertNotSame' : 'assertSame'),
);
}

public function testRestart()
{
$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);
};
$assertBufferOfBufferHandlerEmpty = function () use ($getProperty, $bufferHandler) {
$this->assertEmpty($getProperty($bufferHandler, 'buffer'));
};
$assertBuffersEmpty = function() use ($assertBufferOfBufferHandlerEmpty, $getProperty, $fingersCrossedHandler) {
$assertBufferOfBufferHandlerEmpty();
$this->assertEmpty($getProperty($fingersCrossedHandler, 'buffer'));
};

$logger->debug('debug');
$logger->restart();
$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->restart();
$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->restart();
$assertBuffersEmpty();
$this->assertFalse($testHandler->hasInfoRecords());
$this->assertNotSame($uid1, $uid1 = $processorUid1->getUid());
$this->assertNotSame($uid2, $uid2 = $processorUid2->getUid());

$logger->notice('notice');
$logger->emergency('emergency');
$logger->restart();
$assertBuffersEmpty();
$this->assertFalse($testHandler->hasInfoRecords());
$this->assertTrue($testHandler->hasNoticeRecords());
$this->assertTrue($testHandler->hasEmergencyRecords());
$this->assertNotSame($uid1, $processorUid1->getUid());
$this->assertNotSame($uid2, $processorUid2->getUid());
}
}

0 comments on commit eb81195

Please sign in to comment.