Completed
Pull Request — master (#4)
by Thomas Mauro
03:37
created

Sentry   A

Complexity

Total Complexity 33

Size/Duplication

Total Lines 211
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Test Coverage

Coverage 88.17%

Importance

Changes 0
Metric Value
wmc 33
lcom 1
cbo 4
dl 0
loc 211
ccs 82
cts 93
cp 0.8817
rs 9.3999
c 0
b 0
f 0

7 Methods

Rating   Name   Duplication   Size   Complexity  
B __construct() 0 24 6
C doWrite() 0 47 9
C cleanBacktrace() 0 30 7
A sanitizeContextData() 0 6 1
B sanitizeContextItem() 0 16 6
A objectIsThrowable() 0 4 2
A contextContainsException() 0 8 2
1
<?php
2
3
namespace Facile\SentryModule\Log\Writer;
4
5
use Facile\SentryModule\Service\Client;
6
use Traversable;
7
use Zend\Log\Writer\AbstractWriter;
8
use Zend\Log\Logger;
9
use Raven_Client;
10
11
/**
12
 * Class Sentry.
13
 */
14
class Sentry extends AbstractWriter
15
{
16
    /**
17
     * @var Client
18
     */
19
    protected $client;
20
21
    /**
22
     * @var array
23
     */
24
    protected $priorityMap = [
25
        Logger::EMERG => Raven_Client::FATAL,
26
        Logger::ALERT => Raven_Client::ERROR,
27
        Logger::CRIT => Raven_Client::ERROR,
28
        Logger::ERR => Raven_Client::ERROR,
29
        Logger::WARN => Raven_Client::WARNING,
30
        Logger::NOTICE => Raven_Client::INFO,
31
        Logger::INFO => Raven_Client::INFO,
32
        Logger::DEBUG => Raven_Client::DEBUG,
33
    ];
34
35
    /**
36
     * @var array
37
     */
38
    protected $excludedBacktraceNamespaces = [
39
        'Facile\\SentryModule\\Log\\',
40
        'Psr\\Log\\',
41
        'Zend\\Log\\',
42
        'Monolog\\',
43
    ];
44
45
    /**
46
     * Sentry constructor.
47
     *
48
     * @param array $options
49
     *
50
     * @throws \RuntimeException
51
     * @throws \Zend\Log\Exception\InvalidArgumentException
52
     */
53 7
    public function __construct(array $options = null)
54
    {
55 7
        parent::__construct($options);
56
57 7
        if ($options instanceof Traversable) {
58
            $options = iterator_to_array($options);
59
        }
60
61 7
        if (!is_array($options) || !array_key_exists('client', $options)) {
62 1
            throw new \RuntimeException('No client specified in options');
63
        }
64
65
        if (
66 6
            array_key_exists('excluded_backtrace_namespaces', $options) &&
67
            is_array($options['excluded_backtrace_namespaces'])
68 6
        ) {
69
            $this->excludedBacktraceNamespaces = array_merge(
70
                $this->excludedBacktraceNamespaces,
71
                $options['excluded_backtrace_namespaces']
72
            );
73
        }
74
75 6
        $this->client = $options['client'];
76 6
    }
77
78
    /**
79
     * Write a message to the log.
80
     *
81
     * @param array $event log data event
82
     */
83 5
    protected function doWrite(array $event)
84
    {
85 5
        $priority = $this->priorityMap[$event['priority']];
86
87 5
        $extra = $event['extra'];
88 5
        if ($extra instanceof Traversable) {
89 1
            $extra = iterator_to_array($extra);
90 5
        } elseif (!is_array($extra)) {
91 1
            $extra = [];
92 1
        }
93
94 5
        if ($this->contextContainsException($extra)) {
95
            /** @var \Throwable $exception */
96 2
            $exception = $extra['exception'];
97 2
            unset($extra['exception']);
98
99 2
            if ($event['message'] !== $exception->getMessage()) {
100 1
                $exception = new ContextException($event['message'], $exception->getCode(), $exception);
101 1
            }
102
103 2
            $this->client->getRaven()->captureException(
104 2
                $exception,
105
                [
106 2
                    'extra' => $this->sanitizeContextData($extra),
107 2
                    'level' => $priority,
108
                ]
109 2
            );
110
111 2
            return;
112
        }
113
114 3
        $stack = isset($extra['stack']) && is_array($extra['stack']) ? $extra['stack'] : null;
115
116 3
        if (!$stack) {
117 3
            $stack = $this->cleanBacktrace(debug_backtrace());
118 3
            if (!count($stack)) {
119
                $stack = false;
120
            }
121 3
        }
122
123 3
        $this->client->getRaven()->captureMessage(
124 3
            $event['message'],
125 3
            $this->sanitizeContextData($extra),
126 3
            $priority,
127
            $stack
0 ignored issues
show
Bug introduced by
It seems like $stack can also be of type array; however, Raven_Client::captureMessage() does only seem to accept boolean, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
128 3
        );
129 3
    }
130
131
    /**
132
     * Remove first backtrace items until it founds something different from loggers.
133
     *
134
     * @param array $backtrace
135
     *
136
     * @return array
137
     */
138 3
    protected function cleanBacktrace(array $backtrace)
139
    {
140 3
        $excludeNamespaces = $this->excludedBacktraceNamespaces;
141
142 3
        $lastItem = null;
143 3
        while (count($backtrace)) {
144 3
            $item = $backtrace[0];
145 3
            if (!array_key_exists('class', $item)) {
146
                break;
147
            }
148 3
            $exclude = false;
149 3
            foreach ($excludeNamespaces as $namespace) {
150 3
                if (0 === strpos($item['class'], $namespace)) {
151 3
                    $exclude = true;
152 3
                    break;
153
                }
154 3
            }
155 3
            if (!$exclude) {
156 3
                break;
157
            }
158
159 3
            $lastItem = array_shift($backtrace);
160 3
        }
161
162 3
        if ($lastItem) {
163 3
            array_unshift($backtrace, $lastItem);
164 3
        }
165
166 3
        return $backtrace;
167
    }
168
169
    /**
170
     * @param array $context
171
     *
172
     * @return array
173
     */
174 5
    protected function sanitizeContextData(array $context)
175
    {
176 5
        array_walk_recursive($context, [$this, 'sanitizeContextItem']);
177
178 5
        return $context;
179
    }
180
181
    /**
182
     * @param mixed $value
183
     */
184 4
    protected function sanitizeContextItem(&$value)
185
    {
186 4
        if ($value instanceof Traversable) {
187 1
            $value = iterator_to_array($value);
188 1
        }
189
190 4
        if (is_array($value)) {
191 1
            $value = $this->sanitizeContextData($value);
192 1
        }
193
194 4
        if (is_object($value)) {
195 2
            $value = method_exists($value, '__toString') ? (string) $value : get_class($value);
196 4
        } elseif (is_resource($value)) {
197 2
            $value = get_resource_type($value);
198 2
        }
199 4
    }
200
201
    /**
202
     * @param mixed $object
203
     *
204
     * @return bool
205
     */
206 2
    protected function objectIsThrowable($object)
207
    {
208 2
        return $object instanceof \Throwable || $object instanceof \Exception;
209
    }
210
211
    /**
212
     * @param array $context
213
     *
214
     * @return bool
215
     */
216 5
    protected function contextContainsException(array $context)
217
    {
218 5
        if (!array_key_exists('exception', $context)) {
219 3
            return false;
220
        }
221
222 2
        return $this->objectIsThrowable($context['exception']);
223
    }
224
}
225