Permalink
Browse files

Merge pull request #309 from apancutt/master

Added support for Loggly batch uploads
  • Loading branch information...
2 parents 6b81ac8 + a46413b commit e8604a4eaa00e9f249bc3150e143f3d3ff91de0d @Seldaek committed Feb 25, 2014
@@ -20,6 +20,34 @@
*/
class JsonFormatter implements FormatterInterface
{
+
+ protected $batch_mode;
+
+ const BATCH_MODE_JSON = 1;
+ const BATCH_MODE_NEWLINES = 2;
+
+ /**
+ * @param int $batch_mode
+ */
+ public function __construct($batch_mode = self::BATCH_MODE_JSON)
+ {
+ $this->batch_mode = $batch_mode;
+ }
+
+ /**
+ * 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, alternive styles
+ * are available.
+ *
+ * @return int
+ */
+ public function getBatchMode()
+ {
+ return $this->batch_mode;
+ }
+
/**
* {@inheritdoc}
*/
@@ -33,6 +61,45 @@ public function format(array $record)
*/
public function formatBatch(array $records)
{
+ switch ($this->batch_mode) {
+
+ case static::BATCH_MODE_NEWLINES:
+ return $this->formatBatchNewlines($records);
+
+ case static::BATCH_MODE_JSON:
+ default:
+ return $this->formatBatchJson($records);
+
+ }
+ }
+
+ /**
+ * Return a JSON-encoded array of records.
+ *
+ * @param array $records
+ * @return string
+ */
+ protected function formatBatchJson(array $records)
+ {
return json_encode($records);
}
+
+ /**
+ * Use new lines to separate records instead of a
+ * JSON-encoded array.
+ *
+ * @param array $records
+ * @return string
+ */
+ protected function formatBatchNewlines(array $records)
+ {
+ $instance = $this;
+
+ array_walk($records, function(&$value, $key) use ($instance) {
+ $value = $instance->format($value);
+ });
+
+ return implode("\n", $records);
+ }
+
}
@@ -0,0 +1,48 @@
+<?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;
+
+/**
+ * Encodes message information into JSON in a format compatible with Loggly.
+ *
+ * @author Adam Pancutt <adam@pancutt.com>
+ */
+class LogglyFormatter extends JsonFormatter
+{
+
+ /**
+ * Overrides the default batch mode to new lines for compatibility with the
+ * Loggly bulk API.
+ *
+ * @param integer $batch_mode
+ */
+ public function __construct($batch_mode = self::BATCH_MODE_NEWLINES)
+ {
+ parent::__construct($batch_mode);
+ }
+
+ /**
+ * Appends the 'timestamp' parameter for indexing by Loggly.
+ *
+ * @see https://www.loggly.com/docs/automated-parsing/#json
+ * @see \Monolog\Formatter\JsonFormatter::format()
+ */
+ public function format(array $record)
+ {
+ if (isset($record["datetime"]) && ($record["datetime"] instanceof \DateTime)) {
+ $record["timestamp"] = $record["datetime"]->format("c");
+ // @todo unset the 'datetime' parameter, retained for BC
+ }
+ return parent::format($record);
+ }
+
+}
@@ -12,16 +12,19 @@
namespace Monolog\Handler;
use Monolog\Logger;
-use Monolog\Formatter\JsonFormatter;
+use Monolog\Formatter\LogglyFormatter;
/**
* Sends errors to Loggly.
*
* @author Przemek Sobstel <przemek@sobstel.org>
+ * @author Adam Pancutt <adam@pancutt.com>
*/
class LogglyHandler extends AbstractProcessingHandler
{
const HOST = 'logs-01.loggly.com';
+ const ENDPOINT_SINGLE = 'inputs';
+ const ENDPOINT_BATCH = 'bulk';
protected $token;
@@ -45,17 +48,38 @@ public function setTag($tag)
protected function write(array $record)
{
- $url = sprintf("http://%s/inputs/%s/", self::HOST, $this->token);
+ $this->send($record["formatted"], self::ENDPOINT_SINGLE);
+ }
+
+ public function handleBatch(array $records)
+ {
+ $level = $this->level;
+
+ $records = array_filter($records, function ($record) use ($level) {
+ return ($record['level'] >= $level);
+ });
+
+ if ($records) {
+ $this->send($this->getFormatter()->formatBatch($records), self::ENDPOINT_BATCH);
+ }
+ }
+
+ protected function send($data, $endpoint)
+ {
+ $url = sprintf("https://%s/%s/%s/", self::HOST, $endpoint, $this->token);
+
+ $headers = array('Content-Type: application/json');
+
if ($this->tag) {
- $url .= sprintf("tag/%s/", $this->tag);
+ $headers[] = "X-LOGGLY-TAG: {$this->tag}";
}
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, true);
- curl_setopt($ch, CURLOPT_POSTFIELDS, $record["formatted"]);
- curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json'));
+ curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
+ curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_exec($ch);
@@ -64,6 +88,6 @@ protected function write(array $record)
protected function getDefaultFormatter()
{
- return new JsonFormatter();
+ return new LogglyFormatter();
}
}
@@ -17,6 +17,18 @@
class JsonFormatterTest extends TestCase
{
/**
+ * @covers Monolog\Formatter\JsonFormatter::__construct
+ * @covers Monolog\Formatter\JsonFormatter::getBatchMode
+ */
+ public function testConstruct()
+ {
+ $formatter = new JsonFormatter();
+ $this->assertEquals(JsonFormatter::BATCH_MODE_JSON, $formatter->getBatchMode());
+ $formatter = new JsonFormatter(JsonFormatter::BATCH_MODE_NEWLINES);
+ $this->assertEquals(JsonFormatter::BATCH_MODE_NEWLINES, $formatter->getBatchMode());
+ }
+
+ /**
* @covers Monolog\Formatter\JsonFormatter::format
*/
public function testFormat()
@@ -28,6 +40,7 @@ public function testFormat()
/**
* @covers Monolog\Formatter\JsonFormatter::formatBatch
+ * @covers Monolog\Formatter\JsonFormatter::formatBatchJson
*/
public function testFormatBatch()
{
@@ -38,4 +51,22 @@ public function testFormatBatch()
);
$this->assertEquals(json_encode($records), $formatter->formatBatch($records));
}
+
+ /**
+ * @covers Monolog\Formatter\JsonFormatter::formatBatch
+ * @covers Monolog\Formatter\JsonFormatter::formatBatchNewlines
+ */
+ public function testFormatBatchNewlines()
+ {
+
+ $formatter = new JsonFormatter(JsonFormatter::BATCH_MODE_NEWLINES);
+ $records = $expected = array(
+ $this->getRecord(Logger::WARNING),
+ $this->getRecord(Logger::DEBUG),
+ );
+ array_walk($expected, function(&$value, $key) {
+ $value = json_encode($value);
+ });
+ $this->assertEquals(implode("\n", $expected), $formatter->formatBatch($records));
+ }
}
@@ -0,0 +1,41 @@
+<?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\Logger;
+use Monolog\TestCase;
+
+class LogglyFormatterTest extends TestCase
+{
+ /**
+ * @covers Monolog\Formatter\LogglyFormatter::__construct
+ */
+ public function testConstruct()
+ {
+ $formatter = new LogglyFormatter();
+ $this->assertEquals(LogglyFormatter::BATCH_MODE_NEWLINES, $formatter->getBatchMode());
+ $formatter = new LogglyFormatter(LogglyFormatter::BATCH_MODE_JSON);
+ $this->assertEquals(LogglyFormatter::BATCH_MODE_JSON, $formatter->getBatchMode());
+ }
+
+ /**
+ * @covers Monolog\Formatter\LogglyFormatter::format
+ */
+ public function testFormat()
+ {
+ $formatter = new LogglyFormatter();
+ $record = $this->getRecord();
+ $formatted_decoded = json_decode($formatter->format($record), true);
+ $this->assertArrayHasKey("timestamp", $formatted_decoded);
+ $this->assertEquals(new \DateTime($formatted_decoded["timestamp"]), $record["datetime"]);
+ }
+}

0 comments on commit e8604a4

Please sign in to comment.