1<?php declare(strict_types=1);
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
12namespace Monolog\Formatter;
13
14/**
15 * Serializes a log message to Logstash Event Format
16 *
17 * @see https://www.elastic.co/products/logstash
18 * @see https://github.com/elastic/logstash/blob/master/logstash-core/src/main/java/org/logstash/Event.java
19 *
20 * @author Tim Mower <timothy.mower@gmail.com>
21 */
22class LogstashFormatter extends NormalizerFormatter
23{
24    /**
25     * @var string the name of the system for the Logstash log message, used to fill the @source field
26     */
27    protected $systemName;
28
29    /**
30     * @var string an application name for the Logstash log message, used to fill the @type field
31     */
32    protected $applicationName;
33
34    /**
35     * @var string the key for 'extra' fields from the Monolog record
36     */
37    protected $extraKey;
38
39    /**
40     * @var string the key for 'context' fields from the Monolog record
41     */
42    protected $contextKey;
43
44    /**
45     * @param string      $applicationName The application that sends the data, used as the "type" field of logstash
46     * @param string|null $systemName      The system/machine name, used as the "source" field of logstash, defaults to the hostname of the machine
47     * @param string      $extraKey        The key for extra keys inside logstash "fields", defaults to extra
48     * @param string      $contextKey      The key for context keys inside logstash "fields", defaults to context
49     */
50    public function __construct(string $applicationName, ?string $systemName = null, string $extraKey = 'extra', string $contextKey = 'context')
51    {
52        // logstash requires a ISO 8601 format date with optional millisecond precision.
53        parent::__construct('Y-m-d\TH:i:s.uP');
54
55        $this->systemName = $systemName === null ? (string) gethostname() : $systemName;
56        $this->applicationName = $applicationName;
57        $this->extraKey = $extraKey;
58        $this->contextKey = $contextKey;
59    }
60
61    /**
62     * {@inheritDoc}
63     */
64    public function format(array $record): string
65    {
66        $record = parent::format($record);
67
68        if (empty($record['datetime'])) {
69            $record['datetime'] = gmdate('c');
70        }
71        $message = [
72            '@timestamp' => $record['datetime'],
73            '@version' => 1,
74            'host' => $this->systemName,
75        ];
76        if (isset($record['message'])) {
77            $message['message'] = $record['message'];
78        }
79        if (isset($record['channel'])) {
80            $message['type'] = $record['channel'];
81            $message['channel'] = $record['channel'];
82        }
83        if (isset($record['level_name'])) {
84            $message['level'] = $record['level_name'];
85        }
86        if (isset($record['level'])) {
87            $message['monolog_level'] = $record['level'];
88        }
89        if ($this->applicationName) {
90            $message['type'] = $this->applicationName;
91        }
92        if (!empty($record['extra'])) {
93            $message[$this->extraKey] = $record['extra'];
94        }
95        if (!empty($record['context'])) {
96            $message[$this->contextKey] = $record['context'];
97        }
98
99        return $this->toJson($message) . "\n";
100    }
101}
102