v13.39.0. For basic usage, see Logging. For preparing a provider, see Package development.
Choose the extension point you need
If an existing driver can do the job, there is no need to add a custom driver.via classes and tap classes are resolved from the container, so you can use constructor injection. tap adjusts the channel when it is created; it is not a hook that runs every time a log entry is written.
The
audit in logging.channels.audit is the channel name. The driver inside it is the name of the creation method. Log::channel('audit') selects by channel name, so you can keep the name used by callers even if you change the driver later.Start by exposing a factory with custom and via
The fictionalAcme\Audit package provides a factory for file output. Place it in src/Logging/CreateAuditLogger.php, where it can be loaded via PSR-4.
channels array in config/logging.php. Keep the existing default and stack settings.
LogManager::createCustomDriver() retrieves the via class from the container and calls it with the channel’s configuration array as its only argument. With this approach alone, you do not need a provider to register a driver.
level to the StreamHandler. LogManager does not uniformly apply the configured level to the logger returned by a custom factory. If you add your own configuration options, apply them in the factory as well.
Use extend when providing a custom driver name
If you want to expose adriver: acme-audit setting to consumers, register a driver that uses the same factory. Extend Laravel’s log service in boot() of src/AuditServiceProvider.php.
channels.audit configuration with the following. via is no longer needed.
- Laravel’s
LogServiceProviderregisterslogas a singleton.callAfterResolving()waits if it has not been resolved yet, and applies the registration to the instance if it already has been. For details, see Service resolution hooks. extend()only registers a creation closure. No channel is created and nothing is logged when this callback is registered.callCustomCreator()passes$appand the channel configuration array to the closure. These arguments differ from those passed to aviafactory.- Registering the same driver name again overwrites the creation closure. Do not use built-in names such as
singleor names that collide with other packages.
logging.default or the entire existing logging.channels.
Channels are retained by name
LogManager::get() stores resolved channels in $channels. When you retrieve a channel with the same name, it returns the retained object without rereading the configuration array.
This is object retention within the current LogManager, separate from the configuration cache stored in a file. Changing config() or re-registering with extend() alone does not replace Handlers that have already been created.
$same is the same object as $before. Once forgetChannel('audit') releases the Manager’s retained entry, the next retrieval creates $after from the new configuration.
forgetChannel does not update existing references
AllforgetChannel() does is remove the entry inside the Manager.
- A service that holds
$beforecan continue to use the old logger. This is not an operation that swaps existing references for the new object. - A stack already created with
Log::channel('stack')or similar has its own independent Monolog that collects the Handlers and Processors of its child channels. CallingforgetChannel()on a child alone does not change the composition of the existing stack. If needed, also release the retained entry for that stack’s channel name and retrieve it again. forgetChannel()itself does not callclose()on Handlers. It is not an API that guarantees connections or buffers are shut down.
Stack integration also requires Monolog-compatible operations
A custom driver can return an implementation ofPsr\Log\LoggerInterface, but Laravel’s createStackDriver() calls getHandlers() and getProcessors() on child channels. These are not part of the PSR interface.
Returning an arbitrary PSR logger does not guarantee that it works in a stack, so packages that also support stack integration should return Monolog as in the example, or clearly document and test the supported scope.
Verify actual channel resolution, not just mocks
Log::shouldReceive('channel') alone cannot confirm provider registration, factory selection, or how configuration is applied. The following example runs through the real Manager and factory in a package test environment.
Specify php://memory as the output destination for testing. Here, we verify channel construction and Handler configuration without actually writing logs.
Additional checks for long-term maintenance
Primary sources
- Laravel documentation: Logging
- Framework v13.39.0: LogManager creation, extension, and releasing retained channels
- Framework v13.39.0: Logger wrapping and method forwarding
- Framework v13.39.0: LogServiceProvider singleton registration
- Framework v13.39.0: ServiceProvider::callAfterResolving
- Framework v13.39.0: RebindsCallbacksToSelf
- Laravel framework tests: LogManagerTest