Просмотр исходного кода

Merge remote-tracking branch 'adlawson/dynamodb-handler'

Conflicts:
	README.mdown
	composer.json
Jordi Boggiano 12 лет назад
Родитель
Сommit
3987f88f2c

+ 2 - 0
README.mdown

@@ -149,6 +149,7 @@ Handlers
 - _CouchDBHandler_: Logs records to a CouchDB server.
 - _DoctrineCouchDBHandler_: Logs records to a CouchDB server via the Doctrine CouchDB ODM.
 - _ElasticSearchHandler_: Logs records to an Elastic Search server.
+- _DynamoDbHandler_: Logs records to a DynamoDB table with the [AWS SDK](https://github.com/aws/aws-sdk-php).
 
 ### Wrappers / Special Handlers
 
@@ -177,6 +178,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.
+- _ScalarFormatter_: Used to format log records into an associative array of scalar values.
 - _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.

+ 4 - 2
composer.json

@@ -21,7 +21,8 @@
         "mlehner/gelf-php": "1.0.*",
         "raven/raven": "0.5.*",
         "ruflin/elastica": "0.90.*",
-        "doctrine/couchdb": "dev-master"
+        "doctrine/couchdb": "dev-master",
+        "aws/aws-sdk-php": "~2.4.8"
     },
     "suggest": {
         "mlehner/gelf-php": "Allow sending log messages to a GrayLog2 server",
@@ -29,7 +30,8 @@
         "doctrine/couchdb": "Allow sending log messages to a CouchDB server",
         "ruflin/elastica": "Allow sending log messages to an Elastic Search server",
         "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)",
-        "ext-mongo": "Allow sending log messages to a MongoDB server"
+        "ext-mongo": "Allow sending log messages to a MongoDB server",
+        "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB"
     },
     "autoload": {
         "psr-0": {"Monolog": "src/"}

+ 50 - 0
src/Monolog/Formatter/ScalarFormatter.php

@@ -0,0 +1,50 @@
+<?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 Monolog\Formatter\NormalizerFormatter;
+
+/**
+ * Formats data into an associative array of scalar values.
+ * Objects and arrays will be JSON encoded.
+ *
+ * @author Andrew Lawson <adlawson@gmail.com>
+ */
+class ScalarFormatter extends NormalizerFormatter
+{
+    /**
+     * {@inheritdoc}
+     */
+    public function format(array $record)
+    {
+        foreach ($record as $key => $value) {
+            $record[$key] = $this->normalizeValue($value);
+        }
+
+        return $record;
+    }
+
+    /**
+     * @param mixed $value
+     * @return mixed
+     */
+    protected function normalizeValue($value)
+    {
+        $normalized = $this->normalize($value);
+
+        if (is_array($normalized) || is_object($normalized)) {
+            return $this->toJson($normalized, true);
+        }
+
+        return $normalized;
+    }
+}

+ 85 - 0
src/Monolog/Handler/DynamoDbHandler.php

@@ -0,0 +1,85 @@
+<?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\Handler;
+
+use Aws\DynamoDb\DynamoDbClient;
+use Monolog\Formatter\ScalarFormatter;
+use Monolog\Handler\AbstractProcessingHandler;
+use Monolog\Logger;
+
+/**
+ * Amazon DynamoDB handler (http://aws.amazon.com/dynamodb/)
+ *
+ * @link https://github.com/aws/aws-sdk-php/
+ * @author Andrew Lawson <adlawson@gmail.com>
+ */
+class DynamoDbHandler extends AbstractProcessingHandler
+{
+    const DATE_FORMAT = 'Y-m-d\TH:i:s.uO';
+
+    /**
+     * @var DynamoDbClient
+     */
+    protected $client;
+
+    /**
+     * @var string
+     */
+    protected $table;
+
+    /**
+     * @param DynamoDbClient $client
+     * @param string $table
+     * @param integer $level
+     * @param boolean $bubble
+     */
+    public function __construct(DynamoDbClient $client, $table, $level = Logger::DEBUG, $bubble = true)
+    {
+        $this->client = $client;
+        $this->table = $table;
+
+        parent::__construct($level, $bubble);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function write(array $record)
+    {
+        $filtered = $this->filterEmptyFields($record['formatted']);
+        $formatted = $this->client->formatAttributes($filtered);
+
+        $this->client->putItem(array(
+            'TableName' => $this->table,
+            'Item' => $formatted
+        ));
+    }
+
+    /**
+     * @param array $record
+     * @return array
+     */
+    protected function filterEmptyFields(array $record)
+    {
+        return array_filter($record, function($value) {
+            return !empty($value) || false === $value || 0 === $value;
+        });
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function getDefaultFormatter()
+    {
+        return new ScalarFormatter(self::DATE_FORMAT);
+    }
+}

+ 97 - 0
tests/Monolog/Formatter/ScalarFormatterTest.php

@@ -0,0 +1,97 @@
+<?php
+namespace Monolog\Formatter;
+
+class ScalarFormatterTest extends \PHPUnit_Framework_TestCase
+{
+    public function setUp()
+    {
+        $this->formatter = new ScalarFormatter();
+    }
+
+    public function buildTrace(\Exception $e)
+    {
+        $data = array();
+        $trace = $e->getTrace();
+        array_shift($trace);
+        foreach ($trace as $frame) {
+            if (isset($frame['file'])) {
+                $data[] = $frame['file'].':'.$frame['line'];
+            } else {
+                $data[] = json_encode($frame);
+            }
+        }
+
+        return $data;
+    }
+
+    public function encodeJson($data)
+    {
+        if (version_compare(PHP_VERSION, '5.4.0', '>=')) {
+            return json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
+        }
+
+        return json_encode($data);
+    }
+
+    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-01 00:00:00',
+            'ban' => $this->encodeJson(array(
+                'class'   => get_class($exception),
+                'message' => $exception->getMessage(),
+                'file'    => $exception->getFile() . ':' . $exception->getLine(),
+                'trace'   => $this->buildTrace($exception)
+            ))
+        ), $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(
+                    'class'   => get_class($exception),
+                    'message' => $exception->getMessage(),
+                    'file'    => $exception->getFile() . ':' . $exception->getLine(),
+                    'trace'   => $this->buildTrace($exception)
+                )
+            ))
+        ), $formatted);
+    }
+}

+ 71 - 0
tests/Monolog/Handler/DynamoDbHandlerTest.php

@@ -0,0 +1,71 @@
+<?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\Handler;
+
+use Monolog\TestCase;
+
+class DynamoDbHandlerTest extends TestCase
+{
+    public function setUp()
+    {
+        if (!class_exists('Aws\DynamoDb\DynamoDbClient')) {
+            $this->markTestSkipped('aws/aws-sdk-php not installed');
+        }
+
+        $this->client = $this->getMockBuilder('Aws\DynamoDb\DynamoDbClient')->disableOriginalConstructor()->getMock();
+    }
+
+    public function testConstruct()
+    {
+        $this->assertInstanceOf('Monolog\Handler\DynamoDbHandler', new DynamoDbHandler($this->client, 'foo'));
+    }
+
+    public function testInterface()
+    {
+        $this->assertInstanceOf('Monolog\Handler\HandlerInterface', new DynamoDbHandler($this->client, 'foo'));
+    }
+
+    public function testGetFormatter()
+    {
+        $handler = new DynamoDbHandler($this->client, 'foo');
+        $this->assertInstanceOf('Monolog\Formatter\ScalarFormatter', $handler->getFormatter());
+    }
+
+    public function testHandle()
+    {
+        $record = $this->getRecord();
+        $formatter = $this->getMock('Monolog\Formatter\FormatterInterface');
+        $formatted = array('foo' => 1, 'bar' => 2);
+        $handler = new DynamoDbHandler($this->client, 'foo');
+        $handler->setFormatter($formatter);
+
+        $formatter
+             ->expects($this->once())
+             ->method('format')
+             ->with($record)
+             ->will($this->returnValue($formatted));
+        $this->client
+             ->expects($this->once())
+             ->method('formatAttributes')
+             ->with($this->isType('array'))
+             ->will($this->returnValue($formatted));
+        $this->client
+             ->expects($this->once())
+             ->method('__call')
+             ->with('putItem', array(array(
+                 'TableName' => 'foo',
+                 'Item' => $formatted
+             )));
+
+        $handler->handle($record);
+    }
+}