From 68e6fd6c02397e5a26c1c522efb6b7f595141d52 Mon Sep 17 00:00:00 2001 From: lukinovec Date: Mon, 29 Jun 2026 17:51:35 +0200 Subject: [PATCH] Re-resolve all stack channels that include any of the configured channels Reverting the LogTenancyBootstrapper changes will make the new "stack channels that include any configured channel are re-resolved" test fali --- src/Bootstrappers/LogTenancyBootstrapper.php | 43 +++++++++++-------- .../LogTenancyBootstrapperTest.php | 39 +++++++++++++++++ 2 files changed, 63 insertions(+), 19 deletions(-) diff --git a/src/Bootstrappers/LogTenancyBootstrapper.php b/src/Bootstrappers/LogTenancyBootstrapper.php index 35e1ea12..8894b44b 100644 --- a/src/Bootstrappers/LogTenancyBootstrapper.php +++ b/src/Bootstrappers/LogTenancyBootstrapper.php @@ -107,32 +107,37 @@ class LogTenancyBootstrapper implements TenancyBootstrapper * re-resolved with the new, tenant-specific config on the next use. * * Includes: - * - the default channel (primarily because it can be 'stack') * - all channels in the $storagePathChannels array * - all channels that have custom overrides in the $channelOverrides property + * - any 'stack' channel that includes one of the above as a member + * + * Stack channels are included because once a stack has been used, it keeps logging + * to wherever its members pointed at that moment. So a stack used in the central + * context would keep writing to the central logs, even after tenancy is initialized + * and its member channels are configured for the tenant. + * Forgetting the stack forces it to be re-resolved with its members' updated (tenant) + * config. + * + * Only stacks one level deep are handled. */ protected function getChannels(): array { - /** - * Include the default channel in the list of channels to configure/re-resolve. - * - * Including the default channel is harmless (if it's not overridden and not in $storagePathChannels, - * it'll just be forgotten and re-resolved on the next use with the original config), and for the - * case where 'stack' is the default, this is necessary since the 'stack' channel will be resolved - * and saved in the log manager, and its stale config could accidentally be used instead of the stack member channels. - * - * For example, when you use 'stack' with the 'slack' channel, - * if only 'slack' is forgotten, 'stack' would still use the stale cached 'slack' driver, - * and if only 'stack' is forgotten, the 'slack' channel's config would remain unchanged (central). - */ - $defaultChannel = $this->config->get('logging.default'); + $configuredChannels = array_unique([ + ...static::$storagePathChannels, + ...array_keys(static::$channelOverrides), + ]); + + $stackChannels = []; + + foreach ($this->config->get('logging.channels') as $channel => $config) { + // Include stack channels that have at least one configured channel as a member + if (($config['driver'] ?? null) === 'stack' && array_intersect($config['channels'] ?? [], $configuredChannels)) { + $stackChannels[] = $channel; + } + } return array_filter( - array_unique([ - $defaultChannel, - ...static::$storagePathChannels, - ...array_keys(static::$channelOverrides), - ]), + array_unique([...$configuredChannels, ...$stackChannels]), fn (string $channel): bool => $this->config->has("logging.channels.{$channel}") ); } diff --git a/tests/Bootstrappers/LogTenancyBootstrapperTest.php b/tests/Bootstrappers/LogTenancyBootstrapperTest.php index 6c73fdbb..91c76d97 100644 --- a/tests/Bootstrappers/LogTenancyBootstrapperTest.php +++ b/tests/Bootstrappers/LogTenancyBootstrapperTest.php @@ -350,6 +350,45 @@ test('stack logs are written to all configured channels with tenant-specific pat ->not()->toContain('tenant'); }); +test('stack channels that include any configured channel are re-resolved', function () { + config([ + 'tenancy.bootstrappers' => [ + FilesystemTenancyBootstrapper::class, + LogTenancyBootstrapper::class, + ], + 'logging.channels.custom_stack' => [ + 'driver' => 'stack', + 'channels' => ['single'], + ], + ]); + + $tenant = Tenant::create(['id' => 'stack-tenant']); + $centralLogPath = storage_path('logs/laravel.log'); + + // Resolve the stack channel in the central context first + // (this caches the stack with its members still pointing at the central logs). + Log::channel('custom_stack')->info('central'); + expect(file_get_contents($centralLogPath))->toContain('central'); + + tenancy()->initialize($tenant); + + Log::channel('custom_stack')->info('tenant log message'); + + // The stack channel should have been re-resolved with the + // updated (tenant) config for its member channels, + // so 'tenant log message' should be logged to the tenant log, + // not the central log. + expect(file_get_contents($centralLogPath)) + ->toContain('central') + ->not()->toContain('tenant log message'); + + $tenantLogPath = storage_path('logs/laravel.log'); + + expect(file_exists($tenantLogPath))->toBeTrue(); + expect(file_get_contents($tenantLogPath)) + ->toContain('tenant log message'); +}); + test('slack channel uses correct webhook urls', function () { config([ 'logging.channels.slack.url' => 'central-webhook',