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