mirror of
https://github.com/Seldaek/monolog.git
synced 2025-08-02 19:27:37 +02:00
Merge branch '1.x'
This commit is contained in:
@@ -189,6 +189,9 @@ $logger->pushHandler($firephp);
|
|||||||
$securityLogger = new Logger('security');
|
$securityLogger = new Logger('security');
|
||||||
$securityLogger->pushHandler($stream);
|
$securityLogger->pushHandler($stream);
|
||||||
$securityLogger->pushHandler($firephp);
|
$securityLogger->pushHandler($firephp);
|
||||||
|
|
||||||
|
// Or clone the first one to only change the channel
|
||||||
|
$securityLogger = $logger->withName('security');
|
||||||
```
|
```
|
||||||
|
|
||||||
## Customizing the log format
|
## Customizing the log format
|
||||||
|
@@ -105,6 +105,8 @@
|
|||||||
- _PsrHandler_: Can be used to forward log records to an existing PSR-3 logger
|
- _PsrHandler_: Can be used to forward log records to an existing PSR-3 logger
|
||||||
- _TestHandler_: Used for testing, it records everything that is sent to it and
|
- _TestHandler_: Used for testing, it records everything that is sent to it and
|
||||||
has accessors to read out the information.
|
has accessors to read out the information.
|
||||||
|
- _HandlerWrapper_: A simple handler wrapper you can inherit from to create
|
||||||
|
your own wrappers easily.
|
||||||
|
|
||||||
## Formatters
|
## Formatters
|
||||||
|
|
||||||
|
@@ -11,6 +11,8 @@
|
|||||||
|
|
||||||
namespace Monolog\Formatter;
|
namespace Monolog\Formatter;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encodes whatever record data is passed to it as json
|
* Encodes whatever record data is passed to it as json
|
||||||
*
|
*
|
||||||
@@ -18,13 +20,17 @@ namespace Monolog\Formatter;
|
|||||||
*
|
*
|
||||||
* @author Jordi Boggiano <j.boggiano@seld.be>
|
* @author Jordi Boggiano <j.boggiano@seld.be>
|
||||||
*/
|
*/
|
||||||
class JsonFormatter implements FormatterInterface
|
class JsonFormatter extends NormalizerFormatter
|
||||||
{
|
{
|
||||||
const BATCH_MODE_JSON = 1;
|
const BATCH_MODE_JSON = 1;
|
||||||
const BATCH_MODE_NEWLINES = 2;
|
const BATCH_MODE_NEWLINES = 2;
|
||||||
|
|
||||||
protected $batchMode;
|
protected $batchMode;
|
||||||
protected $appendNewline;
|
protected $appendNewline;
|
||||||
|
/**
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
protected $includeStacktraces = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param int $batchMode
|
* @param int $batchMode
|
||||||
@@ -64,7 +70,7 @@ class JsonFormatter implements FormatterInterface
|
|||||||
*/
|
*/
|
||||||
public function format(array $record)
|
public function format(array $record)
|
||||||
{
|
{
|
||||||
return json_encode($record) . ($this->appendNewline ? "\n" : '');
|
return $this->toJson($this->normalize($record), true) . ($this->appendNewline ? "\n" : '');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -82,6 +88,14 @@ class JsonFormatter implements FormatterInterface
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param bool $include
|
||||||
|
*/
|
||||||
|
public function includeStacktraces($include = true)
|
||||||
|
{
|
||||||
|
$this->includeStacktraces = $include;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a JSON-encoded array of records.
|
* Return a JSON-encoded array of records.
|
||||||
*
|
*
|
||||||
@@ -90,7 +104,7 @@ class JsonFormatter implements FormatterInterface
|
|||||||
*/
|
*/
|
||||||
protected function formatBatchJson(array $records)
|
protected function formatBatchJson(array $records)
|
||||||
{
|
{
|
||||||
return json_encode($records);
|
return $this->toJson($this->normalize($records), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -113,4 +127,76 @@ class JsonFormatter implements FormatterInterface
|
|||||||
|
|
||||||
return implode("\n", $records);
|
return implode("\n", $records);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalizes given $data.
|
||||||
|
*
|
||||||
|
* @param mixed $data
|
||||||
|
*
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
protected function normalize($data)
|
||||||
|
{
|
||||||
|
if (is_array($data) || $data instanceof \Traversable) {
|
||||||
|
$normalized = array();
|
||||||
|
|
||||||
|
$count = 1;
|
||||||
|
foreach ($data as $key => $value) {
|
||||||
|
if ($count++ >= 1000) {
|
||||||
|
$normalized['...'] = 'Over 1000 items, aborting normalization';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$normalized[$key] = $this->normalize($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($data instanceof Exception) {
|
||||||
|
return $this->normalizeException($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalizes given exception with or without its own stack trace based on
|
||||||
|
* `includeStacktraces` property.
|
||||||
|
*
|
||||||
|
* @param Exception|Throwable $e
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function normalizeException($e)
|
||||||
|
{
|
||||||
|
// TODO 2.0 only check for Throwable
|
||||||
|
if (!$e instanceof Exception && !$e instanceof \Throwable) {
|
||||||
|
throw new \InvalidArgumentException('Exception/Throwable expected, got '.gettype($e).' / '.get_class($e));
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = array(
|
||||||
|
'class' => get_class($e),
|
||||||
|
'message' => $e->getMessage(),
|
||||||
|
'code' => $e->getCode(),
|
||||||
|
'file' => $e->getFile().':'.$e->getLine(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($this->includeStacktraces) {
|
||||||
|
$trace = $e->getTrace();
|
||||||
|
foreach ($trace as $frame) {
|
||||||
|
if (isset($frame['file'])) {
|
||||||
|
$data['trace'][] = $frame['file'].':'.$frame['line'];
|
||||||
|
} else {
|
||||||
|
// We should again normalize the frames, because it might contain invalid items
|
||||||
|
$data['trace'][] = $this->normalize($frame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($previous = $e->getPrevious()) {
|
||||||
|
$data['previous'] = $this->normalizeException($previous);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -11,8 +11,6 @@
|
|||||||
|
|
||||||
namespace Monolog\Formatter;
|
namespace Monolog\Formatter;
|
||||||
|
|
||||||
use Exception;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Formats incoming records into a one-line string
|
* Formats incoming records into a one-line string
|
||||||
*
|
*
|
||||||
@@ -78,6 +76,13 @@ class LineFormatter extends NormalizerFormatter
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach ($vars['context'] as $var => $val) {
|
||||||
|
if (false !== strpos($output, '%context.'.$var.'%')) {
|
||||||
|
$output = str_replace('%context.'.$var.'%', $this->stringify($val), $output);
|
||||||
|
unset($vars['context'][$var]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ($this->ignoreEmptyContextAndExtra) {
|
if ($this->ignoreEmptyContextAndExtra) {
|
||||||
if (empty($vars['context'])) {
|
if (empty($vars['context'])) {
|
||||||
unset($vars['context']);
|
unset($vars['context']);
|
||||||
@@ -114,8 +119,13 @@ class LineFormatter extends NormalizerFormatter
|
|||||||
return $this->replaceNewlines($this->convertToString($value));
|
return $this->replaceNewlines($this->convertToString($value));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function normalizeException(Exception $e)
|
protected function normalizeException($e)
|
||||||
{
|
{
|
||||||
|
// TODO 2.0 only check for Throwable
|
||||||
|
if (!$e instanceof \Exception && !$e instanceof \Throwable) {
|
||||||
|
throw new \InvalidArgumentException('Exception/Throwable expected, got '.gettype($e).' / '.get_class($e));
|
||||||
|
}
|
||||||
|
|
||||||
$previousText = '';
|
$previousText = '';
|
||||||
if ($previous = $e->getPrevious()) {
|
if ($previous = $e->getPrevious()) {
|
||||||
do {
|
do {
|
||||||
|
@@ -90,7 +90,8 @@ class NormalizerFormatter implements FormatterInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (is_object($data)) {
|
if (is_object($data)) {
|
||||||
if ($data instanceof Exception) {
|
// TODO 2.0 only check for Throwable
|
||||||
|
if ($data instanceof Exception || (PHP_VERSION_ID > 70000 && $data instanceof \Throwable)) {
|
||||||
return $this->normalizeException($data);
|
return $this->normalizeException($data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,8 +113,13 @@ class NormalizerFormatter implements FormatterInterface
|
|||||||
return '[unknown('.gettype($data).')]';
|
return '[unknown('.gettype($data).')]';
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function normalizeException(Exception $e)
|
protected function normalizeException($e)
|
||||||
{
|
{
|
||||||
|
// TODO 2.0 only check for Throwable
|
||||||
|
if (!$e instanceof Exception && !$e instanceof \Throwable) {
|
||||||
|
throw new \InvalidArgumentException('Exception/Throwable expected, got '.gettype($e).' / '.get_class($e));
|
||||||
|
}
|
||||||
|
|
||||||
$data = array(
|
$data = array(
|
||||||
'class' => get_class($e),
|
'class' => get_class($e),
|
||||||
'message' => $e->getMessage(),
|
'message' => $e->getMessage(),
|
||||||
|
@@ -70,6 +70,15 @@ abstract class AbstractSyslogHandler extends AbstractProcessingHandler
|
|||||||
$this->facilities['local5'] = LOG_LOCAL5;
|
$this->facilities['local5'] = LOG_LOCAL5;
|
||||||
$this->facilities['local6'] = LOG_LOCAL6;
|
$this->facilities['local6'] = LOG_LOCAL6;
|
||||||
$this->facilities['local7'] = LOG_LOCAL7;
|
$this->facilities['local7'] = LOG_LOCAL7;
|
||||||
|
} else {
|
||||||
|
$this->facilities['local0'] = 128; // LOG_LOCAL0
|
||||||
|
$this->facilities['local1'] = 136; // LOG_LOCAL1
|
||||||
|
$this->facilities['local2'] = 144; // LOG_LOCAL2
|
||||||
|
$this->facilities['local3'] = 152; // LOG_LOCAL3
|
||||||
|
$this->facilities['local4'] = 160; // LOG_LOCAL4
|
||||||
|
$this->facilities['local5'] = 168; // LOG_LOCAL5
|
||||||
|
$this->facilities['local6'] = 176; // LOG_LOCAL6
|
||||||
|
$this->facilities['local7'] = 184; // LOG_LOCAL7
|
||||||
}
|
}
|
||||||
|
|
||||||
// convert textual description of facility to syslog constant
|
// convert textual description of facility to syslog constant
|
||||||
|
@@ -55,18 +55,12 @@ class AmqpHandler extends AbstractProcessingHandler
|
|||||||
protected function write(array $record)
|
protected function write(array $record)
|
||||||
{
|
{
|
||||||
$data = $record["formatted"];
|
$data = $record["formatted"];
|
||||||
|
$routingKey = $this->getRoutingKey($record);
|
||||||
$routingKey = sprintf(
|
|
||||||
'%s.%s',
|
|
||||||
// TODO 2.0 remove substr call
|
|
||||||
substr($record['level_name'], 0, 4),
|
|
||||||
$record['channel']
|
|
||||||
);
|
|
||||||
|
|
||||||
if ($this->exchange instanceof AMQPExchange) {
|
if ($this->exchange instanceof AMQPExchange) {
|
||||||
$this->exchange->publish(
|
$this->exchange->publish(
|
||||||
$data,
|
$data,
|
||||||
strtolower($routingKey),
|
$routingKey,
|
||||||
0,
|
0,
|
||||||
array(
|
array(
|
||||||
'delivery_mode' => 2,
|
'delivery_mode' => 2,
|
||||||
@@ -75,18 +69,73 @@ class AmqpHandler extends AbstractProcessingHandler
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
$this->exchange->basic_publish(
|
$this->exchange->basic_publish(
|
||||||
new AMQPMessage(
|
$this->createAmqpMessage($data),
|
||||||
|
$this->exchangeName,
|
||||||
|
$routingKey
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function handleBatch(array $records)
|
||||||
|
{
|
||||||
|
if ($this->exchange instanceof AMQPExchange) {
|
||||||
|
parent::handleBatch($records);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($records as $record) {
|
||||||
|
if (!$this->isHandling($record)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$record = $this->processRecord($record);
|
||||||
|
$data = $this->getFormatter()->format($record);
|
||||||
|
|
||||||
|
$this->exchange->batch_basic_publish(
|
||||||
|
$this->createAmqpMessage($data),
|
||||||
|
$this->exchangeName,
|
||||||
|
$this->getRoutingKey($record)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->exchange->publish_batch();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the routing key for the AMQP exchange
|
||||||
|
*
|
||||||
|
* @param array $record
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private function getRoutingKey(array $record)
|
||||||
|
{
|
||||||
|
$routingKey = sprintf(
|
||||||
|
'%s.%s',
|
||||||
|
// TODO 2.0 remove substr call
|
||||||
|
substr($record['level_name'], 0, 4),
|
||||||
|
$record['channel']
|
||||||
|
);
|
||||||
|
|
||||||
|
return strtolower($routingKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $data
|
||||||
|
* @return AMQPMessage
|
||||||
|
*/
|
||||||
|
private function createAmqpMessage($data)
|
||||||
|
{
|
||||||
|
return new AMQPMessage(
|
||||||
(string) $data,
|
(string) $data,
|
||||||
array(
|
array(
|
||||||
'delivery_mode' => 2,
|
'delivery_mode' => 2,
|
||||||
'content_type' => 'application/json',
|
'content_type' => 'application/json',
|
||||||
)
|
)
|
||||||
),
|
|
||||||
$this->exchangeName,
|
|
||||||
strtolower($routingKey)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
|
@@ -46,9 +46,9 @@ class BrowserConsoleHandler extends AbstractProcessingHandler
|
|||||||
self::$records[] = $record;
|
self::$records[] = $record;
|
||||||
|
|
||||||
// Register shutdown handler if not already done
|
// Register shutdown handler if not already done
|
||||||
if (PHP_SAPI !== 'cli' && !self::$initialized) {
|
if (!self::$initialized) {
|
||||||
self::$initialized = true;
|
self::$initialized = true;
|
||||||
register_shutdown_function(array('Monolog\Handler\BrowserConsoleHandler', 'send'));
|
$this->registerShutdownFunction();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,26 +58,16 @@ class BrowserConsoleHandler extends AbstractProcessingHandler
|
|||||||
*/
|
*/
|
||||||
public static function send()
|
public static function send()
|
||||||
{
|
{
|
||||||
$htmlTags = true;
|
$format = self::getResponseFormat();
|
||||||
// Check content type
|
if ($format === 'unknown') {
|
||||||
foreach (headers_list() as $header) {
|
|
||||||
if (stripos($header, 'content-type:') === 0) {
|
|
||||||
// This handler only works with HTML and javascript outputs
|
|
||||||
// text/javascript is obsolete in favour of application/javascript, but still used
|
|
||||||
if (stripos($header, 'application/javascript') !== false || stripos($header, 'text/javascript') !== false) {
|
|
||||||
$htmlTags = false;
|
|
||||||
} elseif (stripos($header, 'text/html') === false) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (count(self::$records)) {
|
if (count(self::$records)) {
|
||||||
if ($htmlTags) {
|
if ($format === 'html') {
|
||||||
echo '<script>' , self::generateScript() , '</script>';
|
self::writeOutput('<script>' , self::generateScript() , '</script>');
|
||||||
} else {
|
} elseif ($format === 'js') {
|
||||||
echo self::generateScript();
|
self::writeOutput(self::generateScript());
|
||||||
}
|
}
|
||||||
self::reset();
|
self::reset();
|
||||||
}
|
}
|
||||||
@@ -91,6 +81,50 @@ class BrowserConsoleHandler extends AbstractProcessingHandler
|
|||||||
self::$records = array();
|
self::$records = array();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper for register_shutdown_function to allow overriding
|
||||||
|
*/
|
||||||
|
protected function registerShutdownFunction()
|
||||||
|
{
|
||||||
|
if (PHP_SAPI !== 'cli') {
|
||||||
|
register_shutdown_function(array('Monolog\Handler\BrowserConsoleHandler', 'send'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper for echo to allow overriding
|
||||||
|
*
|
||||||
|
* @param string $str
|
||||||
|
*/
|
||||||
|
protected static function writeOutput($str)
|
||||||
|
{
|
||||||
|
echo $str;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks the format of the response
|
||||||
|
*
|
||||||
|
* @return string One of 'js', 'html' or 'unknown'
|
||||||
|
*/
|
||||||
|
protected static function getResponseFormat()
|
||||||
|
{
|
||||||
|
// Check content type
|
||||||
|
foreach (headers_list() as $header) {
|
||||||
|
if (stripos($header, 'content-type:') === 0) {
|
||||||
|
// This handler only works with HTML and javascript outputs
|
||||||
|
// text/javascript is obsolete in favour of application/javascript, but still used
|
||||||
|
if (stripos($header, 'application/javascript') !== false || stripos($header, 'text/javascript') !== false) {
|
||||||
|
return 'js';
|
||||||
|
} elseif (stripos($header, 'text/html') !== false) {
|
||||||
|
return 'html';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'unknown';
|
||||||
|
}
|
||||||
|
|
||||||
private static function generateScript()
|
private static function generateScript()
|
||||||
{
|
{
|
||||||
$script = array();
|
$script = array();
|
||||||
|
@@ -17,6 +17,8 @@ use Monolog\Logger;
|
|||||||
/**
|
/**
|
||||||
* Handler sending logs to the ChromePHP extension (http://www.chromephp.com/)
|
* Handler sending logs to the ChromePHP extension (http://www.chromephp.com/)
|
||||||
*
|
*
|
||||||
|
* This also works out of the box with Firefox 43+
|
||||||
|
*
|
||||||
* @author Christophe Coevoet <stof@notk.org>
|
* @author Christophe Coevoet <stof@notk.org>
|
||||||
*/
|
*/
|
||||||
class ChromePHPHandler extends AbstractProcessingHandler
|
class ChromePHPHandler extends AbstractProcessingHandler
|
||||||
@@ -175,7 +177,8 @@ class ChromePHPHandler extends AbstractProcessingHandler
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return preg_match('{\bChrome/\d+[\.\d+]*\b}', $_SERVER['HTTP_USER_AGENT']);
|
// matches any Chrome, or Firefox 43+
|
||||||
|
return preg_match('{\b(?:Chrome/\d+(?:\.\d+)*|Firefox/(?:4[3-9]|[5-9]\d|\d{3,})(?:\.\d)*)\b}', $_SERVER['HTTP_USER_AGENT']);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -11,6 +11,8 @@
|
|||||||
|
|
||||||
namespace Monolog\Handler;
|
namespace Monolog\Handler;
|
||||||
|
|
||||||
|
use Monolog\Formatter\FormatterInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Forwards records to multiple handlers
|
* Forwards records to multiple handlers
|
||||||
*
|
*
|
||||||
@@ -77,4 +79,16 @@ class GroupHandler extends Handler implements ProcessableHandlerInterface
|
|||||||
$handler->handleBatch($records);
|
$handler->handleBatch($records);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function setFormatter(FormatterInterface $formatter)
|
||||||
|
{
|
||||||
|
foreach ($this->handlers as $handler) {
|
||||||
|
$handler->setFormatter($formatter);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
104
src/Monolog/Handler/HandlerWrapper.php
Normal file
104
src/Monolog/Handler/HandlerWrapper.php
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
<?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;
|
||||||
|
|
||||||
|
use Monolog\Formatter\FormatterInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This simple wrapper class can be used to extend handlers functionality.
|
||||||
|
*
|
||||||
|
* Example: A filtering handle. Inherit from this class, override isHandling() like this
|
||||||
|
*
|
||||||
|
* public function isHandling(array $record)
|
||||||
|
* {
|
||||||
|
* if ($record meets certain conditions) {
|
||||||
|
* return false;
|
||||||
|
* }
|
||||||
|
* return $this->handler->isHandling($record);
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* @author Alexey Karapetov <alexey@karapetov.com>
|
||||||
|
*/
|
||||||
|
class HandlerWrapper implements HandlerInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var HandlerInterface
|
||||||
|
*/
|
||||||
|
private $handler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HandlerWrapper constructor.
|
||||||
|
* @param HandlerInterface $handler
|
||||||
|
*/
|
||||||
|
public function __construct(HandlerInterface $handler)
|
||||||
|
{
|
||||||
|
$this->handler = $handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function isHandling(array $record)
|
||||||
|
{
|
||||||
|
return $this->handler->isHandling($record);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function handle(array $record)
|
||||||
|
{
|
||||||
|
return $this->handler->handle($record);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function handleBatch(array $records)
|
||||||
|
{
|
||||||
|
return $this->handler->handleBatch($records);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function pushProcessor($callback)
|
||||||
|
{
|
||||||
|
$this->handler->pushProcessor($callback);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function popProcessor()
|
||||||
|
{
|
||||||
|
return $this->handler->popProcessor();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function setFormatter(FormatterInterface $formatter)
|
||||||
|
{
|
||||||
|
$this->handler->setFormatter($formatter);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getFormatter()
|
||||||
|
{
|
||||||
|
return $this->handler->getFormatter();
|
||||||
|
}
|
||||||
|
}
|
@@ -38,6 +38,12 @@ class RavenHandler extends AbstractProcessingHandler
|
|||||||
Logger::EMERGENCY => Raven_Client::FATAL,
|
Logger::EMERGENCY => Raven_Client::FATAL,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string should represent the current version of the calling
|
||||||
|
* software. Can be any string (git commit, version number)
|
||||||
|
*/
|
||||||
|
private $release;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var Raven_Client the client object that sends the message to the server
|
* @var Raven_Client the client object that sends the message to the server
|
||||||
*/
|
*/
|
||||||
@@ -139,6 +145,10 @@ class RavenHandler extends AbstractProcessingHandler
|
|||||||
$options['tags'] = array_merge($options['tags'], $record['context']['tags']);
|
$options['tags'] = array_merge($options['tags'], $record['context']['tags']);
|
||||||
unset($record['context']['tags']);
|
unset($record['context']['tags']);
|
||||||
}
|
}
|
||||||
|
if (!empty($record['context']['fingerprint'])) {
|
||||||
|
$options['fingerprint'] = $record['context']['fingerprint'];
|
||||||
|
unset($record['context']['fingerprint']);
|
||||||
|
}
|
||||||
if (!empty($record['context']['logger'])) {
|
if (!empty($record['context']['logger'])) {
|
||||||
$options['logger'] = $record['context']['logger'];
|
$options['logger'] = $record['context']['logger'];
|
||||||
unset($record['context']['logger']);
|
unset($record['context']['logger']);
|
||||||
@@ -165,6 +175,10 @@ class RavenHandler extends AbstractProcessingHandler
|
|||||||
$options['extra']['extra'] = $record['extra'];
|
$options['extra']['extra'] = $record['extra'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!empty($this->release) && !isset($options['release'])) {
|
||||||
|
$options['release'] = $this->release;
|
||||||
|
}
|
||||||
|
|
||||||
if (isset($record['context']['exception']) && $record['context']['exception'] instanceof \Exception) {
|
if (isset($record['context']['exception']) && $record['context']['exception'] instanceof \Exception) {
|
||||||
$options['extra']['message'] = $record['formatted'];
|
$options['extra']['message'] = $record['formatted'];
|
||||||
$this->ravenClient->captureException($record['context']['exception'], $options);
|
$this->ravenClient->captureException($record['context']['exception'], $options);
|
||||||
@@ -204,4 +218,14 @@ class RavenHandler extends AbstractProcessingHandler
|
|||||||
{
|
{
|
||||||
return array('checksum', 'release');
|
return array('checksum', 'release');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $value
|
||||||
|
*/
|
||||||
|
public function setRelease($value)
|
||||||
|
{
|
||||||
|
$this->release = $value;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -115,7 +115,11 @@ class RotatingFileHandler extends StreamHandler
|
|||||||
|
|
||||||
foreach (array_slice($logFiles, $this->maxFiles) as $file) {
|
foreach (array_slice($logFiles, $this->maxFiles) as $file) {
|
||||||
if (is_writable($file)) {
|
if (is_writable($file)) {
|
||||||
|
// suppress errors here as unlink() might fail if two processes
|
||||||
|
// are cleaning up/rotating at the same time
|
||||||
|
set_error_handler(function ($errno, $errstr, $errfile, $errline) {});
|
||||||
unlink($file);
|
unlink($file);
|
||||||
|
restore_error_handler();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -157,6 +157,19 @@ class Logger implements LoggerInterface
|
|||||||
return $this->name;
|
return $this->name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a new cloned instance with the name changed
|
||||||
|
*
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public function withName($name)
|
||||||
|
{
|
||||||
|
$new = clone $this;
|
||||||
|
$new->name = $name;
|
||||||
|
|
||||||
|
return $new;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pushes a handler on to the stack.
|
* Pushes a handler on to the stack.
|
||||||
*
|
*
|
||||||
|
@@ -75,4 +75,48 @@ class JsonFormatterTest extends TestCase
|
|||||||
});
|
});
|
||||||
$this->assertEquals(implode("\n", $expected), $formatter->formatBatch($records));
|
$this->assertEquals(implode("\n", $expected), $formatter->formatBatch($records));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testDefFormatWithException()
|
||||||
|
{
|
||||||
|
$formatter = new JsonFormatter();
|
||||||
|
$exception = new \RuntimeException('Foo');
|
||||||
|
$message = $formatter->format(array(
|
||||||
|
'level_name' => 'CRITICAL',
|
||||||
|
'channel' => 'core',
|
||||||
|
'context' => array('exception' => $exception),
|
||||||
|
'datetime' => new \DateTime(),
|
||||||
|
'extra' => array(),
|
||||||
|
'message' => 'foobar',
|
||||||
|
));
|
||||||
|
|
||||||
|
if (version_compare(PHP_VERSION, '5.4.0', '>=')) {
|
||||||
|
$path = substr(json_encode($exception->getFile(), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE), 1, -1);
|
||||||
|
} else {
|
||||||
|
$path = substr(json_encode($exception->getFile()), 1, -1);
|
||||||
|
}
|
||||||
|
$this->assertEquals('{"level_name":"CRITICAL","channel":"core","context":{"exception":{"class":"RuntimeException","message":"'.$exception->getMessage().'","code":'.$exception->getCode().',"file":"'.$path.':'.$exception->getLine().'"}},"datetime":'.json_encode(new \DateTime()).',"extra":[],"message":"foobar"}'."\n", $message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDefFormatWithPreviousException()
|
||||||
|
{
|
||||||
|
$formatter = new JsonFormatter();
|
||||||
|
$exception = new \RuntimeException('Foo', 0, new \LogicException('Wut?'));
|
||||||
|
$message = $formatter->format(array(
|
||||||
|
'level_name' => 'CRITICAL',
|
||||||
|
'channel' => 'core',
|
||||||
|
'context' => array('exception' => $exception),
|
||||||
|
'datetime' => new \DateTime(),
|
||||||
|
'extra' => array(),
|
||||||
|
'message' => 'foobar',
|
||||||
|
));
|
||||||
|
|
||||||
|
if (version_compare(PHP_VERSION, '5.4.0', '>=')) {
|
||||||
|
$pathPrevious = substr(json_encode($exception->getPrevious()->getFile(), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE), 1, -1);
|
||||||
|
$pathException = substr(json_encode($exception->getFile(), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE), 1, -1);
|
||||||
|
} else {
|
||||||
|
$pathPrevious = substr(json_encode($exception->getPrevious()->getFile()), 1, -1);
|
||||||
|
$pathException = substr(json_encode($exception->getFile()), 1, -1);
|
||||||
|
}
|
||||||
|
$this->assertEquals('{"level_name":"CRITICAL","channel":"core","context":{"exception":{"class":"RuntimeException","message":"'.$exception->getMessage().'","code":'.$exception->getCode().',"file":"'.$pathException.':'.$exception->getLine().'","previous":{"class":"LogicException","message":"'.$exception->getPrevious()->getMessage().'","code":'.$exception->getPrevious()->getCode().',"file":"'.$pathPrevious.':'.$exception->getPrevious()->getLine().'"}}},"datetime":'.json_encode(new \DateTime()).',"extra":[],"message":"foobar"}'."\n", $message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -91,6 +91,20 @@ class LineFormatterTest extends \PHPUnit_Framework_TestCase
|
|||||||
$this->assertEquals('['.date('Y-m-d').'] meh.ERROR: log '."\n", $message);
|
$this->assertEquals('['.date('Y-m-d').'] meh.ERROR: log '."\n", $message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testContextAndExtraReplacement()
|
||||||
|
{
|
||||||
|
$formatter = new LineFormatter('%context.foo% => %extra.foo%');
|
||||||
|
$message = $formatter->format(array(
|
||||||
|
'level_name' => 'ERROR',
|
||||||
|
'channel' => 'meh',
|
||||||
|
'context' => array('foo' => 'bar'),
|
||||||
|
'datetime' => new \DateTime,
|
||||||
|
'extra' => array('foo' => 'xbar'),
|
||||||
|
'message' => 'log',
|
||||||
|
));
|
||||||
|
$this->assertEquals('bar => xbar', $message);
|
||||||
|
}
|
||||||
|
|
||||||
public function testDefFormatWithObject()
|
public function testDefFormatWithObject()
|
||||||
{
|
{
|
||||||
$formatter = new LineFormatter(null, 'Y-m-d');
|
$formatter = new LineFormatter(null, 'Y-m-d');
|
||||||
|
130
tests/Monolog/Handler/HandlerWrapperTest.php
Normal file
130
tests/Monolog/Handler/HandlerWrapperTest.php
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
<?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;
|
||||||
|
|
||||||
|
use Monolog\TestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Alexey Karapetov <alexey@karapetov.com>
|
||||||
|
*/
|
||||||
|
class HandlerWrapperTest extends TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var HandlerWrapper
|
||||||
|
*/
|
||||||
|
private $wrapper;
|
||||||
|
|
||||||
|
private $handler;
|
||||||
|
|
||||||
|
public function setUp()
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
$this->handler = $this->getMock('Monolog\\Handler\\HandlerInterface');
|
||||||
|
$this->wrapper = new HandlerWrapper($this->handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function trueFalseDataProvider()
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
array(true),
|
||||||
|
array(false),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param $result
|
||||||
|
* @dataProvider trueFalseDataProvider
|
||||||
|
*/
|
||||||
|
public function testIsHandling($result)
|
||||||
|
{
|
||||||
|
$record = $this->getRecord();
|
||||||
|
$this->handler->expects($this->once())
|
||||||
|
->method('isHandling')
|
||||||
|
->with($record)
|
||||||
|
->willReturn($result);
|
||||||
|
|
||||||
|
$this->assertEquals($result, $this->wrapper->isHandling($record));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param $result
|
||||||
|
* @dataProvider trueFalseDataProvider
|
||||||
|
*/
|
||||||
|
public function testHandle($result)
|
||||||
|
{
|
||||||
|
$record = $this->getRecord();
|
||||||
|
$this->handler->expects($this->once())
|
||||||
|
->method('handle')
|
||||||
|
->with($record)
|
||||||
|
->willReturn($result);
|
||||||
|
|
||||||
|
$this->assertEquals($result, $this->wrapper->handle($record));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param $result
|
||||||
|
* @dataProvider trueFalseDataProvider
|
||||||
|
*/
|
||||||
|
public function testHandleBatch($result)
|
||||||
|
{
|
||||||
|
$records = $this->getMultipleRecords();
|
||||||
|
$this->handler->expects($this->once())
|
||||||
|
->method('handleBatch')
|
||||||
|
->with($records)
|
||||||
|
->willReturn($result);
|
||||||
|
|
||||||
|
$this->assertEquals($result, $this->wrapper->handleBatch($records));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testPushProcessor()
|
||||||
|
{
|
||||||
|
$processor = function () {};
|
||||||
|
$this->handler->expects($this->once())
|
||||||
|
->method('pushProcessor')
|
||||||
|
->with($processor);
|
||||||
|
|
||||||
|
$this->assertEquals($this->wrapper, $this->wrapper->pushProcessor($processor));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testPopProcessor()
|
||||||
|
{
|
||||||
|
$processor = function () {};
|
||||||
|
$this->handler->expects($this->once())
|
||||||
|
->method('popProcessor')
|
||||||
|
->willReturn($processor);
|
||||||
|
|
||||||
|
$this->assertEquals($processor, $this->wrapper->popProcessor());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSetFormatter()
|
||||||
|
{
|
||||||
|
$formatter = $this->getMock('Monolog\\Formatter\\FormatterInterface');
|
||||||
|
$this->handler->expects($this->once())
|
||||||
|
->method('setFormatter')
|
||||||
|
->with($formatter);
|
||||||
|
|
||||||
|
$this->assertEquals($this->wrapper, $this->wrapper->setFormatter($formatter));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetFormatter()
|
||||||
|
{
|
||||||
|
$formatter = $this->getMock('Monolog\\Formatter\\FormatterInterface');
|
||||||
|
$this->handler->expects($this->once())
|
||||||
|
->method('getFormatter')
|
||||||
|
->willReturn($formatter);
|
||||||
|
|
||||||
|
$this->assertEquals($formatter, $this->wrapper->getFormatter());
|
||||||
|
}
|
||||||
|
}
|
@@ -99,6 +99,18 @@ class RavenHandlerTest extends TestCase
|
|||||||
$this->assertEquals($release, $ravenClient->lastData['release']);
|
$this->assertEquals($release, $ravenClient->lastData['release']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testFingerprint()
|
||||||
|
{
|
||||||
|
$ravenClient = $this->getRavenClient();
|
||||||
|
$handler = $this->getHandler($ravenClient);
|
||||||
|
|
||||||
|
$fingerprint = array('{{ default }}', 'other value');
|
||||||
|
$record = $this->getRecord(Logger::INFO, 'test', array('fingerprint' => $fingerprint));
|
||||||
|
$handler->handle($record);
|
||||||
|
|
||||||
|
$this->assertEquals($fingerprint, $ravenClient->lastData['fingerprint']);
|
||||||
|
}
|
||||||
|
|
||||||
public function testUserContext()
|
public function testUserContext()
|
||||||
{
|
{
|
||||||
$ravenClient = $this->getRavenClient();
|
$ravenClient = $this->getRavenClient();
|
||||||
@@ -192,6 +204,22 @@ class RavenHandlerTest extends TestCase
|
|||||||
$this->assertSame($formatter, $handler->getBatchFormatter());
|
$this->assertSame($formatter, $handler->getBatchFormatter());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testRelease()
|
||||||
|
{
|
||||||
|
$ravenClient = $this->getRavenClient();
|
||||||
|
$handler = $this->getHandler($ravenClient);
|
||||||
|
$release = 'v42.42.42';
|
||||||
|
$handler->setRelease($release);
|
||||||
|
$record = $this->getRecord(Logger::INFO, 'test');
|
||||||
|
$handler->handle($record);
|
||||||
|
$this->assertEquals($release, $ravenClient->lastData['release']);
|
||||||
|
|
||||||
|
$localRelease = 'v41.41.41';
|
||||||
|
$record = $this->getRecord(Logger::INFO, 'test', array('release' => $localRelease));
|
||||||
|
$handler->handle($record);
|
||||||
|
$this->assertEquals($localRelease, $ravenClient->lastData['release']);
|
||||||
|
}
|
||||||
|
|
||||||
private function methodThatThrowsAnException()
|
private function methodThatThrowsAnException()
|
||||||
{
|
{
|
||||||
throw new \Exception('This is an exception');
|
throw new \Exception('This is an exception');
|
||||||
|
@@ -33,6 +33,19 @@ class LoggerTest extends \PHPUnit_Framework_TestCase
|
|||||||
$this->assertEquals('ERROR', Logger::getLevelName(Logger::ERROR));
|
$this->assertEquals('ERROR', Logger::getLevelName(Logger::ERROR));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers Monolog\Logger::withName
|
||||||
|
*/
|
||||||
|
public function testWithName()
|
||||||
|
{
|
||||||
|
$first = new Logger('first', array($handler = new TestHandler()));
|
||||||
|
$second = $first->withName('second');
|
||||||
|
|
||||||
|
$this->assertSame('first', $first->getName());
|
||||||
|
$this->assertSame('second', $second->getName());
|
||||||
|
$this->assertSame($handler, $second->popHandler());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @covers Monolog\Logger::toMonologLevel
|
* @covers Monolog\Logger::toMonologLevel
|
||||||
*/
|
*/
|
||||||
|
Reference in New Issue
Block a user