* * 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 */ 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; } }