Log   B
last analyzed

Complexity

Total Complexity 43

Size/Duplication

Total Lines 398
Duplicated Lines 0 %

Importance

Changes 4
Bugs 1 Features 0
Metric Value
eloc 143
dl 0
loc 398
rs 8.96
c 4
b 1
f 0
wmc 43

20 Methods

Rating   Name   Duplication   Size   Complexity  
A getLogger() 0 2 1
A emergency() 0 2 1
A setLogger() 0 2 1
A info() 0 2 1
A currentLoggerNameCanSaveLog() 0 8 3
A error() 0 2 1
A debug() 0 2 1
A critical() 0 2 1
A log() 0 37 6
A notice() 0 2 1
A alert() 0 2 1
A warning() 0 2 1
A saveLogData() 0 28 2
A levelCanSaveLog() 0 25 6
A getLevelValue() 0 18 2
A isValidConfigLevel() 0 3 1
A getLevelName() 0 16 2
A getLogFilePath() 0 13 4
B getLogDebugBacktraceInfo() 0 31 6
A __construct() 0 1 1

How to fix   Complexity   

Complex Class

Complex classes like Log often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Log, and based on these observations, apply Extract Interface, too.

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 constants for Log level
35
         */
36
        const NONE      = 99999999;
37
        const EMERGENCY = 800;
38
        const ALERT     = 700;
39
        const CRITICAL  = 600;
40
        const ERROR     = 500;
41
        const WARNING   = 400;
42
        const NOTICE    = 300;
43
        const INFO      = 200;
44
        const DEBUG     = 100;
45
46
        /**
47
         * The logger name
48
         * @var string
49
         */
50
        private $logger = 'ROOT_LOGGER';
51
		
52
        /**
53
         * List of valid log level to be checked for the configuration
54
         * @var array
55
         */
56
        private static $validConfigLevel = array(
57
                                                    'off', 
58
                                                    'none', 
59
                                                    'emergency', 
60
                                                    'alert', 
61
                                                    'critical', 
62
                                                    'error', 
63
                                                    'warning', 
64
                                                    'notice', 
65
                                                    'info', 
66
                                                    'debug'
67
                                                );
68
69
        /**
70
         * Create new Log instance
71
         */
72
        public function __construct() {
73
        }
74
75
        /**
76
         * Set the logger to identify each message in the log
77
         * @param string $logger the logger name
78
         */
79
        public  function setLogger($logger) {
80
            $this->logger = $logger;
81
        }
82
83
        /**
84
         * get the logger name
85
         *
86
         * @return string
87
         */
88
        public  function getLogger() {
89
            return $this->logger;
90
        }
91
92
        /**
93
         * System is unusable.
94
         *
95
         * @see Log::log for more detail
96
         * @param  string $message the log message to save
97
         */
98
        public function emergency($message) {
99
            $this->log(self::EMERGENCY, $message);
100
        } 
101
102
        /**
103
         * Action must be taken immediately.
104
         *
105
         * Example: Entire website down, database unavailable, etc. This should
106
         * trigger the SMS alerts and wake you up.
107
         *
108
         * @see Log::log for more detail
109
         * @param  string $message the log message to save
110
         */
111
        public function alert($message) {
112
            $this->log(self::ALERT, $message);
113
        } 
114
115
        /**
116
         * Critical conditions.
117
         *
118
         * Example: Application component unavailable, unexpected exception.
119
         *
120
         * @see Log::log for more detail
121
         * @param  string $message the log message to save
122
         */
123
        public function critical($message) {
124
            $this->log(self::CRITICAL, $message);
125
        } 
126
		
127
        /**
128
         * Runtime errors that do not require immediate action but should typically
129
         * be logged and monitored.
130
         *
131
         * @see Log::log for more detail
132
         * @param  string $message the log message to save
133
         */
134
        public function error($message) {
135
            $this->log(self::ERROR, $message);
136
        } 
137
138
        /**
139
         * Exceptional occurrences that are not errors.
140
         *
141
         * Example: Use of deprecated APIs, poor use of an API, undesirable things
142
         * that are not necessarily wrong.
143
         *
144
         * @see Log::log for more detail
145
         * @param  string $message the log message to save
146
         */
147
        public function warning($message) {
148
            $this->log(self::WARNING, $message);
149
        } 
150
151
        /**
152
         * Normal but significant events.
153
         *
154
         * @see Log::log for more detail
155
         * @param  string $message the log message to save
156
         */
157
        public function notice($message) {
158
            $this->log(self::NOTICE, $message);
159
        } 
160
		
161
        /**
162
         * Interesting events.
163
         *
164
         * Example: User logs in, SQL logs.
165
         *
166
         * @see Log::log for more detail
167
         * @param  string $message the log message to save
168
         */
169
        public function info($message) {
170
            $this->log(self::INFO, $message);
171
        } 
172
		
173
        /**
174
         * Detailed debug information.
175
         *
176
         * @see Log::log for more detail
177
         * @param  string $message the log message to save
178
         */
179
        public function debug($message) {
180
            $this->log(self::DEBUG, $message);
181
        } 
182
		
183
	/**
184
         * Logs with an arbitrary level.
185
         *
186
         * @param  integer|string $level   the log level in integer or string format,
187
         * if is string will convert into integer. 
188
         * @param  string $message the log message to be saved
189
         */
190
        public function log($level, $message) {
191
            $configLogLevel = get_config('log_level');
192
            if (!$configLogLevel) {
193
                //so means no need log just stop here
194
                return;
195
            }
196
            //check config log level
197
            if (!self::isValidConfigLevel($configLogLevel)) {
198
                //NOTE: here need put the show_error() "logging" to false 
199
                //to prevent self function loop call
200
                show_error('Invalid config log level [' . $configLogLevel . '], '
201
                           . 'the value must be one of the following: ' 
202
                           . implode(', ', array_map('strtoupper', self::$validConfigLevel))
203
                           , 'Log Config Error', 
204
                           $logging = false
205
                       );
206
                return;	
207
            }
208
			
209
            //check if config log_logger_name and current log can save log data
210
            if (!$this->currentLoggerNameCanSaveLog()) {
211
                return;
212
            }
213
			
214
            //if $level is not an integer
215
            if (!is_numeric($level)) {
216
                $level = self::getLevelValue($level);
217
            }
218
219
            //check if can logging regarding the log level config
220
            //or custom logger level
221
            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

221
            if (!$this->levelCanSaveLog(/** @scrutinizer ignore-type */ $level)) {
Loading history...
222
                return;
223
            }
224
			  
225
            //save the log data
226
            $this->saveLogData($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

226
            $this->saveLogData(/** @scrutinizer ignore-type */ $level, $message);
Loading history...
227
        }	
228
229
        /**
230
         * Save the log data into file
231
         * @param  integer $level   the log level in integer format.
232
         * @param  string $message the log message to save
233
         * @return void
234
         */
235
        protected function saveLogData($level, $message) {
236
            $path = $this->getLogFilePath();
237
238
            //may be at this time helper user_agent not yet included
239
            require_once CORE_FUNCTIONS_PATH . 'function_user_agent.php';
240
			
241
            ///////////////////// date //////////////
242
            $timestampWithMicro = microtime(true);
243
            $microtime = sprintf('%06d', ($timestampWithMicro - floor($timestampWithMicro)) * 1000000);
244
            $dateTime = new DateTime(date('Y-m-d H:i:s.' . $microtime, $timestampWithMicro));
245
            $logDate = $dateTime->format('Y-m-d H:i:s.u'); 
246
            //ip
247
            $ip = get_ip();
248
249
            //level name
250
            $levelName = self::getLevelName($level);
251
			
252
            //debug info
253
            $fileInfo = $this->getLogDebugBacktraceInfo();
254
255
            $str = $logDate . ' [' . str_pad($levelName, 9 /*emergency length*/) . ']' 
256
                            . ' [' . str_pad($ip, 15) . '] ' . $this->logger . ': ' 
257
                            . $message . ' ' . '[' . substr($fileInfo['file'], strlen(ROOT_PATH)) . ' ' . $fileInfo['class'] . '->' . $fileInfo['function'] . '():' . $fileInfo['line'] . ']' . "\n";
258
            $fp = fopen($path, 'a+');
259
            if (is_resource($fp)) {
260
                flock($fp, LOCK_EX); // exclusive lock, will get released when the file is closed
261
                fwrite($fp, $str);
262
                fclose($fp);
263
            }
264
        }	
265
        
266
        /**
267
         * Check if the given level can save log data
268
         * @param  integer $level the current level value to save the log data
269
         * @return boolean
270
         */
271
        protected function levelCanSaveLog($level) {
272
            $result = true;
273
            $configLogLevel = get_config('log_level');
274
             //check if can save log regarding the log level configuration
275
            $configLevel = self::getLevelValue($configLogLevel);
276
            if ($configLevel > $level) {
277
                //can't log
278
                $result = false;
279
            }
280
            //If result is false so means current log level can not save log data
281
            //using the config level 
282
            if ($result === false) {
283
                //Check for logger rule overwritting
284
                $configLoggersNameLevel = get_config('log_logger_name_level', array());
285
                foreach ($configLoggersNameLevel as $loggerName => $loggerLevel) { 
286
                    if (preg_match('#' . $loggerName . '#', $this->logger)) {
287
                        $loggerLevelValue = self::getLevelValue($loggerLevel);
288
                        if ($loggerLevelValue <= $level) {
289
                            $result = true;
290
                        } 
291
                        break;
292
                    }
293
                }
294
            }
295
            return $result;
296
        }
297
298
        /**
299
         * Check if the current logger can save log data regarding the configuration
300
         * of logger filter
301
         * @return boolean
302
         */
303
        protected function currentLoggerNameCanSaveLog() {
304
                $configLoggersName = get_config('log_logger_name', array());
305
                if (!empty($configLoggersName)) {
306
                    if (!in_array($this->logger, $configLoggersName)) {
307
                        return false;
308
                    }
309
                }
310
            return true;
311
        }
312
313
        /**
314
         * Return the debug backtrace information
315
         * @return array the line number and file path
316
         */
317
        protected function getLogDebugBacktraceInfo() {
318
            $dtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
319
            $i = 0;
320
            while ($dtrace[$i]['file'] == __FILE__ ) {
321
                $i++;
322
            } 
323
            $fileInfo = $dtrace[$i];
324
            
325
            $line = -1;
326
            $file = '';
327
            $function = '';
328
            $class = '';
329
           
330
            if (isset($fileInfo['file'])) {
331
                $file = $fileInfo['file'];
332
            }
333
            if (isset($fileInfo['line'])) {
334
                $line = $fileInfo['line'];
335
            }
336
            if (isset($dtrace[$i+1]['function'])) {
337
                $function = $dtrace[$i+1]['function'];
338
            }
339
            if (isset($dtrace[$i+1]['class'])) {
340
                $class = $dtrace[$i+1]['class'];
341
            }
342
            
343
            return array(
344
                'file' => $file,
345
                'line' => $line,
346
                'function' => $function,
347
                'class' => $class
348
            );
349
        }
350
351
        /**
352
         * return the current log file path to use
353
         * @return string
354
         */
355
        protected function getLogFilePath() {
356
            $logSavePath = get_config('log_save_path', null);
357
            if (!$logSavePath) {
358
                $logSavePath = LOGS_PATH;
359
            }
360
            
361
            if (!is_dir($logSavePath) || !is_writable($logSavePath)) {
362
                //NOTE: here need put the show_error() "logging" to false 
363
                //to prevent self function loop call
364
                show_error('Error : the log dir does not exist or is not writable',
365
                           'Log directory error', $logging = false);
366
            }
367
            return $logSavePath . 'logs-' . date('Y-m-d') . '.log';
368
        }
369
370
        /**
371
         * Check if the given log level is valid
372
         *
373
         * @param  string  $level the log level
374
         *
375
         * @return boolean        true if the given log level is valid, false if not
376
         */
377
        protected static function isValidConfigLevel($level) {
378
            $level = strtolower($level);
379
            return in_array($level, self::$validConfigLevel);
380
        }
381
382
        /**
383
         * Get the log level number for the given level string
384
         * @param  string $level the log level in string format
385
         * 
386
         * @return int the log level in integer format using the predefined constants
387
         */
388
        protected static function getLevelValue($level) {
389
            $level = strtolower($level);
390
            $levelMaps = array(
391
                'emergency' => self::EMERGENCY,
392
                'alert'     => self::ALERT,
393
                'critical'  => self::CRITICAL,
394
                'error'     => self::ERROR,
395
                'warning'   => self::WARNING,
396
                'notice'    => self::NOTICE,
397
                'info'      => self::INFO,
398
                'debug'     => self::DEBUG
399
            );
400
            //the default value is NONE, so means no need test for NONE
401
            $value = self::NONE;
402
            if (isset($levelMaps[$level])) {
403
                $value = $levelMaps[$level];
404
            }
405
            return $value;
406
        }
407
408
        /**
409
         * Get the log level string for the given log level integer
410
         * @param  integer $level the log level in integer format
411
         * @return string        the log level in string format
412
         */
413
        protected static function getLevelName($level) {
414
            $levelMaps = array(
415
                self::EMERGENCY => 'EMERGENCY',
416
                self::ALERT     => 'ALERT',
417
                self::CRITICAL  => 'CRITICAL',
418
                self::ERROR     => 'ERROR',
419
                self::WARNING   => 'WARNING',
420
                self::NOTICE    => 'NOTICE',
421
                self::INFO      => 'INFO',
422
                self::DEBUG     => 'DEBUG'
423
            );
424
            $value = '';
425
            if (isset($levelMaps[$level])) {
426
                $value = $levelMaps[$level];
427
            }
428
            return $value;
429
        }
430
431
    }
432