FileLogger   A
last analyzed

Complexity

Total Complexity 22

Size/Duplication

Total Lines 152
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 22
eloc 59
c 1
b 0
f 0
dl 0
loc 152
rs 10

7 Methods

Rating   Name   Duplication   Size   Complexity  
A __destruct() 0 4 1
A __construct() 0 3 1
B log() 0 42 8
A reset() 0 7 2
A closeLogfile() 0 5 2
A openLogfile() 0 20 6
A prepareText() 0 11 2
1
<?php
2
declare(strict_types=1);
3
4
namespace SKien\XLogger;
5
6
use Psr\Log\LogLevel;
7
8
/**
9
 * PSR-3 compliant logger for Output to plain text file (.log, .csv, .txt)
10
 *
11
 * Each log item is represented by one single line containing the fields
12
 * separated by defined character.
13
 *
14
 * Dependent on the file extension following field separator is used:
15
 * - `.log` :   TAB
16
 * - `.csv` :   semicolon (;)
17
 * - `.txt` :   colon (,)
18
 *
19
 * CR/LF in message are replaced with Space to keep one item in a single line.
20
 * Field separator is replaced to keep the number of fields consistent.
21
 *
22
 * @package XLogger
23
 * @author Stefanius <[email protected]>
24
 * @copyright MIT License - see the LICENSE file for details
25
 */
26
class FileLogger extends XLogger
27
{
28
    /** @var resource|bool file handle of the opened logfile     */
29
    protected $logfile = false;
30
    /** @var string separator     */
31
    protected string $strSep = '';
32
    /** @var string replacement for separator inside of message      */
33
    protected string $strReplace = '';
34
    /** @var bool $bReset reset file when open     */
35
    protected bool $bReset = false;
36
37
    /**
38
     * Init logging level and remote username (if set).
39
     * @see XLogger::setLogLevel()
40
     * @param string $level the min. `LogLevel` to be logged
41
     */
42
    public function __construct(string $level = LogLevel::DEBUG)
43
    {
44
        parent::__construct($level);
45
    }
46
47
    /**
48
     * close file if already opened.
49
     */
50
    public function __destruct()
51
    {
52
        // textbased file kept open...
53
        $this->closeLogfile();
54
    }
55
56
    /**
57
     * Logs with an arbitrary level.
58
     * @param string    $level
59
     * @param mixed     $message
60
     * @param mixed[]   $context
61
     * @return void
62
     * @throws \Psr\Log\InvalidArgumentException
63
     */
64
    public function log($level, $message, array $context = array()) : void
65
    {
66
        // check, if requested level should be logged
67
        // causes InvalidArgumentException in case of unknown level.
68
        if ($this->logLevel($level)) {
69
            // Open file if not opened so far, dependend on the file extension the separator is set.
70
            $this->openLogfile();
71
            if (!is_resource($this->logfile)) {
72
                return;
73
            }
74
75
            // timestamp
76
            $strLine = date('Y-m-d H:i:s');
77
            // IP adress
78
            if (($this->iOptions & self::LOG_IP) != 0) {
79
                $strIP = $_SERVER['REMOTE_ADDR'];
80
                if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
81
                    $strIP = $_SERVER['HTTP_X_FORWARDED_FOR'];
82
                }
83
                $strLine .= $this->strSep . $strIP;
84
            }
85
            // user
86
            if (($this->iOptions & self::LOG_USER) != 0) {
87
                $strLine .= $this->strSep . $this->prepareText($this->strUser);
88
            }
89
            // backtrace - caller
90
            if (($this->iOptions & self::LOG_BT) != 0) {
91
                $strLine .= $this->strSep . $this->getCaller();
92
            }
93
            // the message
94
            $strMessage = $this->replaceContext($message, $context);
95
            $strLine .= $this->strSep . $this->prepareText(strtoupper($level) . ': ' . $strMessage);
96
            // user agent
97
            if (($this->iOptions & self::LOG_BT) != 0) {
98
                $strLine .= $this->strSep . $this->prepareText($_SERVER["HTTP_USER_AGENT"]);
99
            }
100
101
            // and write to the file
102
            flock($this->logfile, LOCK_EX);
103
            fwrite($this->logfile, $strLine . PHP_EOL);
104
            fflush($this->logfile);
105
            flock($this->logfile, LOCK_UN);
106
        }
107
    }
108
109
    /**
110
     * close file if open, reopen and truncate existing file
111
     */
112
    public function reset() : void
113
    {
114
        if (is_resource($this->logfile)) {
115
            fclose($this->logfile);
116
            $this->logfile = fopen($this->getFullpath(), 'w');
117
        } else {
118
            $this->bReset = true;
119
        }
120
    }
121
122
    /**
123
     * Open the logfile if not done so far.
124
     * Dependend on the file extension the speparator is set.
125
     * @return void
126
     */
127
    protected function openLogfile() : void
128
    {
129
        if ($this->logfile === false) {
130
            $strFullPath = $this->getFullpath();
131
            // scrutinizer didn't (or can't...) analyse, that pathinfo returns allways string if the $options param is set!
132
            $strExt = strtolower(/** @scrutinizer ignore-type */ pathinfo($strFullPath, PATHINFO_EXTENSION));
133
            switch ($strExt) {
134
                case 'csv':
135
                case 'txt':
136
                    $this->strSep = ";";
137
                    $this->strReplace = ",";
138
                    break;
139
                case 'log':
140
                default:
141
                    $this->strSep = "\t";
142
                    $this->strReplace = " ";
143
                    break;
144
            }
145
            $this->logfile = fopen($strFullPath, $this->bReset ? 'w' : 'a');
146
            $this->bReset = false;
147
        }
148
    }
149
150
    /**
151
     * close file if already opened.
152
     * @return void
153
     */
154
    protected function closeLogfile() : void
155
    {
156
        if (is_resource($this->logfile)) {
157
            fclose($this->logfile);
158
            $this->logfile = false;
159
        }
160
    }
161
162
163
    /**
164
     * @param string $strMessage
165
     * @return string
166
     */
167
    protected function prepareText(string $strMessage) : string
168
    {
169
        // it make sense to replace LF because each line representing one log item!
170
        // ... and also the separator should not be included in the message itself
171
        if (strlen($this->strSep) > 0) {
172
            $strMessage = str_replace("\r\n", ' ', $strMessage);
173
            $strMessage = str_replace("\r", ' ', $strMessage);
174
            $strMessage = str_replace("\n", ' ', $strMessage);
175
            $strMessage = str_replace($this->strSep, $this->strReplace, $strMessage);
176
        }
177
        return $strMessage;
178
    }
179
}
180