Skip to content
This repository
Browse code

Merge remote-tracking branch 'timmow/logstash-formatter'

  • Loading branch information...
commit 6588c1633ee890b4d3514054a348f62260b206ca 2 parents 546225b + 798a039
Jordi Boggiano authored
1  README.mdown
Source Rendered
@@ -147,6 +147,7 @@ Formatters
147 147 - _WildfireFormatter_: Used to format log records into the Wildfire/FirePHP protocol, only useful for the FirePHPHandler.
148 148 - _ChromePHPFormatter_: Used to format log records into the ChromePHP format, only useful for the ChromePHPHandler.
149 149 - _GelfFormatter_: Used to format log records into Gelf message instances, only useful for the GelfHandler.
  150 +- _LogstashEventFormatter_: Used to format log records into [logstash](http://logstash.net/) event json, useful for any handler listed under inputs [here](http://logstash.net/docs/1.1.1/).
150 151
151 152 Processors
152 153 ----------
93 src/Monolog/Formatter/LogstashFormatter.php
... ... @@ -0,0 +1,93 @@
  1 +<?php
  2 +
  3 +/*
  4 + * This file is part of the Monolog package.
  5 + *
  6 + * (c) Jordi Boggiano <j.boggiano@seld.be>
  7 + *
  8 + * For the full copyright and license information, please view the LICENSE
  9 + * file that was distributed with this source code.
  10 + */
  11 +
  12 +namespace Monolog\Formatter;
  13 +
  14 +use Monolog\Logger;
  15 +
  16 +/**
  17 + * Serializes a log message to Logstash Event Format
  18 + * @see http://logstash.net/
  19 + * @see https://github.com/logstash/logstash/blob/master/lib/logstash/event.rb
  20 + *
  21 + * @author Tim Mower <timothy.mower@gmail.com>
  22 + */
  23 +class LogstashFormatter extends NormalizerFormatter
  24 +{
  25 + /**
  26 + * @var string the name of the system for the Logstash log message, used to fill the @source field
  27 + */
  28 + protected $systemName;
  29 +
  30 + /**
  31 + * @var string an application name for the Logstash log message, used to fill the @type field
  32 + */
  33 + protected $applicationName;
  34 +
  35 + /**
  36 + * @var string a prefix for 'extra' fields from the Monolog record (optional)
  37 + */
  38 + protected $extraPrefix;
  39 +
  40 + /**
  41 + * @var string a prefix for 'context' fields from the Monolog record (optional)
  42 + */
  43 + protected $contextPrefix;
  44 +
  45 +
  46 + public function __construct($systemName = null, $applicationName = null, $extraPrefix = null, $contextPrefix = 'ctxt_')
  47 + {
  48 + //log stash requires a ISO 8601 format date
  49 + parent::__construct('c');
  50 +
  51 + $this->systemName = $systemName ?: gethostname();
  52 + $this->applicationName = $applicationName;
  53 +
  54 + $this->extraPrefix = $extraPrefix;
  55 + $this->contextPrefix = $contextPrefix;
  56 + }
  57 +
  58 + /**
  59 + * {@inheritdoc}
  60 + */
  61 + public function format(array $record)
  62 + {
  63 + $record = parent::format($record);
  64 + $message = array(
  65 + '@timestamp' => $record['datetime'],
  66 + '@message' => $record['message'],
  67 + '@tags' => array($record['channel']),
  68 + '@source' => $this->systemName
  69 + );
  70 +
  71 + if (isset($this->applicationName)) {
  72 + $message['@type'] = $this->applicationName;
  73 + }
  74 + $message['@fields'] = array();
  75 + $message['@fields']['channel'] = $record['channel'];
  76 + $message['@fields']['level'] = $record['level'];
  77 + if (isset($record['extra']['server'])) {
  78 + $message['@source_host'] = $record['extra']['server'];
  79 + }
  80 + if (isset($record['extra']['url'])) {
  81 + $message['@source_path'] = $record['extra']['url'];
  82 + }
  83 + foreach ($record['extra'] as $key => $val) {
  84 + $message['@fields'][$this->extraPrefix . $key] = $val;
  85 + }
  86 +
  87 + foreach ($record['context'] as $key => $val) {
  88 + $message['@fields'][$this->contextPrefix . $key] = $val;
  89 + }
  90 +
  91 + return json_encode($message);
  92 + }
  93 +}
161 tests/Monolog/Formatter/LogstashFormatterTest.php
... ... @@ -0,0 +1,161 @@
  1 +<?php
  2 +
  3 +/*
  4 + * This file is part of the Monolog package.
  5 + *
  6 + * (c) Jordi Boggiano <j.boggiano@seld.be>
  7 + *
  8 + * For the full copyright and license information, please view the LICENSE
  9 + * file that was distributed with this source code.
  10 + */
  11 +
  12 +namespace Monolog\Formatter;
  13 +
  14 +use Monolog\Logger;
  15 +use Monolog\Formatter\LogstashFormatter;
  16 +
  17 +class LogstashFormatterTest extends \PHPUnit_Framework_TestCase
  18 +{
  19 +
  20 + /**
  21 + * @covers Monolog\Formatter\LogstashFormatter::format
  22 + */
  23 + public function testDefaultFormatter()
  24 + {
  25 + $formatter = new LogstashFormatter('test');
  26 + $record = array(
  27 + 'level' => Logger::ERROR,
  28 + 'level_name' => 'ERROR',
  29 + 'channel' => 'meh',
  30 + 'context' => array(),
  31 + 'datetime' => new \DateTime("@0"),
  32 + 'extra' => array(),
  33 + 'message' => 'log',
  34 + );
  35 +
  36 + $message = json_decode($formatter->format($record), true);
  37 +
  38 + $this->assertEquals("1970-01-01T00:00:00+00:00", $message['@timestamp']);
  39 + $this->assertEquals('log', $message['@message']);
  40 + $this->assertEquals('meh', $message['@fields']['channel']);
  41 + $this->assertContains('meh', $message['@tags']);
  42 + $this->assertEquals(Logger::ERROR, $message['@fields']['level']);
  43 + $this->assertEquals('test', $message['@source']);
  44 +
  45 + $formatter = new LogstashFormatter('mysystem');
  46 +
  47 + $message = json_decode($formatter->format($record), true);
  48 +
  49 + $this->assertEquals('mysystem', $message['@source']);
  50 + }
  51 +
  52 + /**
  53 + * @covers Monolog\Formatter\LogstashFormatter::format
  54 + */
  55 + public function testFormatWithFileAndLine()
  56 + {
  57 + $formatter = new LogstashFormatter('test');
  58 + $record = array(
  59 + 'level' => Logger::ERROR,
  60 + 'level_name' => 'ERROR',
  61 + 'channel' => 'meh',
  62 + 'context' => array('from' => 'logger'),
  63 + 'datetime' => new \DateTime("@0"),
  64 + 'extra' => array('file' => 'test', 'line' => 14),
  65 + 'message' => 'log',
  66 + );
  67 +
  68 + $message = json_decode($formatter->format($record), true);
  69 +
  70 + $this->assertEquals('test', $message['@fields']['file']);
  71 + $this->assertEquals(14, $message['@fields']['line']);
  72 + }
  73 +
  74 + /**
  75 + * @covers Monolog\Formatter\LogstashFormatter::format
  76 + */
  77 + public function testFormatWithContext()
  78 + {
  79 + $formatter = new LogstashFormatter('test');
  80 + $record = array(
  81 + 'level' => Logger::ERROR,
  82 + 'level_name' => 'ERROR',
  83 + 'channel' => 'meh',
  84 + 'context' => array('from' => 'logger'),
  85 + 'datetime' => new \DateTime("@0"),
  86 + 'extra' => array('key' => 'pair'),
  87 + 'message' => 'log'
  88 + );
  89 +
  90 + $message = json_decode($formatter->format($record), true);
  91 +
  92 +
  93 + $message_array = $message['@fields'];
  94 +
  95 + $this->assertArrayHasKey('ctxt_from', $message_array);
  96 + $this->assertEquals('logger', $message_array['ctxt_from']);
  97 +
  98 + // Test with extraPrefix
  99 + $formatter = new LogstashFormatter('test', null, null, 'CTX');
  100 + $message = json_decode($formatter->format($record), true);
  101 +
  102 +
  103 + $message_array = $message['@fields'];
  104 +
  105 + $this->assertArrayHasKey('CTXfrom', $message_array);
  106 + $this->assertEquals('logger', $message_array['CTXfrom']);
  107 +
  108 + }
  109 +
  110 + /**
  111 + * @covers Monolog\Formatter\LogstashFormatter::format
  112 + */
  113 + public function testFormatWithExtra()
  114 + {
  115 + $formatter = new LogstashFormatter('test');
  116 + $record = array(
  117 + 'level' => Logger::ERROR,
  118 + 'level_name' => 'ERROR',
  119 + 'channel' => 'meh',
  120 + 'context' => array('from' => 'logger'),
  121 + 'datetime' => new \DateTime("@0"),
  122 + 'extra' => array('key' => 'pair'),
  123 + 'message' => 'log'
  124 + );
  125 +
  126 + $message = json_decode($formatter->format($record), true);
  127 +
  128 + $message_array = $message['@fields'];
  129 +
  130 + $this->assertArrayHasKey('key', $message_array);
  131 + $this->assertEquals('pair', $message_array['key']);
  132 +
  133 + // Test with extraPrefix
  134 + $formatter = new LogstashFormatter('test', null, 'EXT');
  135 + $message = json_decode($formatter->format($record), true);
  136 +
  137 + $message_array = $message['@fields'];
  138 +
  139 + $this->assertArrayHasKey('EXTkey', $message_array);
  140 + $this->assertEquals('pair', $message_array['EXTkey']);
  141 + }
  142 +
  143 + public function testFormatWithApplicationName()
  144 + {
  145 + $formatter = new LogstashFormatter('test', 'app');
  146 + $record = array(
  147 + 'level' => Logger::ERROR,
  148 + 'level_name' => 'ERROR',
  149 + 'channel' => 'meh',
  150 + 'context' => array('from' => 'logger'),
  151 + 'datetime' => new \DateTime("@0"),
  152 + 'extra' => array('key' => 'pair'),
  153 + 'message' => 'log'
  154 + );
  155 +
  156 + $message = json_decode($formatter->format($record), true);
  157 +
  158 + $this->assertArrayHasKey('@type', $message);
  159 + $this->assertEquals('app', $message['@type']);
  160 + }
  161 +}

0 comments on commit 6588c16

Please sign in to comment.
Something went wrong with that request. Please try again.