yoeriboven/laravel-log-db
Store Laravel application logs in the database instead of files. This package adds a database log channel that saves messages, levels, context and timestamps to a queryable logs table, so you can browse, filter and search your logs directly from your app or admin panel.
yoeriboven/laravel-log-db adds a Monolog handler that persists Laravel log records to a database table, so log entries become queryable rows instead of grepping through storage/logs/laravel.log. Version 3.0 is a support-matrix release: it adds Laravel 13 and drops Laravel 9, 10 and 11 (all EOL). Supported targets are now Laravel 12 and 13. There are no new features or schema changes, so day-to-day usage is identical to 2.x.
Minimum setup:
composer require yoeriboven/laravel-log-db — on Laravel ≤11 this resolves to ^2.1; require ^3.0 explicitly and confirm your laravel/framework constraint allows 12/13 before upgrading.php artisan vendor:publish --tag="log-db-config" and php artisan vendor:publish --tag="log-db-migrations".php artisan migrate to create the logs table (message, level, context, channel, timestamps).config/logging.php that uses the package's Monolog handler, then add it to your stack:// config/logging.php
'db' => [
'driver' => 'monolog',
'handler' => \Yoeriboven\LogDb\Handlers\LogDbHandler::class,
'level' => env('LOG_DB_LEVEL', 'error'),
],
LOG_STACK=single,db (or add db to your stack channel) so file logging still happens as a fallback.Log::error('boom', ['order_id' => 42]); then DB::table('logs')->latest('id')->first();.First use case: production error triage. Instead of SSHing to tail a file, you query Log::where('level', 'error')->whereDate('created_at', today()) from Tinker, a Nova/Filament resource, or a scheduled digest command.
Keep file logging in the stack, not the DB channel alone. The DB handler should be an additional sink. LOG_STACK=single,db gives you the queryable row plus a durable file record if the database write itself fails.
Tier the levels per environment. Don't write debug to the database in production. Use a narrow DB channel (error+ in prod, debug locally) and let the file channel absorb the noisy levels. This is the single biggest lever on table growth and write overhead.
Separate channels for separate intents. A common pattern is two DB channels — one for error/critical used by a "recent incidents" dashboard, and one for info/warning on specific domains (payments, webhooks, imports) where you actually want audit-style queryability. Register both as handlers with different level values and point code at them explicitly: Log::channel('payments_db')->info(...).
Query through the package model. Bind Yoeriboven\LogDb\Models\Log (or a subclass) and expose scopes for the queries you run repeatedly — errors(), forChannel($name), since($date). Adding ->where('context->order_id', $id) works well on MySQL/Postgres JSON columns and lets support staff reconstruct a single order's history without touching the filesystem.
Wire retention from day one. Add a scheduled prune rather than hoping the table stays small:
// routes/console.php
Schedule::call(fn () => Log::where('created_at', '<', now()->subDays(30))->delete())
->daily();
Index level and created_at (and add a composite index if you filter by both) before volume arrives, not after.
Route the noisy stuff elsewhere. Framework/queue/vendor chatter dominates row counts. Use config/logging.php per-channel config to keep the DB handler off channels you don't care about, and add context filtering so exceptions with large payloads don't bloat rows.
Test against the channel, not the table. Log::spy() / Log::shouldReceive() assertions stay stable across driver changes; assertions on raw table rows couple tests to the handler's formatting.
3.0 is a hard upgrade gate. If any app or shared package pins Laravel 9/10/11, stay on ^2.1. Nothing in your config/log-db.php, migrations, or handler registration changes between 2.1 and 3.0 — treat this as a Composer constraint change and verify CI matrix and any lockfile-pinned deployments together.
Database-down means logs-down. The handler writes over the same connection it's reporting about. If the DB is unreachable or the connection is exhausted, DB log writes fail silently (or throw inside the logging path). Always keep the file channel first in the stack, and consider pointing the package at a separate connection in config/log-db.php if you log about a primary DB.
Transactions can eat your logs. Rows written inside a transaction that later rolls back disappear — which is exactly when you most wanted them. Don't rely on DB logs for auditing failed writes; log to file or a separate connection for post-mortem-critical events.
logs is a popular table name. Other packages and hand-rolled migrations collide here. Check php artisan migrate output on a fresh DB, and rename via the config's table setting rather than editing the published migration if you need to coexist.
Config caching hides channel changes. After editing config/logging.php or config/log-db.php, run php artisan config:clear locally and config:cache in deploy. A stale cache silently keeps writing to the old level/table and looks like a broken handler.
Sensitive context leaks into the table. Validation failures and request exceptions can carry passwords, tokens, and full payloads. Filter context before it reaches the handler, and treat the logs table with the same access controls as your users table.
Don't debug the handler blind. If rows stop appearing: confirm the channel is in LOG_STACK, confirm the level threshold on both the channel and the package config, and test with Log::channel('db')->error(...) directly — bypassing the stack isolates routing problems from handler problems.
Extension point: subclass the handler (or the Log model) rather than modifying the published config in place, so upgrades stay copy-paste clean.
How can I help you explore Laravel packages today?