Logger::getFilename()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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