1 | <?php |
||||
2 | |||||
3 | /* |
||||
4 | * This file is part of the Koded package. |
||||
5 | * |
||||
6 | * (c) Mihail Binev <[email protected]> |
||||
7 | * |
||||
8 | * Please view the LICENSE distributed with this source code |
||||
9 | * for the full copyright and license information. |
||||
10 | * |
||||
11 | */ |
||||
12 | |||||
13 | namespace Koded\Logging; |
||||
14 | |||||
15 | use Koded\Logging\Processors\{Cli, Processor}; |
||||
16 | use DateTimeZone; |
||||
17 | use Psr\Log\LoggerTrait; |
||||
18 | use Throwable; |
||||
19 | |||||
20 | /** |
||||
21 | * Class Log for logging different types of application messages. |
||||
22 | * |
||||
23 | * $log->notice('foo'); |
||||
24 | * $log->warn('bar'); |
||||
25 | * |
||||
26 | * CONFIGURATION PARAMETERS (Log class) |
||||
27 | * |
||||
28 | * - deferred (bool) [optional], default: false |
||||
29 | * A flag to set the Log instance how to dump messages. |
||||
30 | * Set to TRUE if you want to process all accumulated messages |
||||
31 | * at shutdown time. Otherwise, the default behavior is to process |
||||
32 | * the message immediately after the LoggerInterface method is called. |
||||
33 | * |
||||
34 | * - loggers (array) |
||||
35 | * An array of log processors. Every processor is defined in array with it's own |
||||
36 | * configuration parameters, but ALL must have the following: |
||||
37 | * |
||||
38 | * - class (string) [required] |
||||
39 | * The name of the log processor class. |
||||
40 | * Can create multiple same instances with different config |
||||
41 | * parameters. |
||||
42 | * |
||||
43 | * - levels (integer) [optional], default: -1 (for all levels) |
||||
44 | * Packed integer for bitwise comparison. See the constants in this |
||||
45 | * class. |
||||
46 | * |
||||
47 | * Example: Log::INFO | Log::ERROR | Log::ALERT |
||||
48 | * Processor with these log levels will store only |
||||
49 | * info, error and warning type messages. |
||||
50 | * |
||||
51 | * - dateformat (string) [optional], default: d/m/Y H:i:s.u |
||||
52 | * The date format for the log message. |
||||
53 | * |
||||
54 | * - timezone (string) [optional], default: UTC |
||||
55 | * The desired timezone for the DateTimeZone object. |
||||
56 | * |
||||
57 | * |
||||
58 | * CONFIGURATION PARAMETERS (Processor class) |
||||
59 | * Every processor may have it's own specific parameters. |
||||
60 | * |
||||
61 | */ |
||||
62 | class Log implements Logger |
||||
63 | { |
||||
64 | use LoggerTrait; |
||||
65 | |||||
66 | /** |
||||
67 | * @var bool Flag to control the messages processing |
||||
68 | */ |
||||
69 | private bool $deferred = false; |
||||
70 | |||||
71 | /** |
||||
72 | * @var string The date format for the message. |
||||
73 | */ |
||||
74 | private string $dateFormat; |
||||
75 | |||||
76 | /** |
||||
77 | * @var DateTimeZone Valid timezone for the message. |
||||
78 | */ |
||||
79 | private DateTimeZone|bool $timezone; |
||||
80 | |||||
81 | /** |
||||
82 | * @var Processor[] Hash with all registered log processors. |
||||
83 | */ |
||||
84 | private array $processors = []; |
||||
85 | |||||
86 | /** |
||||
87 | * @var array List with all accumulated messages. |
||||
88 | */ |
||||
89 | private array $messages = []; |
||||
90 | |||||
91 | /** |
||||
92 | * Creates all requested log processors. |
||||
93 | * |
||||
94 | * @param array $settings |
||||
95 | 9 | */ |
|||
96 | public function __construct(array $settings) |
||||
97 | 9 | { |
|||
98 | 9 | $this->deferred = (bool)($settings['deferred'] ?? false); |
|||
99 | 9 | $this->dateFormat = (string)($settings['dateformat'] ?? 'd/m/Y H:i:s.u'); |
|||
100 | if (false === $this->timezone = @\timezone_open((string)($settings['timezone'] ?? 'UTC'))) { |
||||
0 ignored issues
–
show
|
|||||
101 | 9 | $this->timezone = \timezone_open('UTC'); |
|||
102 | 1 | } |
|||
103 | foreach ((array)($settings['loggers'] ?? []) as $processor) { |
||||
104 | $this->attach(new $processor['class']($processor)); |
||||
105 | 9 | } |
|||
106 | 3 | if ($this->deferred) { |
|||
107 | \register_shutdown_function([$this, 'process']); |
||||
108 | 9 | } |
|||
109 | } |
||||
110 | 3 | ||||
111 | public function attach(Processor $processor): Logger |
||||
112 | 3 | { |
|||
113 | 3 | if (0 !== $processor->levels()) { |
|||
114 | $this->processors[\spl_object_hash($processor)] = $processor; |
||||
115 | } |
||||
116 | 3 | return $this; |
|||
117 | } |
||||
118 | |||||
119 | 3 | public function log($level, $message, array $context = []) |
|||
120 | { |
||||
121 | try { |
||||
122 | 3 | $levelName = \strtoupper($level); |
|||
123 | 3 | $level = \constant('static::' . $levelName); |
|||
124 | 1 | } catch (Throwable) { |
|||
125 | 1 | $levelName = 'LOG'; |
|||
126 | 1 | $level = -1; |
|||
127 | } |
||||
128 | $this->messages[] = [ |
||||
129 | 3 | 'level' => $level, |
|||
130 | 3 | 'levelname' => $levelName, |
|||
131 | 3 | 'message' => $this->formatMessage($message, $context), |
|||
132 | 3 | 'timestamp' => \date_create_immutable('now', $this->timezone ?: null)->format($this->dateFormat), |
|||
0 ignored issues
–
show
It seems like
$this->timezone ?: null can also be of type true ; however, parameter $timezone of date_create_immutable() does only seem to accept DateTimeZone|null , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
133 | 3 | ]; |
|||
134 | $this->deferred || $this->process(); |
||||
135 | } |
||||
136 | 3 | ||||
137 | 3 | public function process(): void |
|||
138 | { |
||||
139 | foreach ($this->processors as $processor) { |
||||
140 | $processor->update($this->messages); |
||||
141 | } |
||||
142 | $this->messages = []; |
||||
143 | } |
||||
144 | |||||
145 | public function exception(Throwable $e, Processor $processor = null): void |
||||
146 | { |
||||
147 | 3 | $logger = $processor ?? new Cli([]); |
|||
148 | $message = $e->getMessage() . PHP_EOL . ' -- [Trace]: ' . $e->getTraceAsString(); |
||||
149 | 3 | ||||
150 | 3 | $this->attach($logger)->critical($message); |
|||
151 | 1 | $this->process(); |
|||
152 | $this->detach($logger); |
||||
153 | } |
||||
154 | 3 | ||||
155 | public function detach(Processor $processor): Logger |
||||
156 | { |
||||
157 | 1 | unset($this->processors[\spl_object_hash($processor)]); |
|||
158 | return $this; |
||||
159 | 1 | } |
|||
160 | 1 | ||||
161 | /** |
||||
162 | * Parses the message as in the interface specification. |
||||
163 | 1 | * |
|||
164 | 1 | * @param object|string $message A string or object that implements __toString |
|||
165 | * @param array $params [optional] Arbitrary data with key-value pairs replacements |
||||
166 | 1 | * |
|||
167 | * @return string |
||||
168 | 1 | */ |
|||
169 | 1 | private function formatMessage(object|string $message, array $params = []): string |
|||
170 | { |
||||
171 | 1 | $replacements = []; |
|||
172 | 1 | foreach ($params as $k => $v) { |
|||
173 | 1 | $replacements['{' . $k . '}'] = $v; |
|||
174 | 1 | } |
|||
175 | return \strtr((string)$message, $replacements); |
||||
176 | 2 | } |
|||
177 | } |
||||
178 |
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.
For example, imagine you have a variable
$accountId
that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to theid
property of an instance of theAccount
class. This class holds a proper account, so the id value must no longer be false.Either this assignment is in error or a type check should be added for that assignment.