diff --git a/src/Formatter/Base.php b/src/Formatter/Base.php new file mode 100644 index 00000000..162b95ce --- /dev/null +++ b/src/Formatter/Base.php @@ -0,0 +1,114 @@ +dateTimeFormat = $dateTimeFormat; + } + } + + /** + * Formats data to be written by the writer. + * + * @param array $event event data + * @return array + */ + public function format($event) + { + foreach ($event as $key => $value) { + // Keep extra as an array + if ('extra' === $key) { + $event[$key] = self::format($value); + } else { + $event[$key] = $this->normalize($value); + } + } + + return $event; + } + + /** + * Normalize all non-scalar data types (except null) in a string value + * + * @param mixed $value + * @return mixed + */ + protected function normalize($value) + { + if (is_scalar($value) || null === $value) { + return $value; + } + + if ($value instanceof DateTime) { + $value = $value->format($this->getDateTimeFormat()); + } elseif (is_array($value) || $value instanceof Traversable) { + if ($value instanceof Traversable) { + $value = iterator_to_array($value); + } + foreach ($value as $key => $subvalue) { + $value[$key] = $this->normalize($subvalue); + } + $value = json_encode($value); + } elseif (is_object($value) && !method_exists($value,'__toString')) { + $value = sprintf('object(%s) %s', get_class($value), json_encode($value)); + } elseif (is_resource($value)) { + $value = sprintf('resource(%s)', get_resource_type($value)); + } elseif (!is_object($value)) { + $value = gettype($value); + } + + return (string) $value; + } + + /** + * {@inheritDoc} + */ + public function getDateTimeFormat() + { + return $this->dateTimeFormat; + } + + /** + * {@inheritDoc} + */ + public function setDateTimeFormat($dateTimeFormat) + { + $this->dateTimeFormat = (string) $dateTimeFormat; + return $this; + } +} \ No newline at end of file diff --git a/src/Formatter/Simple.php b/src/Formatter/Simple.php index 761ed799..09e92768 100644 --- a/src/Formatter/Simple.php +++ b/src/Formatter/Simple.php @@ -18,9 +18,9 @@ * @package Zend_Log * @subpackage Formatter */ -class Simple implements FormatterInterface +class Simple extends Base { - const DEFAULT_FORMAT = '%timestamp% %priorityName% (%priority%): %message% %info%'; + const DEFAULT_FORMAT = '%timestamp% %priorityName% (%priority%): %message% %extra%'; /** * Format specifier for log messages @@ -29,14 +29,6 @@ class Simple implements FormatterInterface */ protected $format; - /** - * Format specifier for DateTime objects in event data (default: ISO 8601) - * - * @see http://php.net/manual/en/function.date.php - * @var string - */ - protected $dateTimeFormat = self::DEFAULT_DATETIME_FORMAT; - /** * Class constructor * @@ -53,9 +45,7 @@ public function __construct($format = null, $dateTimeFormat = null) $this->format = isset($format) ? $format : static::DEFAULT_FORMAT; - if (isset($dateTimeFormat)) { - $this->dateTimeFormat = $dateTimeFormat; - } + parent::__construct($dateTimeFormat); } /** @@ -68,41 +58,22 @@ public function format($event) { $output = $this->format; - if (!isset($event['info'])) { - $event['info'] = ''; - } - - if (isset($event['timestamp']) && $event['timestamp'] instanceof DateTime) { - $event['timestamp'] = $event['timestamp']->format($this->getDateTimeFormat()); - } - + $event = parent::format($event); foreach ($event as $name => $value) { - if ((is_object($value) && !method_exists($value,'__toString')) - || is_array($value) - ) { - $value = gettype($value); + if ('extra' == $name && count($value)) { + $value = $this->normalize($value); + } elseif ('extra' == $name) { + // Don't print an empty array + $value = ''; } - $output = str_replace("%$name%", $value, $output); } + if (isset($event['extra']) && empty($event['extra']) + && false !== strpos($this->format, '%extra%') + ) { + $output = rtrim($output, ' '); + } return $output; } - - /** - * {@inheritDoc} - */ - public function getDateTimeFormat() - { - return $this->dateTimeFormat; - } - - /** - * {@inheritDoc} - */ - public function setDateTimeFormat($dateTimeFormat) - { - $this->dateTimeFormat = (string) $dateTimeFormat; - return $this; - } } diff --git a/test/Formatter/BaseTest.php b/test/Formatter/BaseTest.php new file mode 100644 index 00000000..d5d5f8a5 --- /dev/null +++ b/test/Formatter/BaseTest.php @@ -0,0 +1,113 @@ +assertEquals(BaseFormatter::DEFAULT_DATETIME_FORMAT, $formatter->getDateTimeFormat()); + } + + /** + * @dataProvider provideDateTimeFormats + */ + public function testAllowsSpecifyingDateTimeFormatAsConstructorArgument($dateTimeFormat) + { + $formatter = new BaseFormatter($dateTimeFormat); + + $this->assertEquals($dateTimeFormat, $formatter->getDateTimeFormat()); + } + + /** + * @return array + */ + public function provideDateTimeFormats() + { + return array( + array('r'), + array('U'), + array(DateTime::RSS), + ); + } + + /** + * @dataProvider provideDateTimeFormats + */ + public function testSetDateTimeFormat($dateTimeFormat) + { + $formatter = new BaseFormatter(); + $formatter->setDateTimeFormat($dateTimeFormat); + + $this->assertEquals($dateTimeFormat, $formatter->getDateTimeFormat()); + } + + public function testFormatAllTypes() + { + $datetime = new DateTime(); + $object = new stdClass(); + $object->foo = 'bar'; + $formatter = new BaseFormatter(); + + $event = array( + 'timestamp' => $datetime, + 'priority' => 1, + 'message' => 'tottakai', + 'extra' => array( + 'float' => 0.2, + 'boolean' => false, + 'array_empty' => array(), + 'array' => range(0, 4), + 'traversable_empty' => new EmptyIterator(), + 'traversable' => new ArrayIterator(array('id', 42)), + 'null' => null, + 'object_empty' => new stdClass(), + 'object' => $object, + 'string object' => new StringObject(), + 'resource' => fopen('php://stdout', 'w'), + ), + ); + $outputExpected = array( + 'timestamp' => $datetime->format($formatter->getDateTimeFormat()), + 'priority' => 1, + 'message' => 'tottakai', + 'extra' => array( + 'boolean' => false, + 'float' => 0.2, + 'array_empty' => '[]', + 'array' => '[0,1,2,3,4]', + 'traversable_empty' => '[]', + 'traversable' => '["id",42]', + 'null' => null, + 'object_empty' => 'object(stdClass) {}', + 'object' => 'object(stdClass) {"foo":"bar"}', + 'string object' => 'Hello World', + 'resource' => 'resource(stream)', + ), + ); + + $this->assertEquals($outputExpected, $formatter->format($event)); + } +} diff --git a/test/Formatter/SimpleTest.php b/test/Formatter/SimpleTest.php index 8db4f10a..18fe0f62 100644 --- a/test/Formatter/SimpleTest.php +++ b/test/Formatter/SimpleTest.php @@ -13,6 +13,8 @@ use DateTime; use ZendTest\Log\TestAsset\StringObject; use Zend\Log\Formatter\Simple; +use stdClass; +use RuntimeException; /** * @category Zend @@ -30,61 +32,19 @@ public function testConstructorThrowsOnBadFormatString() public function testDefaultFormat() { - $date = new DateTime(); - $fields = array('timestamp' => $date, - 'message' => 'foo', - 'priority' => 42, - 'priorityName' => 'bar'); - - $f = new Simple(); - $line = $f->format($fields); - - $this->assertContains($date->format('c'), $line, 'Default date format is ISO 8601'); - $this->assertContains($fields['message'], $line); - $this->assertContains($fields['priorityName'], $line); - $this->assertContains((string)$fields['priority'], $line); - } - - public function testComplexValues() - { - $fields = array('timestamp' => new DateTime(), - 'priority' => 42, - 'priorityName' => 'bar'); - - $f = new Simple(); - - $fields['message'] = 'Foo'; - $line = $f->format($fields); - $this->assertContains($fields['message'], $line); - - $fields['message'] = 10; - $line = $f->format($fields); - $this->assertContains((string)$fields['message'], $line); - - $fields['message'] = 10.5; - $line = $f->format($fields); - $this->assertContains((string)$fields['message'], $line); - - $fields['message'] = true; - $line = $f->format($fields); - $this->assertContains('1', $line); - - $fields['message'] = fopen('php://stdout', 'w'); - $line = $f->format($fields); - $this->assertContains('Resource id ', $line); - fclose($fields['message']); - - $fields['message'] = range(1,10); - $line = $f->format($fields); - $this->assertContains('array', $line); + $date = new DateTime('2012-08-28T18:15:00Z'); + $fields = array( + 'timestamp' => $date, + 'message' => 'foo', + 'priority' => 42, + 'priorityName' => 'bar', + 'extra' => array() + ); - $fields['message'] = new StringObject(); - $line = $f->format($fields); - $this->assertContains($fields['message']->__toString(), $line); + $outputExpected = '2012-08-28T18:15:00+00:00 bar (42): foo'; + $formatter = new Simple(); - $fields['message'] = new \stdClass(); - $line = $f->format($fields); - $this->assertContains('object', $line); + $this->assertEquals($outputExpected, $formatter->format($fields)); } /** @@ -127,13 +87,13 @@ public function provideDateTimeFormats() public function testDefaultFormatShouldDisplayExtraInformations() { $message = 'custom message'; - $exception = new \RuntimeException($message); + $exception = new RuntimeException($message); $event = array( 'timestamp' => new DateTime(), 'message' => 'Application error', 'priority' => 2, 'priorityName' => 'CRIT', - 'info' => $exception, + 'extra' => array($exception), ); $formatter = new Simple(); @@ -141,4 +101,11 @@ public function testDefaultFormatShouldDisplayExtraInformations() $this->assertContains($message, $output); } + + public function testAllowsSpecifyingFormatAsConstructorArgument() + { + $format = '[%timestamp%] %message%'; + $formatter = new Simple($format); + $this->assertEquals($format, $formatter->format(array())); + } }