Viewing file: LogManager.php (19.62 KB) -rw-r--r-- Select action/file-type: (+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
<?php
namespace Illuminate\Log;
use Closure; use Illuminate\Support\Str; use InvalidArgumentException; use Monolog\Formatter\LineFormatter; use Monolog\Handler\ErrorLogHandler; use Monolog\Handler\FingersCrossedHandler; use Monolog\Handler\FormattableHandlerInterface; use Monolog\Handler\HandlerInterface; use Monolog\Handler\RotatingFileHandler; use Monolog\Handler\SlackWebhookHandler; use Monolog\Handler\StreamHandler; use Monolog\Handler\SyslogHandler; use Monolog\Handler\WhatFailureGroupHandler; use Monolog\Logger as Monolog; use Monolog\Processor\ProcessorInterface; use Monolog\Processor\PsrLogMessageProcessor; use Psr\Log\LoggerInterface; use Throwable;
/** * @mixin \Illuminate\Log\Logger */ class LogManager implements LoggerInterface { use ParsesLogConfiguration;
/** * The application instance. * * @var \Illuminate\Contracts\Foundation\Application */ protected $app;
/** * The array of resolved channels. * * @var array */ protected $channels = [];
/** * The context shared across channels and stacks. * * @var array */ protected $sharedContext = [];
/** * The registered custom driver creators. * * @var array */ protected $customCreators = [];
/** * The standard date format to use when writing logs. * * @var string */ protected $dateFormat = 'Y-m-d H:i:s';
/** * Create a new Log manager instance. * * @param \Illuminate\Contracts\Foundation\Application $app * @return void */ public function __construct($app) { $this->app = $app; }
/** * Build an on-demand log channel. * * @param array $config * @return \Psr\Log\LoggerInterface */ public function build(array $config) { unset($this->channels['ondemand']);
return $this->get('ondemand', $config); }
/** * Create a new, on-demand aggregate logger instance. * * @param array $channels * @param string|null $channel * @return \Psr\Log\LoggerInterface */ public function stack(array $channels, $channel = null) { return (new Logger( $this->createStackDriver(compact('channels', 'channel')), $this->app['events'] ))->withContext($this->sharedContext); }
/** * Get a log channel instance. * * @param string|null $channel * @return \Psr\Log\LoggerInterface */ public function channel($channel = null) { return $this->driver($channel); }
/** * Get a log driver instance. * * @param string|null $driver * @return \Psr\Log\LoggerInterface */ public function driver($driver = null) { return $this->get($this->parseDriver($driver)); }
/** * Attempt to get the log from the local cache. * * @param string $name * @param array|null $config * @return \Psr\Log\LoggerInterface */ protected function get($name, ?array $config = null) { try { return $this->channels[$name] ?? with($this->resolve($name, $config), function ($logger) use ($name) { return $this->channels[$name] = $this->tap($name, new Logger($logger, $this->app['events']))->withContext($this->sharedContext); }); } catch (Throwable $e) { return tap($this->createEmergencyLogger(), function ($logger) use ($e) { $logger->emergency('Unable to create configured logger. Using emergency logger.', [ 'exception' => $e, ]); }); } }
/** * Apply the configured taps for the logger. * * @param string $name * @param \Illuminate\Log\Logger $logger * @return \Illuminate\Log\Logger */ protected function tap($name, Logger $logger) { foreach ($this->configurationFor($name)['tap'] ?? [] as $tap) { [$class, $arguments] = $this->parseTap($tap);
$this->app->make($class)->__invoke($logger, ...explode(',', $arguments)); }
return $logger; }
/** * Parse the given tap class string into a class name and arguments string. * * @param string $tap * @return array */ protected function parseTap($tap) { return str_contains($tap, ':') ? explode(':', $tap, 2) : [$tap, '']; }
/** * Create an emergency log handler to avoid white screens of death. * * @return \Psr\Log\LoggerInterface */ protected function createEmergencyLogger() { $config = $this->configurationFor('emergency');
$handler = new StreamHandler( $config['path'] ?? $this->app->storagePath().'/logs/laravel.log', $this->level(['level' => 'debug']) );
return new Logger( new Monolog('laravel', $this->prepareHandlers([$handler])), $this->app['events'] ); }
/** * Resolve the given log instance by name. * * @param string $name * @param array|null $config * @return \Psr\Log\LoggerInterface * * @throws \InvalidArgumentException */ protected function resolve($name, ?array $config = null) { $config ??= $this->configurationFor($name);
if (is_null($config)) { throw new InvalidArgumentException("Log [{$name}] is not defined."); }
if (isset($this->customCreators[$config['driver']])) { return $this->callCustomCreator($config); }
$driverMethod = 'create'.ucfirst($config['driver']).'Driver';
if (method_exists($this, $driverMethod)) { return $this->{$driverMethod}($config); }
throw new InvalidArgumentException("Driver [{$config['driver']}] is not supported."); }
/** * Call a custom driver creator. * * @param array $config * @return mixed */ protected function callCustomCreator(array $config) { return $this->customCreators[$config['driver']]($this->app, $config); }
/** * Create a custom log driver instance. * * @param array $config * @return \Psr\Log\LoggerInterface */ protected function createCustomDriver(array $config) { $factory = is_callable($via = $config['via']) ? $via : $this->app->make($via);
return $factory($config); }
/** * Create an aggregate log driver instance. * * @param array $config * @return \Psr\Log\LoggerInterface */ protected function createStackDriver(array $config) { if (is_string($config['channels'])) { $config['channels'] = explode(',', $config['channels']); }
$handlers = collect($config['channels'])->flatMap(function ($channel) { return $channel instanceof LoggerInterface ? $channel->getHandlers() : $this->channel($channel)->getHandlers(); })->all();
$processors = collect($config['channels'])->flatMap(function ($channel) { return $channel instanceof LoggerInterface ? $channel->getProcessors() : $this->channel($channel)->getProcessors(); })->all();
if ($config['ignore_exceptions'] ?? false) { $handlers = [new WhatFailureGroupHandler($handlers)]; }
return new Monolog($this->parseChannel($config), $handlers, $processors); }
/** * Create an instance of the single file log driver. * * @param array $config * @return \Psr\Log\LoggerInterface */ protected function createSingleDriver(array $config) { return new Monolog($this->parseChannel($config), [ $this->prepareHandler( new StreamHandler( $config['path'], $this->level($config), $config['bubble'] ?? true, $config['permission'] ?? null, $config['locking'] ?? false ), $config ), ], $config['replace_placeholders'] ?? false ? [new PsrLogMessageProcessor()] : []); }
/** * Create an instance of the daily file log driver. * * @param array $config * @return \Psr\Log\LoggerInterface */ protected function createDailyDriver(array $config) { return new Monolog($this->parseChannel($config), [ $this->prepareHandler(new RotatingFileHandler( $config['path'], $config['days'] ?? 7, $this->level($config), $config['bubble'] ?? true, $config['permission'] ?? null, $config['locking'] ?? false ), $config), ], $config['replace_placeholders'] ?? false ? [new PsrLogMessageProcessor()] : []); }
/** * Create an instance of the Slack log driver. * * @param array $config * @return \Psr\Log\LoggerInterface */ protected function createSlackDriver(array $config) { return new Monolog($this->parseChannel($config), [ $this->prepareHandler(new SlackWebhookHandler( $config['url'], $config['channel'] ?? null, $config['username'] ?? 'Laravel', $config['attachment'] ?? true, $config['emoji'] ?? ':boom:', $config['short'] ?? false, $config['context'] ?? true, $this->level($config), $config['bubble'] ?? true, $config['exclude_fields'] ?? [] ), $config), ], $config['replace_placeholders'] ?? false ? [new PsrLogMessageProcessor()] : []); }
/** * Create an instance of the syslog log driver. * * @param array $config * @return \Psr\Log\LoggerInterface */ protected function createSyslogDriver(array $config) { return new Monolog($this->parseChannel($config), [ $this->prepareHandler(new SyslogHandler( Str::snake($this->app['config']['app.name'], '-'), $config['facility'] ?? LOG_USER, $this->level($config) ), $config), ], $config['replace_placeholders'] ?? false ? [new PsrLogMessageProcessor()] : []); }
/** * Create an instance of the "error log" log driver. * * @param array $config * @return \Psr\Log\LoggerInterface */ protected function createErrorlogDriver(array $config) { return new Monolog($this->parseChannel($config), [ $this->prepareHandler(new ErrorLogHandler( $config['type'] ?? ErrorLogHandler::OPERATING_SYSTEM, $this->level($config) )), ], $config['replace_placeholders'] ?? false ? [new PsrLogMessageProcessor()] : []); }
/** * Create an instance of any handler available in Monolog. * * @param array $config * @return \Psr\Log\LoggerInterface * * @throws \InvalidArgumentException * @throws \Illuminate\Contracts\Container\BindingResolutionException */ protected function createMonologDriver(array $config) { if (! is_a($config['handler'], HandlerInterface::class, true)) { throw new InvalidArgumentException( $config['handler'].' must be an instance of '.HandlerInterface::class ); }
collect($config['processors'] ?? [])->each(function ($processor) { $processor = $processor['processor'] ?? $processor;
if (! is_a($processor, ProcessorInterface::class, true)) { throw new InvalidArgumentException( $processor.' must be an instance of '.ProcessorInterface::class ); } });
$with = array_merge( ['level' => $this->level($config)], $config['with'] ?? [], $config['handler_with'] ?? [] );
$handler = $this->prepareHandler( $this->app->make($config['handler'], $with), $config );
$processors = collect($config['processors'] ?? []) ->map(fn ($processor) => $this->app->make($processor['processor'] ?? $processor, $processor['with'] ?? [])) ->toArray();
return new Monolog( $this->parseChannel($config), [$handler], $processors, ); }
/** * Prepare the handlers for usage by Monolog. * * @param array $handlers * @return array */ protected function prepareHandlers(array $handlers) { foreach ($handlers as $key => $handler) { $handlers[$key] = $this->prepareHandler($handler); }
return $handlers; }
/** * Prepare the handler for usage by Monolog. * * @param \Monolog\Handler\HandlerInterface $handler * @param array $config * @return \Monolog\Handler\HandlerInterface */ protected function prepareHandler(HandlerInterface $handler, array $config = []) { if (isset($config['action_level'])) { $handler = new FingersCrossedHandler( $handler, $this->actionLevel($config), 0, true, $config['stop_buffering'] ?? true ); }
if (! $handler instanceof FormattableHandlerInterface) { return $handler; }
if (! isset($config['formatter'])) { $handler->setFormatter($this->formatter()); } elseif ($config['formatter'] !== 'default') { $handler->setFormatter($this->app->make($config['formatter'], $config['formatter_with'] ?? [])); }
return $handler; }
/** * Get a Monolog formatter instance. * * @return \Monolog\Formatter\FormatterInterface */ protected function formatter() { return new LineFormatter(null, $this->dateFormat, true, true, true); }
/** * Share context across channels and stacks. * * @param array $context * @return $this */ public function shareContext(array $context) { foreach ($this->channels as $channel) { $channel->withContext($context); }
$this->sharedContext = array_merge($this->sharedContext, $context);
return $this; }
/** * The context shared across channels and stacks. * * @return array */ public function sharedContext() { return $this->sharedContext; }
/** * Flush the shared context. * * @return $this */ public function flushSharedContext() { $this->sharedContext = [];
return $this; }
/** * Get fallback log channel name. * * @return string */ protected function getFallbackChannelName() { return $this->app->bound('env') ? $this->app->environment() : 'production'; }
/** * Get the log connection configuration. * * @param string $name * @return array */ protected function configurationFor($name) { return $this->app['config']["logging.channels.{$name}"]; }
/** * Get the default log driver name. * * @return string|null */ public function getDefaultDriver() { return $this->app['config']['logging.default']; }
/** * Set the default log driver name. * * @param string $name * @return void */ public function setDefaultDriver($name) { $this->app['config']['logging.default'] = $name; }
/** * Register a custom driver creator Closure. * * @param string $driver * @param \Closure $callback * @return $this */ public function extend($driver, Closure $callback) { $this->customCreators[$driver] = $callback->bindTo($this, $this);
return $this; }
/** * Unset the given channel instance. * * @param string|null $driver * @return void */ public function forgetChannel($driver = null) { $driver = $this->parseDriver($driver);
if (isset($this->channels[$driver])) { unset($this->channels[$driver]); } }
/** * Parse the driver name. * * @param string|null $driver * @return string|null */ protected function parseDriver($driver) { $driver ??= $this->getDefaultDriver();
if ($this->app->runningUnitTests()) { $driver ??= 'null'; }
return $driver; }
/** * Get all of the resolved log channels. * * @return array */ public function getChannels() { return $this->channels; }
/** * System is unusable. * * @param string $message * @param array $context * @return void */ public function emergency($message, array $context = []): void { $this->driver()->emergency($message, $context); }
/** * Action must be taken immediately. * * Example: Entire website down, database unavailable, etc. This should * trigger the SMS alerts and wake you up. * * @param string $message * @param array $context * @return void */ public function alert($message, array $context = []): void { $this->driver()->alert($message, $context); }
/** * Critical conditions. * * Example: Application component unavailable, unexpected exception. * * @param string $message * @param array $context * @return void */ public function critical($message, array $context = []): void { $this->driver()->critical($message, $context); }
/** * Runtime errors that do not require immediate action but should typically * be logged and monitored. * * @param string $message * @param array $context * @return void */ public function error($message, array $context = []): void { $this->driver()->error($message, $context); }
/** * Exceptional occurrences that are not errors. * * Example: Use of deprecated APIs, poor use of an API, undesirable things * that are not necessarily wrong. * * @param string $message * @param array $context * @return void */ public function warning($message, array $context = []): void { $this->driver()->warning($message, $context); }
/** * Normal but significant events. * * @param string $message * @param array $context * @return void */ public function notice($message, array $context = []): void { $this->driver()->notice($message, $context); }
/** * Interesting events. * * Example: User logs in, SQL logs. * * @param string $message * @param array $context * @return void */ public function info($message, array $context = []): void { $this->driver()->info($message, $context); }
/** * Detailed debug information. * * @param string $message * @param array $context * @return void */ public function debug($message, array $context = []): void { $this->driver()->debug($message, $context); }
/** * Logs with an arbitrary level. * * @param mixed $level * @param string $message * @param array $context * @return void */ public function log($level, $message, array $context = []): void { $this->driver()->log($level, $message, $context); }
/** * Dynamically call the default driver instance. * * @param string $method * @param array $parameters * @return mixed */ public function __call($method, $parameters) { return $this->driver()->$method(...$parameters); } }
|