Completed
Push — master ( 3c6d4a...e8d3de )
by Marwan
25s queued 11s
created

Logger::getFallbackDirectory()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 4
c 1
b 0
f 0
nc 4
nop 0
dl 0
loc 7
ccs 5
cts 5
cp 1
crap 3
rs 10
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