Completed
Push — master ( 309536...2db7cf )
by Thomas Mauro
02:13
created

Sentry::doWrite()   C

Complexity

Conditions 8
Paths 39

Size

Total Lines 45
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 29
CRAP Score 8.017

Importance

Changes 0
Metric Value
dl 0
loc 45
ccs 29
cts 31
cp 0.9355
rs 5.3846
c 0
b 0
f 0
cc 8
eloc 26
nc 39
nop 1
crap 8.017
1
<?php
2
3
namespace Facile\SentryModule\Log\Writer;
4
5
use Facile\SentryModule\Service\Client;
6
use Zend\Log\Writer\AbstractWriter;
7
use Zend\Log\Logger;
8
use Raven_Client;
9
10
/**
11
 * Class Sentry.
12
 */
13
class Sentry extends AbstractWriter
14
{
15
    /**
16
     * @var Client
17
     */
18
    protected $client;
19
20
    /**
21
     * @var array
22
     */
23
    protected $priorityMap = [
24
        Logger::EMERG => Raven_Client::FATAL,
25
        Logger::ALERT => Raven_Client::ERROR,
26
        Logger::CRIT => Raven_Client::ERROR,
27
        Logger::ERR => Raven_Client::ERROR,
28
        Logger::WARN => Raven_Client::WARNING,
29
        Logger::NOTICE => Raven_Client::INFO,
30
        Logger::INFO => Raven_Client::INFO,
31
        Logger::DEBUG => Raven_Client::DEBUG,
32
    ];
33
34
    /**
35
     * Sentry constructor.
36
     *
37
     * @param array $options
38
     *
39
     * @throws \RuntimeException
40
     * @throws \Zend\Log\Exception\InvalidArgumentException
41
     */
42 6
    public function __construct(array $options)
43
    {
44 6
        parent::__construct($options);
45
46 6
        if (!array_key_exists('client', $options)) {
47 1
            throw new \RuntimeException('No client specified in options');
48
        }
49
50 5
        $this->client = $options['client'];
51 5
    }
52
53
    /**
54
     * Write a message to the log.
55
     *
56
     * @param array $event log data event
57
     */
58 4
    protected function doWrite(array $event)
59
    {
60 4
        $priority = $this->priorityMap[$event['priority']];
61
62 4
        $extra = $event['extra'];
63 4
        if ($extra instanceof \Traversable) {
64 1
            $extra = iterator_to_array($extra);
65 4
        } elseif (!is_array($extra)) {
66 1
            $extra = [];
67 1
        }
68
69 4
        if ($this->contextContainsException($extra)) {
70
            /** @var \Throwable $exception */
71 1
            $exception = $extra['exception'];
72 1
            unset($extra['exception']);
73
74 1
            $exception = new ContextException($event['message'], $exception->getCode(), $exception);
75
76 1
            $this->client->getRaven()->captureException(
77 1
                $exception,
78
                [
79 1
                    'extra' => $this->sanitizeContextData($extra),
80 1
                    'level' => $priority,
81
                ]
82 1
            );
83
84 1
            return;
85
        }
86
87 3
        $stack = isset($extra['stack']) && is_array($extra['stack']) ? $extra['stack'] : null;
88
89 3
        if (!$stack) {
90 3
            $stack = $this->cleanBacktrace(debug_backtrace());
91 3
            if (!count($stack)) {
92
                $stack = false;
93
            }
94 3
        }
95
96 3
        $this->client->getRaven()->captureMessage(
97 3
            $event['message'],
98 3
            $this->sanitizeContextData($extra),
99 3
            $priority,
100
            $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...
101 3
        );
102 3
    }
103
104
    /**
105
     * Remove first backtrace items until it founds something different from loggers
106
     *
107
     * @param array $backtrace
108
     * @return array
109
     */
110 3
    protected function cleanBacktrace(array $backtrace)
111
    {
112
        $excludeNamespaces = [
113 3
            'Facile\SentryModule\Log\\',
114 3
            'Psr\Log\\',
115
            'Zend\Log\\'
116 3
        ];
117
118 3
        $lastItem = null;
119 3
        while (count($backtrace)) {
120 3
            $item = $backtrace[0];
121 3
            if (!array_key_exists('class', $item)) {
122
                break;
123
            }
124 3
            $exclude = false;
125 3
            foreach ($excludeNamespaces as $namespace) {
126 3
                if (0 === strpos($item['class'], $namespace)) {
127 3
                    $exclude = true;
128 3
                    break;
129
                }
130 3
            }
131 3
            if (!$exclude) {
132 3
                break;
133
            }
134
135 3
            $lastItem = array_shift($backtrace);
136 3
        };
137
138 3
        if ($lastItem) {
139 3
            array_unshift($backtrace, $lastItem);
140 3
        }
141
142 3
        return $backtrace;
143
    }
144
145
    /**
146
     * @param array $context
147
     *
148
     * @return array
149
     */
150 4
    protected function sanitizeContextData(array $context)
151
    {
152 4
        array_walk_recursive($context, [$this, 'sanitizeContextItem']);
153
154 4
        return $context;
155
    }
156
157
    /**
158
     * @param mixed $value
159
     */
160 3
    protected function sanitizeContextItem(&$value)
161
    {
162 3
        if ($value instanceof \Traversable) {
163 1
            $value = $this->sanitizeContextData(iterator_to_array($value));
164 1
        }
165 3
        if (is_object($value)) {
166 2
            $value = method_exists($value, '__toString') ? (string) $value : get_class($value);
167 3
        } elseif (is_resource($value)) {
168 2
            $value = get_resource_type($value);
169 2
        }
170 3
    }
171
172
    /**
173
     * @param mixed $object
174
     *
175
     * @return bool
176
     */
177 1
    protected function objectIsThrowable($object)
178
    {
179 1
        return $object instanceof \Throwable || $object instanceof \Exception;
180
    }
181
182
    /**
183
     * @param array $context
184
     *
185
     * @return bool
186
     */
187 4
    protected function contextContainsException(array $context)
188
    {
189 4
        if (!array_key_exists('exception', $context)) {
190 3
            return false;
191
        }
192
193 1
        return $this->objectIsThrowable($context['exception']);
194
    }
195
}
196