source: trunk/include/Logger.class.php @ 31102

Last change on this file since 31102 was 31102, checked in by mistic100, 9 years ago

feature 3221 Add Logger class

File size: 12.0 KB
Line 
1<?php
2// +-----------------------------------------------------------------------+
3// | Piwigo - a PHP based photo gallery                                    |
4// +-----------------------------------------------------------------------+
5// | Copyright(C) 2008-2015 Piwigo Team          http://piwigo.org |
6// | Copyright(C) 2003-2008 PhpWebGallery Team  http://phpwebgallery.net |
7// | Copyright(C) 2002-2003 Pierrick LE GALL   http://le-gall.net/pierrick |
8// +-----------------------------------------------------------------------+
9// | This program is free software; you can redistribute it and/or modify  |
10// | it under the terms of the GNU General Public License as published by  |
11// | the Free Software Foundation                                          |
12// |                                                                       |
13// | This program is distributed in the hope that it will be useful, but   |
14// | WITHOUT ANY WARRANTY; without even the implied warranty of            |
15// | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU      |
16// | General Public License for more details.                              |
17// |                                                                       |
18// | You should have received a copy of the GNU General Public License     |
19// | along with this program; if not, write to the Free Software           |
20// | Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, |
21// | USA.                                                                  |
22// +-----------------------------------------------------------------------+
23
24/**
25 * Modified version of KLogger 0.2.0
26 *
27 * @author  Kenny Katzgrau <katzgrau@gmail.com>
28 *
29 * @package logger
30 */
31
32class Logger
33{
34  /**
35   * Error severity, from low to high. From BSD syslog RFC, section 4.1.1
36   * @link http://www.faqs.org/rfcs/rfc3164.html
37   */
38  const EMERGENCY = 0;  // Emergency: system is unusable
39  const ALERT     = 1;  // Alert: action must be taken immediately
40  const CRITICAL  = 2;  // Critical: critical conditions
41  const ERROR     = 3;  // Error: error conditions
42  const WARNING   = 4;  // Warning: warning conditions
43  const NOTICE    = 5;  // Notice: normal but significant condition
44  const INFO      = 6;  // Informational: informational messages
45  const DEBUG     = 7;  // Debug: debug messages
46
47  /**
48   * Custom "disable" level.
49   */
50  const OFF       = -1; // Log nothing at all
51
52  /**
53   * Internal status codes.
54   */
55  const STATUS_LOG_OPEN  = 1;
56  const STATUS_OPEN_FAILED = 2;
57  const STATUS_LOG_CLOSED  = 3;
58
59  /**
60   * Disable archive purge.
61   */
62  const ARCHIVE_NO_PURGE = -1;
63
64  /**
65   * Standard messages produced by the class.
66   * @var array
67   */
68  private static $_messages = array(
69    'writefail'   => 'The file could not be written to. Check that appropriate permissions have been set.',
70    'opensuccess' => 'The log file was opened successfully.',
71    'openfail'  => 'The file could not be opened. Check permissions.',
72  );
73
74  /**
75   * Instance options.
76   * @var array
77   */
78  private $options = array(
79    'directory' => null, // Log files directory
80    'filename' => null, // Path to the log file
81    'globPattern' => 'log_*.txt', // Pattern to select all log files with glob()
82    'severity' => self::DEBUG, // Current minimum logging threshold
83    'dateFormat' => 'Y-m-d G:i:s', // Date format
84    'archiveDays' => self::ARCHIVE_NO_PURGE, // Number of files to keep
85    );
86
87  /**
88   * Current status of the logger.
89   * @var integer
90   */
91  private $_logStatus = self::STATUS_LOG_CLOSED;
92  /**
93   * File handle for this instance's log file.
94   * @var resource
95   */
96  private $_fileHandle = null;
97
98  /**
99   * Class constructor.
100   *
101   * @param array $options
102   * @return void
103   */
104  public function __construct($options)
105  {
106    $this->options = array_merge($this->options, $options);
107   
108    if (is_string($this->options['severity'])) {
109      $this->options['severity'] = self::codeToLevel($this->options['severity']);
110    }
111
112    if ($this->options['severity'] === self::OFF) {
113      return;
114    }
115
116    $this->options['directory'] = rtrim($this->options['directory'], '\\/') . DIRECTORY_SEPARATOR;
117
118    if ($this->options['filename'] == null) {
119      $this->options['filename'] = 'log_' . date('Y-m-d') . '.txt';
120    }
121
122    $this->options['filePath'] = $this->options['directory'] . $this->options['filename'];
123
124    if (!file_exists($this->options['directory'])) {
125      mkgetdir($this->options['directory'], MKGETDIR_DEFAULT|MKGETDIR_PROTECT_HTACCESS);
126    }
127
128    if (file_exists($this->options['filePath']) && !is_writable($this->options['filePath'])) {
129      $this->_logStatus = self::STATUS_OPEN_FAILED;
130      throw new RuntimeException(self::$_messages['writefail']);
131      return;
132    }
133
134    if (($this->_fileHandle = fopen($this->options['filePath'], 'a'))) {
135      $this->_logStatus = self::STATUS_LOG_OPEN;
136    }
137    else {
138      $this->_logStatus = self::STATUS_OPEN_FAILED;
139      throw new RuntimeException(self::$_messages['openfail']);
140    }
141
142    if ($this->options['archiveDays'] != self::ARCHIVE_NO_PURGE && rand() % 97 == 0) {
143      $this->purge();
144    }
145  }
146
147  /**
148   * Class destructor.
149   */
150  public function __destruct()
151  {
152    if ($this->_fileHandle) {
153      fclose($this->_fileHandle);
154    }
155  }
156
157  /**
158   * Returns logger status.
159   *
160   * @return int
161   */
162  public function status()
163  {
164    return $this->_logStatus;
165  }
166
167  /**
168   * Returns logger severity threshold.
169   *
170   * @return int
171   */
172  public function severity()
173  {
174    return $this->options['severity'];
175  }
176
177  /**
178   * Writes a $line to the log with a severity level of DEBUG.
179   *
180   * @param string $line
181   * @param string $cat
182   * @param array $args
183   */
184  public function debug($line, $cat = null, $args = array())
185  {
186    $this->log(self::DEBUG, $line, $cat, $args);
187  }
188
189  /**
190   * Writes a $line to the log with a severity level of INFO.
191   *
192   * @param string $line
193   * @param string $cat
194   * @param array $args
195   */
196  public function info($line, $cat = null, $args = array())
197  {
198    $this->log(self::INFO, $line, $cat, $args);
199  }
200
201  /**
202   * Writes a $line to the log with a severity level of NOTICE.
203   *
204   * @param string $line
205   * @param string $cat
206   * @param array $args
207   */
208  public function notice($line, $cat = null, $args = array())
209  {
210    $this->log(self::NOTICE, $line, $cat, $args);
211  }
212
213  /**
214   * Writes a $line to the log with a severity level of WARNING.
215   *
216   * @param string $line
217   * @param string $cat
218   * @param array $args
219   */
220  public function warn($line, $cat = null, $args = array())
221  {
222    $this->log(self::WARNING, $line, $cat, $args);
223  }
224
225  /**
226   * Writes a $line to the log with a severity level of ERROR.
227   *
228   * @param string $line
229   * @param string $cat
230   * @param array $args
231   */
232  public function error($line, $cat = null, $args = array())
233  {
234    $this->log(self::ERROR, $line, $cat, $args);
235  }
236
237  /**
238   * Writes a $line to the log with a severity level of ALERT.
239   *
240   * @param string $line
241   * @param string $cat
242   * @param array $args
243   */
244  public function alert($line, $cat = null, $args = array())
245  {
246    $this->log(self::ALERT, $line, $cat, $args);
247  }
248
249  /**
250   * Writes a $line to the log with a severity level of CRITICAL.
251   *
252   * @param string $line
253   * @param string $cat
254   * @param array $args
255   */
256  public function critical($line, $cat = null, $args = array())
257  {
258    $this->log(self::CRITICAL, $line, $cat, $args);
259  }
260
261  /**
262   * Writes a $line to the log with a severity level of EMERGENCY.
263   *
264   * @param string $line
265   * @param string $cat
266   * @param array $args
267   */
268  public function emergency($line, $cat = null, $args = array())
269  {
270    $this->log(self::EMERGENCY, $line, $cat, $args);
271  }
272
273  /**
274   * Writes a $line to the log with the given severity.
275   *
276   * @param integer $severity
277   * @param string $line
278   * @param string $cat
279   * @param array $args
280   */
281  public function log($severity, $message, $cat = null, $args = array())
282  {
283    if ($this->severity() >= $severity) {
284      if (is_array($cat)) {
285        $args = $cat;
286        $cat = null;
287      }
288      $line = $this->formatMessage($severity, $message, $cat, $args);
289      $this->write($line);
290    }
291  }
292
293  /**
294   * Directly writes a line to the log without adding level and time.
295   *
296   * @param string $line
297   */
298  public function write($line)
299  {
300    if ($this->_logStatus == self::STATUS_LOG_OPEN) {
301      if (fwrite($this->_fileHandle, $line) === false) {
302        throw new RuntimeException(self::$_messages['writefail']);
303      }
304    }
305  }
306
307  /**
308   * Purges files matching 'globPattern' older than 'archiveDays'.
309   */
310  public function purge() {
311    $files = glob($this->options['directory'] . $this->options['globPattern']);
312    $limit = time() - $this->options['archiveDays'] * 86400;
313
314    foreach ($files as $file) {
315      if (@filemtime($file) < $limit) {
316        @unlink($file);
317      }
318    }
319  }
320
321  /**
322   * Formats the message for logging.
323   *
324   * @param  string $level
325   * @param  string $message
326   * @param  array  $context
327   * @return string
328   */
329  private function formatMessage($level, $message, $cat, $context)
330  {
331    if (!empty($context)) {
332      $message .= "\n" . $this->indent($this->contextToString($context));
333    }
334    $line = "[" . $this->getTimestamp() . "]\t[" . self::levelToCode($level) . "]\t";
335    if ($cat != null) {
336      $line .= "[" . $cat . "]\t";
337    }
338    return $line . $message . "\n";
339  }
340
341  /**
342   * Gets the formatted Date/Time for the log entry.
343   *
344   * PHP DateTime is dumb, and you have to resort to trickery to get microseconds
345   * to work correctly, so here it is.
346   *
347   * @return string
348   */
349  private function getTimestamp()
350  {
351    $originalTime = microtime(true);
352    $micro = sprintf("%06d", ($originalTime - floor($originalTime)) * 1000000);
353    $date = new DateTime(date('Y-m-d H:i:s.'.$micro, $originalTime));
354    return $date->format($this->options['dateFormat']);
355  }
356
357  /**
358   * Takes the given context and converts it to a string.
359   *
360   * @param  array $context
361   * @return string
362   */
363  private function contextToString($context)
364  {
365    $export = '';
366    foreach ($context as $key => $value) {
367      $export .= "{$key}: ";
368      $export .= preg_replace(array(
369        '/=>\s+([a-zA-Z])/im',
370        '/array\(\s+\)/im',
371        '/^  |\G  /m'
372      ), array(
373        '=> $1',
374        'array()',
375        '  '
376      ), str_replace('array (', 'array(', var_export($value, true)));
377      $export .= PHP_EOL;
378    }
379    return str_replace(array('\\\\', '\\\''), array('\\', '\''), rtrim($export));
380  }
381
382  /**
383   * Indents the given string with the given indent.
384   *
385   * @param  string $string The string to indent
386   * @param  string $indent What to use as the indent.
387   * @return string
388   */
389  private function indent($string, $indent = '  ')
390  {
391    return $indent.str_replace("\n", "\n".$indent, $string);
392  }
393
394  /**
395   * Converts level constants to string name.
396   *
397   * @param int $level
398   * @return string
399   */
400  static function levelToCode($level)
401  {
402    switch ($level) {
403      case self::EMERGENCY:
404        return 'EMERGENCY';
405      case self::ALERT:
406        return 'ALERT';
407      case self::CRITICAL:
408        return 'CRITICAL';
409      case self::NOTICE:
410        return 'NOTICE';
411      case self::INFO:
412        return 'INFO';
413      case self::WARNING:
414        return 'WARNING';
415      case self::DEBUG:
416        return 'DEBUG';
417      case self::ERROR:
418        return 'ERROR';
419      default:
420        throw new RuntimeException('Unknown severity level ' . $level);
421    }
422  }
423
424  /**
425   * Converts level names to constant.
426   *
427   * @param string $code
428   * @return int
429   */
430  static function codeToLevel($code)
431  {
432    switch (strtoupper($code)) {
433      case 'EMERGENCY':
434        return self::EMERGENCY;
435      case 'ALERT':
436        return self::ALERT;
437      case 'CRITICAL':
438        return self::CRITICAL;
439      case 'NOTICE':
440        return self::NOTICE;
441      case 'INFO':
442        return self::INFO;
443      case 'WARNING':
444        return self::WARNING;
445      case 'DEBUG':
446        return self::DEBUG;
447      case 'ERROR':
448        return self::ERROR;
449      default:
450        throw new RuntimeException('Unknown severity code ' . $code);
451    }
452  }
453}
Note: See TracBrowser for help on using the repository browser.