Completed
Push — zend-sentry-inject-static-nonc... ( 530b7d...c3347b )
by Markus
34:36 queued 11:58
created

Module.php (1 issue)

Upgrade to new PHP Analysis Engine

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
 * Bright Answer ZendSentry
5
 *
6
 * This source file is part of the Bright Answer ZendSentry package
7
 *
8
 * @package    ZendSentry\Module
9
 * @license    MIT License {@link /docs/LICENSE}
10
 * @copyright  Copyright (c) 2018, Bright Answer OÜ
11
 */
12
13
namespace ZendSentry;
14
15
use Zend\EventManager\EventManager;
16
use Zend\Mvc\MvcEvent;
17
use Zend\ServiceManager\ServiceManager;
18
use Zend\View\Helper\HeadScript;
19
use ZendSentry\Mvc\View\Http\ExceptionStrategy as SentryHttpStrategy;
20
use ZendSentry\Mvc\View\Console\ExceptionStrategy as SentryConsoleStrategy;
21
use Zend\Mvc\View\Http\ExceptionStrategy;
22
use Raven_Client as Raven;
23
use Zend\Log\Logger;
24
25
/**
26
 * Class Module
27
 *
28
 * @package ZendSentry
29
 */
30
class Module
31
{
32
    /**
33
     * Translates Zend Framework log levels to Raven log levels.
34
     */
35
    private $logLevels = [
36
        0 => Raven::FATAL,
37
        1 => Raven::FATAL,
38
        2 => Raven::FATAL,
39
        3 => Raven::ERROR,
40
        4 => Raven::WARNING,
41
        5 => Raven::INFO,
42
        6 => Raven::INFO,
43
        7 => Raven::DEBUG,
44
    ];
45
46
    /**
47
     * @var Raven $ravenClient
48
     */
49
    protected $ravenClient;
50
51
    /**
52
     * @var ZendSentry $zendSentry
53
     */
54
    protected $zendSentry;
55
56
    /**
57
     * @var $config
58
     */
59
    protected $config;
60
61
    /**
62
     * @var EventManager $eventManager
63
     */
64
    protected $eventManager;
65
66
    /**
67
     * @param MvcEvent $event
68
     */
69
    public function onBootstrap(MvcEvent $event): void
70
    {
71
        // Setup RavenClient (provided by Sentry) and Sentry (provided by this module)
72
        $this->config = $event->getApplication()->getServiceManager()->get('Config');
73
74
        if (!$this->config['zend-sentry']['use-module']) {
75
            return;
76
        }
77
78
        if (isset($this->config['zend-sentry']['raven-config']) && \is_array($this->config['zend-sentry']['raven-config'])) {
79
            $ravenConfig = $this->config['zend-sentry']['raven-config'];
80
        } else {
81
            $ravenConfig = [];
82
        }
83
84
        $sentryApiKey = $this->config['zend-sentry']['sentry-api-key'];
85
        $ravenClient  = new Raven($sentryApiKey, $ravenConfig);
86
87
        // Register the RavenClient as a application wide service
88
        /** @var ServiceManager $serviceManager */
89
        $serviceManager = $event->getApplication()->getServiceManager();
90
        $serviceManager->setService('raven', $ravenClient);
91
92
        $this->ravenClient = $ravenClient;
93
        $this->zendSentry  = new ZendSentry($ravenClient);
94
95
        // Get the eventManager and set it as a member for convenience
96
        $this->eventManager = $event->getApplication()->getEventManager();
97
98
        // If ZendSentry is configured to use the custom logger, attach the listener
99
        if ($this->config['zend-sentry']['attach-log-listener']) {
100
            $this->setupBasicLogging($event);
101
        }
102
103
        // If ZendSentry is configured to log exceptions, a few things need to be set up
104
        if ($this->config['zend-sentry']['handle-exceptions']) {
105
            $this->setupExceptionLogging($event);
106
        }
107
108
        // If ZendSentry is configured to log errors, register it as error handler
109
        if ($this->config['zend-sentry']['handle-errors']) {
110
            $errorReportingLevel = $this->config['zend-sentry']['error-reporting'] ?? -1;
111
            $this->zendSentry->registerErrorHandler($this->config['zend-sentry']['call-existing-error-handler'], $errorReportingLevel);
112
        }
113
114
        // If ZendSentry is configured to log shutdown errors, register it
115
        if ($this->config['zend-sentry']['handle-shutdown-errors']) {
116
            $this->zendSentry->registerShutdownFunction();
117
        }
118
119
        // If ZendSentry is configured to log Javascript errors, add needed scripts to the view
120
        if ($this->config['zend-sentry']['handle-javascript-errors']) {
121
            $this->setupJavascriptLogging($event);
122
        }
123
    }
124
125
    /**
126
     * @return array
127
     */
128
    public function getAutoloaderConfig(): array
129
    {
130
        return [
131
            'Zend\Loader\StandardAutoloader' => [
132
                'namespaces' => [
133
                    __NAMESPACE__ => __DIR__ . '/src/' . __NAMESPACE__,
134
                ]
135
            ]
136
        ];
137
    }
138
139
    /**
140
     * @return mixed
141
     */
142
    public function getConfig()
143
    {
144
        return include __DIR__ . '/config/module.config.php';
145
    }
146
147
    /**
148
     * Gives us the possibility to write logs to Sentry from anywhere in the application
149
     * Doesn't use the ZF compatible Log Writer because we want to return the Sentry event ID
150
     * ZF Logging doesn't provide the possibility to return values from writers
151
     *
152
     * @param MvcEvent $event
153
     */
154
    protected function setupBasicLogging(MvcEvent $event): void
0 ignored issues
show
The parameter $event is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
155
    {
156
        // Get the shared event manager and attach a logging listener for the log event on application level
157
        $sharedManager = $this->eventManager->getSharedManager();
158
        $raven         = $this->ravenClient;
159
        $logLevels     = $this->logLevels;
160
161
        $sharedManager->attach(
162
            '*', 'log', function ($event) use ($raven, $logLevels) {
163
            /** @var $event MvcEvent */
164
            if (\is_object($event->getTarget())) {
165
                $target = \get_class($event->getTarget());
166
            } else {
167
                $target = (string)$event->getTarget();
168
            }
169
            $message  = $event->getParam('message', 'No message provided');
170
            $priority = (int)$event->getParam('priority', Logger::INFO);
171
            $message  = sprintf('%s: %s', $target, $message);
172
            $tags     = $event->getParam('tags', []);
173
            $extra    = $event->getParam('extra', []);
174
            $eventID  = $raven->captureMessage(
175
                $message, [], ['tags' => $tags, 'level' => $logLevels[$priority], 'extra' => $extra]
176
            );
177
            return $eventID;
178
        }, 2
179
        );
180
    }
181
182
    /**
183
     * 1. Registers Sentry as exception handler
184
     * 2. Replaces the default ExceptionStrategy so Exception that are caught by Zend Framework can still be logged
185
     *
186
     * @param MvcEvent $event
187
     */
188
    protected function setupExceptionLogging(MvcEvent $event): void
189
    {
190
        // Register Sentry as exception handler for exception that bubble up to the top
191
        $this->zendSentry->registerExceptionHandler($this->config['zend-sentry']['call-existing-exception-handler']);
192
193
        // Replace the default ExceptionStrategy with ZendSentry's strategy
194
        if ($event->getApplication()->getServiceManager()->has('HttpExceptionStrategy')) {
195
            /** @var $exceptionStrategy ExceptionStrategy */
196
            $exceptionStrategy = $event->getApplication()->getServiceManager()->get('HttpExceptionStrategy');
197
            $exceptionStrategy->detach($this->eventManager);
198
        }
199
200
        // Check if script is running in console
201
        $exceptionStrategy = (PHP_SAPI == 'cli') ? new SentryConsoleStrategy() : new SentryHttpStrategy();
202
        $exceptionStrategy->attach($this->eventManager);
203
        $exceptionStrategy->setDisplayExceptions($this->config['zend-sentry']['display-exceptions']);
204
        $exceptionStrategy->setDefaultExceptionMessage($this->config['zend-sentry'][(PHP_SAPI == 'cli') ? 'default-exception-console-message' : 'default-exception-message']);
205
        if ($exceptionStrategy instanceof SentryHttpStrategy && isset($this->config['view_manager']['exception_template'])) {
206
            $exceptionStrategy->setExceptionTemplate($this->config['view_manager']['exception_template']);
207
        }
208
        $ravenClient = $this->ravenClient;
209
210
        // Attach an exception listener for the ZendSentry exception strategy, can be triggered from anywhere else too
211
        $this->eventManager->getSharedManager()->attach(
212
            '*', 'logException', function ($event) use ($ravenClient) {
213
            /** @var $event MvcEvent */
214
            $exception = $event->getParam('exception');
215
            $tags      = $event->getParam('tags', []);
216
            return $ravenClient->captureException($exception, ['tags' => $tags]);
217
        }
218
        );
219
    }
220
221
    /**
222
     * Adds the necessary javascript, tries to prepend
223
     *
224
     * @param MvcEvent $event
225
     */
226
    protected function setupJavascriptLogging(MvcEvent $event): void
227
    {
228
        /** @var HeadScript $headScript */
229
        $headScript    = $event->getApplication()->getServiceManager()->get('ViewHelperManager')->get('headscript');
230
        $useRavenjsCDN = $this->config['zend-sentry']['use-ravenjs-cdn'];
231
232
        if (!isset($useRavenjsCDN) || $useRavenjsCDN) {
233
            $headScript->offsetSetFile(0, '//cdn.ravenjs.com/3.26.2/raven.min.js');
234
        }
235
236
        $publicApiKey  = $this->convertKeyToPublic($this->config['zend-sentry']['sentry-api-key']);
237
        $ravenjsConfig = json_encode($this->config['zend-sentry']['ravenjs-config']);
238
239
        $attributes = \is_null($this->zendSentry->getCSPNonce()) ? [] : ['nonce' => $this->zendSentry->getCSPNonce()];
240
241
        $headScript->offsetSetScript(1, sprintf("if (typeof Raven !== 'undefined') Raven.config('%s', %s).install()", $publicApiKey, $ravenjsConfig), 'text/javascript', $attributes);
242
    }
243
244
    /**
245
     * @param string $key
246
     *
247
     * @return string $publicKey
248
     */
249
    private function convertKeyToPublic($key): string
250
    {
251
        // If new DSN is configured, no converting is needed
252
        if (substr_count($key, ':') == 1) {
253
            return $key;
254
        }
255
        // If legacy DSN with private part is configured...
256
        // ...find private part
257
        $start       = strpos($key, ':', 6);
258
        $end         = strpos($key, '@');
259
        $privatePart = substr($key, $start, $end - $start);
260
261
        // ... replace it with an empty string
262
        return str_replace($privatePart, '', $key);
263
    }
264
}
265