Completed
Push — master ( d38aea...aecaa2 )
by Russell
04:32 queued 11s
created

SentryAdaptor::setData()   B

Complexity

Conditions 8
Paths 6

Size

Total Lines 37

Duplication

Lines 14
Ratio 37.84 %

Importance

Changes 0
Metric Value
dl 14
loc 37
rs 8.0835
c 0
b 0
f 0
cc 8
nc 6
nop 2
1
<?php
2
3
/**
4
 * Class: SentryAdaptor.
5
 *
6
 * @author  Russell Michell 2017-2019 <[email protected]>
7
 * @package phptek/sentry
8
 */
9
10
namespace PhpTek\Sentry\Adaptor;
11
12
use Sentry\State\Hub;
13
use Sentry\ClientBuilder;
14
use Sentry\State\Scope;
15
use Sentry\ClientInterface;
16
use Sentry\Severity;
17
use SilverStripe\Core\Config\Configurable;
18
use SilverStripe\Core\Injector\Injector;
19
use PhpTek\Sentry\Adaptor\SentrySeverity;
20
21
/**
22
 * The SentryAdaptor provides a functionality bridge between the getsentry/sentry
23
 * PHP SDK and {@link SentryLogger} itself.
24
 */
25
class SentryAdaptor
26
{
27
    use Configurable;
28
29
    /**
30
     * @var ClientInterface
31
     */
32
    protected $sentry;
33
    
34
    /**
35
     * Internal storage for context. Used only in the case of non-exception
36
     * data sent to Sentry.
37
     * 
38
     * @var array
39
     */
40
    protected $context = [];
41
42
    /**
43
     * @return void
0 ignored issues
show
Comprehensibility Best Practice introduced by
Adding a @return annotation to constructors is generally not recommended as a constructor does not have a meaningful return value.

Adding a @return annotation to a constructor is not recommended, since a constructor does not have a meaningful return value.

Please refer to the PHP core documentation on constructors.

Loading history...
44
     */
45
    public function __construct()
46
    {
47
        $client = ClientBuilder::create($this->getOpts() ?: [])->getClient();
48
        Hub::setCurrent(new Hub($client));
49
50
        $this->sentry = $client;
51
    }
52
53
    /**
54
     * @return ClientInterface
55
     */
56
    public function getSDK() : ClientInterface
57
    {
58
        return $this->sentry;
59
    }
60
61
    /**
62
     * Configures Sentry "context" to display additional information about a SilverStripe
63
     * application's runtime and context.
64
     * 
65
     * @param  string $field
66
     * @param  mixed  $data
67
     * @return void
68
     * @throws SentryLogWriterException
69
     */
70
    public function setContext(string $field, $data) : void
71
    {
72
        $options = Hub::getCurrent()->getClient()->getOptions();
73
        
74
        switch ($field) {
75
            case 'env':
76
                $options->setEnvironment($data);
77
                $this->context['env'] = $data;
78
                break;
79 View Code Duplication
            case 'tags':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
80
                Hub::getCurrent()->configureScope(function (Scope $scope) use($data) : void {
81
                    foreach ($data as $tagName => $tagData) {
82
                        $scope->setTag($tagName, $tagData);
83
                        $this->context['tags'][$tagName] = $tagData;
84
                    }
85
                });
86
                break;
87
            case 'user':
88
                Hub::getCurrent()->configureScope(function (Scope $scope) use($data) : void {
89
                    $scope->setUser($data);
90
                    $this->context['user'] = $data;
91
                });
92
                break;
93 View Code Duplication
            case 'extra':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
94
                Hub::getCurrent()->configureScope(function (Scope $scope) use($data) : void {
95
                    foreach ($data as $extraKey => $extraData) {
96
                        $scope->setExtra($extraKey, $extraData);
97
                        $this->context['extra'][$extraKey] = $extraData;
98
                    }
99
                });
100
                break;
101
            case 'level':
102
                Hub::getCurrent()->configureScope(function (Scope $scope) use($data) : void {
103
                    $scope->setLevel(new Severity(SentrySeverity::process_severity($level = $data)));
104
                });
105
                break;
106
            default:
107
                $msg = sprintf('Unknown field "%s" passed to %s().', $field, __FUNCTION__);
108
                throw new SentryLogWriterException($msg);
109
        }
110
    }
111
    
112
    /**
113
     * Get _locally_ set contextual data, that we should be able to get from Sentry's
114
     * current {@link Scope}.
115
     * 
116
     * Note: This (re) sets data to a new instance of {@link Scope} for passing to 
117
     * captureMessage(). One would expect this to be set by default, as it is for
118
     * $record data sent to Sentry via captureException(), but it isn't.
119
     * 
120
     * @todo Investigate sentry/php-sdk's API for alternative ways to handle "manual"
121
     * logging, where data is not an exception and results in being passed to captureMessage().
122
     * 
123
     * @return Scope
124
     */
125
    public function getContext() : Scope
126
    {
127
        $scope = new Scope();
128
129
        $scope->setUser($this->context['user']);
130
        
131
        foreach ($this->context['tags'] as $tagKey => $tagData) {
132
            $scope->setTag($tagKey, $tagData);
133
        }
134
        
135
        foreach ($this->context['extra'] as $extraKey => $extraData) {
136
            $scope->setExtra($extraKey, $extraData);
137
        }
138
        
139
        return $scope;
140
    }
141
142
    /**
143
     * Get various userland options to pass to Raven. Includes detecting and setting
144
     * proxy options too.
145
     *
146
     * @param  string $opt
147
     * @return mixed  string|array|null depending on whether $opts is passed.
148
     */
149
    protected function getOpts(string $opt = '')
150
    {
151
        // Extract env-vars from YML config
152
        $opts = Injector::inst()->convertServiceProperty($this->config()->get('opts'));
153
154
        // Deal with proxy settings. Raven_Client permits host:port format but SilverStripe's
155
        // YML config only permits single backtick-enclosed env/consts per config
156
        if (!empty($opts['http_proxy'])) {
157
            if (!empty($opts['http_proxy']['host']) && !empty($opts['http_proxy']['port'])) {
158
                $opts['http_proxy'] = sprintf(
159
                    '%s:%s',
160
                    $opts['http_proxy']['host'],
161
                    $opts['http_proxy']['port']
162
                );
163
            }
164
        }
165
166
        if ($opt && !empty($opts[$opt])) {
167
            // Return one
168
            return $opts[$opt];
169
        } else if (!$opt) {
170
            // Return all
171
            return $opts;
172
        }
173
174
        return null;
175
    }
176
}
177