[4.x] Add LogTenancyBootstrapper#1381
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #1381 +/- ##
============================================
+ Coverage 86.00% 86.18% +0.18%
- Complexity 1175 1192 +17
============================================
Files 185 186 +1
Lines 3429 3475 +46
============================================
+ Hits 2949 2995 +46
Misses 480 480 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
…ook used by the slack channel correctly)
There was a problem hiding this comment.
Pull Request Overview
This PR adds a new LogTenancyBootstrapper to provide tenant-specific logging configuration. The bootstrapper automatically configures storage path channels to use tenant-specific directories and supports custom channel overrides for more complex logging scenarios.
Key changes:
- Implements
LogTenancyBootstrapperwith automatic storage path channel configuration and custom override support - Adds comprehensive test coverage for default behavior, custom overrides, and real-world usage scenarios
- Registers the new bootstrapper in the test environment
Reviewed Changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
src/Bootstrappers/LogTenancyBootstrapper.php |
Main implementation of the log tenancy bootstrapper with storage path handling and channel override functionality |
tests/Bootstrappers/LogTenancyBootstrapperTest.php |
Comprehensive test suite covering default behavior, custom overrides, stack channels, and real logging scenarios |
tests/TestCase.php |
Adds import and singleton registration for the new bootstrapper in test environment |
Comments suppressed due to low confidence (1)
tests/Bootstrappers/LogTenancyBootstrapperTest.php:345
- The test relies on catching exceptions to verify webhook URLs, but this approach is fragile and may not work reliably across different environments or Laravel versions. Consider mocking the HTTP client or using a more deterministic testing approach.
try {
| } elseif (in_array($channel, static::$storagePathChannels)) { | ||
| // Set storage path channels to use tenant-specific directory (default behavior) | ||
| // The tenant log will be located at e.g. "storage/tenant{$tenantKey}/logs/laravel.log" | ||
| $this->config->set("logging.channels.{$channel}.path", storage_path('logs/laravel.log')); |
There was a problem hiding this comment.
The hardcoded filename 'laravel.log' should be made configurable or use the original filename from the channel configuration. This prevents customization of log filenames and could overwrite existing configurations.
| $this->config->set("logging.channels.{$channel}.path", storage_path('logs/laravel.log')); | |
| $logFilename = $this->getTenantLogFilename($tenant); | |
| $this->config->set("logging.channels.{$channel}.path", storage_path("logs/{$logFilename}")); |
There was a problem hiding this comment.
I don't think so, this is fine for the default behavior, it can still be customized. Resolving this
There was a problem hiding this comment.
Out of the box, if no customization is used, $storagePathChannels includes daily which does not use laravel.log names, but day-specific names. Seems like something that should be checked and tested.
There was a problem hiding this comment.
Since this is checked and tested already, I'd just add a short comment with an explanation of how daily works.
daily driver uses RotatingFileHandler that parses the file name. The current code (= storage_path('logs/laravel.log')) corresponds to the daily log channel config. It is correct, so I'd just clarify this since this can indeed be quite confusing
There was a problem hiding this comment.
To clarify further, 'logging.channels.daily.path' => storage_path('logs/laravel.log') is correct, consistent with Laravel's default daily channel path. When using a config like this, the log will be created e.g. at storage/tenantfoo/logs/laravel-2026-04-14.log (this is tested in stack logs are written to all configured channels with tenant-specific paths)
…is set (otherwise, just skip the override and keep the default config value)
| /** | ||
| * Custom channel configuration overrides. | ||
| * | ||
| * Examples: | ||
| * - Array mapping (the default approach): ['slack' => ['url' => 'webhookUrl']] maps $tenant->webhookUrl to slack.url (if $tenant->webhookUrl is not null, otherwise, the override is ignored) | ||
| * - Closure: ['slack' => fn ($config, $tenant) => $config->set('logging.channels.slack.url', $tenant->slackUrl)] | ||
| */ | ||
| public static array $channelOverrides = []; |
There was a problem hiding this comment.
If the key in this array is e.g. slack, and we can provide either a "partial array override" or a closure that returns the override dynamically based on $tenant, why would the closure approach be:
function (Config\Repository $config, Tenant $tenant): void {
$config->set('something', something based on $tenant);
}As opposed to returning a value that'd directly override the channel:
function (array $channel, Tenant $tenant): array {
return array_merge($channel, [overrides based on $tenant]);
}The current approach would let you do for instance $channelOverrides['foo'] = fn ($config, $tenant) => $config->set('bar', ...) which doesn't make sense. And requiring the user to do $config->set() manually in the first place is unnecessary complexity for the user.
There was a problem hiding this comment.
Swapped the parameter order. Example for current usage of overrides:
LogTenancyBootstrapper::$channelOverrides = [
'single' => function (Tenant $tenant, array $channel) {
return array_merge($channel, ['path' => storage_path("logs/override-{$tenant->id}.log")]);
},
];Also updated the comments to clarify the bootstrapper's behavior. So I think this review can be resolved now
…ehavior, improve test by making assertions more specific
There was a problem hiding this comment.
♻️ Duplicate comments (1)
tests/Bootstrappers/LogTenancyBootstrapperTest.php (1)
174-185:⚠️ Potential issue | 🟠 MajorExplicitly end tenancy in tests that call
tenancy()->initialize().These cases still exit with tenancy active. That keeps the bootstrapper state/live tenant context around for later assertions and can make the suite order-dependent if the application instance is reused. Add
tenancy()->end()in afinallyblock or at the end of each test.Suggested fix
test('channel config keys remain unchanged if the specified tenant override attribute is null', function() { @@ tenancy()->initialize(Tenant::create()); // The username should remain unchanged since the tenant attribute is null expect(config('logging.channels.slack.username'))->toBe('Default username'); + tenancy()->end(); }); @@ test('channel overrides take precedence over the default storage path channel updating logic', function () { @@ tenancy()->initialize($tenant); // Should use override, not the default storage path updating behavior expect(config('logging.channels.single.path'))->toEndWith('storage/logs/override-tenant1.log'); + tenancy()->end(); });Also applies to: 187-200, 276-307
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/Bootstrappers/LogTenancyBootstrapperTest.php` around lines 174 - 185, The test calls tenancy()->initialize(Tenant::create()) but never ends tenancy, leaving the bootstrapper/tenant context active for later tests; update this test (and the other affected tests that call tenancy()->initialize()) to ensure tenancy()->end() is always called—either wrap the assertions in a try/finally where tenancy()->end() is called in finally, or call tenancy()->end() at the end of the test so LogTenancyBootstrapper::$channelOverrides and the live tenant context are cleaned up before subsequent assertions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@tests/Bootstrappers/LogTenancyBootstrapperTest.php`:
- Around line 174-185: The test calls tenancy()->initialize(Tenant::create())
but never ends tenancy, leaving the bootstrapper/tenant context active for later
tests; update this test (and the other affected tests that call
tenancy()->initialize()) to ensure tenancy()->end() is always called—either wrap
the assertions in a try/finally where tenancy()->end() is called in finally, or
call tenancy()->end() at the end of the test so
LogTenancyBootstrapper::$channelOverrides and the live tenant context are
cleaned up before subsequent assertions.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 5eb2d039-3573-44ee-a85c-3e1387c7726d
📒 Files selected for processing (2)
src/Bootstrappers/LogTenancyBootstrapper.phptests/Bootstrappers/LogTenancyBootstrapperTest.php
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/Bootstrappers/LogTenancyBootstrapper.php`:
- Around line 60-61: The bootstrap() method computes $channels via getChannels()
but doesn't persist that exact set; change LogTenancyBootstrapper to store the
computed channels (e.g. a new private property $bootstrappedChannels) when
bootstrap() runs and then have revert() use $this->bootstrappedChannels (with a
safe null/empty check fallback) instead of recomputing getChannels() or reading
logging.default/storagePathChannels/channelOverrides again; update any code
paths that reference the prior temporary $channels (including lines around
74-78) to read from $this->bootstrappedChannels so tenant-specific logger
bindings are reliably forgotten on revert.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 17d62ba3-c4db-44b3-876b-2777b37122c3
📒 Files selected for processing (2)
src/Bootstrappers/LogTenancyBootstrapper.phptests/Bootstrappers/LogTenancyBootstrapperTest.php
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/Bootstrappers/LogTenancyBootstrapper.php`:
- Around line 138-163: The throw in overrideChannelConfig uses a fully-qualified
\InvalidArgumentException; to match file import style, add an import for
InvalidArgumentException at the top of the file and update the throw expression
in overrideChannelConfig to throw new InvalidArgumentException("Channel override
closure for '{$channel}' must return an array."); so the class is referenced by
its short name instead of the prefixed FQN.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 3262507a-2e82-4c22-b68d-0cbbca21aa33
📒 Files selected for processing (1)
src/Bootstrappers/LogTenancyBootstrapper.php
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/Bootstrappers/LogTenancyBootstrapper.php`:
- Around line 43-53: The docblock for $channelOverrides is misleading: clarify
that overrides may be either an array mapping or a Closure, and that only the
Closure's return value must be an array (not the Closure itself). Update the
comment near $channelOverrides in LogTenancyBootstrapper.php to state: supported
override types are an array mapping (e.g. ['slack' => ['url' => 'webhookUrl']])
or a Closure (e.g. fn(Tenant $tenant, array $channel): array =>
array_merge($channel, ['url' => $tenant->slackUrl'])), and in the Closure case
the Closure must return the channel configuration as an array. Ensure the
wording removes the contradiction that "the override should be an array" and
instead specifies the Closure’s return value requirement.
- Around line 131-135: The code in LogTenancyBootstrapper that handles channels
listed in static::$storagePathChannels currently overwrites any existing
logging.channels.{channel}.path with storage_path('logs/laravel.log'), which
destroys custom filenames and causes different tenant channels to collide;
instead read the existing configured path (e.g.
$this->config->get("logging.channels.{$channel}.path")), compute its relative
subpath (preserving directories and basename) and then set
logging.channels.{channel}.path to the tenant-scoped storage path by joining the
tenant directory (tenant key) with that relative subpath (use storage_path for
absolute base), ensuring you preserve the original subpath if it’s already
outside storage/logs by rebasing rather than replacing in the $this->config->set
call.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 9658fcee-45fd-4728-86ed-1158a5a4f0b4
📒 Files selected for processing (1)
src/Bootstrappers/LogTenancyBootstrapper.php
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
src/Bootstrappers/LogTenancyBootstrapper.php (1)
136-138:⚠️ Potential issue | 🟠 MajorPreserve the relative log subpath, not just the filename.
basename($path)keeps only the last segment. If two tenantized channels are configured asstorage/logs/api/app.logandstorage/logs/web/app.log, they both collapse tostorage/.../logs/app.logand their output mixes. Rebase the path relative tostorage_path()when the original path already lives under storage, and only fall back tobasename()for non-storage paths.💡 Suggested fix
- $path = $this->config->get("logging.channels.{$channel}.path"); - - $this->config->set("logging.channels.{$channel}.path", storage_path('logs/' . ($path ? basename($path) : 'laravel.log'))); + $path = (string) $this->config->get( + "logging.channels.{$channel}.path", + storage_path('logs/laravel.log') + ); + $storageRoot = rtrim(storage_path(), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; + + $relativePath = str_starts_with($path, $storageRoot) + ? ltrim(substr($path, strlen($storageRoot)), DIRECTORY_SEPARATOR) + : 'logs/' . basename($path); + + $this->config->set("logging.channels.{$channel}.path", storage_path($relativePath));🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Bootstrappers/LogTenancyBootstrapper.php` around lines 136 - 138, The current logic uses basename($path) which loses intermediate directories and causes different tenant channels (e.g., storage/logs/api/app.log vs storage/logs/web/app.log) to collide; update the code in LogTenancyBootstrapper where you fetch and set "logging.channels.{$channel}.path" to preserve a relative subpath when the original path resides under storage_path(): if $path starts with storage_path() compute the relative path by stripping storage_path() and trimming leading slashes and join that under storage_path('logs/' . $relative), otherwise fall back to using basename($path) (and default to 'laravel.log' when $path is empty); use existing symbols $this->config->get, $this->config->set, storage_path() and basename() to locate and change the logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@tests/Bootstrappers/LogTenancyBootstrapperTest.php`:
- Around line 32-34: In beforeEach capture the current static values of
LogTenancyBootstrapper::$channelOverrides and
LogTenancyBootstrapper::$storagePathChannels into local variables (e.g.
$originalChannelOverrides, $originalStoragePathChannels) and then in afterEach
restore those exact captured values back to
LogTenancyBootstrapper::$channelOverrides and
LogTenancyBootstrapper::$storagePathChannels instead of hard-coding arrays so
the tests revert to the true originals; apply the same pattern for the other
reset points that currently hard-code values.
---
Duplicate comments:
In `@src/Bootstrappers/LogTenancyBootstrapper.php`:
- Around line 136-138: The current logic uses basename($path) which loses
intermediate directories and causes different tenant channels (e.g.,
storage/logs/api/app.log vs storage/logs/web/app.log) to collide; update the
code in LogTenancyBootstrapper where you fetch and set
"logging.channels.{$channel}.path" to preserve a relative subpath when the
original path resides under storage_path(): if $path starts with storage_path()
compute the relative path by stripping storage_path() and trimming leading
slashes and join that under storage_path('logs/' . $relative), otherwise fall
back to using basename($path) (and default to 'laravel.log' when $path is
empty); use existing symbols $this->config->get, $this->config->set,
storage_path() and basename() to locate and change the logic.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 8006ceab-0634-4428-a3b7-6ac00a203f75
📒 Files selected for processing (2)
src/Bootstrappers/LogTenancyBootstrapper.phptests/Bootstrappers/LogTenancyBootstrapperTest.php
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/Bootstrappers/LogTenancyBootstrapper.php`:
- Around line 133-141: The code currently resolves and bakes an absolute central
storage path into tenant channels; add a runtime guard in LogTenancyBootstrapper
(the branch handling static::$storagePathChannels where you call
$this->config->get("logging.channels.{$channel}.path") and set
storage_path(...)) that detects if the existing channel path is already an
absolute/central storage path (e.g., starts with storage_path() or is an
absolute filesystem path) and immediately throw a clear RuntimeException
instructing the user to enable/position FilesystemTenancyBootstrapper earlier in
the bootstrappers list; do this check before calling storage_path(...) and avoid
mutating the config if the guard triggers.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 6db5d9fe-f993-4381-b44e-a9709a300325
📒 Files selected for processing (1)
src/Bootstrappers/LogTenancyBootstrapper.php
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
src/Bootstrappers/LogTenancyBootstrapper.php (1)
145-155:⚠️ Potential issue | 🟠 MajorSkip storage-path rewrites when storage suffixing is disabled.
This branch still rewrites
logging.channels.{channel}.patheven whentenancy.filesystem.suffix_storage_pathisfalse. In that modestorage_path()stays central, so these channels keep writing to shared storage instead of being tenant-isolated. Please gate the default path-rewrite logic on that flag and silently skip it when suffixing is off.💡 Suggested fix
foreach ($channels as $channel) { if (isset(static::$channelOverrides[$channel])) { $this->overrideChannelConfig($channel, static::$channelOverrides[$channel], $tenant); - } elseif (in_array($channel, static::$storagePathChannels)) { + } elseif (in_array($channel, static::$storagePathChannels, true)) { + if ($this->config->get('tenancy.filesystem.suffix_storage_path') === false) { + continue; + } + // Set storage path channels to use tenant-specific directory (default behavior). // The tenant log will be located at e.g. "storage/tenant{$tenantKey}/logs/laravel.log" // (assuming FilesystemTenancyBootstrapper is used before this bootstrapper). $originalChannelPath = $this->config->get("logging.channels.{$channel}.path"); $centralStoragePath = Str::before(storage_path(), $this->config->get('tenancy.filesystem.suffix_base') . $tenant->getTenantKey());Based on learnings, in
archtechx/tenancyLogTenancyBootstrapper, the$storagePathChannelslogic depends on bothFilesystemTenancyBootstrapperrunning first andtenancy.filesystem.suffix_storage_pathbeingtrue; whensuffix_storage_pathisfalse, storage-path channels should be silently skipped.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Bootstrappers/LogTenancyBootstrapper.php` around lines 145 - 155, The branch in LogTenancyBootstrapper that rewrites logging.channels.{channel}.path for entries in $storagePathChannels must be skipped when tenancy.filesystem.suffix_storage_path is false; update the conditional around the in_array($channel, static::$storagePathChannels) block to also check config('tenancy.filesystem.suffix_storage_path') (or $this->config->get(...)) and return/continue silently when suffixing is disabled so no storage_path() rewrite occurs and channels remain on central storage.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@tests/Bootstrappers/LogTenancyBootstrapperTest.php`:
- Around line 23-26: The cleanup only globs one-level *.log files into $logFiles
so nested files like storage/logs/single/*.log, storage/logs/daily/*.log and
tenantized storage/tenant*/logs/*/*.log are missed; update the globbing used
where $logFiles is constructed (and the similar block at the other occurrence)
to include nested directories (e.g. add patterns for logs/*/*.log and
tenant*/logs/*/*.log or switch to a recursive iterator/RegexIterator using
storage_path()) so all nested .log files are collected and removed during
teardown.
---
Duplicate comments:
In `@src/Bootstrappers/LogTenancyBootstrapper.php`:
- Around line 145-155: The branch in LogTenancyBootstrapper that rewrites
logging.channels.{channel}.path for entries in $storagePathChannels must be
skipped when tenancy.filesystem.suffix_storage_path is false; update the
conditional around the in_array($channel, static::$storagePathChannels) block to
also check config('tenancy.filesystem.suffix_storage_path') (or
$this->config->get(...)) and return/continue silently when suffixing is disabled
so no storage_path() rewrite occurs and channels remain on central storage.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: fd32f838-0fe9-4557-bf08-fe6b4594b276
📒 Files selected for processing (2)
src/Bootstrappers/LogTenancyBootstrapper.phptests/Bootstrappers/LogTenancyBootstrapperTest.php
|
@coderabbitai resume |
✅ Actions performedReviews resumed. |
too many mentions of "default" behavior when there are just different approaches for different channels
| * All the storage path channels are configured to use tenant | ||
| * directories by default (see the $storagePathChannels property). |
There was a problem hiding this comment.
This language seems weird. single and daily may be the default in a fresh Laravel app but they're just usual channels, there may be many more or different ones.
Would phrase this differently.
| * This is the default behavior. The $channelOverrides property can be used to override | ||
| * this behavior (the overrides take precedence over $storagePathChannels). |
There was a problem hiding this comment.
I clarified the language in b51d5ca (too many mentions of "defaults" which don't make sense to me) but still why are we calling the storage approach default when it just serves a different purpose than the approach for overriding specific channels?
It might be "default" in the sense that Laravel apps would by default use one of the two filesystem based logs, but that's more of a default configuration of this bootstrapper rather than being something like the default mode of this bootstrapper when there are just two separate config options for different purposes.
It's likely that even apps using filesystem based logs would still be customizing this config. So we do want to have sane default values for these static props but I'd avoid saying something is the default approach or default behavior. The default value of the property speaks for itself but the docblock is intended for people customizing that value.
| /** | ||
| * Channels to configure and forget from the log manager so they can be | ||
| * 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 | ||
| */ | ||
| 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). | ||
| */ |
There was a problem hiding this comment.
Why are we assuming that the only way a stack channel might show up is that it'd be the default channel and so we only forget the default channel in addition to the specific channels here. It's a bit of a crude assumption about what the default channel might be. And it seems to entirely miss the possibility of other stack channels including our scoped channels?
To me it'd make way more sense and be much cleaner to simply look at all the stack-based channels and forget those that include the channels in either of our static props.
There might be an extra complication there where perhaps stack channels could support a higher depth than 1 (would require a recursive solution) but we can probably ignore that case and simply assume the depth of any stack is just 1. Not sure if stack supports anything deeper even if it does it seems unlikely that many would use it so I'd go with the simple implementation. And if higher depth stacks are supported, just add a comment documenting that we only look 1 level deep.
|
Seems some of the minor logic changes here b51d5ca (the getAttribute thing perhaps?) made a test start failing. |
This PR adds the LogTenancyBootstrapper to provide tenant-specific logging configuration. The bootstrapper automatically configures storage path channels to use tenant-specific directories (NOTE: for this to work correctly, the bootstrapper has to run AFTER FilesystemTenancyBootstrapper, otherwise, the logs still won't be separated, unless you use overrides) and supports custom channel overrides for custom logging scenarios -- mapping tenant properties to the channel config, or using custom closures an array with the logging config, e.g. for making the slack channel (that's not handled by the bootstrapper by default) tenant-specific.
The bootstrapper first modifies the channel config, then forgets the channel from LogManager so that on the next logging attempt, the channel is re-resolved with the modified config. Otherwise, the channel would just use the initial config if the channel was resolved before. If the channel wasn't resolved before, it'll always be resolved with the updated (tenant) config, unless the configuration fails. In that case, the config will be reverted (the central config will be restored) and the error will be logged at the original logging path.
When using a channel stack, the
stackchannel itself also has to be forgotten, since the LogManager could retain e.g. the originalstackchannel's webhook URL, while the underlyingslackchannel would use the updated one, and while logging, the app would actually use the initial webhook URL instead of the updated one (encountered this issue while testing).Note that all channels in
$storagePathChannelsand$channelOverridesare affected.Also, adding
'attachment' => 'false'to the slack channel's config makes the slack channel work with Discord webhooks (just a cool thing we figured out whlle testing the bootstrapper).Summary by CodeRabbit
New Features
Tests
Chores