Plop
A simple logging library for PHP
Plop.php
1 <?php
2 /*
3  This file is part of Plop, a simple logging library for PHP.
4 
5  Copyright © 2010-2014 François Poirotte
6 
7  Plop is free software: you can redistribute it and/or modify
8  it under the terms of the GNU General Public License as published by
9  the Free Software Foundation, either version 3 of the License, or
10  (at your option) any later version.
11 
12  Plop is distributed in the hope that it will be useful,
13  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  GNU General Public License for more details.
16 
17  You should have received a copy of the GNU General Public License
18  along with Plop. If not, see <http://www.gnu.org/licenses/>.
19 */
20 
21 namespace Plop;
22 
24 const NOTSET = 0;
25 
27 const DEBUG = 10;
28 
34 const INFO = 20;
35 
37 const NOTICE = 30;
38 
45 const WARNING = 40;
46 
48 const WARN = \Plop\WARNING;
49 
54 const ERROR = 50;
55 
61 const CRITICAL = 60;
62 
69 const ALERT = 70;
70 
72 const EMERGENCY = 80;
73 
74 
143 {
145  const BASIC_FORMAT = '[%(levelname)s] %(message)s';
146 
148  protected static $instance = null;
149 
151  protected $loggers;
153  protected $levelNames;
155  protected $created;
156 
160  protected function __construct()
161  {
162  $this->loggers = array();
163  $rootLogger = new \Plop\Logger(null, null);
164  $basicHandler = new \Plop\Handler\Stream(fopen('php://stderr', 'w'));
165  $this[] = $rootLogger;
166  $handlers = $rootLogger->getHandlers();
167  $formatter = new \Plop\Formatter(self::BASIC_FORMAT);
168  $handlers[] = $basicHandler->setFormatter($formatter);
169  $this->created = microtime(true);
170  $this->levelNames = array(
171  \Plop\NOTSET => 'NOTSET',
172  \Plop\DEBUG => 'DEBUG',
173  \Plop\INFO => 'INFO',
174  \Plop\NOTICE => 'NOTICE',
175  \Plop\WARNING => 'WARNING',
176  \Plop\ERROR => 'ERROR',
177  \Plop\CRITICAL => 'CRITICAL',
178  \Plop\ALERT => 'ALERT',
179  \Plop\EMERGENCY => 'EMERGENCY',
180  );
181  }
182 
184  public function __clone()
185  {
186  throw new \Plop\Exception('Cloning this class is forbidden');
187  }
188 
195  public static function & getInstance()
196  {
197  if (static::$instance === null) {
198  $c = __CLASS__;
199  static::$instance = new $c();
200  }
201  return static::$instance;
202  }
203 
212  public function getCreationDate()
213  {
214  return $this->created;
215  }
216 
229  public function addLevelName($levelName, $levelValue)
230  {
231  if (!is_int($levelValue)) {
232  throw new \Plop\Exception('Invalid level value');
233  }
234  if (!is_string($levelName)) {
235  throw new \Plop\Exception('Invalid level name');
236  }
237  $this->levelNames[$levelValue] = $levelName;
238  return $this;
239  }
240 
255  public function getLevelName($level)
256  {
257  if (!is_int($level)) {
258  throw new \Plop\Exception('Invalid level value');
259  }
260  if (!isset($this->levelNames[$level])) {
261  return "Level $level";
262  }
263  return $this->levelNames[$level];
264  }
265 
283  public function getLevelValue($levelName)
284  {
285  if (!is_string($levelName)) {
286  throw new \Plop\Exception('Invalid level name');
287  }
288  $key = array_search($levelName, $this->levelNames, true);
289  return (int) $key; // false is silently converted to 0.
290  }
291 
345  public function getLogger($namespace = '', $class = '', $method = '')
346  {
347  // Remove any potential namespace from the class and method names.
348  $class = substr($class, strrpos('\\' . $class, '\\'));
349  $method = substr($method, strrpos('\\' . $method, '\\'));
350 
351  // If __METHOD__ was used instead of __FUNCTION__, it also contains
352  // the class name as a prefix. We get rid of that too.
353  $method = substr((string) $method, strrpos(':' . $method, ':'));
354 
355  return $this["$method:$class:$namespace"];
356  }
357 
390  public function addLogger(\Plop\LoggerInterface $logger /*, ... */)
391  {
392  $loggers = func_get_args();
393  foreach ($loggers as $logger) {
394  if (!($logger instanceof \Plop\LoggerInterface)) {
395  throw new \Plop\Exception('Not a logger');
396  }
397  }
398 
399  foreach ($loggers as $logger) {
400  $this[] = $logger;
401  }
402  return $this;
403  }
404 
414  protected static function getLoggerId(\Plop\LoggerInterface $logger)
415  {
416  $func = $logger->getMethod();
417  $cls = $logger->getClass();
418  $ns = $logger->getNamespace();
419  return "$func:$cls:$ns";
420  }
421 
429  public function count()
430  {
431  return count($this->loggers);
432  }
433 
461  public function offsetSet($offset, $logger)
462  {
463  if (!($logger instanceof \Plop\LoggerInterface)) {
464  throw new \Plop\Exception('Invalid logger');
465  }
466 
467  $id = static::getLoggerId($logger);
468  if (is_string($offset)) {
469  if ($offset != $id) {
470  throw new \Plop\Exception('Identifier mismatch');
471  }
472  }
473 
474  $this->loggers[$id] = $logger;
475  }
476 
493  public function offsetGet($offset)
494  {
495  if (!is_string($offset)) {
496  throw new \Plop\Exception('Invalid identifier');
497  }
498 
499  $parts = explode(':', $offset, 3);
500  if (count($parts) != 3) {
501  throw new \Plop\Exception('Invalid identifier');
502  }
503  list($method, $class, $ns) = $parts;
504 
505  $len = -strlen('\\');
506  while (substr($ns, $len) == '\\') {
507  $ns = (string) substr($ns, 0, $len);
508  }
509 
510  // Namespace, class and method/function match.
511  if (isset($this->loggers["$method:$class:$ns"])) {
512  return $this->loggers["$method:$class:$ns"];
513  }
514 
515  // Namespace and class match.
516  if ($class != "" && isset($this->loggers[":$class:$ns"])) {
517  return $this->loggers[":$class:$ns"];
518  }
519 
520  // Namespace match.
521  $parts = explode('\\', $ns);
522  while (count($parts)) {
523  $offset = implode('\\', $parts);
524  if ($offset == '') {
525  break;
526  }
527 
528  if (isset($this->loggers["::$offset"])) {
529  return $this->loggers["::$offset"];
530  }
531  array_pop($parts);
532  }
533 
534  // Root logger.
535  return $this->loggers['::'];
536  }
537 
557  public function offsetExists($offset)
558  {
559  if ($offset instanceof \Plop\LoggerInterface) {
560  $offset = static::getLoggerId($offset);
561  }
562  if (!is_string($offset)) {
563  throw new \Plop\Exception('Invalid identifier');
564  }
565  return isset($this->loggers[$offset]);
566  }
567 
582  public function offsetUnset($offset)
583  {
584  if ($offset instanceof \Plop\LoggerInterface) {
585  $offset = static::getLoggerId($offset);
586  }
587  if ($offset == "::") {
588  throw new \Plop\Exception('The root logger cannot be unset');
589  }
590  unset($this->loggers[$offset]);
591  }
592 
594  protected function getIndirectLogger()
595  {
596  $caller = static::findCaller();
597  return $this["{$caller['func']}:{$caller['cls']}:{$caller['ns']}"];
598  }
599 
624  public static function findCaller()
625  {
626  if (version_compare(PHP_VERSION, '5.3.6', '>=')) {
627  $bt = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
628  } else {
629  $bt = debug_backtrace(false);
630  }
631 
632  $max = count($bt);
633  $ns = __NAMESPACE__ . '\\';
634  $len = strlen($ns);
635  $ns2 = $ns . 'Tests' . '\\';
636  for ($i = 0; $i < $max; $i++) {
637  if (isset($bt[$i]['function']) &&
638  !strncmp($ns, $bt[$i]['function'], $len) &&
639  strncmp($ns2, $bt[$i]['function'], $len + 6)) {
640  // Skip frames until we get out of Plop's code.
641  continue;
642  }
643 
644  if (isset($bt[$i]['class']) &&
645  !strncmp($ns, $bt[$i]['class'], $len) &&
646  strncmp($ns2, $bt[$i]['class'], $len + 6)) {
647  // Skip frames until we get out of Plop's code.
648  continue;
649  }
650 
651  break;
652  }
653 
654  if ($i == $max) {
655  return array(
656  'ns' => null,
657  'file' => null,
658  'line' => 0,
659  'func' => null,
660  'cls' => null,
661  );
662  }
663 
664  $ns = '';
665  $func = isset($bt[$i]['function']) ? $bt[$i]['function'] : null;
666  $cls = isset($bt[$i]['class']) ? $bt[$i]['class'] : null;
667  $file = isset($bt[$i - 1]['file']) ? $bt[$i - 1]['file'] : null;
668  $line = isset($bt[$i - 1]['line']) ? $bt[$i - 1]['line'] : 0;
669 
670  if (($pos = strrpos($func, '\\')) !== false) {
671  $ns = substr($func, 0, $pos);
672  $func = substr($func, $pos + 1);
673  } elseif (($pos = strrpos($cls, '\\')) !== false) {
674  $ns = substr($cls, 0, $pos);
675  $cls = substr($cls, $pos + 1);
676  }
677 
678  return array(
679  'ns' => $ns,
680  'file' => $file,
681  'line' => $line,
682  'func' => $func,
683  'cls' => $cls,
684  );
685  }
686 }
__construct()
Definition: Plop.php:160
getCreationDate()
Definition: Plop.php:212
Interface to provide accessing objects as arrays.
$levelNames
Mapping between level names and their value.
Definition: Plop.php:153
addLevelName($levelName, $levelValue)
Definition: Plop.php:229
offsetSet($offset, $logger)
Definition: Plop.php:461
offsetGet($offset)
Definition: Plop.php:493
static & getInstance()
Definition: Plop.php:195
$created
Date and time when the logging service was initialized.
Definition: Plop.php:155
static getLoggerId(\Plop\LoggerInterface $logger)
Definition: Plop.php:414
getLevelName($level)
Definition: Plop.php:255
getIndirectLogger()
Definition: Plop.php:594
getLogger($namespace= '', $class= '', $method= '')
Definition: Plop.php:345
static findCaller()
Definition: Plop.php:624
offsetExists($offset)
Definition: Plop.php:557
Interface for a logger.
offsetUnset($offset)
Definition: Plop.php:582
count()
Definition: Plop.php:429
An abstract class that can be used to proxy logging calls to an actual logger.
getLevelValue($levelName)
Definition: Plop.php:283
$loggers
Associative array of loggers, indexed by their ID.
Definition: Plop.php:151
addLogger(\Plop\LoggerInterface $logger)
Definition: Plop.php:390
__clone()
This class is not clone-safe.
Definition: Plop.php:184
Classes implementing Countable can be used with the count() function.