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('now', 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 Level number (monolog) or name (PSR-3)
526     * @return int
527     */
528    public static function toMonologLevel($level)
529    {
530        if (is_string($level)) {
531            // Contains chars of all log levels and avoids using strtoupper() which may have
532            // strange results depending on locale (for example, "i" will become "İ")
533            $upper = strtr($level, 'abcdefgilmnortuwy', 'ABCDEFGILMNORTUWY');
534            if (defined(__CLASS__.'::'.$upper)) {
535                return constant(__CLASS__ . '::' . $upper);
536            }
537        }
538
539        return $level;
540    }
541
542    /**
543     * Checks whether the Logger has a handler that listens on the given level
544     *
545     * @param  int     $level
546     * @return bool
547     */
548    public function isHandling($level)
549    {
550        $record = array(
551            'level' => $level,
552        );
553
554        foreach ($this->handlers as $handler) {
555            if ($handler->isHandling($record)) {
556                return true;
557            }
558        }
559
560        return false;
561    }
562
563    /**
564     * Set a custom exception handler
565     *
566     * @param  callable $callback
567     * @return $this
568     */
569    public function setExceptionHandler($callback)
570    {
571        if (!is_callable($callback)) {
572            throw new \InvalidArgumentException('Exception handler must be valid callable (callback or object with an __invoke method), '.var_export($callback, true).' given');
573        }
574        $this->exceptionHandler = $callback;
575
576        return $this;
577    }
578
579    /**
580     * @return callable
581     */
582    public function getExceptionHandler()
583    {
584        return $this->exceptionHandler;
585    }
586
587    /**
588     * Delegates exception management to the custom exception handler,
589     * or throws the exception if no custom handler is set.
590     */
591    protected function handleException(Exception $e, array $record)
592    {
593        if (!$this->exceptionHandler) {
594            throw $e;
595        }
596
597        call_user_func($this->exceptionHandler, $e, $record);
598    }
599
600    /**
601     * Adds a log record at an arbitrary level.
602     *
603     * This method allows for compatibility with common interfaces.
604     *
605     * @param  mixed   $level   The log level
606     * @param  string $message The log message
607     * @param  array  $context The log context
608     * @return bool   Whether the record has been processed
609     */
610    public function log($level, $message, array $context = array())
611    {
612        $level = static::toMonologLevel($level);
613
614        return $this->addRecord($level, $message, $context);
615    }
616
617    /**
618     * Adds a log record at the DEBUG level.
619     *
620     * This method allows for compatibility with common interfaces.
621     *
622     * @param  string $message The log message
623     * @param  array  $context The log context
624     * @return bool   Whether the record has been processed
625     */
626    public function debug($message, array $context = array())
627    {
628        return $this->addRecord(static::DEBUG, $message, $context);
629    }
630
631    /**
632     * Adds a log record at the INFO level.
633     *
634     * This method allows for compatibility with common interfaces.
635     *
636     * @param  string $message The log message
637     * @param  array  $context The log context
638     * @return bool   Whether the record has been processed
639     */
640    public function info($message, array $context = array())
641    {
642        return $this->addRecord(static::INFO, $message, $context);
643    }
644
645    /**
646     * Adds a log record at the NOTICE level.
647     *
648     * This method allows for compatibility with common interfaces.
649     *
650     * @param  string $message The log message
651     * @param  array  $context The log context
652     * @return bool   Whether the record has been processed
653     */
654    public function notice($message, array $context = array())
655    {
656        return $this->addRecord(static::NOTICE, $message, $context);
657    }
658
659    /**
660     * Adds a log record at the WARNING level.
661     *
662     * This method allows for compatibility with common interfaces.
663     *
664     * @param  string $message The log message
665     * @param  array  $context The log context
666     * @return bool   Whether the record has been processed
667     */
668    public function warn($message, array $context = array())
669    {
670        return $this->addRecord(static::WARNING, $message, $context);
671    }
672
673    /**
674     * Adds a log record at the WARNING level.
675     *
676     * This method allows for compatibility with common interfaces.
677     *
678     * @param  string $message The log message
679     * @param  array  $context The log context
680     * @return bool   Whether the record has been processed
681     */
682    public function warning($message, array $context = array())
683    {
684        return $this->addRecord(static::WARNING, $message, $context);
685    }
686
687    /**
688     * Adds a log record at the ERROR level.
689     *
690     * This method allows for compatibility with common interfaces.
691     *
692     * @param  string $message The log message
693     * @param  array  $context The log context
694     * @return bool   Whether the record has been processed
695     */
696    public function err($message, array $context = array())
697    {
698        return $this->addRecord(static::ERROR, $message, $context);
699    }
700
701    /**
702     * Adds a log record at the ERROR level.
703     *
704     * This method allows for compatibility with common interfaces.
705     *
706     * @param  string $message The log message
707     * @param  array  $context The log context
708     * @return bool   Whether the record has been processed
709     */
710    public function error($message, array $context = array())
711    {
712        return $this->addRecord(static::ERROR, $message, $context);
713    }
714
715    /**
716     * Adds a log record at the CRITICAL level.
717     *
718     * This method allows for compatibility with common interfaces.
719     *
720     * @param  string $message The log message
721     * @param  array  $context The log context
722     * @return bool   Whether the record has been processed
723     */
724    public function crit($message, array $context = array())
725    {
726        return $this->addRecord(static::CRITICAL, $message, $context);
727    }
728
729    /**
730     * Adds a log record at the CRITICAL level.
731     *
732     * This method allows for compatibility with common interfaces.
733     *
734     * @param  string $message The log message
735     * @param  array  $context The log context
736     * @return bool   Whether the record has been processed
737     */
738    public function critical($message, array $context = array())
739    {
740        return $this->addRecord(static::CRITICAL, $message, $context);
741    }
742
743    /**
744     * Adds a log record at the ALERT level.
745     *
746     * This method allows for compatibility with common interfaces.
747     *
748     * @param  string $message The log message
749     * @param  array  $context The log context
750     * @return bool   Whether the record has been processed
751     */
752    public function alert($message, array $context = array())
753    {
754        return $this->addRecord(static::ALERT, $message, $context);
755    }
756
757    /**
758     * Adds a log record at the EMERGENCY level.
759     *
760     * This method allows for compatibility with common interfaces.
761     *
762     * @param  string $message The log message
763     * @param  array  $context The log context
764     * @return bool   Whether the record has been processed
765     */
766    public function emerg($message, array $context = array())
767    {
768        return $this->addRecord(static::EMERGENCY, $message, $context);
769    }
770
771    /**
772     * Adds a log record at the EMERGENCY level.
773     *
774     * This method allows for compatibility with common interfaces.
775     *
776     * @param  string $message The log message
777     * @param  array  $context The log context
778     * @return bool   Whether the record has been processed
779     */
780    public function emergency($message, array $context = array())
781    {
782        return $this->addRecord(static::EMERGENCY, $message, $context);
783    }
784
785    /**
786     * Set the timezone to be used for the timestamp of log records.
787     *
788     * This is stored globally for all Logger instances
789     *
790     * @param \DateTimeZone $tz Timezone object
791     */
792    public static function setTimezone(\DateTimeZone $tz)
793    {
794        self::$timezone = $tz;
795    }
796}
797