mirror of
https://github.com/Seldaek/monolog.git
synced 2025-08-05 20:57:36 +02:00
Add ability to include exception's stack traces in Monolog\Formatter\JsonFormatter
| Q | A | ------------- | --- | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | | License | MIT | Doc PR |
This commit is contained in:
@@ -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,71 @@ 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 $e
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function normalizeException(Exception $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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user