From 0bd4d930065107ba22ba9fa24b6607e45c9ef903 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Sun, 22 Apr 2012 12:34:41 +0200 Subject: [PATCH] Split off line formatter in normalizer + line formatters --- CHANGELOG.mdown | 1 + README.mdown | 1 + src/Monolog/Formatter/LineFormatter.php | 45 +++----- src/Monolog/Formatter/NormalizerFormatter.php | 87 ++++++++++++++ tests/Monolog/Formatter/LineFormatterTest.php | 6 +- .../Formatter/NormalizerFormatterTest.php | 107 ++++++++++++++++++ 6 files changed, 217 insertions(+), 30 deletions(-) create mode 100644 src/Monolog/Formatter/NormalizerFormatter.php create mode 100644 tests/Monolog/Formatter/NormalizerFormatterTest.php diff --git a/CHANGELOG.mdown b/CHANGELOG.mdown index 8362daa9..94c07915 100644 --- a/CHANGELOG.mdown +++ b/CHANGELOG.mdown @@ -5,6 +5,7 @@ * Added Monolog\Logger::isHandling() to check if a handler will handle the given log level * Added ChromePHPHandler + * Added NormalizerFormatter * Added possibility to show microseconds in logs * Added `server` and `referer` to WebProcessor output diff --git a/README.mdown b/README.mdown index 516c6513..843c444d 100644 --- a/README.mdown +++ b/README.mdown @@ -59,6 +59,7 @@ Formatters ---------- - _LineFormatter_: Formats a log record into a one-line string. +- _NormalizerFormatter_: Normalizes objects/resources down to strings so a record can easily be serialized/encoded. - _JsonFormatter_: Encodes a log record into json. - _WildfireFormatter_: Used to format log records into the Wildfire/FirePHP protocol, only useful for the FirePHPHandler. - _ChromePHPFormatter_: Used to format log records into the ChromePHP format, only useful for the ChromePHPHandler. diff --git a/src/Monolog/Formatter/LineFormatter.php b/src/Monolog/Formatter/LineFormatter.php index 53100b79..61d3b8c0 100644 --- a/src/Monolog/Formatter/LineFormatter.php +++ b/src/Monolog/Formatter/LineFormatter.php @@ -21,13 +21,11 @@ use Monolog\Logger; * @author Jordi Boggiano * @author Christophe Coevoet */ -class LineFormatter implements FormatterInterface +class LineFormatter extends NormalizerFormatter { const SIMPLE_FORMAT = "[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n"; - const SIMPLE_DATE = "Y-m-d H:i:s"; protected $format; - protected $dateFormat; /** * @param string $format The format of the message @@ -36,7 +34,7 @@ class LineFormatter implements FormatterInterface public function __construct($format = null, $dateFormat = null) { $this->format = $format ?: static::SIMPLE_FORMAT; - $this->dateFormat = $dateFormat ?: static::SIMPLE_DATE; + parent::__construct($dateFormat); } /** @@ -44,8 +42,7 @@ class LineFormatter implements FormatterInterface */ public function format(array $record) { - $vars = $record; - $vars['datetime'] = $vars['datetime']->format($this->dateFormat); + $vars = parent::format($record); $output = $this->format; foreach ($vars['extra'] as $var => $val) { @@ -71,35 +68,25 @@ class LineFormatter implements FormatterInterface return $message; } + protected function normalize($data) + { + if (is_bool($data) || is_null($data)) { + return var_export($data, true); + } + + return parent::normalize($data); + } + protected function convertToString($data) { if (null === $data || is_scalar($data)) { return (string) $data; } + if (version_compare(PHP_VERSION, '5.4.0', '>=')) { + return json_encode($this->normalize($data), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); + } + return stripslashes(json_encode($this->normalize($data))); } - - protected function normalize($data) - { - if (null === $data || is_scalar($data)) { - return $data; - } - - if (is_array($data) || $data instanceof \Traversable) { - $normalized = array(); - - foreach ($data as $key => $value) { - $normalized[$key] = $this->normalize($value); - } - - return $normalized; - } - - if (is_resource($data)) { - return '[resource]'; - } - - return sprintf("[object] (%s: %s)", get_class($data), json_encode($data)); - } } diff --git a/src/Monolog/Formatter/NormalizerFormatter.php b/src/Monolog/Formatter/NormalizerFormatter.php new file mode 100644 index 00000000..673302d8 --- /dev/null +++ b/src/Monolog/Formatter/NormalizerFormatter.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Logger; + +/** + * Normalizes incoming records to remove objects/resources so it's easier to dump to various targets + * + * @author Jordi Boggiano + */ +class NormalizerFormatter implements FormatterInterface +{ + const SIMPLE_DATE = "Y-m-d H:i:s"; + + protected $dateFormat; + + /** + * @param string $dateFormat The format of the timestamp: one supported by DateTime::format + */ + public function __construct($dateFormat = null) + { + $this->dateFormat = $dateFormat ?: static::SIMPLE_DATE; + } + + /** + * {@inheritdoc} + */ + public function format(array $record) + { + return $this->normalize($record); + } + + /** + * {@inheritdoc} + */ + public function formatBatch(array $records) + { + foreach ($records as $key => $record) { + $records[$key] = $this->format($record); + } + + return $records; + } + + protected function normalize($data) + { + if (null === $data || is_scalar($data)) { + return $data; + } + + if (is_array($data) || $data instanceof \Traversable) { + $normalized = array(); + + foreach ($data as $key => $value) { + $normalized[$key] = $this->normalize($value); + } + + return $normalized; + } + + if ($data instanceof \DateTime) { + return $data->format($this->dateFormat); + } + + if (is_resource($data)) { + return '[resource]'; + } + + if (version_compare(PHP_VERSION, '5.4.0', '>=')) { + $encoded = json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); + } else { + $encoded = json_encode($data); + } + + return sprintf("[object] (%s: %s)", get_class($data), $encoded); + } +} diff --git a/tests/Monolog/Formatter/LineFormatterTest.php b/tests/Monolog/Formatter/LineFormatterTest.php index f2adc199..0c07dc22 100644 --- a/tests/Monolog/Formatter/LineFormatterTest.php +++ b/tests/Monolog/Formatter/LineFormatterTest.php @@ -88,7 +88,11 @@ class LineFormatterTest extends \PHPUnit_Framework_TestCase 'extra' => array('foo' => new TestFoo, 'bar' => new TestBar, 'baz' => array(), 'res' => fopen('php://memory', 'rb')), 'message' => 'foobar', )); - $this->assertEquals('['.date('Y-m-d').'] meh.ERROR: foobar [] {"foo":"[object] (Monolog\\Formatter\\TestFoo: {"foo":"foo"})","bar":"[object] (Monolog\\Formatter\\TestBar: {})","baz":[],"res":"[resource]"}'."\n", $message); + if (version_compare(PHP_VERSION, '5.4.0', '>=')) { + $this->assertEquals('['.date('Y-m-d').'] meh.ERROR: foobar [] {"foo":"[object] (Monolog\\\\Formatter\\\\TestFoo: {\\"foo\\":\\"foo\\"})","bar":"[object] (Monolog\\\\Formatter\\\\TestBar: {})","baz":[],"res":"[resource]"}'."\n", $message); + } else { + $this->assertEquals('['.date('Y-m-d').'] meh.ERROR: foobar [] {"foo":"[object] (Monolog\\Formatter\\TestFoo: {"foo":"foo"})","bar":"[object] (Monolog\\Formatter\\TestBar: {})","baz":[],"res":"[resource]"}'."\n", $message); + } } public function testBatchFormat() diff --git a/tests/Monolog/Formatter/NormalizerFormatterTest.php b/tests/Monolog/Formatter/NormalizerFormatterTest.php new file mode 100644 index 00000000..8a5e6d6d --- /dev/null +++ b/tests/Monolog/Formatter/NormalizerFormatterTest.php @@ -0,0 +1,107 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Logger; + +/** + * @covers Monolog\Formatter\NormalizerFormatter + */ +class NormalizerFormatterTest extends \PHPUnit_Framework_TestCase +{ + public function testFormat() + { + $formatter = new NormalizerFormatter('Y-m-d'); + $formatted = $formatter->format(array( + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'message' => 'foo', + 'datetime' => new \DateTime, + 'extra' => array('foo' => new TestFooNorm, 'bar' => new TestBarNorm, 'baz' => array(), 'res' => fopen('php://memory', 'rb')), + 'context' => array( + 'foo' => 'bar', + 'baz' => 'qux', + ) + )); + + $this->assertEquals(array( + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'message' => 'foo', + 'datetime' => date('Y-m-d'), + 'extra' => array( + 'foo' => '[object] (Monolog\\Formatter\\TestFooNorm: {"foo":"foo"})', + 'bar' => '[object] (Monolog\\Formatter\\TestBarNorm: {})', + 'baz' => array(), + 'res' => '[resource]', + ), + 'context' => array( + 'foo' => 'bar', + 'baz' => 'qux', + ) + ), $formatted); + } + + public function testBatchFormat() + { + $formatter = new NormalizerFormatter('Y-m-d'); + $formatted = $formatter->formatBatch(array( + array( + 'level_name' => 'CRITICAL', + 'channel' => 'test', + 'message' => 'bar', + 'context' => array(), + 'datetime' => new \DateTime, + 'extra' => array(), + ), + array( + 'level_name' => 'WARNING', + 'channel' => 'log', + 'message' => 'foo', + 'context' => array(), + 'datetime' => new \DateTime, + 'extra' => array(), + ), + )); + $this->assertEquals(array( + array( + 'level_name' => 'CRITICAL', + 'channel' => 'test', + 'message' => 'bar', + 'context' => array(), + 'datetime' => date('Y-m-d'), + 'extra' => array(), + ), + array( + 'level_name' => 'WARNING', + 'channel' => 'log', + 'message' => 'foo', + 'context' => array(), + 'datetime' => date('Y-m-d'), + 'extra' => array(), + ), + ), $formatted); + } +} + +class TestFooNorm +{ + public $foo = 'foo'; +} + +class TestBarNorm +{ + public function __toString() + { + return 'bar'; + } +}