Completed
Push — sentry-feature-csp-inline-scri... ( e5f5b4 )
by Markus
69:20
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 ZendSentry\Mvc\View\Http\ExceptionStrategy as SentryHttpStrategy;
18
use ZendSentry\Mvc\View\Console\ExceptionStrategy as SentryConsoleStrategy;
19
use Zend\Mvc\View\Http\ExceptionStrategy;
20
use Raven_Client as Raven;
21
use Zend\Log\Logger;
22
23
/**
24
 * Class Module
25
 *
26
 * @package ZendSentry
27
 */
28
class Module
29
{
30
    /**
31
     * Translates Zend Framework log levels to Raven log levels.
32
     */
33
    private $logLevels = [
34
        7 => Raven::DEBUG,
35
        6 => Raven::INFO,
36
        5 => Raven::INFO,
37
        4 => Raven::WARNING,
38
        3 => Raven::ERROR,
39
        2 => Raven::FATAL,
40
        1 => Raven::FATAL,
41
        0 => Raven::FATAL,
42
    ];
43
44
    /**
45
     * @var Raven $ravenClient
46
     */
47
    protected $ravenClient;
48
49
    /**
50
     * @var ZendSentry $zendSentry
51
     */
52
    protected $zendSentry;
53
54
    /**
55
     * @var $config
56
     */
57
    protected $config;
58
59
    /**
60
     * @var EventManager $eventManager
61
     */
62
    protected $eventManager;
63
64
    /**
65
     * @param MvcEvent $event
66
     */
67
    public function onBootstrap(MvcEvent $event): void
68
    {
69
        // Setup RavenClient (provided by Sentry) and Sentry (provided by this module)
70
        $this->config = $event->getApplication()->getServiceManager()->get('Config');
71
72
        if (!$this->config['zend-sentry']['use-module']) {
73
            return;
74
        }
75
76
        if (isset($this->config['zend-sentry']['raven-config']) && \is_array($this->config['zend-sentry']['raven-config'])) {
77
            $ravenConfig = $this->config['zend-sentry']['raven-config'];
78
        } else {
79
            $ravenConfig = [];
80
        }
81
82
        $sentryApiKey = $this->config['zend-sentry']['sentry-api-key'];
83
        $ravenClient = new Raven($sentryApiKey, $ravenConfig);
84
85
        // Register the RavenClient as a application wide service
86
        /** @noinspection PhpUndefinedMethodInspection */
87
        $event->getApplication()->getServiceManager()->setService('raven', $ravenClient);
88
        $this->ravenClient = $ravenClient;
89
        $this->zendSentry = new ZendSentry($ravenClient);
90
91
        // Get the eventManager and set it as a member for convenience
92
        $this->eventManager = $event->getApplication()->getEventManager();
93
94
        // If ZendSentry is configured to use the custom logger, attach the listener
95
        if ($this->config['zend-sentry']['attach-log-listener']) {
96
            $this->setupBasicLogging($event);
97
        }
98
99
        // If ZendSentry is configured to log exceptions, a few things need to be set up
100
        if ($this->config['zend-sentry']['handle-exceptions']) {
101
            $this->setupExceptionLogging($event);
102
        }
103
104
        // If ZendSentry is configured to log errors, register it as error handler
105
        if ($this->config['zend-sentry']['handle-errors']) {
106
            $errorReportingLevel = $this->config['zend-sentry']['error-reporting'] ?? -1;
107
            $this->zendSentry->registerErrorHandler($this->config['zend-sentry']['call-existing-error-handler'], $errorReportingLevel);
108
        }
109
110
        // If ZendSentry is configured to log shutdown errors, register it
111
        if ($this->config['zend-sentry']['handle-shutdown-errors']) {
112
            $this->zendSentry->registerShutdownFunction();
113
        }
114
115
        // If ZendSentry is configured to log Javascript errors, add needed scripts to the view
116
        if ($this->config['zend-sentry']['handle-javascript-errors']) {
117
            $this->setupJavascriptLogging($event);
118
        }
119
    }
120
121
    /**
122
     * @return array
123
     */
124
    public function getAutoloaderConfig(): array
125
    {
126
        return [
127
            'Zend\Loader\StandardAutoloader' => [
128
                'namespaces' => [
129
                __NAMESPACE__ => __DIR__.'/src/'.__NAMESPACE__,
130
                ]
131
            ]
132
        ];
133
    }
134
135
    /**
136
     * @return mixed
137
     */
138
    public function getConfig()
139
    {
140
        return include __DIR__.'/config/module.config.php';
141
    }
142
143
    /**
144
     * Gives us the possibility to write logs to Sentry from anywhere in the application
145
     * Doesn't use the ZF compatible Log Writer because we want to return the Sentry event ID
146
     * ZF Logging doesn't provide the possibility to return values from writers
147
     *
148
     * @param MvcEvent $event
149
     */
150
    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...
151
    {
152
        // Get the shared event manager and attach a logging listener for the log event on application level
153
        $sharedManager = $this->eventManager->getSharedManager();
154
        $raven = $this->ravenClient;
155
        $logLevels = $this->logLevels;
156
157
        $sharedManager->attach('*', 'log', function($event) use ($raven, $logLevels) {
158
            /** @var $event MvcEvent */
159
            if (\is_object($event->getTarget())) {
160
                $target = \get_class($event->getTarget());
161
            } else {
162
                $target = (string) $event->getTarget();
163
            }
164
            $message  = $event->getParam('message', 'No message provided');
165
            $priority = (int) $event->getParam('priority', Logger::INFO);
166
            $message  = sprintf('%s: %s', $target, $message);
167
            $tags     = $event->getParam('tags', []);
168
            $extra   = $event->getParam('extra', []);
169
            $eventID = $raven->captureMessage($message, [], ['tags' => $tags, 'level' => $logLevels[$priority], 'extra' => $extra]
170
);
171
            return $eventID;
172
        }, 2);
173
    }
174
175
    /**
176
     * 1. Registers Sentry as exception handler
177
     * 2. Replaces the default ExceptionStrategy so Exception that are caught by Zend Framework can still be logged
178
     *
179
     * @param MvcEvent $event
180
     */
181
    protected function setupExceptionLogging(MvcEvent $event): void
182
    {
183
        // Register Sentry as exception handler for exception that bubble up to the top
184
        $this->zendSentry->registerExceptionHandler($this->config['zend-sentry']['call-existing-exception-handler']);
185
186
        // Replace the default ExceptionStrategy with ZendSentry's strategy
187
        if ($event->getApplication()->getServiceManager()->has('HttpExceptionStrategy')) {
188
            /** @var $exceptionStrategy ExceptionStrategy */
189
            $exceptionStrategy = $event->getApplication()->getServiceManager()->get('HttpExceptionStrategy');
190
            $exceptionStrategy->detach($this->eventManager);
191
        }
192
193
        // Check if script is running in console
194
        $exceptionStrategy = (PHP_SAPI == 'cli') ? new SentryConsoleStrategy() : new SentryHttpStrategy();
195
        $exceptionStrategy->attach($this->eventManager);
196
        $exceptionStrategy->setDisplayExceptions($this->config['zend-sentry']['display-exceptions']);
197
        $exceptionStrategy->setDefaultExceptionMessage($this->config['zend-sentry'][(PHP_SAPI == 'cli') ? 'default-exception-console-message' : 'default-exception-message']);
198
        if ($exceptionStrategy instanceof SentryHttpStrategy && isset($this->config['view_manager']['exception_template'])) {
199
            $exceptionStrategy->setExceptionTemplate($this->config['view_manager']['exception_template']);
200
        }
201
        $ravenClient = $this->ravenClient;
202
203
        // Attach an exception listener for the ZendSentry exception strategy, can be triggered from anywhere else too
204
        $this->eventManager->getSharedManager()->attach('*', 'logException', function($event) use ($ravenClient) {
205
            /** @var $event MvcEvent */
206
            $exception = $event->getParam('exception');
207
            $tags = $event->getParam('tags', []);
208
            return $ravenClient->captureException($exception, ['tags' => $tags]);
209
        });
210
    }
211
212
    /**
213
     * Adds the necessary javascript, tries to prepend
214
     *
215
     * @param MvcEvent $event
216
     */
217
    protected function setupJavascriptLogging(MvcEvent $event): void
218
    {
219
        $viewHelper = $event->getApplication()->getServiceManager()->get('ViewHelperManager')->get('headscript');
220
        $useRavenjsCDN = $this->config['zend-sentry']['use-ravenjs-cdn'];
221
        if (!isset($useRavenjsCDN) || $useRavenjsCDN) {
222
            /** @noinspection PhpUndefinedMethodInspection */
223
            $viewHelper->offsetSetFile(0, '//cdn.ravenjs.com/3.26.2/raven.min.js');
224
        }
225
        $publicApiKey = $this->convertKeyToPublic($this->config['zend-sentry']['sentry-api-key']);
226
        $ravenjsConfig = json_encode($this->config['zend-sentry']['ravenjs-config']);
227
        /** @noinspection PhpUndefinedMethodInspection */
228
        $viewHelper->offsetSetScript(1, sprintf("if (typeof Raven !== 'undefined') Raven.config('%s', %s).install()", $publicApiKey, $ravenjsConfig));
229
    }
230
231
    /**
232
     * @param string $key
233
     * @return string $publicKey
234
     */
235
    private function convertKeyToPublic($key): string
236
    {
237
        // If new DSN is configured, no converting is needed
238
        if (substr_count($key, ':') == 1) {
239
            return $key;
240
        }
241
        // If legacy DSN with private part is configured...
242
        // ...find private part
243
        $start = strpos($key, ':', 6);
244
        $end = strpos($key, '@');
245
        $privatePart = substr($key, $start, $end - $start);
246
247
        // ... replace it with an empty string
248
        return str_replace($privatePart, '', $key);
249
    }
250
}
251