diff --git a/README.mdown b/README.mdown index ccd3392d..a6954c20 100644 --- a/README.mdown +++ b/README.mdown @@ -181,6 +181,7 @@ Formatters - _ChromePHPFormatter_: Used to format log records into the ChromePHP format, only useful for the ChromePHPHandler. - _GelfFormatter_: Used to format log records into Gelf message instances, only useful for the GelfHandler. - _LogstashFormatter_: Used to format log records into [logstash](http://logstash.net/) event json, useful for any handler listed under inputs [here](http://logstash.net/docs/1.1.5/). +- _ScalarFormatter_: Used to format log records into an associative array of scalar values. Processors ---------- diff --git a/src/Monolog/Formatter/ScalarFormatter.php b/src/Monolog/Formatter/ScalarFormatter.php new file mode 100644 index 00000000..88be8e23 --- /dev/null +++ b/src/Monolog/Formatter/ScalarFormatter.php @@ -0,0 +1,125 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Formatter\FormatterInterface; + +/** + * Formats data into an associative array of scalar values. + * Objects and arrays will be JSON encoded. + * + * @author Andrew Lawson + */ +class ScalarFormatter implements FormatterInterface +{ + /** + * @var string + */ + protected $dateFormat; + + /** + * @param string $dateFormat + */ + public function __construct($dateFormat = \DateTime::ISO8601) + { + $this->dateFormat = $dateFormat; + } + + /** + * {@inheritdoc} + */ + public function format(array $record) + { + $formatted = array(); + $record = $this->exposeContext($record); + + foreach ($record as $key => $value) { + $formatted[$key] = $this->normalizeValue($value); + } + + return $formatted; + } + + /** + * {@inheritdoc} + */ + public function formatBatch(array $records) + { + $formatted = array(); + + foreach ($records as $record) { + $formatted[] = $this->format($record); + } + + return $formatted; + } + + /** + * @param mixed $data + * @return string + */ + protected function encodeData($data) + { + return json_encode($data, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT); + } + + /** + * @param array $record + * @return array + */ + protected function exposeContext(array $record) + { + if (isset($record['context'])) { + $context = $record['context']; + + if (isset($context['exception'])) { + $record['context']['exception'] = $this->normalizeException($context['exception']); + } + } + + return $record; + } + + /** + * @param Exception $e + * @return string + */ + protected function normalizeException(\Exception $e) + { + return array( + 'message' => $e->getMessage(), + 'code' => $e->getCode(), + 'class' => get_class($e), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'trace' => $e->getTraceAsString(), + 'debug' => $e->getTrace() + ); + } + + /** + * @param mixed $value + * @return mixed + */ + protected function normalizeValue($value) + { + if ($value instanceof \DateTime) { + return $value->format($this->dateFormat); + } elseif ($value instanceof \Exception) { + return $this->encodeData($this->normalizeException($value)); + } elseif (is_array($value) || is_object($value)) { + return $this->encodeData($value); + } + + return $value; + } +} diff --git a/tests/Monolog/Formatter/ScalarFormatterTest.php b/tests/Monolog/Formatter/ScalarFormatterTest.php new file mode 100644 index 00000000..7b8a5274 --- /dev/null +++ b/tests/Monolog/Formatter/ScalarFormatterTest.php @@ -0,0 +1,83 @@ +formatter = new ScalarFormatter(); + } + + public function encodeJson($data) + { + return json_encode($data, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT); + } + + public function testFormat() + { + $exception = new \Exception('foo'); + $formatted = $this->formatter->format(array( + 'foo' => 'string', + 'bar' => 1, + 'baz' => false, + 'bam' => array(1,2,3), + 'bat' => array('foo' => 'bar'), + 'bap' => \DateTime::createFromFormat(\DateTime::ISO8601, '1970-01-01T00:00:00+0000'), + 'ban' => $exception + )); + + $this->assertSame(array( + 'foo' => 'string', + 'bar' => 1, + 'baz' => false, + 'bam' => $this->encodeJson(array(1,2,3)), + 'bat' => $this->encodeJson(array('foo' => 'bar')), + 'bap' => '1970-01-01T00:00:00+0000', + 'ban' => $this->encodeJson(array( + 'message' => $exception->getMessage(), + 'code' => $exception->getCode(), + 'class' => get_class($exception), + 'file' => $exception->getFile(), + 'line' => $exception->getLine(), + 'trace' => $exception->getTraceAsString(), + 'debug' => $exception->getTrace() + )) + ), $formatted); + } + + public function testFormatWithErrorContext() + { + $context = array('file' => 'foo', 'line' => 1); + $formatted = $this->formatter->format(array( + 'context' => $context + )); + + $this->assertSame(array( + 'context' => $this->encodeJson($context) + ), $formatted); + } + + public function testFormatWithExceptionContext() + { + $exception = new \Exception('foo'); + $formatted = $this->formatter->format(array( + 'context' => array( + 'exception' => $exception + ) + )); + + $this->assertSame(array( + 'context' => $this->encodeJson(array( + 'exception' => array( + 'message' => $exception->getMessage(), + 'code' => $exception->getCode(), + 'class' => get_class($exception), + 'file' => $exception->getFile(), + 'line' => $exception->getLine(), + 'trace' => $exception->getTraceAsString(), + 'debug' => $exception->getTrace() + ) + )) + ), $formatted); + } +}