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\Handler;
13
14use Rollbar\RollbarLogger;
15use Throwable;
16use Monolog\Logger;
17
18/**
19 * Sends errors to Rollbar
20 *
21 * If the context data contains a `payload` key, that is used as an array
22 * of payload options to RollbarLogger's log method.
23 *
24 * Rollbar's context info will contain the context + extra keys from the log record
25 * merged, and then on top of that a few keys:
26 *
27 *  - level (rollbar level name)
28 *  - monolog_level (monolog level name, raw level, as rollbar only has 5 but monolog 8)
29 *  - channel
30 *  - datetime (unix timestamp)
31 *
32 * @author Paul Statezny <paulstatezny@gmail.com>
33 */
34class RollbarHandler extends AbstractProcessingHandler
35{
36    /**
37     * @var RollbarLogger
38     */
39    protected $rollbarLogger;
40
41    /** @var string[] */
42    protected $levelMap = [
43        Logger::DEBUG     => 'debug',
44        Logger::INFO      => 'info',
45        Logger::NOTICE    => 'info',
46        Logger::WARNING   => 'warning',
47        Logger::ERROR     => 'error',
48        Logger::CRITICAL  => 'critical',
49        Logger::ALERT     => 'critical',
50        Logger::EMERGENCY => 'critical',
51    ];
52
53    /**
54     * Records whether any log records have been added since the last flush of the rollbar notifier
55     *
56     * @var bool
57     */
58    private $hasRecords = false;
59
60    /** @var bool */
61    protected $initialized = false;
62
63    /**
64     * @param RollbarLogger $rollbarLogger RollbarLogger object constructed with valid token
65     */
66    public function __construct(RollbarLogger $rollbarLogger, $level = Logger::ERROR, bool $bubble = true)
67    {
68        $this->rollbarLogger = $rollbarLogger;
69
70        parent::__construct($level, $bubble);
71    }
72
73    /**
74     * {@inheritDoc}
75     */
76    protected function write(array $record): void
77    {
78        if (!$this->initialized) {
79            // __destructor() doesn't get called on Fatal errors
80            register_shutdown_function(array($this, 'close'));
81            $this->initialized = true;
82        }
83
84        $context = $record['context'];
85        $context = array_merge($context, $record['extra'], [
86            'level' => $this->levelMap[$record['level']],
87            'monolog_level' => $record['level_name'],
88            'channel' => $record['channel'],
89            'datetime' => $record['datetime']->format('U'),
90        ]);
91
92        if (isset($context['exception']) && $context['exception'] instanceof Throwable) {
93            $exception = $context['exception'];
94            unset($context['exception']);
95            $toLog = $exception;
96        } else {
97            $toLog = $record['message'];
98        }
99
100        // @phpstan-ignore-next-line
101        $this->rollbarLogger->log($context['level'], $toLog, $context);
102
103        $this->hasRecords = true;
104    }
105
106    public function flush(): void
107    {
108        if ($this->hasRecords) {
109            $this->rollbarLogger->flush();
110            $this->hasRecords = false;
111        }
112    }
113
114    /**
115     * {@inheritDoc}
116     */
117    public function close(): void
118    {
119        $this->flush();
120    }
121
122    /**
123     * {@inheritDoc}
124     */
125    public function reset()
126    {
127        $this->flush();
128
129        parent::reset();
130    }
131}
132