Test Failed
Push — 1.0.0-dev ( c48145...a13fd7 )
by nguereza
03:13
created

Log::isValidConfigLevel()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
    defined('ROOT_PATH') || exit('Access denied');
3
    /**
4
     * TNH Framework
5
     *
6
     * A simple PHP framework using HMVC architecture
7
     *
8
     * This content is released under the MIT License (MIT)
9
     *
10
     * Copyright (c) 2017 TNH Framework
11
     *
12
     * Permission is hereby granted, free of charge, to any person obtaining a copy
13
     * of this software and associated documentation files (the "Software"), to deal
14
     * in the Software without restriction, including without limitation the rights
15
     * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16
     * copies of the Software, and to permit persons to whom the Software is
17
     * furnished to do so, subject to the following conditions:
18
     *
19
     * The above copyright notice and this permission notice shall be included in all
20
     * copies or substantial portions of the Software.
21
     *
22
     * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23
     * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24
     * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25
     * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26
     * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27
     * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28
     * SOFTWARE.
29
     */
30
31
    class Log {
32
		
33
        /**
34
         * The defined constante for Log level
35
         */
36
        const NONE = 99999999;
37
        const FATAL = 500;
38
        const ERROR = 400;
39
        const WARNING = 300;
40
        const INFO = 200;
41
        const DEBUG = 100;
42
        const ALL = -99999999;
43
44
        /**
45
         * The logger name
46
         * @var string
47
         */
48
        private $logger = 'ROOT_LOGGER';
49
		
50
        /**
51
         * List of valid log level to be checked for the configuration
52
         * @var array
53
         */
54
        private static $validConfigLevel = array('off', 'none', 'fatal', 'error', 'warning', 'warn', 'info', 'debug', 'all');
55
56
        /**
57
         * Create new Log instance
58
         */
59
        public function __construct() {
60
        }
61
62
        /**
63
         * Set the logger to identify each message in the log
64
         * @param string $logger the logger name
65
         */
66
        public  function setLogger($logger) {
67
            $this->logger = $logger;
68
        }
69
70
        /**
71
         * get the logger name
72
         *
73
         * @return string
74
         */
75
        public  function getLogger() {
76
            return $this->logger;
77
        }
78
79
        /**
80
         * Save the fatal message in the log
81
         * @see Log::writeLog for more detail
82
         * @param  string $message the log message to save
83
         */
84
        public function fatal($message) {
85
            $this->writeLog($message, self::FATAL);
86
        } 
87
		
88
        /**
89
         * Save the error message in the log
90
         * @see Log::writeLog for more detail
91
         * @param  string $message the log message to save
92
         */
93
        public function error($message) {
94
            $this->writeLog($message, self::ERROR);
95
        } 
96
97
        /**
98
         * Save the warning message in the log
99
         * @see Log::writeLog for more detail
100
         * @param  string $message the log message to save
101
         */
102
        public function warning($message) {
103
            $this->writeLog($message, self::WARNING);
104
        } 
105
		
106
        /**
107
         * Save the info message in the log
108
         * @see Log::writeLog for more detail
109
         * @param  string $message the log message to save
110
         */
111
        public function info($message) {
112
            $this->writeLog($message, self::INFO);
113
        } 
114
		
115
        /**
116
         * Save the debug message in the log
117
         * @see Log::writeLog for more detail
118
         * @param  string $message the log message to save
119
         */
120
        public function debug($message) {
121
            $this->writeLog($message, self::DEBUG);
122
        } 
123
		
124
		
125
        /**
126
         * Save the log message
127
         * @param  string $message the log message to be saved
128
         * @param  integer|string $level   the log level in integer or string format, 
129
         * if is string will convert into integer
130
         * to allow check the log level threshold.
131
         */
132
        public function writeLog($message, $level = self::INFO) {
133
            $configLogLevel = get_config('log_level');
134
            if (!$configLogLevel) {
135
                //so means no need log just stop here
136
                return;
137
            }
138
            //check config log level
139
            if (!self::isValidConfigLevel($configLogLevel)) {
140
                //NOTE: here need put the show_error() "logging" to false 
141
                //to prevent self function loop call
142
                show_error('Invalid config log level [' . $configLogLevel . '], '
143
                           . 'the value must be one of the following: ' 
144
                           . implode(', ', array_map('strtoupper', self::$validConfigLevel))
145
                           , 'Log Config Error', 
146
                           $logging = false
147
                       );
148
                return;	
149
            }
150
			
151
            //check if config log_logger_name and current log can save log data
152
            if (!$this->currentLoggerNameCanSaveLog()) {
153
                return;
154
            }
155
			
156
            //if $level is not an integer
157
            if (!is_numeric($level)) {
158
                $level = self::getLevelValue($level);
159
            }
160
161
            //check if can logging regarding the log level config
162
            //or custom logger level
163
            if (!$this->levelCanSaveLog($level)) {
0 ignored issues
show
Bug introduced by
It seems like $level can also be of type string; however, parameter $level of Log::levelCanSaveLog() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

163
            if (!$this->levelCanSaveLog(/** @scrutinizer ignore-type */ $level)) {
Loading history...
164
                return;
165
            }
166
			
167
            //check log file and directory
168
            $path = $this->checkAndSetLogFileDirectory();
169
            //save the log data
170
            $this->saveLogData($path, $level, $message);
0 ignored issues
show
Bug introduced by
It seems like $level can also be of type string; however, parameter $level of Log::saveLogData() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

170
            $this->saveLogData($path, /** @scrutinizer ignore-type */ $level, $message);
Loading history...
171
        }	
172
173
        /**
174
         * Save the log data into file
175
         * @param  string $path    the path of the log file
176
         * @param  integer $level   the log level in integer format.
177
         * @param  string $message the log message to save
178
         * @return void
179
         */
180
        protected function saveLogData($path, $level, $message) {
181
            //may be at this time helper user_agent not yet included
182
            require_once CORE_FUNCTIONS_PATH . 'function_user_agent.php';
183
			
184
            ///////////////////// date //////////////
185
            $timestampWithMicro = microtime(true);
186
            $microtime = sprintf('%06d', ($timestampWithMicro - floor($timestampWithMicro)) * 1000000);
187
            $dateTime = new DateTime(date('Y-m-d H:i:s.' . $microtime, $timestampWithMicro));
188
            $logDate = $dateTime->format('Y-m-d H:i:s.u'); 
189
            //ip
190
            $ip = get_ip();
191
192
            //level name
193
            $levelName = self::getLevelName($level);
194
			
195
            //debug info
196
            $fileInfo = $this->getLogDebugBacktraceInfo();
197
198
            $str = $logDate . ' [' . str_pad($levelName, 7 /*warning len*/) . '] ' 
199
                            . ' [' . str_pad($ip, 15) . '] ' . $this->logger . ': ' 
200
                            . $message . ' ' . '[' . $fileInfo['file'] . ':' . $fileInfo['line'] . ']' . "\n";
201
            $fp = fopen($path, 'a+');
202
            if (is_resource($fp)) {
203
                flock($fp, LOCK_EX); // exclusive lock, will get released when the file is closed
204
                fwrite($fp, $str);
205
                fclose($fp);
206
            }
207
        }	
208
209
        /**
210
         * Check if the given level can save log data
211
         * @param  integer $level the current level value to save the log data
212
         * @return boolean
213
         */
214
        protected function levelCanSaveLog($level) {
215
            $result = true;
216
            $configLogLevel = get_config('log_level');
217
             //check if can save log regarding the log level configuration
218
            $configLevel = self::getLevelValue($configLogLevel);
219
            if ($configLevel > $level) {
220
                //can't log
221
                $result = false;
222
            }
223
            //If result is false so means current log level can not save log data
224
            //using the config level 
225
            if ($result === false) {
226
                //Check for logger rule overwritting
227
                $configLoggersNameLevel = get_config('log_logger_name_level', array());
228
                foreach ($configLoggersNameLevel as $loggerName => $loggerLevel) { 
229
                    if (preg_match('#' . $loggerName . '#', $this->logger)) {
230
                        $loggerLevelValue = self::getLevelValue($loggerLevel);
231
                        if ($loggerLevelValue <= $level) {
232
                            $result = true;
233
                        } 
234
                        break;
235
                    }
236
                }
237
            }
238
            return $result;
239
        }
240
241
        /**
242
         * Check if the current logger can save log data regarding the configuration
243
         * of logger filter
244
         * @return boolean
245
         */
246
        protected function currentLoggerNameCanSaveLog() {
247
                $configLoggersName = get_config('log_logger_name', array());
248
                if (!empty($configLoggersName)) {
249
                    if (!in_array($this->logger, $configLoggersName)) {
250
                        return false;
251
                    }
252
                }
253
            return true;
254
        }
255
256
        /**
257
         * Return the debug backtrace information
258
         * @return array the line number and file path
259
         */
260
        protected function getLogDebugBacktraceInfo() {
261
            $dtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
262
            $i = 0;
263
            while ($dtrace[$i]['file'] == __FILE__ ) {
264
                $i++;
265
            } 
266
            $fileInfo = $dtrace[$i];
267
            
268
            $line = -1;
269
            $file = '';
270
           
271
            if (isset($fileInfo['file'])) {
272
                $file = $fileInfo['file'];
273
            }
274
            if (isset($fileInfo['line'])) {
275
                $line = $fileInfo['line'];
276
            }
277
            return array(
278
                'file' => $file,
279
                'line' => $line
280
            );
281
        }
282
283
        /**
284
         * Check the file and directory 
285
         * @return string|null the log file path
286
         */
287
        protected function checkAndSetLogFileDirectory() {
288
            $logSavePath = get_config('log_save_path');
289
            if (!$logSavePath) {
290
                $logSavePath = LOGS_PATH;
291
            }
292
			
293
            if (!is_dir($logSavePath) || !is_writable($logSavePath)) {
294
                //NOTE: here need put the show_error() "logging" to false 
295
                //to prevent self function loop call
296
                show_error('Error : the log dir does not exist or is not writable',
297
                           'Log directory error', $logging = false);
298
            }
299
			
300
            $path = $logSavePath . 'logs-' . date('Y-m-d') . '.log';
301
            if (!file_exists($path)) {
302
                touch($path);
303
            }
304
            return $path;
305
        }
306
		
307
        /**
308
         * Check if the given log level is valid
309
         *
310
         * @param  string  $level the log level
311
         *
312
         * @return boolean        true if the given log level is valid, false if not
313
         */
314
        protected static function isValidConfigLevel($level) {
315
            $level = strtolower($level);
316
            return in_array($level, self::$validConfigLevel);
317
        }
318
319
        /**
320
         * Get the log level number for the given level string
321
         * @param  string $level the log level in string format
322
         * 
323
         * @return int the log level in integer format using the predefined constants
324
         */
325
        protected static function getLevelValue($level) {
326
            $level = strtolower($level);
327
            $levelMaps = array(
328
                'fatal'   => self::FATAL,
329
                'error'   => self::ERROR,
330
                'warning' => self::WARNING,
331
                'warn'    => self::WARNING,
332
                'info'    => self::INFO,
333
                'debug'   => self::DEBUG,
334
                'all'     => self::ALL
335
            );
336
            //the default value is NONE, so means no need test for NONE
337
            $value = self::NONE;
338
            if (isset($levelMaps[$level])) {
339
                $value = $levelMaps[$level];
340
            }
341
            return $value;
342
        }
343
344
        /**
345
         * Get the log level string for the given log level integer
346
         * @param  integer $level the log level in integer format
347
         * @return string        the log level in string format
348
         */
349
        protected static function getLevelName($level) {
350
            $levelMaps = array(
351
                self::FATAL   => 'FATAL',
352
                self::ERROR   => 'ERROR',
353
                self::WARNING => 'WARNING',
354
                self::INFO    => 'INFO',
355
                self::DEBUG   => 'DEBUG'
356
            );
357
            $value = '';
358
            if (isset($levelMaps[$level])) {
359
                $value = $levelMaps[$level];
360
            }
361
            return $value;
362
        }
363
364
    }
365