These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | /** |
||
4 | * Class: SentryLogger. |
||
5 | * |
||
6 | * @author Russell Michell 2017-2019 <[email protected]> |
||
7 | * @package phptek/sentry |
||
8 | */ |
||
9 | |||
10 | namespace PhpTek\Sentry\Log; |
||
11 | |||
12 | use SilverStripe\Control\Director; |
||
13 | use SilverStripe\Control\Middleware\TrustedProxyMiddleware; |
||
14 | use SilverStripe\Core\Injector\Injector; |
||
15 | use SilverStripe\Dev\Backtrace; |
||
16 | use SilverStripe\Security\Security; |
||
17 | use SilverStripe\Core\Config\Configurable; |
||
18 | use PhpTek\Sentry\Log\SentryLogger; |
||
19 | use PhpTek\Sentry\Adaptor\SentryAdaptor; |
||
20 | |||
21 | /** |
||
22 | * The SentryLogWriter class is a bridge between {@link SentryAdaptor} and |
||
23 | * SilverStripe's use of Monolog. |
||
24 | */ |
||
25 | class SentryLogger |
||
26 | { |
||
27 | use Configurable; |
||
28 | |||
29 | /** |
||
30 | * @var SentryAdaptor |
||
31 | */ |
||
32 | public $client = null; |
||
33 | |||
34 | /** |
||
35 | * Stipulates what gets shown in the Sentry UI, should some metric not be |
||
36 | * available for any reason. |
||
37 | * |
||
38 | * @const string |
||
39 | */ |
||
40 | const SLW_NOOP = 'Unavailable'; |
||
41 | |||
42 | /** |
||
43 | * A static constructor as per {@link Zend_Log_FactoryInterface}. |
||
44 | * |
||
45 | * @param array $config An array of optional additional configuration for |
||
46 | * passing custom information to Sentry. See the README |
||
47 | * for more detail. |
||
48 | * @return SentryLogger |
||
49 | */ |
||
50 | public static function factory(array $config = []) : SentryLogger |
||
51 | { |
||
52 | $env = $config['env'] ?? []; |
||
53 | $user = $config['user'] ?? []; |
||
54 | $tags = $config['tags'] ?? []; |
||
55 | $extra = $config['extra'] ?? []; |
||
56 | // Set the minimum reporting level |
||
57 | $level = $config['level'] ?? self::config()->get('log_level'); |
||
58 | $logger = Injector::inst()->create(static::class); |
||
59 | |||
60 | // Set default environment |
||
61 | $env = $env ?: $logger->defaultEnv(); |
||
62 | // Set any available user data |
||
63 | $user = $user ?: $logger->defaultUser(); |
||
64 | // Set any available tags available in SS config |
||
65 | $tags = array_merge($logger->defaultTags(), $tags); |
||
66 | // Set any available additional (extra) data |
||
67 | $extra = array_merge($logger->defaultExtra(), $extra); |
||
68 | |||
69 | $logger->adaptor->setContext('env', $env); |
||
70 | $logger->adaptor->setContext('tags', $tags); |
||
71 | $logger->adaptor->setContext('extra', $extra); |
||
72 | $logger->adaptor->setContext('user', $user); |
||
73 | $logger->adaptor->setContext('level', $level); |
||
74 | |||
75 | return $logger; |
||
76 | } |
||
77 | |||
78 | /** |
||
79 | * @return SentryAdaptor |
||
80 | */ |
||
81 | public function getAdaptor() : SentryAdaptor |
||
82 | { |
||
83 | return $this->adaptor; |
||
84 | } |
||
85 | |||
86 | /** |
||
87 | * Returns a default environment when one isn't passed to the factory() |
||
88 | * method. |
||
89 | * |
||
90 | * @return string |
||
91 | */ |
||
92 | public function defaultEnv() : string |
||
93 | { |
||
94 | return Director::get_environment_type(); |
||
95 | } |
||
96 | |||
97 | /** |
||
98 | * Returns a default set of additional "tags" we wish to send to Sentry. |
||
99 | * By default, Sentry reports on several mertrics, and we're already sending |
||
100 | * {@link Member} data. But there are additional data that would be useful |
||
101 | * for debugging via the Sentry UI. |
||
102 | * |
||
103 | * These data can augment that which is sent to Sentry at setup |
||
104 | * time in _config.php. See the README for more detail. |
||
105 | * |
||
106 | * N.b. Tags can be used to group messages within the Sentry UI itself, so there |
||
107 | * should only be "static" data being sent, not something that can drastically |
||
108 | * or minutely change, such as memory usage for example. |
||
109 | * |
||
110 | * @return array |
||
111 | */ |
||
112 | public function defaultTags() : array |
||
113 | { |
||
114 | return [ |
||
115 | 'Request-Method'=> $this->getReqMethod(), |
||
116 | 'Request-Type' => $this->getRequestType(), |
||
117 | 'SAPI' => $this->getSAPI(), |
||
118 | 'SS-Version' => $this->getPackageInfo('silverstripe/framework') |
||
119 | ]; |
||
120 | } |
||
121 | |||
122 | /** |
||
123 | * Returns a default set of extra data to show upon selecting a message for |
||
124 | * analysis in the Sentry UI. This can augment the data sent to Sentry at setup |
||
125 | * time in _config.php as well as at runtime when calling SS_Log itself. |
||
126 | * See the README for more detail. |
||
127 | * |
||
128 | * @return array |
||
129 | */ |
||
130 | public function defaultExtra() : array |
||
131 | { |
||
132 | return [ |
||
133 | 'Peak-Memory' => $this->getPeakMemory() |
||
134 | ]; |
||
135 | } |
||
136 | |||
137 | /** |
||
138 | * Return the version of $pkg taken from composer.lock. |
||
139 | * |
||
140 | * @param string $pkg e.g. "silverstripe/framework" |
||
141 | * @return string |
||
142 | */ |
||
143 | public function getPackageInfo(string $pkg) : string |
||
144 | { |
||
145 | $lockFileJSON = BASE_PATH . '/composer.lock'; |
||
146 | |||
147 | if (!file_exists($lockFileJSON) || !is_readable($lockFileJSON)) { |
||
148 | return self::SLW_NOOP; |
||
149 | } |
||
150 | |||
151 | $lockFileData = json_decode(file_get_contents($lockFileJSON), true); |
||
152 | |||
153 | foreach ($lockFileData['packages'] as $package) { |
||
154 | if ($package['name'] === $pkg) { |
||
155 | return $package['version']; |
||
156 | } |
||
157 | } |
||
158 | |||
159 | return self::SLW_NOOP; |
||
160 | } |
||
161 | |||
162 | /** |
||
163 | * What sort of request is this? (A harder question to answer than you might |
||
164 | * think: http://stackoverflow.com/questions/6275363/what-is-the-correct-terminology-for-a-non-ajax-request) |
||
165 | * |
||
166 | * @return string |
||
167 | */ |
||
168 | public function getRequestType() : string |
||
169 | { |
||
170 | $isCLI = $this->getSAPI() !== 'cli'; |
||
171 | $isAjax = Director::is_ajax(); |
||
172 | |||
173 | return $isCLI && $isAjax ? 'AJAX' : 'Non-Ajax'; |
||
174 | } |
||
175 | |||
176 | /** |
||
177 | * Return peak memory usage. |
||
178 | * |
||
179 | * @return string |
||
180 | */ |
||
181 | public function getPeakMemory() : string |
||
182 | { |
||
183 | $peak = memory_get_peak_usage(true) / 1024 / 1024; |
||
184 | |||
185 | return (string) round($peak, 2) . 'Mb'; |
||
186 | } |
||
187 | |||
188 | /** |
||
189 | * Basic User-Agent check and return. |
||
190 | * |
||
191 | * @return string |
||
192 | */ |
||
193 | public function getUserAgent() : string |
||
194 | { |
||
195 | $ua = @$_SERVER['HTTP_USER_AGENT']; |
||
196 | |||
197 | if (!empty($ua)) { |
||
198 | return $ua; |
||
199 | } |
||
200 | |||
201 | return self::SLW_NOOP; |
||
202 | } |
||
203 | |||
204 | /** |
||
205 | * Basic request method check and return. |
||
206 | * |
||
207 | * @return string |
||
208 | */ |
||
209 | public function getReqMethod() : string |
||
210 | { |
||
211 | $method = @$_SERVER['REQUEST_METHOD']; |
||
212 | |||
213 | if (!empty($method)) { |
||
214 | return $method; |
||
215 | } |
||
216 | |||
217 | return self::SLW_NOOP; |
||
218 | } |
||
219 | |||
220 | /** |
||
221 | * @return string |
||
222 | */ |
||
223 | public function getSAPI() : string |
||
224 | { |
||
225 | return php_sapi_name(); |
||
226 | } |
||
227 | |||
228 | /** |
||
229 | * Returns the client IP address which originated this request. |
||
230 | * Lifted and modified from SilverStripe 3's SS_HTTPRequest. |
||
231 | * |
||
232 | * @return string |
||
233 | */ |
||
234 | public function getIP() : string |
||
235 | { |
||
236 | $headerOverrideIP = null; |
||
237 | |||
238 | if (defined('TRUSTED_PROXY')) { |
||
239 | $headers = (defined('SS_TRUSTED_PROXY_IP_HEADER')) ? |
||
240 | [SS_TRUSTED_PROXY_IP_HEADER] : |
||
241 | null; |
||
242 | |||
243 | if(!$headers) { |
||
244 | // Backwards compatible defaults |
||
245 | $headers = ['HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR']; |
||
246 | } |
||
247 | |||
248 | foreach($headers as $header) { |
||
249 | if(!empty($_SERVER[$header])) { |
||
250 | $headerOverrideIP = $_SERVER[$header]; |
||
251 | |||
252 | break; |
||
253 | } |
||
254 | } |
||
255 | } |
||
256 | |||
257 | $proxy = Injector::inst()->create(TrustedProxyMiddleware::class); |
||
258 | |||
259 | if ($headerOverrideIP) { |
||
260 | return $proxy->getIPFromHeaderValue($headerOverrideIP); |
||
261 | } |
||
262 | |||
263 | if (isset($_SERVER['REMOTE_ADDR'])) { |
||
264 | return $_SERVER['REMOTE_ADDR']; |
||
265 | } |
||
266 | |||
267 | return ''; |
||
268 | } |
||
269 | |||
270 | /** |
||
271 | * Returns a default set of additional data specific to the user's part in |
||
272 | * the request. |
||
273 | * |
||
274 | * @param mixed Member|null $member |
||
275 | * @return array |
||
276 | */ |
||
277 | View Code Duplication | public function defaultUser(Member $member = null) : array |
|
0 ignored issues
–
show
|
|||
278 | { |
||
279 | if (!$member) { |
||
280 | $member = Security::getCurrentUser(); |
||
281 | } |
||
282 | |||
283 | return [ |
||
284 | 'IPAddress' => $this->getIP() ?: self::SLW_NOOP, |
||
285 | 'ID' => $member ? $member->getField('ID') : self::SLW_NOOP, |
||
286 | 'Email' => $member ? $member->getField('Email') : self::SLW_NOOP, |
||
287 | ]; |
||
288 | } |
||
289 | |||
290 | /** |
||
291 | * Generate a cleaned-up backtrace of the event that got us here. |
||
292 | * |
||
293 | * @param array $record |
||
294 | * @return array |
||
295 | * @todo Unused in sentry-sdk 2.0?? |
||
296 | */ |
||
297 | View Code Duplication | public static function backtrace(array $record) : array |
|
0 ignored issues
–
show
This method seems to be duplicated in 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...
|
|||
298 | { |
||
299 | // Provided trace |
||
300 | if (!empty($record['context']['trace'])) { |
||
301 | return $record['context']['trace']; |
||
302 | } |
||
303 | |||
304 | // Generate trace from exception |
||
305 | if (isset($record['context']['exception'])) { |
||
306 | $exception = $record['context']['exception']; |
||
307 | |||
308 | return $exception->getTrace(); |
||
309 | } |
||
310 | |||
311 | // Failover: build custom trace |
||
312 | $bt = debug_backtrace(); |
||
313 | |||
314 | // Push current line into context |
||
315 | array_unshift($bt, [ |
||
316 | 'file' => !empty($bt['file']) ? $bt['file'] : 'N/A', |
||
317 | 'line' => !empty($bt['line']) ? $bt['line'] : 'N/A', |
||
318 | 'function' => '', |
||
319 | 'class' => '', |
||
320 | 'type' => '', |
||
321 | 'args' => [], |
||
322 | ]); |
||
323 | |||
324 | return Backtrace::filter_backtrace($bt, [ |
||
325 | '', |
||
326 | 'Monolog\\Handler\\AbstractProcessingHandler->handle', |
||
327 | 'Monolog\\Logger->addRecord', |
||
328 | 'Monolog\\Logger->log', |
||
329 | 'Monolog\\Logger->warn', |
||
330 | 'PhpTek\\Sentry\\Handler\\SentryMonologHandler->write', |
||
331 | 'PhpTek\\Sentry\\Handler\\SentryMonologHandler->backtrace', |
||
332 | ]); |
||
333 | } |
||
334 | |||
335 | } |
||
336 |
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.