Passed
Push — 1.0.0-dev ( e3bc99...72449d )
by nguereza
03:01
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->canSaveLogDataForLogger()) {
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
            $configLevel = self::getLevelValue($configLogLevel);
163
            if ($configLevel > $level) {
164
                //can't log
165
                return;
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
            $dtrace = debug_backtrace();
197
            $fileInfo = $dtrace[0];
198
            if ($dtrace[0]['file'] == __FILE__ || $dtrace[1]['file'] == __FILE__) {
199
                $fileInfo = $dtrace[2];
200
            }
201
202
            $line = -1;
203
            $file = -1;
204
205
            if (isset($fileInfo['file'])) {
206
                $file = $fileInfo['file'];
207
            }
208
209
            if (isset($fileInfo['line'])) {
210
                $line = $fileInfo['line'];
211
            }
212
			
213
            $str = $logDate . ' [' . str_pad($levelName, 7 /*warning len*/) . '] ' 
214
                            . ' [' . str_pad($ip, 15) . '] ' . $this->logger . ': ' 
215
                            . $message . ' ' . '[' . $file . '::' . $line . ']' . "\n";
216
            $fp = fopen($path, 'a+');
217
            if (is_resource($fp)) {
218
                flock($fp, LOCK_EX); // exclusive lock, will get released when the file is closed
219
                fwrite($fp, $str);
220
                fclose($fp);
221
            }
222
        }	
223
224
        /**
225
         * Check if the current logger can save log data regarding the configuration
226
         * of logger filter
227
         * @return boolean
228
         */
229
        protected function canSaveLogDataForLogger() {
230
                $configLoggersName = get_config('log_logger_name', array());
231
                if (!empty($configLoggersName)) {
232
                    //for best comparaison put all string to lowercase
233
                    $configLoggersName = array_map('strtolower', $configLoggersName);
234
                    if (!in_array(strtolower($this->logger), $configLoggersName)) {
235
                        return false;
236
                    }
237
                }
238
            return true;
239
        }
240
241
        /**
242
         * Check the file and directory 
243
         * @return string|null the log file path
244
         */
245
        protected function checkAndSetLogFileDirectory() {
246
            $logSavePath = get_config('log_save_path');
247
            if (!$logSavePath) {
248
                $logSavePath = LOGS_PATH;
249
            }
250
			
251
            if (!is_dir($logSavePath) || !is_writable($logSavePath)) {
252
                //NOTE: here need put the show_error() "logging" to false 
253
                //to prevent self function loop call
254
                show_error('Error : the log dir does not exist or is not writable', 'Log directory error', $logging = false);
255
            }
256
			
257
            $path = $logSavePath . 'logs-' . date('Y-m-d') . '.log';
258
            if (!file_exists($path)) {
259
                touch($path);
260
            }
261
            return $path;
262
        }
263
		
264
        /**
265
         * Check if the given log level is valid
266
         *
267
         * @param  string  $level the log level
268
         *
269
         * @return boolean        true if the given log level is valid, false if not
270
         */
271
        protected static function isValidConfigLevel($level) {
272
            $level = strtolower($level);
273
            return in_array($level, self::$validConfigLevel);
274
        }
275
276
        /**
277
         * Get the log level number for the given level string
278
         * @param  string $level the log level in string format
279
         * 
280
         * @return int the log level in integer format using the predefined constants
281
         */
282
        protected static function getLevelValue($level) {
283
            $level = strtolower($level);
284
            $levelMaps = array(
285
                'fatal'   => self::FATAL,
286
                'error'   => self::ERROR,
287
                'warning' => self::WARNING,
288
                'warn'    => self::WARNING,
289
                'info'    => self::INFO,
290
                'debug'   => self::DEBUG,
291
                'all'     => self::ALL
292
            );
293
            //the default value is NONE, so means no need test for NONE
294
            $value = self::NONE;
295
            if (isset($levelMaps[$level])) {
296
                $value = $levelMaps[$level];
297
            }
298
            return $value;
299
        }
300
301
        /**
302
         * Get the log level string for the given log level integer
303
         * @param  integer $level the log level in integer format
304
         * @return string        the log level in string format
305
         */
306
        protected static function getLevelName($level) {
307
            $levelMaps = array(
308
                self::FATAL   => 'FATAL',
309
                self::ERROR   => 'ERROR',
310
                self::WARNING => 'WARNING',
311
                self::INFO    => 'INFO',
312
                self::DEBUG   => 'DEBUG'
313
            );
314
            $value = '';
315
            if (isset($levelMaps[$level])) {
316
                $value = $levelMaps[$level];
317
            }
318
            return $value;
319
        }
320
321
    }
322