LogChannelBootstrapper
The LogChannelBootstrapper makes logging tenant-aware. By default, it points Laravel’s single and daily channels
at the tenant’s storage directory, so each tenant’s logs end up in their own folder rather than in the central log file.
It does this by reconfiguring the channels and forgetting them from the LogManager.
Channels that don’t write to the filesystem — e.g. slack — can be given tenant-specific config using channel
overrides.
Enabling the bootstrapper
Section titled “Enabling the bootstrapper”To enable this bootstrapper, uncomment the LogChannelBootstrapper line in the bootstrappers section of the tenancy config:
'bootstrappers' => [ // Bootstrappers\LogChannelBootstrapper::class,],Storage path channels
Section titled “Storage path channels”Channels listed in the $storagePathChannels static property get their
path rewritten to the tenant’s storage directory:
LogChannelBootstrapper::$storagePathChannels = ['single', 'daily'];The part of the path that follows the central storage path is preserved, so a channel configured as
storage_path('logs/laravel.log') writes to storage/tenant{$tenantKey}/logs/laravel.log in the tenant context.
Note that this uses FilesystemTenancyBootstrapper::getTenantStoragePath(), which is a public static method — you don’t
need to have the FilesystemTenancyBootstrapper enabled for this to work though
keeping that bootstrapper enabled is recommended in general.
Channel overrides
Section titled “Channel overrides”The $channelOverrides static property configures channels that aren’t filesystem-based. It accepts two syntaxes.
An array maps tenant attributes to the channel’s config keys:
LogChannelBootstrapper::$channelOverrides = [ 'slack' => ['url' => 'webhookUrl'],];This maps $tenant->webhookUrl to the channel’s url config. If the attribute is null, the override is ignored and the
channel keeps its central config.
A closure receives the tenant and the channel’s current config, and returns the full config to use:
LogChannelBootstrapper::$channelOverrides = [ 'slack' => fn (Tenant $tenant, array $channel) => array_merge( $channel, ['url' => $tenant->slackUrl] ),];Unlike the array syntax, a closure is fully in control — null values are not ignored and the returned value isn’t merged into the existing config, it replaces it.
Overrides take precedence over $storagePathChannels when a channel is listed in both.
Stack channels
Section titled “Stack channels”Once a stack channel has been used, it keeps logging to wherever its members pointed at that moment. A stack used in the
central context would therefore keep writing to the central logs even after tenancy is initialized.
To avoid this, the bootstrapper also forgets any stack channel that has one of the configured channels among its members, forcing it to be re-resolved with the tenant config. Stacks are only inspected one level deep — they aren’t traversed recursively, so a stack whose only configured channel sits inside another nested stack won’t be picked up.