Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
36 / 36
100.00% covered (success)
100.00%
13 / 13
CRAP
100.00% covered (success)
100.00%
1 / 1
Sentry
100.00% covered (success)
100.00%
36 / 36
100.00% covered (success)
100.00%
13 / 13
28
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
6
 getPostProcessors
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setTag
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setExtra
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setExtras
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 setTags
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 unsetTag
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 unsetExtra
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 unsetTags
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 unsetExtras
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 getTag
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getExtra
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 logExceptionsToSentry
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
7
1<?php
2
3namespace Miniframe\Middleware;
4
5use Miniframe\Core\AbstractMiddleware;
6use Miniframe\Core\Config;
7use Miniframe\Core\Request;
8use Miniframe\Core\Response;
9use Miniframe\Response\InternalServerErrorResponse;
10
11use function Sentry\init as InitSentry;
12use function Sentry\captureException;
13
14class Sentry extends AbstractMiddleware
15{
16    /**
17     * List of fully qualified classnames that shouldn't be logged when sent to Sentry
18     *
19     * @var string[]
20     */
21    private $exclusions = array();
22
23    /**
24     * List of tags to be sent to Sentry
25     *
26     * @var array
27     */
28    private $tags = array();
29
30    /**
31     * List of extra data to be sent to Sentry
32     *
33     * @var array
34     */
35    private $extra = array();
36
37    /**
38     * Initializes the Sentry middleware
39     *
40     * @param Request $request The request object.
41     * @param Config  $config  The config object.
42     */
43    public function __construct(Request $request, Config $config)
44    {
45        parent::__construct($request, $config);
46
47        InitSentry([
48            'dsn' => $config->get('sentry', 'dsn'),
49            'before_send' => function (\Sentry\Event $event, ?\Sentry\EventHint $hint): ?\Sentry\Event {
50               // Ignore excluded exceptions
51                if (
52                    $hint !== null && (
53                        in_array(get_class($hint->exception), $this->exclusions)
54                        || (
55                            $hint->exception->getPrevious()
56                            && in_array(get_class($hint->exception->getPrevious()), $this->exclusions)
57                        )
58                    )
59                ) {
60                    return null;
61                }
62
63                $event->setTags($this->tags);
64                $event->setExtra($this->extra);
65
66                return $event;
67            },
68        ]);
69
70        $this->exclusions = $config->has('sentry', 'exclude') ? $config->get('sentry', 'exclude') : [];
71    }
72
73    /**
74     * Returns a list of the post processors
75     *
76     * @return callable[]
77     */
78    public function getPostProcessors(): array
79    {
80        return [[$this, 'logExceptionsToSentry']];
81    }
82
83    /**
84     * Defines a tag to be sent to Sentry. It's possible to filter by tag in Sentry.
85     *
86     * @param string      $label Tag label.
87     * @param string|null $value Tag value.
88     *
89     * @return void
90     */
91    public function setTag(string $label, ?string $value): void
92    {
93        $this->tags[$label] = $value;
94    }
95
96    /**
97     * Defines an extra value to be sent to Sentry. It's NOT possible to filter by this value in Sentry.
98     *
99     * @param string $label Extra label.
100     * @param mixed  $value Extra data.
101     *
102     * @return void
103     */
104    public function setExtra(string $label, $value): void
105    {
106        $this->extra[$label] = $value;
107    }
108
109    /**
110     * Defines a set of extra values to be sent to Sentry. It's NOT possible to filter by these values in Sentry.
111     *
112     * @param array $associativeArray Extra data (key => value pairs).
113     *
114     * @return void
115     */
116    public function setExtras(array $associativeArray): void
117    {
118        foreach ($associativeArray as $label => $value) {
119            $this->setExtra($label, $value);
120        }
121    }
122
123    /**
124     * Defines a set of tags to be sent to Sentry. It's possible to filter by tag in Sentry.
125     *
126     * @param array $associativeArray Tags (key => value pairs).
127     *
128     * @return void
129     */
130    public function setTags(array $associativeArray): void
131    {
132        foreach ($associativeArray as $label => $value) {
133            $this->setTag($label, $value);
134        }
135    }
136
137    /**
138     * Removes a tag to be sent to Sentry
139     *
140     * @param string $label Tag label.
141     *
142     * @return void
143     */
144    public function unsetTag(string $label): void
145    {
146        unset($this->tags[$label]);
147    }
148
149    /**
150     * Removes a set of extra data to be sent to Sentry
151     *
152     * @param string $label Extra label.
153     *
154     * @return void
155     */
156    public function unsetExtra(string $label): void
157    {
158        unset($this->extra[$label]);
159    }
160
161    /**
162     * Removes a set of tags to be sent to Sentry
163     *
164     * @param string ...$labels Tag labels.
165     *
166     * @return void
167     */
168    public function unsetTags(string ...$labels): void
169    {
170        foreach ($labels as $label) {
171            $this->unsetTag($label);
172        }
173    }
174
175    /**
176     * Removes a set of extra data to be sent to Sentry
177     *
178     * @param string ...$labels Extra labels.
179     *
180     * @return void
181     */
182    public function unsetExtras(string ...$labels): void
183    {
184        foreach ($labels as $label) {
185            $this->unsetExtra($label);
186        }
187    }
188
189    /**
190     * Gets a tag to be sent to Sentry
191     *
192     * @param string $label Tag label.
193     *
194     * @return string|null
195     */
196    public function getTag(string $label): ?string
197    {
198        return $this->tags[$label] ?? null;
199    }
200
201    /**
202     * Gets an extra to be sent to Sentry
203     *
204     * @param string $label Extra label.
205     *
206     * @return mixed|null
207     */
208    public function getExtra(string $label)
209    {
210        return $this->extra[$label] ?? null;
211    }
212
213    /**
214     * Sentry Post processor
215     *
216     * @param Response $response The current response.
217     * @param boolean  $thrown   True when the response is thrown, otherwise false.
218     *
219     * @return Response
220     */
221    public function logExceptionsToSentry(Response $response, bool $thrown): Response
222    {
223        // Log all thrown responses, and responses with an exit code different from 0
224        if (!$thrown && $response->getExitCode() === 0) {
225            return $response;
226        }
227
228        if (is_a($response, InternalServerErrorResponse::class) && $response->getPrevious()) {
229            if (!in_array(get_class($response->getPrevious()), $this->exclusions)) {
230                captureException($response->getPrevious());
231            }
232        } else {
233            if (!in_array(get_class($response), $this->exclusions)) {
234                captureException($response);
235            }
236        }
237
238        return $response;
239    }
240}