1<?php 2 3/* 4 * This file is part of the Monolog package. 5 * 6 * (c) Jordi Boggiano <j.boggiano@seld.be> 7 * 8 * For the full copyright and license information, please view the LICENSE 9 * file that was distributed with this source code. 10 */ 11 12namespace Monolog; 13 14use Monolog\Handler\HandlerInterface; 15use Monolog\Handler\StreamHandler; 16use Psr\Log\LoggerInterface; 17use Psr\Log\InvalidArgumentException; 18use Exception; 19 20/** 21 * Monolog log channel 22 * 23 * It contains a stack of Handlers and a stack of Processors, 24 * and uses them to store records that are added to it. 25 * 26 * @author Jordi Boggiano <j.boggiano@seld.be> 27 */ 28class Logger implements LoggerInterface, ResettableInterface 29{ 30 /** 31 * Detailed debug information 32 */ 33 const DEBUG = 100; 34 35 /** 36 * Interesting events 37 * 38 * Examples: User logs in, SQL logs. 39 */ 40 const INFO = 200; 41 42 /** 43 * Uncommon events 44 */ 45 const NOTICE = 250; 46 47 /** 48 * Exceptional occurrences that are not errors 49 * 50 * Examples: Use of deprecated APIs, poor use of an API, 51 * undesirable things that are not necessarily wrong. 52 */ 53 const WARNING = 300; 54 55 /** 56 * Runtime errors 57 */ 58 const ERROR = 400; 59 60 /** 61 * Critical conditions 62 * 63 * Example: Application component unavailable, unexpected exception. 64 */ 65 const CRITICAL = 500; 66 67 /** 68 * Action must be taken immediately 69 * 70 * Example: Entire website down, database unavailable, etc. 71 * This should trigger the SMS alerts and wake you up. 72 */ 73 const ALERT = 550; 74 75 /** 76 * Urgent alert. 77 */ 78 const EMERGENCY = 600; 79 80 /** 81 * Monolog API version 82 * 83 * This is only bumped when API breaks are done and should 84 * follow the major version of the library 85 * 86 * @var int 87 */ 88 const API = 1; 89 90 /** 91 * Logging levels from syslog protocol defined in RFC 5424 92 * 93 * @var array $levels Logging levels 94 */ 95 protected static $levels = array( 96 self::DEBUG => 'DEBUG', 97 self::INFO => 'INFO', 98 self::NOTICE => 'NOTICE', 99 self::WARNING => 'WARNING', 100 self::ERROR => 'ERROR', 101 self::CRITICAL => 'CRITICAL', 102 self::ALERT => 'ALERT', 103 self::EMERGENCY => 'EMERGENCY', 104 ); 105 106 /** 107 * @var \DateTimeZone 108 */ 109 protected static $timezone; 110 111 /** 112 * @var string 113 */ 114 protected $name; 115 116 /** 117 * The handler stack 118 * 119 * @var HandlerInterface[] 120 */ 121 protected $handlers; 122 123 /** 124 * Processors that will process all log records 125 * 126 * To process records of a single handler instead, add the processor on that specific handler 127 * 128 * @var callable[] 129 */ 130 protected $processors; 131 132 /** 133 * @var bool 134 */ 135 protected $microsecondTimestamps = true; 136 137 /** 138 * @var callable 139 */ 140 protected $exceptionHandler; 141 142 /** 143 * @param string $name The logging channel 144 * @param HandlerInterface[] $handlers Optional stack of handlers, the first one in the array is called first, etc. 145 * @param callable[] $processors Optional array of processors 146 */ 147 public function __construct($name, array $handlers = array(), array $processors = array()) 148 { 149 $this->name = $name; 150 $this->setHandlers($handlers); 151 $this->processors = $processors; 152 } 153 154 /** 155 * @return string 156 */ 157 public function getName() 158 { 159 return $this->name; 160 } 161 162 /** 163 * Return a new cloned instance with the name changed 164 * 165 * @return static 166 */ 167 public function withName($name) 168 { 169 $new = clone $this; 170 $new->name = $name; 171 172 return $new; 173 } 174 175 /** 176 * Pushes a handler on to the stack. 177 * 178 * @param HandlerInterface $handler 179 * @return $this 180 */ 181 public function pushHandler(HandlerInterface $handler) 182 { 183 array_unshift($this->handlers, $handler); 184 185 return $this; 186 } 187 188 /** 189 * Pops a handler from the stack 190 * 191 * @return HandlerInterface 192 */ 193 public function popHandler() 194 { 195 if (!$this->handlers) { 196 throw new \LogicException('You tried to pop from an empty handler stack.'); 197 } 198 199 return array_shift($this->handlers); 200 } 201 202 /** 203 * Set handlers, replacing all existing ones. 204 * 205 * If a map is passed, keys will be ignored. 206 * 207 * @param HandlerInterface[] $handlers 208 * @return $this 209 */ 210 public function setHandlers(array $handlers) 211 { 212 $this->handlers = array(); 213 foreach (array_reverse($handlers) as $handler) { 214 $this->pushHandler($handler); 215 } 216 217 return $this; 218 } 219 220 /** 221 * @return HandlerInterface[] 222 */ 223 public function getHandlers() 224 { 225 return $this->handlers; 226 } 227 228 /** 229 * Adds a processor on to the stack. 230 * 231 * @param callable $callback 232 * @return $this 233 */ 234 public function pushProcessor($callback) 235 { 236 if (!is_callable($callback)) { 237 throw new \InvalidArgumentException('Processors must be valid callables (callback or object with an __invoke method), '.var_export($callback, true).' given'); 238 } 239 array_unshift($this->processors, $callback); 240 241 return $this; 242 } 243 244 /** 245 * Removes the processor on top of the stack and returns it. 246 * 247 * @return callable 248 */ 249 public function popProcessor() 250 { 251 if (!$this->processors) { 252 throw new \LogicException('You tried to pop from an empty processor stack.'); 253 } 254 255 return array_shift($this->processors); 256 } 257 258 /** 259 * @return callable[] 260 */ 261 public function getProcessors() 262 { 263 return $this->processors; 264 } 265 266 /** 267 * Control the use of microsecond resolution timestamps in the 'datetime' 268 * member of new records. 269 * 270 * Generating microsecond resolution timestamps by calling 271 * microtime(true), formatting the result via sprintf() and then parsing 272 * the resulting string via \DateTime::createFromFormat() can incur 273 * a measurable runtime overhead vs simple usage of DateTime to capture 274 * a second resolution timestamp in systems which generate a large number 275 * of log events. 276 * 277 * @param bool $micro True to use microtime() to create timestamps 278 */ 279 public function useMicrosecondTimestamps($micro) 280 { 281 $this->microsecondTimestamps = (bool) $micro; 282 } 283 284 /** 285 * Adds a log record. 286 * 287 * @param int $level The logging level 288 * @param string $message The log message 289 * @param array $context The log context 290 * @return bool Whether the record has been processed 291 */ 292 public function addRecord($level, $message, array $context = array()) 293 { 294 if (!$this->handlers) { 295 $this->pushHandler(new StreamHandler('php://stderr', static::DEBUG)); 296 } 297 298 $levelName = static::getLevelName($level); 299 300 // check if any handler will handle this message so we can return early and save cycles 301 $handlerKey = null; 302 reset($this->handlers); 303 while ($handler = current($this->handlers)) { 304 if ($handler->isHandling(array('level' => $level))) { 305 $handlerKey = key($this->handlers); 306 break; 307 } 308 309 next($this->handlers); 310 } 311 312 if (null === $handlerKey) { 313 return false; 314 } 315 316 if (!static::$timezone) { 317 static::$timezone = new \DateTimeZone(date_default_timezone_get() ?: 'UTC'); 318 } 319 320 // php7.1+ always has microseconds enabled, so we do not need this hack 321 if ($this->microsecondTimestamps && PHP_VERSION_ID < 70100) { 322 $ts = \DateTime::createFromFormat('U.u', sprintf('%.6F', microtime(true)), static::$timezone); 323 } else { 324 $ts = new \DateTime(null, static::$timezone); 325 } 326 $ts->setTimezone(static::$timezone); 327 328 $record = array( 329 'message' => (string) $message, 330 'context' => $context, 331 'level' => $level, 332 'level_name' => $levelName, 333 'channel' => $this->name, 334 'datetime' => $ts, 335 'extra' => array(), 336 ); 337 338 try { 339 foreach ($this->processors as $processor) { 340 $record = call_user_func($processor, $record); 341 } 342 343 while ($handler = current($this->handlers)) { 344 if (true === $handler->handle($record)) { 345 break; 346 } 347 348 next($this->handlers); 349 } 350 } catch (Exception $e) { 351 $this->handleException($e, $record); 352 } 353 354 return true; 355 } 356 357 /** 358 * Ends a log cycle and frees all resources used by handlers. 359 * 360 * Closing a Handler means flushing all buffers and freeing any open resources/handles. 361 * Handlers that have been closed should be able to accept log records again and re-open 362 * themselves on demand, but this may not always be possible depending on implementation. 363 * 364 * This is useful at the end of a request and will be called automatically on every handler 365 * when they get destructed. 366 */ 367 public function close() 368 { 369 foreach ($this->handlers as $handler) { 370 if (method_exists($handler, 'close')) { 371 $handler->close(); 372 } 373 } 374 } 375 376 /** 377 * Ends a log cycle and resets all handlers and processors to their initial state. 378 * 379 * Resetting a Handler or a Processor means flushing/cleaning all buffers, resetting internal 380 * state, and getting it back to a state in which it can receive log records again. 381 * 382 * This is useful in case you want to avoid logs leaking between two requests or jobs when you 383 * have a long running process like a worker or an application server serving multiple requests 384 * in one process. 385 */ 386 public function reset() 387 { 388 foreach ($this->handlers as $handler) { 389 if ($handler instanceof ResettableInterface) { 390 $handler->reset(); 391 } 392 } 393 394 foreach ($this->processors as $processor) { 395 if ($processor instanceof ResettableInterface) { 396 $processor->reset(); 397 } 398 } 399 } 400 401 /** 402 * Adds a log record at the DEBUG level. 403 * 404 * @param string $message The log message 405 * @param array $context The log context 406 * @return bool Whether the record has been processed 407 */ 408 public function addDebug($message, array $context = array()) 409 { 410 return $this->addRecord(static::DEBUG, $message, $context); 411 } 412 413 /** 414 * Adds a log record at the INFO level. 415 * 416 * @param string $message The log message 417 * @param array $context The log context 418 * @return bool Whether the record has been processed 419 */ 420 public function addInfo($message, array $context = array()) 421 { 422 return $this->addRecord(static::INFO, $message, $context); 423 } 424 425 /** 426 * Adds a log record at the NOTICE level. 427 * 428 * @param string $message The log message 429 * @param array $context The log context 430 * @return bool Whether the record has been processed 431 */ 432 public function addNotice($message, array $context = array()) 433 { 434 return $this->addRecord(static::NOTICE, $message, $context); 435 } 436 437 /** 438 * Adds a log record at the WARNING level. 439 * 440 * @param string $message The log message 441 * @param array $context The log context 442 * @return bool Whether the record has been processed 443 */ 444 public function addWarning($message, array $context = array()) 445 { 446 return $this->addRecord(static::WARNING, $message, $context); 447 } 448 449 /** 450 * Adds a log record at the ERROR level. 451 * 452 * @param string $message The log message 453 * @param array $context The log context 454 * @return bool Whether the record has been processed 455 */ 456 public function addError($message, array $context = array()) 457 { 458 return $this->addRecord(static::ERROR, $message, $context); 459 } 460 461 /** 462 * Adds a log record at the CRITICAL level. 463 * 464 * @param string $message The log message 465 * @param array $context The log context 466 * @return bool Whether the record has been processed 467 */ 468 public function addCritical($message, array $context = array()) 469 { 470 return $this->addRecord(static::CRITICAL, $message, $context); 471 } 472 473 /** 474 * Adds a log record at the ALERT level. 475 * 476 * @param string $message The log message 477 * @param array $context The log context 478 * @return bool Whether the record has been processed 479 */ 480 public function addAlert($message, array $context = array()) 481 { 482 return $this->addRecord(static::ALERT, $message, $context); 483 } 484 485 /** 486 * Adds a log record at the EMERGENCY level. 487 * 488 * @param string $message The log message 489 * @param array $context The log context 490 * @return bool Whether the record has been processed 491 */ 492 public function addEmergency($message, array $context = array()) 493 { 494 return $this->addRecord(static::EMERGENCY, $message, $context); 495 } 496 497 /** 498 * Gets all supported logging levels. 499 * 500 * @return array Assoc array with human-readable level names => level codes. 501 */ 502 public static function getLevels() 503 { 504 return array_flip(static::$levels); 505 } 506 507 /** 508 * Gets the name of the logging level. 509 * 510 * @param int $level 511 * @return string 512 */ 513 public static function getLevelName($level) 514 { 515 if (!isset(static::$levels[$level])) { 516 throw new InvalidArgumentException('Level "'.$level.'" is not defined, use one of: '.implode(', ', array_keys(static::$levels))); 517 } 518 519 return static::$levels[$level]; 520 } 521 522 /** 523 * Converts PSR-3 levels to Monolog ones if necessary 524 * 525 * @param string|int Level number (monolog) or name (PSR-3) 526 * @return int 527 */ 528 public static function toMonologLevel($level) 529 { 530 if (is_string($level) && defined(__CLASS__.'::'.strtoupper($level))) { 531 return constant(__CLASS__.'::'.strtoupper($level)); 532 } 533 534 return $level; 535 } 536 537 /** 538 * Checks whether the Logger has a handler that listens on the given level 539 * 540 * @param int $level 541 * @return bool 542 */ 543 public function isHandling($level) 544 { 545 $record = array( 546 'level' => $level, 547 ); 548 549 foreach ($this->handlers as $handler) { 550 if ($handler->isHandling($record)) { 551 return true; 552 } 553 } 554 555 return false; 556 } 557 558 /** 559 * Set a custom exception handler 560 * 561 * @param callable $callback 562 * @return $this 563 */ 564 public function setExceptionHandler($callback) 565 { 566 if (!is_callable($callback)) { 567 throw new \InvalidArgumentException('Exception handler must be valid callable (callback or object with an __invoke method), '.var_export($callback, true).' given'); 568 } 569 $this->exceptionHandler = $callback; 570 571 return $this; 572 } 573 574 /** 575 * @return callable 576 */ 577 public function getExceptionHandler() 578 { 579 return $this->exceptionHandler; 580 } 581 582 /** 583 * Delegates exception management to the custom exception handler, 584 * or throws the exception if no custom handler is set. 585 */ 586 protected function handleException(Exception $e, array $record) 587 { 588 if (!$this->exceptionHandler) { 589 throw $e; 590 } 591 592 call_user_func($this->exceptionHandler, $e, $record); 593 } 594 595 /** 596 * Adds a log record at an arbitrary level. 597 * 598 * This method allows for compatibility with common interfaces. 599 * 600 * @param mixed $level The log level 601 * @param string $message The log message 602 * @param array $context The log context 603 * @return bool Whether the record has been processed 604 */ 605 public function log($level, $message, array $context = array()) 606 { 607 $level = static::toMonologLevel($level); 608 609 return $this->addRecord($level, $message, $context); 610 } 611 612 /** 613 * Adds a log record at the DEBUG level. 614 * 615 * This method allows for compatibility with common interfaces. 616 * 617 * @param string $message The log message 618 * @param array $context The log context 619 * @return bool Whether the record has been processed 620 */ 621 public function debug($message, array $context = array()) 622 { 623 return $this->addRecord(static::DEBUG, $message, $context); 624 } 625 626 /** 627 * Adds a log record at the INFO level. 628 * 629 * This method allows for compatibility with common interfaces. 630 * 631 * @param string $message The log message 632 * @param array $context The log context 633 * @return bool Whether the record has been processed 634 */ 635 public function info($message, array $context = array()) 636 { 637 return $this->addRecord(static::INFO, $message, $context); 638 } 639 640 /** 641 * Adds a log record at the NOTICE level. 642 * 643 * This method allows for compatibility with common interfaces. 644 * 645 * @param string $message The log message 646 * @param array $context The log context 647 * @return bool Whether the record has been processed 648 */ 649 public function notice($message, array $context = array()) 650 { 651 return $this->addRecord(static::NOTICE, $message, $context); 652 } 653 654 /** 655 * Adds a log record at the WARNING level. 656 * 657 * This method allows for compatibility with common interfaces. 658 * 659 * @param string $message The log message 660 * @param array $context The log context 661 * @return bool Whether the record has been processed 662 */ 663 public function warn($message, array $context = array()) 664 { 665 return $this->addRecord(static::WARNING, $message, $context); 666 } 667 668 /** 669 * Adds a log record at the WARNING level. 670 * 671 * This method allows for compatibility with common interfaces. 672 * 673 * @param string $message The log message 674 * @param array $context The log context 675 * @return bool Whether the record has been processed 676 */ 677 public function warning($message, array $context = array()) 678 { 679 return $this->addRecord(static::WARNING, $message, $context); 680 } 681 682 /** 683 * Adds a log record at the ERROR level. 684 * 685 * This method allows for compatibility with common interfaces. 686 * 687 * @param string $message The log message 688 * @param array $context The log context 689 * @return bool Whether the record has been processed 690 */ 691 public function err($message, array $context = array()) 692 { 693 return $this->addRecord(static::ERROR, $message, $context); 694 } 695 696 /** 697 * Adds a log record at the ERROR level. 698 * 699 * This method allows for compatibility with common interfaces. 700 * 701 * @param string $message The log message 702 * @param array $context The log context 703 * @return bool Whether the record has been processed 704 */ 705 public function error($message, array $context = array()) 706 { 707 return $this->addRecord(static::ERROR, $message, $context); 708 } 709 710 /** 711 * Adds a log record at the CRITICAL level. 712 * 713 * This method allows for compatibility with common interfaces. 714 * 715 * @param string $message The log message 716 * @param array $context The log context 717 * @return bool Whether the record has been processed 718 */ 719 public function crit($message, array $context = array()) 720 { 721 return $this->addRecord(static::CRITICAL, $message, $context); 722 } 723 724 /** 725 * Adds a log record at the CRITICAL level. 726 * 727 * This method allows for compatibility with common interfaces. 728 * 729 * @param string $message The log message 730 * @param array $context The log context 731 * @return bool Whether the record has been processed 732 */ 733 public function critical($message, array $context = array()) 734 { 735 return $this->addRecord(static::CRITICAL, $message, $context); 736 } 737 738 /** 739 * Adds a log record at the ALERT level. 740 * 741 * This method allows for compatibility with common interfaces. 742 * 743 * @param string $message The log message 744 * @param array $context The log context 745 * @return bool Whether the record has been processed 746 */ 747 public function alert($message, array $context = array()) 748 { 749 return $this->addRecord(static::ALERT, $message, $context); 750 } 751 752 /** 753 * Adds a log record at the EMERGENCY level. 754 * 755 * This method allows for compatibility with common interfaces. 756 * 757 * @param string $message The log message 758 * @param array $context The log context 759 * @return bool Whether the record has been processed 760 */ 761 public function emerg($message, array $context = array()) 762 { 763 return $this->addRecord(static::EMERGENCY, $message, $context); 764 } 765 766 /** 767 * Adds a log record at the EMERGENCY level. 768 * 769 * This method allows for compatibility with common interfaces. 770 * 771 * @param string $message The log message 772 * @param array $context The log context 773 * @return bool Whether the record has been processed 774 */ 775 public function emergency($message, array $context = array()) 776 { 777 return $this->addRecord(static::EMERGENCY, $message, $context); 778 } 779 780 /** 781 * Set the timezone to be used for the timestamp of log records. 782 * 783 * This is stored globally for all Logger instances 784 * 785 * @param \DateTimeZone $tz Timezone object 786 */ 787 public static function setTimezone(\DateTimeZone $tz) 788 { 789 self::$timezone = $tz; 790 } 791} 792