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