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) { |
|
|
|
|
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) { |
|
|
|
|
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; |
|
|
|
|
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'])) { |
|
|
|
|
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'])) { |
|
|
|
|
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'])) { |
|
|
|
|
149
|
|
|
$options['fingerprint'] = $record['context']['fingerprint']; |
150
|
|
|
unset($record['context']['fingerprint']); |
151
|
|
|
} |
152
|
|
View Code Duplication |
if (!empty($record['context']['logger'])) { |
|
|
|
|
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
|
|
|
|
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.