1<?php
2
3use dokuwiki\plugin\sentry\Event;
4
5/**
6 * DokuWiki Plugin sentry (Action Component)
7 *
8 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
9 * @author  Andreas Gohr, Michael Große <dokuwiki@cosmocode.de>
10 */
11class action_plugin_sentry_errors extends DokuWiki_Action_Plugin
12{
13
14    protected $lastHandledError;
15
16    /**
17     * Registers a callback function for a given event
18     *
19     * @param Doku_Event_Handler $controller DokuWiki's event controller object
20     *
21     * @return void
22     */
23    public function register(Doku_Event_Handler $controller)
24    {
25        if (!$this->getConf('dsn')) {
26            return;
27        }
28
29        // catch all exceptions
30        set_exception_handler([$this, 'exceptionHandler']);
31        // log non fatal errors
32        set_error_handler([$this, 'errorHandler']);
33        // log fatal errors
34        register_shutdown_function([$this, 'fatalHandler']);
35
36        // retry to send pending events
37        $controller->register_hook('INDEXER_TASKS_RUN', 'AFTER', $this, 'handle_indexer');
38
39        // log deprecated function use
40        $controller->register_hook('INFO_DEPRECATION_LOG', 'AFTER', $this, 'handle_deprecation');
41    }
42
43    /**
44     * Send pending tasks on indexer run
45     *
46     * Called for event: INDEXER_TASKS_RUN
47     *
48     * @param Doku_Event $event event object by reference
49     * @param mixed $param [the parameters passed as fifth argument to register_hook() when this
50     *                           handler was registered]
51     *
52     * @return void
53     */
54    public function handle_indexer(Doku_Event $event, $param)
55    {
56        /** @var helper_plugin_sentry $helper */
57        $helper = plugin_load('helper', 'sentry');
58        $events = $helper->getPendingEventIDs();
59        if (!count($events)) {
60            return;
61        }
62
63        $event->preventDefault();
64        $event->stopPropagation();
65
66        foreach ($events as $eid) {
67            $event = $helper->loadEvent($eid);
68            if ($event === null) {
69                continue;
70            }
71            if ($helper->sendEvent($event)) {
72                $helper->deleteEvent($eid);
73            }
74        }
75    }
76
77    /**
78     * Send deprecated function use to Sentry
79     *
80     * Called for event: INFO_DEPRECATION_LOG
81     *
82     * @param Doku_Event $event event object by reference
83     * @param mixed $param [the parameters passed as fifth argument to register_hook() when this
84     *                           handler was registered]
85     *
86     * @return void
87     */
88    public function handle_deprecation(Doku_Event $event, $param)
89    {
90        /** @var helper_plugin_sentry $helper */
91        $helper = plugin_load('helper', 'sentry');
92
93        $msg = $event->data['called'] . ' is deprecated.';
94        if (!empty($event->data['alternative'])) {
95            $msg .= ' Use ' . $event->data['alternative'] . ' instead.';
96        }
97
98        $data = [
99            'exception' => [
100                'values' => [
101                    [
102                        'type' => 'Deprecated',
103                        'value' => $msg,
104                        'stacktrace' => [
105                            'frames' => Event::backTraceFrames($event->data['trace']),
106                        ],
107                    ],
108                ],
109            ],
110            'level' => Event::LVL_WARN,
111        ];
112
113        $event = new Event($data);
114        $helper->logEvent($event);
115    }
116
117    /**
118     * Log errors that killed the application
119     */
120    public function fatalHandler()
121    {
122        $error = error_get_last();
123        if ($error === null) {
124            return;
125        }
126        // was this error already processed in error handler? ignore it
127        if ($error == $this->lastHandledError) {
128            return;
129        }
130
131        /** @var helper_plugin_sentry $helper */
132        $helper = plugin_load('helper', 'sentry');
133        $event = Event::fromError($error);
134        $helper->logEvent($event);
135    }
136
137    /**
138     * Send exceptions to sentry
139     *
140     * @param \Throwable|\Exception $e
141     */
142    public function exceptionHandler($e)
143    {
144        /** @var helper_plugin_sentry $helper */
145        $helper = plugin_load('helper', 'sentry');
146        $helper->logException($e);
147        echo $helper->formatException($e);
148    }
149
150    /**
151     * Error handler to log old school warnings, notices, etc
152     *
153     * @param int $type
154     * @param string $message
155     * @param string $file
156     * @param int $line
157     * @return false we always let the default handler continue
158     */
159    public function errorHandler($type, $message, $file, $line)
160    {
161        $error = compact('type', 'message', 'file', 'line');
162        $this->lastHandledError = $error;
163
164        // error_reporting = 0 -> error was supressed, never handle it
165        if (error_reporting() === 0) {
166            return false;
167        }
168
169        /** @var helper_plugin_sentry $helper */
170        $helper = plugin_load('helper', 'sentry');
171
172        // Check if this error code is wanted for sentry logging
173        if (!($helper->errorReporting() & $type)) {
174            return false;
175        }
176
177        // add backtrace
178        $error['trace'] = debug_backtrace();
179        array_shift($error['trace']);
180
181        // log it
182        $event = Event::fromError($error);
183        $helper->logEvent($event);
184
185        return false;
186    }
187
188}
189
190