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

Last change on this file since 31157 was 31157, checked in by rvelices, 9 years ago

?> at end of php (even if not required)

File size: 12.4 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  /**
100   * Class constructor.
101   *
102   * @param array $options
103   * @return void
104   */
105  public function __construct($options)
106  {
107    $this->options = array_merge($this->options, $options);
108   
109    if (is_string($this->options['severity']))
110    {
111      $this->options['severity'] = self::codeToLevel($this->options['severity']);
112    }
113
114    if ($this->options['severity'] === self::OFF)
115    {
116      return;
117    }
118
119    $this->options['directory'] = rtrim($this->options['directory'], '\\/') . DIRECTORY_SEPARATOR;
120
121    if ($this->options['filename'] == null)
122    {
123      $this->options['filename'] = 'log_' . date('Y-m-d') . '.txt';
124    }
125
126    $this->options['filePath'] = $this->options['directory'] . $this->options['filename'];
127
128    if ($this->options['archiveDays'] != self::ARCHIVE_NO_PURGE && rand() % 97 == 0)
129    {
130      $this->purge();
131    }
132  }
133 
134  /**
135   * Open the log file if not already oppenned
136   */
137  private function open()
138  {
139    if ($this->status() == self::STATUS_LOG_CLOSED)
140    {
141      if (!file_exists($this->options['directory']))
142      {
143        mkgetdir($this->options['directory'], MKGETDIR_DEFAULT|MKGETDIR_PROTECT_HTACCESS);
144      }
145
146      if (file_exists($this->options['filePath']) && !is_writable($this->options['filePath']))
147      {
148        $this->_logStatus = self::STATUS_OPEN_FAILED;
149        throw new RuntimeException(self::$_messages['writefail']);
150        return;
151      }
152
153      if (($this->_fileHandle = fopen($this->options['filePath'], 'a')) != false)
154      {
155        $this->_logStatus = self::STATUS_LOG_OPEN;
156      }
157      else
158      {
159        $this->_logStatus = self::STATUS_OPEN_FAILED;
160        throw new RuntimeException(self::$_messages['openfail']);
161      }
162    }
163  }
164
165  /**
166   * Class destructor.
167   */
168  public function __destruct()
169  {
170    if ($this->_fileHandle)
171    {
172      fclose($this->_fileHandle);
173    }
174  }
175
176  /**
177   * Returns logger status.
178   *
179   * @return int
180   */
181  public function status()
182  {
183    return $this->_logStatus;
184  }
185
186  /**
187   * Returns logger severity threshold.
188   *
189   * @return int
190   */
191  public function severity()
192  {
193    return $this->options['severity'];
194  }
195
196  /**
197   * Writes a $line to the log with a severity level of DEBUG.
198   *
199   * @param string $line
200   * @param string $cat
201   * @param array $args
202   */
203  public function debug($line, $cat = null, $args = array())
204  {
205    $this->log(self::DEBUG, $line, $cat, $args);
206  }
207
208  /**
209   * Writes a $line to the log with a severity level of INFO.
210   *
211   * @param string $line
212   * @param string $cat
213   * @param array $args
214   */
215  public function info($line, $cat = null, $args = array())
216  {
217    $this->log(self::INFO, $line, $cat, $args);
218  }
219
220  /**
221   * Writes a $line to the log with a severity level of NOTICE.
222   *
223   * @param string $line
224   * @param string $cat
225   * @param array $args
226   */
227  public function notice($line, $cat = null, $args = array())
228  {
229    $this->log(self::NOTICE, $line, $cat, $args);
230  }
231
232  /**
233   * Writes a $line to the log with a severity level of WARNING.
234   *
235   * @param string $line
236   * @param string $cat
237   * @param array $args
238   */
239  public function warn($line, $cat = null, $args = array())
240  {
241    $this->log(self::WARNING, $line, $cat, $args);
242  }
243
244  /**
245   * Writes a $line to the log with a severity level of ERROR.
246   *
247   * @param string $line
248   * @param string $cat
249   * @param array $args
250   */
251  public function error($line, $cat = null, $args = array())
252  {
253    $this->log(self::ERROR, $line, $cat, $args);
254  }
255
256  /**
257   * Writes a $line to the log with a severity level of ALERT.
258   *
259   * @param string $line
260   * @param string $cat
261   * @param array $args
262   */
263  public function alert($line, $cat = null, $args = array())
264  {
265    $this->log(self::ALERT, $line, $cat, $args);
266  }
267
268  /**
269   * Writes a $line to the log with a severity level of CRITICAL.
270   *
271   * @param string $line
272   * @param string $cat
273   * @param array $args
274   */
275  public function critical($line, $cat = null, $args = array())
276  {
277    $this->log(self::CRITICAL, $line, $cat, $args);
278  }
279
280  /**
281   * Writes a $line to the log with a severity level of EMERGENCY.
282   *
283   * @param string $line
284   * @param string $cat
285   * @param array $args
286   */
287  public function emergency($line, $cat = null, $args = array())
288  {
289    $this->log(self::EMERGENCY, $line, $cat, $args);
290  }
291
292  /**
293   * Writes a $line to the log with the given severity.
294   *
295   * @param integer $severity
296   * @param string $line
297   * @param string $cat
298   * @param array $args
299   */
300  public function log($severity, $message, $cat = null, $args = array())
301  {
302    if ($this->severity() >= $severity)
303    {
304      if (is_array($cat))
305      {
306        $args = $cat;
307        $cat = null;
308      }
309      $line = $this->formatMessage($severity, $message, $cat, $args);
310      $this->write($line);
311    }
312  }
313
314  /**
315   * Directly writes a line to the log without adding level and time.
316   *
317   * @param string $line
318   */
319  public function write($line)
320  {
321    $this->open();
322    if ($this->status() == self::STATUS_LOG_OPEN)
323    {
324      if (fwrite($this->_fileHandle, $line) === false)
325      {
326        throw new RuntimeException(self::$_messages['writefail']);
327      }
328    }
329  }
330
331  /**
332   * Purges files matching 'globPattern' older than 'archiveDays'.
333   */
334  public function purge()
335  {
336    $files = glob($this->options['directory'] . $this->options['globPattern']);
337    $limit = time() - $this->options['archiveDays'] * 86400;
338
339    foreach ($files as $file)
340    {
341      if (@filemtime($file) < $limit)
342      {
343        @unlink($file);
344      }
345    }
346  }
347
348  /**
349   * Formats the message for logging.
350   *
351   * @param  string $level
352   * @param  string $message
353   * @param  array  $context
354   * @return string
355   */
356  private function formatMessage($level, $message, $cat, $context)
357  {
358    if (!empty($context))
359    {
360      $message.= "\n" . $this->indent($this->contextToString($context));
361    }
362    $line = "[" . $this->getTimestamp() . "]\t[" . self::levelToCode($level) . "]\t";
363    if ($cat != null)
364    {
365      $line.= "[" . $cat . "]\t";
366    }
367    return $line . $message . "\n";
368  }
369
370  /**
371   * Gets the formatted Date/Time for the log entry.
372   *
373   * PHP DateTime is dumb, and you have to resort to trickery to get microseconds
374   * to work correctly, so here it is.
375   *
376   * @return string
377   */
378  private function getTimestamp()
379  {
380    $originalTime = microtime(true);
381    $micro = sprintf('%06d', ($originalTime - floor($originalTime)) * 1000000);
382    $date = new DateTime(date('Y-m-d H:i:s.'.$micro, $originalTime));
383    return $date->format($this->options['dateFormat']);
384  }
385
386  /**
387   * Takes the given context and converts it to a string.
388   *
389   * @param  array $context
390   * @return string
391   */
392  private function contextToString($context)
393  {
394    $export = '';
395    foreach ($context as $key => $value)
396    {
397      $export.= $key . ': ';
398      $export.= preg_replace(array(
399        '/=>\s+([a-zA-Z])/im',
400        '/array\(\s+\)/im',
401        '/^  |\G  /m'
402        ),
403        array(
404        '=> $1',
405        'array()',
406        '  '
407        ),
408        str_replace('array (', 'array(', var_export($value, true))
409        );
410      $export.= PHP_EOL;
411    }
412    return str_replace(array('\\\\', '\\\''), array('\\', '\''), rtrim($export));
413  }
414
415  /**
416   * Indents the given string with the given indent.
417   *
418   * @param  string $string The string to indent
419   * @param  string $indent What to use as the indent.
420   * @return string
421   */
422  private function indent($string, $indent = '  ')
423  {
424    return $indent . str_replace("\n", "\n" . $indent, $string);
425  }
426
427  /**
428   * Converts level constants to string name.
429   *
430   * @param int $level
431   * @return string
432   */
433  static function levelToCode($level)
434  {
435    switch ($level)
436    {
437      case self::EMERGENCY:
438        return 'EMERGENCY';
439      case self::ALERT:
440        return 'ALERT';
441      case self::CRITICAL:
442        return 'CRITICAL';
443      case self::NOTICE:
444        return 'NOTICE';
445      case self::INFO:
446        return 'INFO';
447      case self::WARNING:
448        return 'WARNING';
449      case self::DEBUG:
450        return 'DEBUG';
451      case self::ERROR:
452        return 'ERROR';
453      default:
454        throw new RuntimeException('Unknown severity level ' . $level);
455    }
456  }
457
458  /**
459   * Converts level names to constant.
460   *
461   * @param string $code
462   * @return int
463   */
464  static function codeToLevel($code)
465  {
466    switch (strtoupper($code))
467    {
468      case 'EMERGENCY':
469        return self::EMERGENCY;
470      case 'ALERT':
471        return self::ALERT;
472      case 'CRITICAL':
473        return self::CRITICAL;
474      case 'NOTICE':
475        return self::NOTICE;
476      case 'INFO':
477        return self::INFO;
478      case 'WARNING':
479        return self::WARNING;
480      case 'DEBUG':
481        return self::DEBUG;
482      case 'ERROR':
483        return self::ERROR;
484      default:
485        throw new RuntimeException('Unknown severity code ' . $code);
486    }
487  }
488}
489?>
Note: See TracBrowser for help on using the repository browser.