1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* This file is part of FacturaScripts |
4
|
|
|
* Copyright (C) 2017-2024 Carlos Garcia Gomez <[email protected]> |
5
|
|
|
* |
6
|
|
|
* This program is free software: you can redistribute it and/or modify |
7
|
|
|
* it under the terms of the GNU Lesser General Public License as |
8
|
|
|
* published by the Free Software Foundation, either version 3 of the |
9
|
|
|
* License, or (at your option) any later version. |
10
|
|
|
* |
11
|
|
|
* This program is distributed in the hope that it will be useful, |
12
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
13
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
14
|
|
|
* GNU Lesser General Public License for more details. |
15
|
|
|
* |
16
|
|
|
* You should have received a copy of the GNU Lesser General Public License |
17
|
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
18
|
|
|
*/ |
19
|
|
|
|
20
|
|
|
namespace FacturaScripts\Core\Base; |
21
|
|
|
|
22
|
|
|
use FacturaScripts\Core\Base\Contract\MiniLogStorageInterface; |
23
|
|
|
|
24
|
|
|
/** |
25
|
|
|
* Manage all log message information types. |
26
|
|
|
* |
27
|
|
|
* @author Carlos García Gómez <[email protected]> |
28
|
|
|
*/ |
29
|
|
|
final class MiniLog |
30
|
|
|
{ |
31
|
|
|
const DEFAULT_CHANNEL = 'master'; |
32
|
|
|
const LIMIT = 5000; |
33
|
|
|
|
34
|
|
|
/** @var string */ |
35
|
|
|
private $channel; |
36
|
|
|
|
37
|
|
|
/** @var array */ |
38
|
|
|
private static $context = []; |
39
|
|
|
|
40
|
|
|
/** @var array */ |
41
|
|
|
private static $data = []; |
42
|
|
|
|
43
|
|
|
/** @var bool */ |
44
|
|
|
private static $disabled = false; |
45
|
|
|
|
46
|
|
|
/** @var MiniLogStorageInterface */ |
47
|
|
|
private static $storage; |
48
|
|
|
|
49
|
|
|
/** @var Translator|null */ |
50
|
|
|
private $translator; |
51
|
|
|
|
52
|
|
|
public function __construct(string $channel = '', $translator = null) |
53
|
|
|
{ |
54
|
|
|
$this->channel = empty($channel) ? self::DEFAULT_CHANNEL : $channel; |
55
|
|
|
$this->translator = $translator; |
56
|
|
|
} |
57
|
|
|
|
58
|
|
|
/** |
59
|
|
|
* Clears all data for one or all channels. |
60
|
|
|
* |
61
|
|
|
* @param string $channel |
62
|
|
|
*/ |
63
|
|
|
public static function clear(string $channel = ''): void |
64
|
|
|
{ |
65
|
|
|
if (empty($channel)) { |
66
|
|
|
self::$data = []; |
67
|
|
|
return; |
68
|
|
|
} |
69
|
|
|
|
70
|
|
|
foreach (self::$data as $key => $item) { |
71
|
|
|
if ($item['channel'] === $channel) { |
72
|
|
|
unset(self::$data[$key]); |
73
|
|
|
} |
74
|
|
|
} |
75
|
|
|
} |
76
|
|
|
|
77
|
|
|
/** |
78
|
|
|
* Critical conditions. |
79
|
|
|
* |
80
|
|
|
* Example: Application component unavailable, unexpected exception. |
81
|
|
|
* |
82
|
|
|
* @param string $message |
83
|
|
|
* @param array $context |
84
|
|
|
*/ |
85
|
|
|
public function critical(string $message, array $context = []): void |
86
|
|
|
{ |
87
|
|
|
$this->log('critical', $message, $context); |
88
|
|
|
} |
89
|
|
|
|
90
|
|
|
/** |
91
|
|
|
* Detailed debug information. |
92
|
|
|
* |
93
|
|
|
* @param string $message |
94
|
|
|
* @param array $context |
95
|
|
|
*/ |
96
|
|
|
public function debug(string $message, array $context = []): void |
97
|
|
|
{ |
98
|
|
|
if (FS_DEBUG) { |
99
|
|
|
$this->log('debug', $message, $context); |
100
|
|
|
} |
101
|
|
|
} |
102
|
|
|
|
103
|
|
|
public static function disable(bool $value = true): void |
104
|
|
|
{ |
105
|
|
|
self::$disabled = $value; |
106
|
|
|
} |
107
|
|
|
|
108
|
|
|
/** |
109
|
|
|
* Runtime errors that do not require immediate action but should typically |
110
|
|
|
* be logged and monitored. |
111
|
|
|
* |
112
|
|
|
* @param string $message |
113
|
|
|
* @param array $context |
114
|
|
|
*/ |
115
|
|
|
public function error(string $message, array $context = []): void |
116
|
|
|
{ |
117
|
|
|
$this->log('error', $message, $context); |
118
|
|
|
} |
119
|
|
|
|
120
|
|
|
/** |
121
|
|
|
* Gets the stored context value for a given key. |
122
|
|
|
* |
123
|
|
|
* @param string $key |
124
|
|
|
* |
125
|
|
|
* @return string |
126
|
|
|
*/ |
127
|
|
|
public static function getContext(string $key): string |
128
|
|
|
{ |
129
|
|
|
return self::$context[$key] ?? ''; |
130
|
|
|
} |
131
|
|
|
|
132
|
|
|
/** |
133
|
|
|
* Interesting information, advices. |
134
|
|
|
* |
135
|
|
|
* @param string $message |
136
|
|
|
* @param array $context |
137
|
|
|
*/ |
138
|
|
|
public function info(string $message, array $context = []): void |
139
|
|
|
{ |
140
|
|
|
$this->log('info', $message, $context); |
141
|
|
|
} |
142
|
|
|
|
143
|
|
|
/** |
144
|
|
|
* Normal but significant events. |
145
|
|
|
* |
146
|
|
|
* @param string $message |
147
|
|
|
* @param array $context |
148
|
|
|
*/ |
149
|
|
|
public function notice(string $message, array $context = []): void |
150
|
|
|
{ |
151
|
|
|
$this->log('notice', $message, $context); |
152
|
|
|
} |
153
|
|
|
|
154
|
|
|
/** |
155
|
|
|
* Returns all messages for a given channel (or all channels) and some levels. |
156
|
|
|
* |
157
|
|
|
* @param string $channel |
158
|
|
|
* @param array $levels |
159
|
|
|
* |
160
|
|
|
* @return array |
161
|
|
|
*/ |
162
|
|
|
public static function read(string $channel = '', array $levels = []): array |
163
|
|
|
{ |
164
|
|
|
$messages = []; |
165
|
|
|
foreach (self::$data as $data) { |
166
|
|
|
if ($channel && $data['channel'] != $channel) { |
167
|
|
|
continue; |
168
|
|
|
} |
169
|
|
|
|
170
|
|
|
if ($levels && false === in_array($data['level'], $levels)) { |
|
|
|
|
171
|
|
|
continue; |
172
|
|
|
} |
173
|
|
|
|
174
|
|
|
$messages[] = $data; |
175
|
|
|
} |
176
|
|
|
|
177
|
|
|
return $messages; |
178
|
|
|
} |
179
|
|
|
|
180
|
|
|
/** |
181
|
|
|
* Stores all messages on the default storage. |
182
|
|
|
* |
183
|
|
|
* @param string $channel |
184
|
|
|
* |
185
|
|
|
* @return bool |
186
|
|
|
*/ |
187
|
|
|
public static function save(string $channel = ''): bool |
188
|
|
|
{ |
189
|
|
|
if (!isset(self::$storage)) { |
190
|
|
|
self::$storage = new MiniLogStorage(); |
191
|
|
|
} |
192
|
|
|
|
193
|
|
|
$data = empty($channel) ? self::$data : self::read($channel); |
194
|
|
|
self::clear($channel); |
195
|
|
|
|
196
|
|
|
self::disable(); |
197
|
|
|
$return = self::$storage->save($data); |
198
|
|
|
self::disable(false); |
199
|
|
|
|
200
|
|
|
return $return; |
201
|
|
|
} |
202
|
|
|
|
203
|
|
|
/** |
204
|
|
|
* Sets the context value for a given key. |
205
|
|
|
* |
206
|
|
|
* @param string $key |
207
|
|
|
* @param string $value |
208
|
|
|
*/ |
209
|
|
|
public static function setContext(string $key, string $value): void |
210
|
|
|
{ |
211
|
|
|
self::$context[$key] = $value; |
212
|
|
|
} |
213
|
|
|
|
214
|
|
|
/** |
215
|
|
|
* Sets a new storage. |
216
|
|
|
* |
217
|
|
|
* @param MiniLogStorageInterface $storage |
218
|
|
|
*/ |
219
|
|
|
public static function setStorage(MiniLogStorageInterface $storage): void |
220
|
|
|
{ |
221
|
|
|
self::$storage = $storage; |
222
|
|
|
} |
223
|
|
|
|
224
|
|
|
/** |
225
|
|
|
* Exceptional occurrences that are not errors. |
226
|
|
|
* |
227
|
|
|
* Example: Use of deprecated APIs, poor use of an API, undesirable things |
228
|
|
|
* that are not necessarily wrong. |
229
|
|
|
* |
230
|
|
|
* @param string $message |
231
|
|
|
* @param array $context |
232
|
|
|
*/ |
233
|
|
|
public function warning(string $message, array $context = []): void |
234
|
|
|
{ |
235
|
|
|
$this->log('warning', $message, $context); |
236
|
|
|
} |
237
|
|
|
|
238
|
|
|
/** |
239
|
|
|
* Logs with an arbitrary level. |
240
|
|
|
* |
241
|
|
|
* @param string $level |
242
|
|
|
* @param string $message |
243
|
|
|
* @param array $context |
244
|
|
|
*/ |
245
|
|
|
private function log(string $level, string $message, array $context = []): void |
246
|
|
|
{ |
247
|
|
|
if (empty($message) || self::$disabled) { |
248
|
|
|
return; |
249
|
|
|
} |
250
|
|
|
|
251
|
|
|
// if we find this message in the log, we increase the counter |
252
|
|
|
$finalContext = array_merge($context, self::$context); |
253
|
|
|
$transMessage = is_null($this->translator) ? $message : $this->translator->trans($message, $context); |
254
|
|
|
foreach (self::$data as $key => $value) { |
255
|
|
|
if ($value['channel'] === $this->channel && $value['level'] === $level && |
256
|
|
|
$value['message'] === $transMessage && $value['context'] === $finalContext) { |
257
|
|
|
self::$data[$key]['count']++; |
258
|
|
|
return; |
259
|
|
|
} |
260
|
|
|
} |
261
|
|
|
|
262
|
|
|
// add message |
263
|
|
|
self::$data[] = [ |
264
|
|
|
'channel' => $this->channel, |
265
|
|
|
'context' => $finalContext, |
266
|
|
|
'count' => 1, |
267
|
|
|
'level' => $level, |
268
|
|
|
'message' => $transMessage, |
269
|
|
|
'original' => $message, |
270
|
|
|
'time' => $context['time'] ?? microtime(true), |
271
|
|
|
]; |
272
|
|
|
$this->reduce(); |
273
|
|
|
} |
274
|
|
|
|
275
|
|
|
/** |
276
|
|
|
* Saves on the default storage and clear all data. |
277
|
|
|
*/ |
278
|
|
|
protected function reduce(): void |
279
|
|
|
{ |
280
|
|
|
if (count(self::$data) > self::LIMIT) { |
281
|
|
|
self::save($this->channel); |
282
|
|
|
} |
283
|
|
|
} |
284
|
|
|
} |
285
|
|
|
|
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.