1
|
|
|
<?php |
2
|
|
|
declare(strict_types=1); |
3
|
|
|
|
4
|
|
|
namespace SKien\XLogger; |
5
|
|
|
|
6
|
|
|
use Psr\Log\AbstractLogger; |
7
|
|
|
use Psr\Log\InvalidArgumentException; |
8
|
|
|
use Psr\Log\LogLevel; |
9
|
|
|
|
10
|
|
|
/** |
11
|
|
|
* abstract class for several types of logger. |
12
|
|
|
* |
13
|
|
|
* Class implements the PSR-3 LoggerInferface through AbstractLogger. |
14
|
|
|
* |
15
|
|
|
* @package XLogger |
16
|
|
|
* @author Stefanius <[email protected]> |
17
|
|
|
* @copyright MIT License - see the LICENSE file for details |
18
|
|
|
*/ |
19
|
|
|
abstract class XLogger extends AbstractLogger |
20
|
|
|
{ |
21
|
|
|
/** include IP-adress in log item */ |
22
|
|
|
const LOG_IP = 0x01; |
23
|
|
|
/** include backtrace (caller) in log item */ |
24
|
|
|
const LOG_BT = 0x02; |
25
|
|
|
/** include user in log item */ |
26
|
|
|
const LOG_USER = 0x04; |
27
|
|
|
/** include user-agent in log item */ |
28
|
|
|
const LOG_UA = 0x08; |
29
|
|
|
|
30
|
|
|
/** @var int levels to include in the log (bitmask of internal levels) */ |
31
|
|
|
protected int $iLogLevel = 0; |
32
|
|
|
/** @var int options bitmask */ |
33
|
|
|
protected int $iOptions = self::LOG_IP | self::LOG_USER | self::LOG_UA; |
34
|
|
|
/** @var string username for logging (if LOG_USER is set) */ |
35
|
|
|
protected string $strUser = ''; |
36
|
|
|
/** @var string path to store the logfile */ |
37
|
|
|
protected string $strPath = '.'; |
38
|
|
|
/** @var string filename of the logfile */ |
39
|
|
|
protected string $strFilename = ''; |
40
|
|
|
|
41
|
|
|
/** |
42
|
|
|
* Init logging level and remote username (if set). |
43
|
|
|
* @see XLogger::setLogLevel() |
44
|
|
|
* @param string $level the min. `LogLevel` to be logged |
45
|
|
|
*/ |
46
|
|
|
public function __construct(string $level = LogLevel::DEBUG) |
47
|
|
|
{ |
48
|
|
|
$this->setLogLevel($level); |
49
|
|
|
// init with remote user, if available |
50
|
|
|
$this->strUser = isset($_SERVER["REMOTE_USER"]) ? $_SERVER["REMOTE_USER"] : ''; |
51
|
|
|
} |
52
|
|
|
|
53
|
|
|
/** |
54
|
|
|
* Set the level from witch items should be logged. |
55
|
|
|
* All entries with a lower level than the specified are ignored. <br/> |
56
|
|
|
* The relevance of the defined PSR-3 level from hight to low are: <br/><ul> |
57
|
|
|
* <li> EMERGENCY </li> |
58
|
|
|
* <li> ALERT </li> |
59
|
|
|
* <li> CRITICAL </li> |
60
|
|
|
* <li> ERROR </li> |
61
|
|
|
* <li> WARNING </li> |
62
|
|
|
* <li> NOTICE </li> |
63
|
|
|
* <li>INFO </li> |
64
|
|
|
* <li> DEBUG </li></ul> |
65
|
|
|
* |
66
|
|
|
* @param string $strLogLevel the min. `LogLevel` to be logged |
67
|
|
|
* @return void |
68
|
|
|
*/ |
69
|
|
|
public function setLogLevel(string $strLogLevel) : void |
70
|
|
|
{ |
71
|
|
|
$this->iLogLevel = $this->getIntLevel($strLogLevel); |
72
|
|
|
} |
73
|
|
|
|
74
|
|
|
/** |
75
|
|
|
* Set Options for logging. |
76
|
|
|
* Use any combination of: <ul> |
77
|
|
|
* <li> XLogger::LOG_IP : include IP-adress in log item </li> |
78
|
|
|
* <li> XLogger::LOG_BT : include backtrace 'filename (line)' in log item </li> |
79
|
|
|
* <li> XLogger::LOG_USER : include user in log item </li> |
80
|
|
|
* <li> XLogger::LOG_UA : include user-agent in log item </li></ul> |
81
|
|
|
* |
82
|
|
|
* @param int $iOptions any combination (bitwise or, '|') of the flags described |
83
|
|
|
* @return void |
84
|
|
|
*/ |
85
|
|
|
public function setOptions(int $iOptions) : void |
86
|
|
|
{ |
87
|
|
|
$this->iOptions = $iOptions; |
88
|
|
|
} |
89
|
|
|
|
90
|
|
|
/** |
91
|
|
|
* Set the username. |
92
|
|
|
* @param string $strUser |
93
|
|
|
* @return void |
94
|
|
|
*/ |
95
|
|
|
public function setUser(string $strUser) : void |
96
|
|
|
{ |
97
|
|
|
$this->strUser = $strUser; |
98
|
|
|
} |
99
|
|
|
|
100
|
|
|
/** |
101
|
|
|
* Set the path and filename for the logfile. |
102
|
|
|
* In order to be able to assign logfiles chronologically and / or to a user, it is |
103
|
|
|
* possible to use placeholders in the file name or path, which are replaced accordingly |
104
|
|
|
* before the file is created. |
105
|
|
|
* Following placeholders are supported: <br/><ul> |
106
|
|
|
* <li> {date} : will be replaced by current date (Format 'YYYY-MM-DD') </li> |
107
|
|
|
* <li> {month} : will be replaced by current month (Format 'YYYY-MM') </li> |
108
|
|
|
* <li> {year} : will be replaced by current year (Format 'YYYY') </li> |
109
|
|
|
* <li> {week} : will be replaced by current ISO-8601 week (Format 'YYYY_WW') </li> |
110
|
|
|
* <li> {name} : will be replaced by the username </li></ul> |
111
|
|
|
* |
112
|
|
|
* > Note: <br/> |
113
|
|
|
* > If you use the placeholder for the user name, this have to be set BEFORE the call of |
114
|
|
|
* this method. In the username, all characters except A-Z, a-z, 0-9, '_' and '-' are |
115
|
|
|
* filtered out (to always get a valid file name)! |
116
|
|
|
* @param string $strFullpath |
117
|
|
|
* @return void |
118
|
|
|
*/ |
119
|
|
|
public function setFullpath(string $strFullpath) : void |
120
|
|
|
{ |
121
|
|
|
$this->closeLogfile(); |
122
|
|
|
if (strlen($strFullpath) > 0) { |
123
|
|
|
$strFullpath = $this->replacePathPlaceholder($strFullpath); |
124
|
|
|
$this->strPath = pathinfo($strFullpath, PATHINFO_DIRNAME); |
|
|
|
|
125
|
|
|
$this->strFilename = pathinfo($strFullpath, PATHINFO_BASENAME); |
|
|
|
|
126
|
|
|
} |
127
|
|
|
} |
128
|
|
|
|
129
|
|
|
/** |
130
|
|
|
* Set the path for the logfile. |
131
|
|
|
* Some placeholders can be used for date/month/year/week. |
132
|
|
|
* @param string $strPath |
133
|
|
|
* @return void |
134
|
|
|
* @see XLogger::setFullpath() |
135
|
|
|
*/ |
136
|
|
|
public function setPath(string $strPath) : void |
137
|
|
|
{ |
138
|
|
|
$this->closeLogfile(); |
139
|
|
|
$this->strPath = $this->replacePathPlaceholder($strPath); |
140
|
|
|
} |
141
|
|
|
|
142
|
|
|
/** |
143
|
|
|
* Set the filename for the logfile. |
144
|
|
|
* Some placeholders can be used for date/month/year/week. |
145
|
|
|
* @param string $strFilename |
146
|
|
|
* @return void |
147
|
|
|
* @see XLogger::setFullpath() |
148
|
|
|
*/ |
149
|
|
|
public function setFilename(string $strFilename) : void |
150
|
|
|
{ |
151
|
|
|
$this->closeLogfile(); |
152
|
|
|
$this->strFilename = $this->replacePathPlaceholder($strFilename); |
153
|
|
|
} |
154
|
|
|
|
155
|
|
|
/** |
156
|
|
|
* Get current filename (may be some placeholders are supplemented). |
157
|
|
|
* @return string |
158
|
|
|
* @see XLogger::setFullpath() |
159
|
|
|
*/ |
160
|
|
|
public function getFilename() : string |
161
|
|
|
{ |
162
|
|
|
return $this->strFilename; |
163
|
|
|
} |
164
|
|
|
|
165
|
|
|
/** |
166
|
|
|
* Get full path of the logfile. |
167
|
|
|
* @return string |
168
|
|
|
* @see XLogger::setFullpath() |
169
|
|
|
*/ |
170
|
|
|
public function getFullpath() : string |
171
|
|
|
{ |
172
|
|
|
$strFullPath = rtrim($this->strPath, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $this->strFilename; |
173
|
|
|
return $strFullPath; |
174
|
|
|
} |
175
|
|
|
|
176
|
|
|
/** |
177
|
|
|
* may be implemented in extending classes |
178
|
|
|
* @internal |
179
|
|
|
*/ |
180
|
|
|
public function reset() : void |
181
|
|
|
{ |
182
|
|
|
} |
183
|
|
|
|
184
|
|
|
/** |
185
|
|
|
* Check, if item for requested level should be logged. |
186
|
|
|
* @param string $level |
187
|
|
|
* @return bool |
188
|
|
|
*/ |
189
|
|
|
protected function logLevel($level) : bool |
190
|
|
|
{ |
191
|
|
|
return ($this->iLogLevel <= $this->getIntLevel($level)); |
192
|
|
|
} |
193
|
|
|
|
194
|
|
|
/** |
195
|
|
|
* Replace placeholders with the coresponding values. |
196
|
|
|
* @param mixed $message string or object implements __toString() method |
197
|
|
|
* @param array $context |
198
|
|
|
* @return string |
199
|
|
|
*/ |
200
|
|
|
protected function replaceContext($message, array $context = array()) : string |
201
|
|
|
{ |
202
|
|
|
// build a replacement array with braces around the context keys |
203
|
|
|
$replace = array(); |
204
|
|
|
foreach ($context as $key => $val) { |
205
|
|
|
// check that the value can be cast to string |
206
|
|
|
if (!is_array($val) && (!is_object($val) || method_exists($val, '__toString'))) { |
207
|
|
|
$replace['{' . $key . '}'] = $val; |
208
|
|
|
} |
209
|
|
|
} |
210
|
|
|
// interpolate replacement values into the message and return |
211
|
|
|
$strMessage = (string)$message; |
212
|
|
|
$strMessage = strtr($strMessage, $replace); |
213
|
|
|
|
214
|
|
|
return $strMessage; |
215
|
|
|
} |
216
|
|
|
|
217
|
|
|
/** |
218
|
|
|
* Get the caller from backtrace in format "<filename>(<line>)". |
219
|
|
|
* Document root is cut off from the full path. |
220
|
|
|
* @return string |
221
|
|
|
*/ |
222
|
|
|
protected function getCaller() : string |
223
|
|
|
{ |
224
|
|
|
$strCaller = ''; |
225
|
|
|
$aBacktrace = debug_backtrace(); |
226
|
|
|
while (($aCaller = array_shift($aBacktrace)) !== null) { |
227
|
|
|
// just get latest call outside of the logger interface |
228
|
|
|
// if ($aCaller['class'] != get_class() ) { |
229
|
|
|
if (!is_subclass_of($aCaller['class'], 'Psr\Log\AbstractLogger')) { |
230
|
|
|
break; |
231
|
|
|
} |
232
|
|
|
} |
233
|
|
|
if ($aCaller) { |
234
|
|
|
// the base path on server isn't from interest.. |
235
|
|
|
$strFile = str_replace($_SERVER['DOCUMENT_ROOT'], '', $aCaller['file']); |
236
|
|
|
$strCaller = $strFile . ' (' . $aCaller['line'] . ')'; |
237
|
|
|
} |
238
|
|
|
return $strCaller; |
239
|
|
|
} |
240
|
|
|
|
241
|
|
|
/** |
242
|
|
|
* |
243
|
|
|
* @param string $level |
244
|
|
|
* @throws InvalidArgumentException |
245
|
|
|
* @return int |
246
|
|
|
*/ |
247
|
|
|
protected function getIntLevel(string $level) : int |
248
|
|
|
{ |
249
|
|
|
$aLevel = array( |
250
|
|
|
LogLevel::EMERGENCY => 7, |
251
|
|
|
LogLevel::ALERT => 6, |
252
|
|
|
LogLevel::CRITICAL => 5, |
253
|
|
|
LogLevel::ERROR => 4, |
254
|
|
|
LogLevel::WARNING => 3, |
255
|
|
|
LogLevel::NOTICE => 2, |
256
|
|
|
LogLevel::INFO => 1, |
257
|
|
|
LogLevel::DEBUG => 0 |
258
|
|
|
); |
259
|
|
|
if (!isset($aLevel[$level])) { |
260
|
|
|
throw new InvalidArgumentException("Unknown logging level ($level)"); |
261
|
|
|
} |
262
|
|
|
return $aLevel[$level]; |
263
|
|
|
} |
264
|
|
|
|
265
|
|
|
/** |
266
|
|
|
* Replaces placeholders in path/filename/fullpath. |
267
|
|
|
* @see XLogger::setFullPath() |
268
|
|
|
* @param string $strPath |
269
|
|
|
* @return string |
270
|
|
|
*/ |
271
|
|
|
protected function replacePathPlaceholder(string $strPath) : string |
272
|
|
|
{ |
273
|
|
|
$strPath = str_replace('{date}', date('Y-m-d'), $strPath); |
274
|
|
|
$strPath = str_replace('{month}', date('Y-m'), $strPath); |
275
|
|
|
$strPath = str_replace('{year}', date('Y'), $strPath); |
276
|
|
|
$strPath = str_replace('{week}', date('Y_W'), $strPath); |
277
|
|
|
|
278
|
|
|
$strUser = preg_replace("/[^A-Za-z0-9_-]/",'', $this->strUser); |
279
|
|
|
$strPath = str_replace('{name}', $strUser, $strPath); |
280
|
|
|
|
281
|
|
|
return $strPath; |
282
|
|
|
} |
283
|
|
|
|
284
|
|
|
/** |
285
|
|
|
* Has nothing to do in the abstract class... |
286
|
|
|
* ...but does not necessarily have to be implemented in extended classes |
287
|
|
|
* and is therefore not declared as an abstract at this point. |
288
|
|
|
* @return void |
289
|
|
|
*/ |
290
|
|
|
protected function openLogfile() : void |
291
|
|
|
{ |
292
|
|
|
} |
293
|
|
|
|
294
|
|
|
/** |
295
|
|
|
* Has nothing to do in the abstract class... |
296
|
|
|
* ...but does not necessarily have to be implemented in extended classes |
297
|
|
|
* and is therefore not declared as an abstract at this point. |
298
|
|
|
* @return void |
299
|
|
|
*/ |
300
|
|
|
protected function createLogfile() : void |
301
|
|
|
{ |
302
|
|
|
} |
303
|
|
|
|
304
|
|
|
/** |
305
|
|
|
* Has nothing to do in the abstract class... |
306
|
|
|
* ...but does not necessarily have to be implemented in extended classes |
307
|
|
|
* and is therefore not declared as an abstract at this point. |
308
|
|
|
* @return void |
309
|
|
|
*/ |
310
|
|
|
protected function closeLogfile() : void |
311
|
|
|
{ |
312
|
|
|
} |
313
|
|
|
} |
314
|
|
|
|
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.