Test Failed
Push — 1.0.0-dev ( dc6bfa...14bd99 )
by nguereza
02:55
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 GNU GPL License (GPL)
9
	 *
10
	 * Copyright (C) 2017 Tony NGUEREZA
11
	 *
12
	 * This program is free software; you can redistribute it and/or
13
	 * modify it under the terms of the GNU General Public License
14
	 * as published by the Free Software Foundation; either version 3
15
	 * of the License, or (at your option) any later version.
16
	 *
17
	 * This program is distributed in the hope that it will be useful,
18
	 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19
	 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20
	 * GNU General Public License for more details.
21
	 *
22
	 * You should have received a copy of the GNU General Public License
23
	 * along with this program; if not, write to the Free Software
24
	 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
25
	*/
26
27
	class Log{
28
		
29
		/**
30
		 * The defined constante for Log level
31
		 */
32
		const NONE = 99999999;
33
		const FATAL = 500;
34
		const ERROR = 400;
35
		const WARNING = 300;
36
		const INFO = 200;
37
		const DEBUG = 100;
38
		const ALL = -99999999;
39
40
		/**
41
		 * The logger name
42
		 * @var string
43
		 */
44
		private $logger = 'ROOT_LOGGER';
45
		
46
		/**
47
		 * List of valid log level to be checked for the configuration
48
		 * @var array
49
		 */
50
		private static $validConfigLevel = array('off', 'none', 'fatal', 'error', 'warning', 'warn', 'info', 'debug', 'all');
51
52
		/**
53
		 * Create new Log instance
54
		 */
55
		public function __construct(){
56
		}
57
58
		/**
59
		 * Set the logger to identify each message in the log
60
		 * @param string $logger the logger name
61
		 */
62
		public  function setLogger($logger){
63
			$this->logger = $logger;
64
		}
65
66
		/**
67
		 * Save the fatal message in the log
68
		 * @see Log::writeLog for more detail
69
		 * @param  string $message the log message to save
70
		 */
71
		public function fatal($message){
72
			$this->writeLog($message, self::FATAL);
73
		} 
74
		
75
		/**
76
		 * Save the error message in the log
77
		 * @see Log::writeLog for more detail
78
		 * @param  string $message the log message to save
79
		 */
80
		public function error($message){
81
			$this->writeLog($message, self::ERROR);
82
		} 
83
84
		/**
85
		 * Save the warning message in the log
86
		 * @see Log::writeLog for more detail
87
		 * @param  string $message the log message to save
88
		 */
89
		public function warning($message){
90
			$this->writeLog($message, self::WARNING);
91
		} 
92
		
93
		/**
94
		 * Save the info message in the log
95
		 * @see Log::writeLog for more detail
96
		 * @param  string $message the log message to save
97
		 */
98
		public function info($message){
99
			$this->writeLog($message, self::INFO);
100
		} 
101
		
102
		/**
103
		 * Save the debug message in the log
104
		 * @see Log::writeLog for more detail
105
		 * @param  string $message the log message to save
106
		 */
107
		public function debug($message){
108
			$this->writeLog($message, self::DEBUG);
109
		} 
110
		
111
		
112
		/**
113
		 * Save the log message
114
		 * @param  string $message the log message to be saved
115
		 * @param  integer|string $level   the log level in integer or string format, if is string will convert into integer
116
		 * to allow check the log level threshold.
117
		 */
118
		public function writeLog($message, $level = self::INFO){
119
			$configLogLevel = get_config('log_level');
120
			if(! $configLogLevel){
121
				//so means no need log just stop here
122
				return;
123
			}
124
			//check config log level
125
			if(! self::isValidConfigLevel($configLogLevel)){
126
				//NOTE: here need put the show_error() "logging" to false to prevent loop
127
				show_error('Invalid config log level [' . $configLogLevel . '], the value must be one of the following: ' . implode(', ', array_map('strtoupper', self::$validConfigLevel)), $title = 'Log Config Error', $logging = false);	
128
			}
129
			
130
			//check if config log_logger_name and current log can save log data
131
			if(! $this->canSaveLogDataForLogger()){
132
				return;
133
			}
134
			
135
			//if $level is not an integer
136
			if(! is_numeric($level)){
137
				$level = self::getLevelValue($level);
138
			}
139
			
140
			//check if can logging regarding the log level config
141
			$configLevel = self::getLevelValue($configLogLevel);
142
			if($configLevel > $level){
143
				//can't log
144
				return;
145
			}
146
			//check log file and directory
147
			$path = $this->checkAndSetLogFileDirectory();
148
			//save the log data
149
			$this->saveLogData($path, $level, $message);
150
		}	
151
152
		/**
153
		 * Save the log data into file
154
		 * @param  string $path    the path of the log file
155
		 * @param  integer|string $level   the log level in integer or string format, if is string will convert into integer
156
		 * @param  string $message the log message to save
157
		 * @return void
158
		 */
159
		protected function saveLogData($path, $level, $message){
160
			//may be at this time helper user_agent not yet included
161
			require_once CORE_FUNCTIONS_PATH . 'function_user_agent.php';
162
			
163
			///////////////////// date //////////////
164
			$timestampWithMicro = microtime(true);
165
			$microtime = sprintf('%06d', ($timestampWithMicro - floor($timestampWithMicro)) * 1000000);
166
			$dateTime = new DateTime(date('Y-m-d H:i:s.' . $microtime, $timestampWithMicro));
167
			$logDate = $dateTime->format('Y-m-d H:i:s.u'); 
168
			//ip
169
			$ip = get_ip();
170
			
171
			//if $level is not an integer
172
			if(! is_numeric($level)){
173
				$level = self::getLevelValue($level);
174
			}
175
176
			//level name
177
			$levelName = self::getLevelName($level);
0 ignored issues
show
Bug introduced by
It seems like $level can also be of type string; however, parameter $level of Log::getLevelName() 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

177
			$levelName = self::getLevelName(/** @scrutinizer ignore-type */ $level);
Loading history...
178
			
179
			//debug info
180
			$dtrace = debug_backtrace();
181
			$fileInfo = $dtrace[0]['file'] == __FILE__ ? $dtrace[1] : $dtrace[0];
182
			
183
			$str = $logDate . ' [' . str_pad($levelName, 7 /*warning len*/) . '] ' . ' [' . str_pad($ip, 15) . '] ' . $this->logger . ' : ' . $message . ' ' . '[' . $fileInfo['file'] . '::' . $fileInfo['line'] . ']' . "\n";
184
			$fp = fopen($path, 'a+');
185
			if(is_resource($fp)){
186
				flock($fp, LOCK_EX); // exclusive lock, will get released when the file is closed
187
				fwrite($fp, $str);
188
				fclose($fp);
189
			}
190
		}	
191
192
		/**
193
		 * Check if the current logger can save log data regarding the configuration
194
		 * of logger filter
195
		 * @return boolean
196
		 */
197
		protected function canSaveLogDataForLogger(){
198
			if(! empty($this->logger)){
199
				$configLoggersName = get_config('log_logger_name', array());
200
				if (!empty($configLoggersName)) {
201
					//for best comparaison put all string to lowercase
202
					$configLoggersName = array_map('strtolower', $configLoggersName);
203
					if(! in_array(strtolower($this->logger), $configLoggersName)){
204
						return false;
205
					}
206
				}
207
			}
208
			return true;
209
		}
210
211
		/**
212
		 * Check the file and directory 
213
		 * @return string the log file path
214
		 */
215
		protected function checkAndSetLogFileDirectory(){
216
			$logSavePath = get_config('log_save_path');
217
			if(! $logSavePath){
218
				$logSavePath = LOGS_PATH;
219
			}
220
			
221
			if(! is_dir($logSavePath) || !is_writable($logSavePath)){
222
				//NOTE: here need put the show_error() "logging" to false to prevent loop
223
				show_error('Error : the log dir does not exists or is not writable', $title = 'Log directory error', $logging = false);
224
			}
225
			
226
			$path = $logSavePath . 'logs-' . date('Y-m-d') . '.log';
227
			if(! file_exists($path)){
228
				touch($path);
229
			}
230
			return $path;
231
		}
232
		
233
		/**
234
		 * Check if the given log level is valid
235
		 *
236
		 * @param  string  $level the log level
237
		 *
238
		 * @return boolean        true if the given log level is valid, false if not
239
		 */
240
		protected static function isValidConfigLevel($level){
241
			$level = strtolower($level);
242
			return in_array($level, self::$validConfigLevel);
243
		}
244
245
		/**
246
		 * Get the log level number for the given level string
247
		 * @param  string $level the log level in string format
248
		 * 
249
		 * @return int        the log level in integer format using the predefined constants
250
		 */
251
		protected static function getLevelValue($level){
252
			$level = strtolower($level);
253
			$levelMaps = array(
254
				'fatal'   => self::FATAL,
255
				'error'   => self::ERROR,
256
				'warning' => self::WARNING,
257
				'warn'    => self::WARNING,
258
				'info'    => self::INFO,
259
				'debug'   => self::DEBUG,
260
				'all'     => self::ALL
261
			);
262
			//the default value is NONE, so means no need test for NONE
263
			$value = self::NONE;
264
			foreach ($levelMaps as $k => $v) {
265
				if($level == $k){
266
					$value = $v;
267
					break;
268
				}
269
			}
270
			return $value;
271
		}
272
273
		/**
274
		 * Get the log level string for the given log level integer
275
		 * @param  integer $level the log level in integer format
276
		 * @return string        the log level in string format
277
		 */
278
		protected static function getLevelName($level){
279
			$levelMaps = array(
280
				self::FATAL   => 'FATAL',
281
				self::ERROR   => 'ERROR',
282
				self::WARNING => 'WARNING',
283
				self::INFO    => 'INFO',
284
				self::DEBUG   => 'DEBUG'
285
			);
286
			$value = '';
287
			foreach ($levelMaps as $k => $v) {
288
				if($level == $k){
289
					$value = $v;
290
					break;
291
				}
292
			}
293
			//no need for ALL
294
			return $value;
295
		}
296
297
	}
298