Skip to content

[4.x] Add LogChannelBootstrapper#1381

Merged
stancl merged 63 commits into
masterfrom
add-log-bootstrapper
Jul 8, 2026
Merged

[4.x] Add LogChannelBootstrapper#1381
stancl merged 63 commits into
masterfrom
add-log-bootstrapper

Conversation

@lukinovec

@lukinovec lukinovec commented Jul 28, 2025

Copy link
Copy Markdown
Contributor

This PR adds the LogChannelBootstrapper 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 using the original channel.

When using a channel stack, the stack channel itself also has to be forgotten, since the LogManager could retain e.g. the original stack channel's webhook URL, while the underlying slack channel 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 $storagePathChannels and $channelOverrides are 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

    • Added tenant-aware logging so log channels can resolve separately for each tenant, including file-based and stacked channels.
    • Improved storage-path handling to keep tenant log files isolated and correctly re-resolved when switching contexts.
  • Tests

    • Added coverage for tenant log routing, override behavior, stack channel handling, rollback on failure, and configuration reset after bootstrapping.

@codecov

codecov Bot commented Jul 28, 2025

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 86.57%. Comparing base (3156c87) to head (24029f7).
⚠️ Report is 1 commits behind head on master.

Additional details and impacted files
@@             Coverage Diff              @@
##             master    #1381      +/-   ##
============================================
+ Coverage     86.37%   86.57%   +0.19%     
- Complexity     1200     1221      +21     
============================================
  Files           186      187       +1     
  Lines          3524     3575      +51     
============================================
+ Hits           3044     3095      +51     
  Misses          480      480              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@lukinovec lukinovec requested a review from Copilot July 29, 2025 11:28

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 LogTenancyBootstrapper with 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 {

Comment thread src/Bootstrappers/LogTenancyBootstrapper.php Outdated
@lukinovec lukinovec marked this pull request as ready for review July 29, 2025 11:36
@lukinovec lukinovec requested a review from stancl July 29, 2025 11:36
Comment thread src/Bootstrappers/LogChannelBootstrapper.php
@lukinovec

Copy link
Copy Markdown
Contributor Author

@coderabbitai resume

@coderabbitai

coderabbitai Bot commented Apr 14, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Reviews resumed.

Comment thread src/Bootstrappers/LogTenancyBootstrapper.php Outdated
Comment thread src/Bootstrappers/LogTenancyBootstrapper.php Outdated
Comment thread src/Bootstrappers/LogTenancyBootstrapper.php Outdated
@stancl

stancl commented Jun 11, 2026

Copy link
Copy Markdown
Member

Seems some of the minor logic changes here b51d5ca (the getAttribute thing perhaps?) made a test start failing.

The bootstrapper should be able to map nested tenant attributes to config, and `getAttribute()` doesn't support dot notation, so e.g. `$tenant->getAttribute('logging.slackUrl')` would return `null` instead of the actual attribute, so the test failed.

`data_get()` supports dot notation, so we'll use that.
…nels

Reverting the LogTenancyBootstrapper changes will make the new "stack channels that include any configured channel are re-resolved" test fali
@lukinovec

lukinovec commented Jun 29, 2026

Copy link
Copy Markdown
Contributor Author

Seems some of the minor logic changes here b51d5ca (the getAttribute thing perhaps?) made a test start failing.

Yes, the test started failing because in the test, $tenant->logging['slackUrl'] (nested attribute) should be mapped to the config:

// 'slack channel uses correct webhook urls'
LogTenancyBootstrapper::$channelOverrides = [
    'slack' => ['url' => 'logging.slackUrl'],
]

getAttributes() doesn't support dot notation, so the case of logging.slackUrl, it returned null and the config's value stayed the same as it was in the central context

Fixed by using data_get() instead of getAttributes() (dc331bf)

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/Bootstrappers/LogTenancyBootstrapperTest.php`:
- Around line 373-390: The test currently verifies `custom_stack` only while
tenancy is active, so it does not cover the channel reset path after
`tenancy()->end()`. After the existing tenant log assertions in
`LogTenancyBootstrapperTest`, exercise `Log::channel('custom_stack')` again once
tenancy has ended and add an assertion that the post-revert log goes back to the
central log (using the existing `centralLogPath` and `tenantLogPath` checks).
This will validate the `forgetChannel()` behavior triggered by `TenancyEnded`
and ensure the stack channel is re-resolved back to the non-tenant
configuration.
🪄 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: f50ddf7a-bc8b-43b4-a5a0-df590bf44372

📥 Commits

Reviewing files that changed from the base of the PR and between 8107ed4 and 68e6fd6.

📒 Files selected for processing (2)
  • src/Bootstrappers/LogTenancyBootstrapper.php
  • tests/Bootstrappers/LogTenancyBootstrapperTest.php

Comment thread tests/Bootstrappers/LogChannelBootstrapperTest.php
lukinovec and others added 5 commits June 29, 2026 18:01
we use FilesystemTenancyBootstrapper::getBoundCentralStoragePath() in
LogTenancyBootstrapper to simplify the logic for extracting a path
inside the central storage directory

we could use this method in more places, such as DeleteTenantStorage:
  $centralStoragePath = tenancy()->central(fn () => storage_path());
  $tenantStoragePath = tenancy()->run($this->tenant, fn () => storage_path());

however for consistency (we're getting both the central and tenant
paths there) we're keeping it simple as it is now

using the newly added method would also introduce more coupling to
FilesystemTenancyBootstrapper - its specific implementation. this isn't
a big issue in LogTenancyBootstrapper which already has several
dependencies on the FS bootstrapper, and we do use app(static::class)
in the added method so extending the bootstrapper should work
the main change here is adding similar assertions related to
*re-resolving* channels to the 'stack channels that include any
configured channel are re-resolved' test, similar to the 'channels are
forgotten and re-resolved during bootstrap and revert' test
@stancl

stancl commented Jul 8, 2026

Copy link
Copy Markdown
Member

This bootstrapper should be renamed following our existing naming pattern, where XTenancyBootstrapper is only used for core Laravel components like databases or cache:

'bootstrappers' => [
    // Basic Laravel features
    Bootstrappers\DatabaseTenancyBootstrapper::class,
    Bootstrappers\CacheTenancyBootstrapper::class,
    // Bootstrappers\CacheTagsBootstrapper::class, // Alternative to CacheTenancyBootstrapper
    // Bootstrappers\DatabaseCacheBootstrapper::class, // Separates cache by DB rather than by prefix, must run after DatabaseTenancyBootstrapper
    Bootstrappers\FilesystemTenancyBootstrapper::class,
    Bootstrappers\QueueTenancyBootstrapper::class,
    // Bootstrappers\RedisTenancyBootstrapper::class, // Note: phpredis is needed

    // Adds support for the database session driver
    Bootstrappers\DatabaseSessionBootstrapper::class,

    // Configurable bootstrappers
    // Bootstrappers\TenantConfigBootstrapper::class,
    // Bootstrappers\RootUrlBootstrapper::class,
    // Bootstrappers\UrlGeneratorBootstrapper::class,
    // Bootstrappers\MailConfigBootstrapper::class,
    // Bootstrappers\LogChannelBootstrapper::class,
    // Bootstrappers\BroadcastingConfigBootstrapper::class,
    // Bootstrappers\BroadcastChannelPrefixBootstrapper::class,

    // Integration bootstrappers
    // Bootstrappers\Integrations\FortifyRouteBootstrapper::class,
    // Bootstrappers\Integrations\ScoutPrefixBootstrapper::class,

    // Bootstrappers\PostgresRLSBootstrapper::class,
],

I think LogChannelBootstrapper is the most reasonable and short name we can use here.

@stancl stancl changed the title [4.x] Add LogTenancyBootstrapper [4.x] Add LogChannelBootstrapper Jul 8, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/Bootstrappers/LogChannelBootstrapper.php (1)

159-165: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Validate storage-path channel paths before rebasing. A custom storagePathChannels entry can still reach this branch with a missing or non-string path, and an out-of-root path will be rebased into an invalid tenant path. Guard the config before calling Str::after()/storage_path().

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Bootstrappers/LogChannelBootstrapper.php` around lines 159 - 165, The
channel path rebasing in LogChannelBootstrapper::bootstrap currently assumes
each storagePathChannels entry has a valid string path inside the central
storage root. Add a guard before calling Str::after() and storage_path() to
verify the configured logging.channels.{channel}.path exists, is a string, and
is actually rooted under
FilesystemTenancyBootstrapper::getBoundCentralStoragePath(); if not, skip
rebasing or handle it explicitly so invalid tenant paths are not generated.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/Bootstrappers/LogChannelBootstrapper.php`:
- Around line 159-165: The channel path rebasing in
LogChannelBootstrapper::bootstrap currently assumes each storagePathChannels
entry has a valid string path inside the central storage root. Add a guard
before calling Str::after() and storage_path() to verify the configured
logging.channels.{channel}.path exists, is a string, and is actually rooted
under FilesystemTenancyBootstrapper::getBoundCentralStoragePath(); if not, skip
rebasing or handle it explicitly so invalid tenant paths are not generated.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 7c934def-6b6a-440b-b4b0-dc38e12efc53

📥 Commits

Reviewing files that changed from the base of the PR and between cef7f11 and 24029f7.

📒 Files selected for processing (4)
  • assets/config.php
  • src/Bootstrappers/LogChannelBootstrapper.php
  • tests/Bootstrappers/LogChannelBootstrapperTest.php
  • tests/TestCase.php

@stancl stancl merged commit 76e5f96 into master Jul 8, 2026
14 checks passed
@stancl stancl deleted the add-log-bootstrapper branch July 8, 2026 08:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants