1
0
mirror of https://github.com/Seldaek/monolog.git synced 2025-08-11 15:44:34 +02:00

Merge pull request #309 from apancutt/master

Added support for Loggly batch uploads
This commit is contained in:
Jordi Boggiano
2014-02-25 23:34:04 +01:00
5 changed files with 217 additions and 6 deletions

View File

@@ -20,6 +20,34 @@ namespace Monolog\Formatter;
*/ */
class JsonFormatter implements FormatterInterface 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} * {@inheritdoc}
*/ */
@@ -32,7 +60,46 @@ class JsonFormatter implements FormatterInterface
* {@inheritdoc} * {@inheritdoc}
*/ */
public function formatBatch(array $records) 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); 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);
}
} }

View File

@@ -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);
}
}

View File

@@ -12,16 +12,19 @@
namespace Monolog\Handler; namespace Monolog\Handler;
use Monolog\Logger; use Monolog\Logger;
use Monolog\Formatter\JsonFormatter; use Monolog\Formatter\LogglyFormatter;
/** /**
* Sends errors to Loggly. * Sends errors to Loggly.
* *
* @author Przemek Sobstel <przemek@sobstel.org> * @author Przemek Sobstel <przemek@sobstel.org>
* @author Adam Pancutt <adam@pancutt.com>
*/ */
class LogglyHandler extends AbstractProcessingHandler class LogglyHandler extends AbstractProcessingHandler
{ {
const HOST = 'logs-01.loggly.com'; const HOST = 'logs-01.loggly.com';
const ENDPOINT_SINGLE = 'inputs';
const ENDPOINT_BATCH = 'bulk';
protected $token; protected $token;
@@ -45,17 +48,38 @@ class LogglyHandler extends AbstractProcessingHandler
protected function write(array $record) 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) { if ($this->tag) {
$url .= sprintf("tag/%s/", $this->tag); $headers[] = "X-LOGGLY-TAG: {$this->tag}";
} }
$ch = curl_init(); $ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $record["formatted"]); curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json')); curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_exec($ch); curl_exec($ch);
@@ -64,6 +88,6 @@ class LogglyHandler extends AbstractProcessingHandler
protected function getDefaultFormatter() protected function getDefaultFormatter()
{ {
return new JsonFormatter(); return new LogglyFormatter();
} }
} }

View File

@@ -16,6 +16,18 @@ use Monolog\TestCase;
class JsonFormatterTest extends TestCase 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 * @covers Monolog\Formatter\JsonFormatter::format
*/ */
@@ -28,6 +40,7 @@ class JsonFormatterTest extends TestCase
/** /**
* @covers Monolog\Formatter\JsonFormatter::formatBatch * @covers Monolog\Formatter\JsonFormatter::formatBatch
* @covers Monolog\Formatter\JsonFormatter::formatBatchJson
*/ */
public function testFormatBatch() public function testFormatBatch()
{ {
@@ -38,4 +51,22 @@ class JsonFormatterTest extends TestCase
); );
$this->assertEquals(json_encode($records), $formatter->formatBatch($records)); $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));
}
} }

View File

@@ -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"]);
}
}