News

Monthly Log Driver in Laravel 13.23

Published
Monthly Log Driver in Laravel 13.23 image

Release updates

Never miss a Laravel release

Get an email whenever a new Laravel release lands.

Laravel 13.23 adds a monthly log driver that rotates one file per calendar month, per-message tenant selection for the SES v2 mail transport, support for PostgreSQL's USING clause when changing column types, and a timing-safe comparison for the maintenance mode bypass secret. The Laravel team released v13.23.0 on July 27, 2026.

  • New monthly log driver and matching monthly logging channel
  • X-SES-TENANT-NAME header support in the SES v2 transport
  • ->using() on ->change() migrations for PostgreSQL type casts
  • Timing-safe maintenance mode bypass secret comparison
  • A rewritten schedule:list timezone converter and assorted fixes

What's New

Monthly Log Driver

The daily driver rotates a file per day, which suits applications that log enough to fill one. Smaller applications end up with a directory of near-empty files instead. The new monthly driver sits between single and daily, writing one file per calendar month (laravel-2026-07.log, laravel-2026-08.log, and so on):

'monthly' => [
'driver' => 'monthly',
'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'),
'max_files' => 3,
'replace_placeholders' => true,
],

Both rotating drivers now share a createRotatingDriver() method on the LogManager and differ only in the Monolog date format they pass (FILE_PER_DAY versus FILE_PER_MONTH) and their retention default: 14 files for daily, 3 for monthly.

Retention also has a new configuration key. The daily channel reads max_files first and falls back to the existing days key, so applications that already set days keep working and the LOG_DAILY_DAYS environment variable is unchanged. The shipped config now uses max_files for both channels. Contributed by @SjorsO in #60892, with the config channel added by @yazansalhi in #60895.

SES Tenant Support in the SES v2 Transport

Amazon SES tenants isolate sending reputation and configuration between groups of senders. Until now the transport could only work with a tenant configured for the whole application. The SES v2 transport now reads an X-SES-TENANT-NAME header off the outgoing message and forwards it as the TenantName parameter on the SDK call, following the pattern already used for X-SES-LIST-MANAGEMENT-OPTIONS.

You can set the header from a mailable:

use Illuminate\Mail\Mailables\Headers;
 
public function headers(): Headers
{
return new Headers(text: ['X-SES-TENANT-NAME' => 'tenant-id']);
}

Or from a notification's mail message:

return (new MailMessage)->withSymfonyMessage(function ($message) {
$message->getHeaders()->addTextHeader('X-SES-TENANT-NAME', 'tenant-id');
});

When the header is absent, sending behaves exactly as before, and the parameter is omitted on AWS SDK versions that predate the feature. Contributed by @atymic in #60886.

->using() for PostgreSQL Column Changes

PostgreSQL refuses to alter a column to an incompatible type without an explicit cast, returning an error like column "name" cannot be cast automatically to type date. There was no way to supply that cast through the schema builder, so migrations written against MySQL or SQLite would fail on PostgreSQL. Column definitions now accept a using() clause:

Schema::table('currency_rates', function (Blueprint $table) {
$table->date('name')->using('name::date')->change();
});

The clause applies to PostgreSQL only; other grammars ignore it. Contributed by @shemgp in #60901.

Timing-Safe Maintenance Mode Bypass Secret

The maintenance mode bypass flow compared the secret two different ways: hash_equals() when validating the incoming secret, and a plain === in the PreventRequestsDuringMaintenance middleware and the maintenance-mode.stub bootstrap path. Both comparisons now use hash_equals(), and the middleware's isset() guard was replaced with is_string() so a non-string cookie value cannot raise a TypeError.

This is a defense-in-depth change rather than a fix for an exploitable bug, since a wrong-length guess never reaches a byte-by-byte comparison. Behavior for valid input is unchanged. Contributed by @yazansalhi in #60896.

Other Fixes and Improvements

  • The schedule:list timezone converter was rewritten. It previously shifted only plain numeric cron fields, leaving ranges (8-23), steps (*/2), and wildcards untouched while still labeling the output with the converted timezone. It now expands each field, shifts the values, and collapses them back into compact syntax, splitting an event into multiple entries when a shifted range crosses midnight against a fixed day field. It also accounts for DST transitions and fractional offsets such as UTC+5:45 (#60877)
  • Failures inside the deprecation logger no longer escalate into fatal errors (#60893)
  • CookieJar::queued() returns the supplied default correctly (#60904), and Arr::last() handles null without erroring (#60887)
  • ImageManager::fromStorage() accepts a backed enum as the disk name (#60889), and the Image class received type improvements (#60890)
  • Collection::select() has more accurate generic types for static analysis (#60898)
  • The earlier fix for ShouldBeUniqueUntilProcessing jobs force-releasing locks they do not own was reverted (#60905)

References

Paul Redmond photo

Staff writer at Laravel News. Full stack web developer and author.

Sponsored

acquaintsoft logo
Acquaint Softtech

Hire Laravel developers with AI expertise at $20/hr. Get started in 48 hours.

Visit Acquaint Softtech

The latest

View all →
Bifrost Turns One With AI Builds and New Workflows image

Bifrost Turns One With AI Builds and New Workflows

Read article
Preview Blade Templates in macOS Finder with Quick Blade image

Preview Blade Templates in macOS Finder with Quick Blade

Read article
Artisan Debugging Commands in Laravel Telescope 5.24.0 image

Artisan Debugging Commands in Laravel Telescope 5.24.0

Read article
Queue totalSize() and JobInterrupted Event in Laravel 13.31 image

Queue totalSize() and JobInterrupted Event in Laravel 13.31

Read article
Find Unexpected Test Inputs with Fuzz for Pest image

Find Unexpected Test Inputs with Fuzz for Pest

Read article
Laravel Rulebook: Business Rules That Change by Date image

Laravel Rulebook: Business Rules That Change by Date

Read article