Log   B
last analyzed

Complexity

Total Complexity 38

Size/Duplication

Total Lines 246
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
dl 0
loc 246
rs 8.3999
c 0
b 0
f 0
wmc 38

10 Methods

Rating   Name   Duplication   Size   Complexity  
A injectAdapter() 0 17 4
C _format() 0 51 15
A hasAdapter() 0 3 1
A addContext() 0 5 1
A getDefaultAdapter() 0 3 1
B setOptions() 0 20 5
A __construct() 0 3 1
A getAdapters() 0 14 4
A getAdapter() 0 7 2
A log() 0 16 4
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * Balloon
7
 *
8
 * @author      Raffael Sahli <[email protected]>
9
 * @copyright   Copryright (c) 2012-2017 gyselroth GmbH (https://gyselroth.com)
10
 * @license     GPL-3.0 https://opensource.org/licenses/GPL-3.0
11
 */
12
13
namespace Micro\Log;
14
15
use Micro\Container\AdapterAwareInterface;
16
use Micro\Log\Adapter\AdapterInterface;
17
use Psr\Log\AbstractLogger;
18
use Psr\Log\LoggerInterface;
19
use Psr\Log\LogLevel;
20
21
class Log extends AbstractLogger implements LoggerInterface, AdapterAwareInterface
22
{
23
    /**
24
     * Priorities.
25
     */
26
    const PRIORITIES = [
27
        LogLevel::EMERGENCY => 0,
28
        LogLevel::ALERT => 1,
29
        LogLevel::CRITICAL => 2,
30
        LogLevel::ERROR => 3,
31
        LogLevel::WARNING => 4,
32
        LogLevel::NOTICE => 5,
33
        LogLevel::INFO => 6,
34
        LogLevel::DEBUG => 7,
35
    ];
36
37
    /**
38
     * Adapters.
39
     *
40
     * @var array
41
     */
42
    protected $adapter = [];
43
44
    /**
45
     * static context.
46
     *
47
     * @var array
48
     */
49
    protected $context = [];
50
51
    /**
52
     * Initialize logger.
53
     *
54
     * @param iterable $config
55
     */
56
    public function __construct(? Iterable $config = null)
57
    {
58
        $this->setOptions($config);
59
    }
60
61
    /**
62
     * Set options.
63
     *
64
     * @param iterable $config
65
     *
66
     * @return Log
67
     */
68
    public function setOptions(? Iterable $config = null): self
69
    {
70
        if (null === $config) {
71
            return $this;
72
        }
73
74
        foreach ($config as $option => $value) {
75
            switch ($option) {
76
                case 'adapter':
77
                    foreach ($value as $name => $adapter) {
78
                        $this->injectAdapter($name, $adapter);
79
                    }
80
81
                break;
82
                default:
83
                    throw new Exception('invalid option '.$option.' given');
84
            }
85
        }
86
87
        return $this;
88
    }
89
90
    /**
91
     * {@inheritdoc}
92
     */
93
    public function getDefaultAdapter(): array
94
    {
95
        return [];
96
    }
97
98
    /**
99
     * {@inheritdoc}
100
     */
101
    public function hasAdapter(string $name): bool
102
    {
103
        return isset($this->adapter[$name]);
104
    }
105
106
    /**
107
     * {@inheritdoc}
108
     */
109
    public function injectAdapter($adapter, ?string $name = null): AdapterAwareInterface
110
    {
111
        if (!($adapter instanceof AdapterInterface)) {
112
            throw new Exception('adapter needs to implement AdapterInterface');
113
        }
114
115
        if (null === $name) {
116
            $name = get_class($adapter);
117
        }
118
119
        if ($this->hasAdapter($name)) {
120
            throw new Exception('log adapter '.$name.' is already registered');
121
        }
122
123
        $this->adapter[$name] = $adapter;
124
125
        return $this;
126
    }
127
128
    /**
129
     * {@inheritdoc}
130
     */
131
    public function getAdapter(string $name)
132
    {
133
        if (!$this->hasAdapter($name)) {
134
            throw new Exception('log adapter '.$name.' is not registered');
135
        }
136
137
        return $this->adapter[$name];
138
    }
139
140
    /**
141
     * Get adapters.
142
     *
143
     * @param array $adapters
144
     *
145
     * @return array
146
     */
147
    public function getAdapters(array $adapters = []): array
148
    {
149
        if (empty($adapter)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $adapter does not exist. Did you maybe mean $adapters?
Loading history...
150
            return $this->adapter;
151
        }
152
        $list = [];
153
        foreach ($adapter as $name) {
154
            if (!$this->hasAdapter($name)) {
155
                throw new Exception('log adapter '.$name.' is not registered');
156
            }
157
            $list[$name] = $this->adapter[$name];
158
        }
159
160
        return $list;
161
    }
162
163
    /**
164
     * Log message.
165
     *
166
     * @param string $level
167
     * @param string $message
168
     * @param array  $context
169
     *
170
     * @return bool
171
     */
172
    public function log($level, $message, array $context = []): bool
173
    {
174
        if (!array_key_exists($level, self::PRIORITIES)) {
175
            throw new Exception('log level '.$level.' is unkown');
176
        }
177
178
        foreach ($this->adapter as $adapter) {
179
            $prio = $adapter->getLevel();
180
181
            if (self::PRIORITIES[$level] <= $prio) {
182
                $msg = $this->_format($message, $adapter->getFormat(), $adapter->getDateFormat(), $level, $context);
183
                $adapter->log($level, $msg);
184
            }
185
        }
186
187
        return true;
0 ignored issues
show
Bug Best Practice introduced by
The expression return true returns the type true which is incompatible with the return type mandated by Psr\Log\LoggerInterface::log() of void.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
188
    }
189
190
    /**
191
     *  Add static context.
192
     *
193
     * @param string $name
194
     * @param string $value
195
     *
196
     * @return Log
197
     */
198
    public function addContext(string $name, string $value): self
199
    {
200
        $this->context[$name] = $value;
201
202
        return $this;
203
    }
204
205
    /**
206
     * Log message.
207
     *
208
     * @param string $message
209
     * @param string $format
210
     * @param string $date_format
211
     * @param string $level
212
     * @param array  $context
213
     *
214
     * @return string
215
     */
216
    protected function _format(string $message, string $format, string $date_format, string $level, array $context = []): string
217
    {
218
        $parsed = preg_replace_callback('/(\{(([a-z]\.*)+)\})/', function ($match) use ($message, $level, $date_format, $context) {
219
            $key = '';
220
            $context = array_merge($this->context, $context);
221
222
            if ($sub_context = strpos($match[2], '.')) {
223
                $parts = explode('.', $match[2]);
224
                $name = $parts[0];
225
                $key = $parts[1];
226
            } else {
227
                $name = $match[2];
228
            }
229
230
            switch ($name) {
231
                case 'level':
232
                    return $match[0] = $level;
233
                    break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
234
                case 'date':
235
                    return $match[0] = date($date_format);
236
                    break;
237
                case 'message':
238
                    $replace = [];
239
                    foreach ($context as $key => $val) {
240
                        if (!is_array($val) && (!is_object($val) || method_exists($val, '__toString'))) {
241
                            $replace['{'.$key.'}'] = $val;
242
                        } else {
243
                            $replace['{'.$key.'}'] = json_encode($val);
244
                        }
245
                    }
246
247
                    return $match[0] = strtr($message, $replace);
248
                    break;
249
                case 'context':
250
                    if ($sub_context) {
251
                        if (array_key_exists($key, $context)) {
252
                            if (!is_array($context[$key]) && (!is_object($context[$key]) || method_exists($context[$key], '__toString'))) {
253
                                return $match[0] = $context[$key];
254
                            }
255
256
                            return $match[0] = json_encode($context[$key]);
257
                        }
258
                    } else {
259
                        return $match[0] = json_encode($context);
260
                    }
261
262
                    break;
263
            }
264
        }, $format);
265
266
        return $parsed;
267
    }
268
}
269