Completed
Push — master ( 193986...cdf399 )
by Andrey
11:47
created

RavenHandler::getExtraParameters()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
/*
4
 * This file is part of the Monolog package.
5
 *
6
 * (c) Jordi Boggiano <[email protected]>
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\Handler;
13
14
use Monolog\Formatter\LineFormatter;
15
use Monolog\Formatter\FormatterInterface;
16
use Monolog\Logger;
17
use Raven_Client;
18
19
/**
20
 * Handler to send messages to a Sentry (https://github.com/getsentry/sentry) server
21
 * using raven-php (https://github.com/getsentry/raven-php)
22
 *
23
 * @author Marc Abramowitz <[email protected]>
24
 */
25
class RavenHandler extends AbstractProcessingHandler
26
{
27
    /**
28
     * Translates Monolog log levels to Raven log levels.
29
     */
30
    private $logLevels = array(
31
        Logger::DEBUG     => Raven_Client::DEBUG,
32
        Logger::INFO      => Raven_Client::INFO,
33
        Logger::NOTICE    => Raven_Client::INFO,
34
        Logger::WARNING   => Raven_Client::WARNING,
35
        Logger::ERROR     => Raven_Client::ERROR,
36
        Logger::CRITICAL  => Raven_Client::FATAL,
37
        Logger::ALERT     => Raven_Client::FATAL,
38
        Logger::EMERGENCY => Raven_Client::FATAL,
39
    );
40
41
    /**
42
     * @var string should represent the current version of the calling
43
     *             software. Can be any string (git commit, version number)
44
     */
45
    private $release;
46
47
    /**
48
     * @var Raven_Client the client object that sends the message to the server
49
     */
50
    protected $ravenClient;
51
52
    /**
53
     * @var LineFormatter The formatter to use for the logs generated via handleBatch()
54
     */
55
    protected $batchFormatter;
56
57
    /**
58
     * @param Raven_Client $ravenClient
59
     * @param int          $level       The minimum logging level at which this handler will be triggered
60
     * @param Boolean      $bubble      Whether the messages that are handled can bubble up the stack or not
61
     */
62
    public function __construct(Raven_Client $ravenClient, $level = Logger::DEBUG, $bubble = true)
63
    {
64
        parent::__construct($level, $bubble);
65
66
        $this->ravenClient = $ravenClient;
67
    }
68
69
    /**
70
     * {@inheritdoc}
71
     */
72
    public function handleBatch(array $records)
73
    {
74
        $level = $this->level;
75
76
        // filter records based on their level
77
        $records = array_filter($records, function ($record) use ($level) {
78
            return $record['level'] >= $level;
79
        });
80
81
        if (!$records) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $records of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
82
            return;
83
        }
84
85
        // the record with the highest severity is the "main" one
86
        $record = array_reduce($records, function ($highest, $record) {
87
            if ($record['level'] > $highest['level']) {
88
                return $record;
89
            }
90
91
            return $highest;
92
        });
93
94
        // the other ones are added as a context item
95
        $logs = array();
96
        foreach ($records as $r) {
97
            $logs[] = $this->processRecord($r);
98
        }
99
100
        if ($logs) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $logs of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
101
            $record['context']['logs'] = (string) $this->getBatchFormatter()->formatBatch($logs);
102
        }
103
104
        $this->handle($record);
105
    }
106
107
    /**
108
     * Sets the formatter for the logs generated by handleBatch().
109
     *
110
     * @param FormatterInterface $formatter
111
     */
112
    public function setBatchFormatter(FormatterInterface $formatter)
113
    {
114
        $this->batchFormatter = $formatter;
0 ignored issues
show
Documentation Bug introduced by
$formatter is of type object<Monolog\Formatter\FormatterInterface>, but the property $batchFormatter was declared to be of type object<Monolog\Formatter\LineFormatter>. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
115
    }
116
117
    /**
118
     * Gets the formatter for the logs generated by handleBatch().
119
     *
120
     * @return FormatterInterface
121
     */
122
    public function getBatchFormatter()
123
    {
124
        if (!$this->batchFormatter) {
125
            $this->batchFormatter = $this->getDefaultBatchFormatter();
126
        }
127
128
        return $this->batchFormatter;
129
    }
130
131
    /**
132
     * {@inheritdoc}
133
     */
134
    protected function write(array $record)
135
    {
136
        $previousUserContext = false;
137
        $options = array();
138
        $options['level'] = $this->logLevels[$record['level']];
139
        $options['tags'] = array();
140 View Code Duplication
        if (!empty($record['extra']['tags'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
141
            $options['tags'] = array_merge($options['tags'], $record['extra']['tags']);
142
            unset($record['extra']['tags']);
143
        }
144 View Code Duplication
        if (!empty($record['context']['tags'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
145
            $options['tags'] = array_merge($options['tags'], $record['context']['tags']);
146
            unset($record['context']['tags']);
147
        }
148 View Code Duplication
        if (!empty($record['context']['fingerprint'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
149
            $options['fingerprint'] = $record['context']['fingerprint'];
150
            unset($record['context']['fingerprint']);
151
        }
152 View Code Duplication
        if (!empty($record['context']['logger'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
153
            $options['logger'] = $record['context']['logger'];
154
            unset($record['context']['logger']);
155
        } else {
156
            $options['logger'] = $record['channel'];
157
        }
158
        foreach ($this->getExtraParameters() as $key) {
159
            foreach (array('extra', 'context') as $source) {
160
                if (!empty($record[$source][$key])) {
161
                    $options[$key] = $record[$source][$key];
162
                    unset($record[$source][$key]);
163
                }
164
            }
165
        }
166
        if (!empty($record['context'])) {
167
            $options['extra']['context'] = $record['context'];
168
            if (!empty($record['context']['user'])) {
169
                $previousUserContext = $this->ravenClient->context->user;
170
                $this->ravenClient->user_context($record['context']['user']);
171
                unset($options['extra']['context']['user']);
172
            }
173
        }
174
        if (!empty($record['extra'])) {
175
            $options['extra']['extra'] = $record['extra'];
176
        }
177
178
        if (!empty($this->release) && !isset($options['release'])) {
179
            $options['release'] = $this->release;
180
        }
181
182
        if (isset($record['context']['exception']) && ($record['context']['exception'] instanceof \Exception || (PHP_VERSION_ID >= 70000 && $record['context']['exception'] instanceof \Throwable))) {
183
            $options['extra']['message'] = $record['formatted'];
184
            $this->ravenClient->captureException($record['context']['exception'], $options);
185
        } else {
186
            $this->ravenClient->captureMessage($record['formatted'], array(), $options);
187
        }
188
189
        if ($previousUserContext !== false) {
190
            $this->ravenClient->user_context($previousUserContext);
191
        }
192
    }
193
194
    /**
195
     * {@inheritDoc}
196
     */
197
    protected function getDefaultFormatter()
198
    {
199
        return new LineFormatter('[%channel%] %message%');
200
    }
201
202
    /**
203
     * Gets the default formatter for the logs generated by handleBatch().
204
     *
205
     * @return FormatterInterface
206
     */
207
    protected function getDefaultBatchFormatter()
208
    {
209
        return new LineFormatter();
210
    }
211
212
    /**
213
     * Gets extra parameters supported by Raven that can be found in "extra" and "context"
214
     *
215
     * @return array
216
     */
217
    protected function getExtraParameters()
218
    {
219
        return array('checksum', 'release', 'event_id');
220
    }
221
222
    /**
223
     * @param string $value
224
     * @return self
225
     */
226
    public function setRelease($value)
227
    {
228
        $this->release = $value;
229
230
        return $this;
231
    }
232
}
233