Completed
Push — master ( 365154...1be49a )
by Raffael
02:37
created

Log   A

Complexity

Total Complexity 35

Size/Duplication

Total Lines 234
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 2

Importance

Changes 0
Metric Value
wmc 35
lcom 1
cbo 2
dl 0
loc 234
rs 9
c 0
b 0
f 0

8 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
B setOptions() 0 14 5
A addAdapter() 0 13 3
A getAdapter() 0 8 2
A getAdapters() 0 16 4
A log() 0 17 4
A addContext() 0 5 1
C _format() 0 51 15
1
<?php
2
declare(strict_types=1);
3
4
/**
5
 * Micro
6
 *
7
 * @copyright Copyright (c) 2017 gyselroth GmbH (https://gyselroth.com)
8
 * @license   MIT https://opensource.org/licenses/MIT
9
 */
10
11
namespace Micro;
12
13
use \Psr\Log\AbstractLogger;
14
use \Psr\Log\LogLevel;
15
use \Psr\Log\LoggerInterface;
16
use \Micro\Log\Adapter\AbstractAdapter;
17
use \Micro\Log\Adapter\AdapterInterface;
18
use \Micro\Config;
19
20
class Log extends AbstractLogger implements LoggerInterface
21
{
22
    /**
23
     * Priorities
24
     */
25
    const PRIORITIES = [
26
        LogLevel::EMERGENCY => 0,
27
        LogLevel::ALERT     => 1,
28
        LogLevel::CRITICAL  => 2,
29
        LogLevel::ERROR     => 3,
30
        LogLevel::WARNING   => 4,
31
        LogLevel::NOTICE    => 5,
32
        LogLevel::INFO      => 6,
33
        LogLevel::DEBUG     => 7,
34
    ];
35
36
37
    /**
38
     * Adapters
39
     *
40
     * @var array
41
     */
42
    protected $adapter = [];
43
44
45
    /**
46
     * static context
47
     *
48
     * @var array
49
     */
50
    protected $context = [];
51
52
53
    /**
54
     * Initialize logger
55
     *
56
     * @param   Iterable $config
57
     * @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...
58
     */
59
    public function __construct(?Iterable $config=null)
60
    {
61
        $this->setOptions($config);
62
    }
63
64
65
    /**
66
     * Set options
67
     *
68
     * @param  Iterable $config
69
     * @return Logger
70
     */
71
    public function setOptions(?Iterable $config=null)
72
    {
73
        if($config === null) {
74
            return $this;
75
        }
76
77
        foreach($config as $option => $value) {
78
            if(!isset($value['enabled']) || $value['enabled'] === '1') {
79
                $this->addAdapter($option, $value['class'], $value['config']);
80
            }
81
        }
82
        
83
        return $this;
84
    }
85
86
87
    /**
88
     * Add datatype
89
     *
90
     * @param  string $name
91
     * @param  string $class
92
     * @param  Iterable $config
93
     * @return AdapterInterface
94
     */
95
    public function addAdapter(string $name, string $class, ?Iterable $config=null): AdapterInterface
96
    {
97
        if(isset($this->adapter[$name])) {
98
            throw new Exception\InvalidArgument('log adapter '.$name.' is already registered');
99
        }
100
            
101
        $adapter = new $class($config);
102
        if(!($adapter instanceof AdapterInterface)) {
103
            throw new Exception\InvalidArgument('log adapter must include AdapterInterface interface');
104
        }
105
        $this->adapter[$name] = $adapter;
106
        return $adapter;
107
    }
108
109
110
    /**
111
     * Get adapter
112
     *      
113
     * @param  string $name
114
     * @return AdapterInterface
115
     */
116
    public function getAdapter(string $name): AdapterInterface
117
    {
118
        if(!isset($this->adapter[$name])) {
119
            throw new Exception('log adapter '.$name.' is not registered');
120
        }
121
122
        return $this->adapter[$name];
123
    }
124
125
126
    /**
127
     * Get adapters
128
     *      
129
     * @param  array $adapters
130
     * @return array
131
     */
132
    public function getAdapters(array $adapters=[]): array
0 ignored issues
show
Unused Code introduced by
The parameter $adapters is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
133
    {
134
        if(empty($adapter)) {
0 ignored issues
show
Bug introduced by
The variable $adapter does not exist. Did you mean $adapters?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
135
            return $this->adapter;
136
        } else {
137
            $list = [];
138
            foreach($adapter as $name) {
139
                if(!isset($this->adapter[$name])) {
140
                    throw new Exception('log adapter '.$name.' is not registered');
141
                }
142
                $list[$name] = $this->adapter[$name];
143
            }
144
145
            return $list;
146
        }
147
    }
148
149
150
    
151
    /**
152
     * Log message
153
     *
154
     * @param   string $level
155
     * @param   string $message
156
     * @param   array $context
157
     * @return  bool
158
     */
159
    public function log($level, $message, array $context=[]): bool
160
    {
161
        if (!array_key_exists($level, self::PRIORITIES)) {
162
            throw new Exception\InvalidArgument('log level '.$level.' is unkown');
163
        }
164
165
        foreach ($this->adapter as $adapter) {
166
            $prio = $adapter->getLevel();
167
 
168
           if (self::PRIORITIES[$level] <= $prio) {
169
                $msg = $this->_format($message, $adapter->getFormat(), $adapter->getDateFormat(), $level, $context);
170
                $adapter->log($level, $msg);
171
            }
172
        }
173
174
        return true;
175
    }
176
  
177
178
    /**
179
     *  Add static context
180
     *
181
     * @param  string $name
182
     * @param  string $value
183
     * @return Logger
184
     */
185
    public function addContext(string $name, string $value): Logger
186
    {
187
        $this->context[$name] = $value;
188
        return $this;
189
    }
190
191
192
    /**
193
     * Log message
194
     *
195
     * @param   string $message
196
     * @param   string $format
197
     * @param   string $date_format
198
     * @param   string $level
199
     * @param   array $context
200
     * @return  void
201
     */
202
    protected function _format(string $message, string $format, string $date_format, string $level, array $context=[]): string
203
    {
204
        $parsed = preg_replace_callback('/(\{(([a-z]\.*)+)\})/', function ($match) use ($message, $level, $date_format, $context) {
205
            $key = '';
206
            $context = array_merge($this->context, $context);
0 ignored issues
show
Bug introduced by
Consider using a different name than the imported variable $context, or did you forget to import by reference?

It seems like you are assigning to a variable which was imported through a use statement which was not imported by reference.

For clarity, we suggest to use a different name or import by reference depending on whether you would like to have the change visibile in outer-scope.

Change not visible in outer-scope

$x = 1;
$callable = function() use ($x) {
    $x = 2; // Not visible in outer scope. If you would like this, how
            // about using a different variable name than $x?
};

$callable();
var_dump($x); // integer(1)

Change visible in outer-scope

$x = 1;
$callable = function() use (&$x) {
    $x = 2;
};

$callable();
var_dump($x); // integer(2)
Loading history...
207
                    
208
            if ($sub_context = strpos($match[2], '.')) {
209
                $parts = explode('.', $match[2]);
210
                $name = $parts[0];
211
                $key = $parts[1];
212
            } else {
213
                $name = $match[2];
214
            }
215
            
216
            switch ($name) {
217
                case 'level':
218
                    return $match[0] = $level;
219
                    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...
220
                case 'date':
221
                    return $match[0] = date($date_format);
222
                    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...
223
                case 'message':
224
                    $replace = [];
225
                    foreach ($context as $key => $val) {
226
                        if (!is_array($val) && (!is_object($val) || method_exists($val, '__toString'))) {
227
                            $replace['{' . $key . '}'] = $val;
228
                        } else {
229
                            $replace['{' . $key . '}'] = json_encode($val);
230
                        }
231
                    }
232
233
                    return $match[0] = strtr($message, $replace);
234
                    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...
235
                case 'context':
236
                    if ($sub_context) {
237
                        if (array_key_exists($key, $context)) {
238
                            if (!is_array($context[$key]) && (!is_object($context[$key]) || method_exists($context[$key], '__toString'))) {
239
                                return $match[0] = $context[$key];
240
                            } else {
241
                                return $match[0] = json_encode($context[$key]);
242
                            }
243
                        }
244
                    } else {
245
                        return $match[0] = json_encode($context);
246
                    }
247
                    break;
248
            }
249
        }, $format);
250
        
251
        return $parsed;
252
    }
253
}
254