1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* @author Marwan Al-Soltany <[email protected]> |
4
|
|
|
* @copyright Marwan Al-Soltany 2020 |
5
|
|
|
* For the full copyright and license information, please view |
6
|
|
|
* the LICENSE file that was distributed with this source code. |
7
|
|
|
*/ |
8
|
|
|
|
9
|
|
|
namespace MAKS\AmqpAgent\Helper; |
10
|
|
|
|
11
|
|
|
use MAKS\AmqpAgent\Helper\Utility; |
12
|
|
|
|
13
|
|
|
/** |
14
|
|
|
* A class to write logs, exposing methods that work statically and on instantiation. |
15
|
|
|
* This class DOES NOT implement `Psr\Log\LoggerInterface`. |
16
|
|
|
* |
17
|
|
|
* Example: |
18
|
|
|
* ``` |
19
|
|
|
* // static |
20
|
|
|
* Logger::log('Some message to log.', 'filename', 'path/to/some/directory'); |
21
|
|
|
* // instantiated |
22
|
|
|
* $logger = new Logger(); |
23
|
|
|
* $logger->setFilename('filename'); |
24
|
|
|
* $logger->setDirectory('path/to/some/directory'); |
25
|
|
|
* $logger->write('Some message to log.'); |
26
|
|
|
* ``` |
27
|
|
|
* |
28
|
|
|
* @since 1.0.0 |
29
|
|
|
*/ |
30
|
|
|
class Logger |
31
|
|
|
{ |
32
|
|
|
/** |
33
|
|
|
* The filename of the log file. |
34
|
|
|
* @var string |
35
|
|
|
*/ |
36
|
|
|
public $filename; |
37
|
|
|
|
38
|
|
|
/** |
39
|
|
|
* The directory where the log file gets written. |
40
|
|
|
* @var string |
41
|
|
|
*/ |
42
|
|
|
public $directory; |
43
|
|
|
|
44
|
|
|
|
45
|
|
|
/** |
46
|
|
|
* Passing null for $directory will raise a warning and force the logger to find a reasonable directory to write the file in. |
47
|
|
|
* @param string $filename The name wished to be given to the file. Pass null for auto-generate. |
48
|
|
|
* @param string $directory The directory where the log file should be written. |
49
|
|
|
*/ |
50
|
5 |
|
public function __construct(?string $filename, ?string $directory) |
51
|
|
|
{ |
52
|
5 |
|
$this->filename = $filename; |
53
|
5 |
|
$this->directory = $directory; |
54
|
5 |
|
} |
55
|
|
|
|
56
|
|
|
|
57
|
|
|
/** |
58
|
|
|
* Logs a message to a file, generates it if it does not exist and raises a user-level warning and/or notice on misuse. |
59
|
|
|
* @param string $message The message wished to be logged. |
60
|
|
|
* @return bool True on success. |
61
|
|
|
*/ |
62
|
2 |
|
public function write(string $message): bool |
63
|
|
|
{ |
64
|
2 |
|
return self::log($message, $this->filename, $this->directory); |
65
|
|
|
} |
66
|
|
|
|
67
|
|
|
/** |
68
|
|
|
* Gets filename property. |
69
|
|
|
* @return string |
70
|
|
|
*/ |
71
|
1 |
|
public function getFilename() |
72
|
|
|
{ |
73
|
1 |
|
return $this->filename; |
74
|
|
|
} |
75
|
|
|
|
76
|
|
|
/** |
77
|
|
|
* Sets filename property. |
78
|
|
|
* @param string $filename Filename |
79
|
|
|
* @return self |
80
|
|
|
*/ |
81
|
1 |
|
public function setFilename(string $filename) |
82
|
|
|
{ |
83
|
1 |
|
$this->filename = $filename; |
84
|
1 |
|
return $this; |
85
|
|
|
} |
86
|
|
|
|
87
|
|
|
/** |
88
|
|
|
* Gets directory property. |
89
|
|
|
* @return string |
90
|
|
|
*/ |
91
|
1 |
|
public function getDirectory() |
92
|
|
|
{ |
93
|
1 |
|
return $this->directory; |
94
|
|
|
} |
95
|
|
|
|
96
|
|
|
/** |
97
|
|
|
* Sets directory property. |
98
|
|
|
* @param string $directory Directory |
99
|
|
|
* @return self |
100
|
|
|
*/ |
101
|
1 |
|
public function setDirectory(string $directory) |
102
|
|
|
{ |
103
|
1 |
|
$this->directory = $directory; |
104
|
1 |
|
return $this; |
105
|
|
|
} |
106
|
|
|
|
107
|
|
|
|
108
|
|
|
/** |
109
|
|
|
* Logs a message to a file, generates it if it does not exist and raises a user-level warning and/or notice on misuse. |
110
|
|
|
* @param string $message The message wished to be logged. |
111
|
|
|
* @param string $filename [optional] The name wished to be given to the file. |
112
|
|
|
* @param string $directory [optional] The directory where the log file should be written. |
113
|
|
|
* @return bool True if message was written. |
114
|
|
|
*/ |
115
|
3 |
|
public static function log(string $message, ?string $filename = null, ?string $directory = null): bool |
116
|
|
|
{ |
117
|
3 |
|
$passed = false; |
118
|
|
|
|
119
|
3 |
|
if (null === $filename) { |
120
|
1 |
|
$filename = self::getFallbackFilename(); |
121
|
1 |
|
Utility::emit( |
122
|
|
|
[ |
123
|
1 |
|
'yellow' => sprintf('%s() was called without specifying a filename.', __METHOD__), |
124
|
1 |
|
'green' => sprintf('Log file will be named: "%s".', $filename) |
125
|
|
|
], |
126
|
1 |
|
null, |
127
|
1 |
|
E_USER_NOTICE |
128
|
|
|
); |
129
|
|
|
} |
130
|
|
|
|
131
|
3 |
|
if (null === $directory) { |
132
|
1 |
|
$directory = self::getFallbackDirectory(); |
133
|
1 |
|
Utility::emit( |
134
|
|
|
[ |
135
|
1 |
|
'yellow' => sprintf('%s() was called without specifying a directory.', __METHOD__), |
136
|
1 |
|
'red' => sprintf('Log file will be written in: "%s".', $directory) |
137
|
|
|
], |
138
|
1 |
|
null, |
139
|
1 |
|
E_USER_WARNING |
140
|
|
|
); |
141
|
|
|
} |
142
|
|
|
|
143
|
3 |
|
$file = self::getNormalizedPath($directory, $filename); |
144
|
|
|
|
145
|
|
|
// create log file if it does not exist |
146
|
3 |
|
if (!is_file($file) && is_writable($directory)) { |
147
|
3 |
|
$signature = 'Created by ' . __METHOD__ . date('() \o\\n l jS \of F Y h:i:s A (Ymdhis)') . PHP_EOL; |
148
|
3 |
|
file_put_contents($file, $signature, null, stream_context_create()); |
149
|
3 |
|
chmod($file, 0775); |
150
|
|
|
} |
151
|
|
|
|
152
|
|
|
// write in the log file |
153
|
3 |
|
if (is_writable($file)) { |
154
|
|
|
// empty the the file if it exceeds 64MB |
155
|
|
|
// @codeCoverageIgnoreStart |
156
|
|
|
if (filesize($file) > 6.4e+7) { |
157
|
|
|
$stream = fopen($file, 'r'); |
158
|
|
|
if (is_resource($stream)) { |
159
|
|
|
$signature = fgets($stream) . 'For exceeding 64MB, it was overwritten on ' . date('l jS \of F Y h:i:s A (Ymdhis)') . PHP_EOL; |
160
|
|
|
fclose($stream); |
161
|
|
|
file_put_contents($file, $signature, null, stream_context_create()); |
162
|
|
|
chmod($file, 0775); |
163
|
|
|
} |
164
|
|
|
} |
165
|
|
|
// @codeCoverageIgnoreEnd |
166
|
|
|
|
167
|
3 |
|
$timestamp = Utility::time()->format(DATE_ISO8601); |
168
|
3 |
|
$log = $timestamp . ' ' . $message . PHP_EOL; |
169
|
|
|
|
170
|
3 |
|
$stream = fopen($file, 'a+'); |
171
|
3 |
|
if (is_resource($stream)) { |
172
|
3 |
|
fwrite($stream, $log); |
173
|
3 |
|
fclose($stream); |
174
|
3 |
|
$passed = true; |
175
|
|
|
} |
176
|
|
|
} |
177
|
|
|
|
178
|
3 |
|
return $passed; |
179
|
|
|
} |
180
|
|
|
|
181
|
|
|
/** |
182
|
|
|
* Returns a fallback filename based on date. |
183
|
|
|
* @since 1.2.1 |
184
|
|
|
* @return string |
185
|
|
|
*/ |
186
|
1 |
|
protected static function getFallbackFilename(): string |
187
|
|
|
{ |
188
|
1 |
|
return 'maks-amqp-agent-log-' . date("Ymd"); |
189
|
|
|
} |
190
|
|
|
|
191
|
|
|
/** |
192
|
|
|
* Returns a fallback writing directory based on caller. |
193
|
|
|
* @since 1.2.1 |
194
|
|
|
* @return string |
195
|
|
|
*/ |
196
|
1 |
|
protected static function getFallbackDirectory(): string |
197
|
|
|
{ |
198
|
1 |
|
$backtrace = Utility::backtrace(['file'], 0); |
199
|
1 |
|
$fallback1 = strlen($_SERVER["DOCUMENT_ROOT"]) ? $_SERVER["DOCUMENT_ROOT"] : null; |
200
|
1 |
|
$fallback2 = isset($backtrace['file']) ? dirname($backtrace['file']) : __DIR__; |
201
|
|
|
|
202
|
1 |
|
return $fallback1 ?? $fallback2; |
203
|
|
|
} |
204
|
|
|
|
205
|
|
|
/** |
206
|
|
|
* Returns a normalized path based on OS. |
207
|
|
|
* @since 1.2.1 |
208
|
|
|
* @param string $directory The directory. |
209
|
|
|
* @param string $filename The Filename. |
210
|
|
|
* @return string The full normalized path. |
211
|
|
|
*/ |
212
|
3 |
|
protected static function getNormalizedPath(string $directory, string $filename): string |
213
|
|
|
{ |
214
|
3 |
|
$ext = '.log'; |
215
|
3 |
|
$filename = substr($filename, -strlen($ext)) === $ext ? $filename : $filename . $ext; |
216
|
3 |
|
$directory = $directory . DIRECTORY_SEPARATOR; |
217
|
3 |
|
$path = $directory . $filename; |
218
|
|
|
|
219
|
3 |
|
return preg_replace("/\/+|\\+/", DIRECTORY_SEPARATOR, $path); |
220
|
|
|
} |
221
|
|
|
} |
222
|
|
|
|