Test Failed
Push — 1.0.0-dev ( 877729...f17fe5 )
by nguereza
02:30
created

Log::setLogger()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

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

210
            if (!$this->levelCanSaveLog(/** @scrutinizer ignore-type */ $level)) {
Loading history...
211
                return;
212
            }
213
			
214
            //check log file and directory
215
            $path = $this->checkAndSetLogFileDirectory();
216
            //save the log data
217
            $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

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