215 lines
5.5 KiB
PHP
215 lines
5.5 KiB
PHP
|
<?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\Formatter;
|
||
|
|
||
|
use Exception;
|
||
|
use Monolog\Utils;
|
||
|
use Throwable;
|
||
|
|
||
|
/**
|
||
|
* Encodes whatever record data is passed to it as json
|
||
|
*
|
||
|
* This can be useful to log to databases or remote APIs
|
||
|
*
|
||
|
* @author Jordi Boggiano <j.boggiano@seld.be>
|
||
|
*/
|
||
|
class JsonFormatter extends NormalizerFormatter
|
||
|
{
|
||
|
const BATCH_MODE_JSON = 1;
|
||
|
const BATCH_MODE_NEWLINES = 2;
|
||
|
|
||
|
protected $batchMode;
|
||
|
protected $appendNewline;
|
||
|
|
||
|
/**
|
||
|
* @var bool
|
||
|
*/
|
||
|
protected $includeStacktraces = false;
|
||
|
|
||
|
/**
|
||
|
* @param int $batchMode
|
||
|
* @param bool $appendNewline
|
||
|
*/
|
||
|
public function __construct($batchMode = self::BATCH_MODE_JSON, $appendNewline = true)
|
||
|
{
|
||
|
$this->batchMode = $batchMode;
|
||
|
$this->appendNewline = $appendNewline;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* The batch mode option configures the formatting style for
|
||
|
* multiple records. By default, multiple records will be
|
||
|
* formatted as a JSON-encoded array. However, for
|
||
|
* compatibility with some API endpoints, alternative styles
|
||
|
* are available.
|
||
|
*
|
||
|
* @return int
|
||
|
*/
|
||
|
public function getBatchMode()
|
||
|
{
|
||
|
return $this->batchMode;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* True if newlines are appended to every formatted record
|
||
|
*
|
||
|
* @return bool
|
||
|
*/
|
||
|
public function isAppendingNewlines()
|
||
|
{
|
||
|
return $this->appendNewline;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* {@inheritdoc}
|
||
|
*/
|
||
|
public function format(array $record)
|
||
|
{
|
||
|
return $this->toJson($this->normalize($record), true) . ($this->appendNewline ? "\n" : '');
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* {@inheritdoc}
|
||
|
*/
|
||
|
public function formatBatch(array $records)
|
||
|
{
|
||
|
switch ($this->batchMode) {
|
||
|
case static::BATCH_MODE_NEWLINES:
|
||
|
return $this->formatBatchNewlines($records);
|
||
|
|
||
|
case static::BATCH_MODE_JSON:
|
||
|
default:
|
||
|
return $this->formatBatchJson($records);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param bool $include
|
||
|
*/
|
||
|
public function includeStacktraces($include = true)
|
||
|
{
|
||
|
$this->includeStacktraces = $include;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Return a JSON-encoded array of records.
|
||
|
*
|
||
|
* @param array $records
|
||
|
* @return string
|
||
|
*/
|
||
|
protected function formatBatchJson(array $records)
|
||
|
{
|
||
|
return $this->toJson($this->normalize($records), true);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Use new lines to separate records instead of a
|
||
|
* JSON-encoded array.
|
||
|
*
|
||
|
* @param array $records
|
||
|
* @return string
|
||
|
*/
|
||
|
protected function formatBatchNewlines(array $records)
|
||
|
{
|
||
|
$instance = $this;
|
||
|
|
||
|
$oldNewline = $this->appendNewline;
|
||
|
$this->appendNewline = false;
|
||
|
array_walk($records, function (&$value, $key) use ($instance) {
|
||
|
$value = $instance->format($value);
|
||
|
});
|
||
|
$this->appendNewline = $oldNewline;
|
||
|
|
||
|
return implode("\n", $records);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Normalizes given $data.
|
||
|
*
|
||
|
* @param mixed $data
|
||
|
*
|
||
|
* @return mixed
|
||
|
*/
|
||
|
protected function normalize($data, $depth = 0)
|
||
|
{
|
||
|
if ($depth > 9) {
|
||
|
return 'Over 9 levels deep, aborting normalization';
|
||
|
}
|
||
|
|
||
|
if (is_array($data) || $data instanceof \Traversable) {
|
||
|
$normalized = array();
|
||
|
|
||
|
$count = 1;
|
||
|
foreach ($data as $key => $value) {
|
||
|
if ($count++ > 1000) {
|
||
|
$normalized['...'] = 'Over 1000 items ('.count($data).' total), aborting normalization';
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
$normalized[$key] = $this->normalize($value, $depth+1);
|
||
|
}
|
||
|
|
||
|
return $normalized;
|
||
|
}
|
||
|
|
||
|
if ($data instanceof Exception || $data instanceof Throwable) {
|
||
|
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).' / '.Utils::getClass($e));
|
||
|
}
|
||
|
|
||
|
$data = array(
|
||
|
'class' => Utils::getClass($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'];
|
||
|
} elseif (isset($frame['function']) && $frame['function'] === '{closure}') {
|
||
|
// We should again normalize the frames, because it might contain invalid items
|
||
|
$data['trace'][] = $frame['function'];
|
||
|
} 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;
|
||
|
}
|
||
|
}
|