Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Log Database Laravel Package

sinemacula/laravel-log-database

A Laravel package that stores application log entries in a database table instead of flat files, making logs easier to query, filter, and retain. It plugs into Laravel's logging channels so existing Log calls work unchanged while records are persisted and browsable.

View on GitHub
Deep Wiki
Context7

Getting Started

sinemacula/laravel-log-database adds a Monolog handler that persists Laravel log records to a database table instead of (or alongside) files, so you can query, filter, and join application logs with the rest of your data.

  1. Install the package:
    composer require sinemacula/laravel-log-database
    
  2. Publish its config and migration, then migrate:
    php artisan vendor:publish --tag=log-database-config
    php artisan vendor:publish --tag=log-database-migrations
    php artisan migrate
    
  3. Register a channel in config/logging.php that uses the package's driver, e.g.:
    'channels' => [
        'database' => [
            'driver' => 'database',
            'connection' => env('LOG_DB_CONNECTION'),
            'table' => 'logs',
            'level' => env('LOG_LEVEL', 'debug'),
        ],
    ],
    
  4. Point LOG_CHANNEL=database (or add it to a stack) and write a log:
    Log::info('Invoice generated', ['invoice_id' => $invoice->id]);
    

Where to look first: the published config file (connection, table name, minimum level, and any except/context options) and the generated migration (column types and indexes). Verify the driver name and config keys against the published file — the keys above are the conventional shape, not a guarantee.

First use case: replacing daily file logs on a multi-container or serverless deployment where local files are ephemeral or scattered, giving you a single queryable log table:

DB::table('logs')->where('level_name', 'error')->latest()->limit(50)->get();

Upgrading to v1.1.0: this release only widens the illuminate/* constraints to include Laravel 13. There are no API, config, or schema changes. If you were blocked on Laravel 13 with v1.0.0, composer update sinemacula/laravel-log-database is sufficient; no migration or republish is required.

Implementation Patterns

Stack it, don't replace everything. The most common production setup is a stack that keeps a fallback file channel alive so logging survives a database outage:

'stack' => [
    'driver' => 'stack',
    'channels' => ['database', 'single'],
],

Ordering matters: the first channel that throws can prevent later ones from running.

Split by severity. Define two channels over the same driver — a low-threshold database channel for debug+, and an errors channel at error+ routed to a separate table or connection for alerting and retention policies:

Log::channel('errors')->error('Payment gateway timeout', ['gateway' => 'stripe']);

Per-tenant / per-service connections. Because the channel accepts a connection, you can route logs to a dedicated logging database (or a separate RDS/read-replica-friendly instance) without touching the app's default connection. Set LOG_DB_CONNECTION and leave application writes on the primary.

Query and display. Wrap the log table in your own Eloquent model or repository for admin panels and dashboards — a LogEntry model with casts for context (JSON → array) and created_at gives you Scout/filtering for free:

LogEntry::query()
    ->where('level_name', '>=', 'warning')
    ->where('context->invoice_id', $id)
    ->latest('created_at')
    ->paginate(50);

Retention as a scheduled command. Always pair the channel with pruning. A nightly model:prune on your log model, or a raw delete by date, keeps the table from becoming the largest object in the database:

LogEntry::where('created_at', '<', now()->subDays(30))->delete();

Correlating with requests and jobs. Push a request/job identifier into Monolog's shared context in a middleware or JobProcessing listener so every row is traceable; the package records Monolog's context, so this arrives without per-call boilerplate.

Testing. In phpunit.xml set LOG_CHANNEL=null (or array) so the suite doesn't hammer the log table; add a dedicated test that swaps the channel and asserts a row is written when you want coverage of your logging contract.

Gotchas and Tips

  • Circular failure risk. A DB-backed logger cannot log database failures. If the logging connection dies, the handler throws inside the error path and can mask the original exception or abort the request. Always keep a file/stderr fallback in the stack channel and set a sensible connection that isn't the one most likely to fail.
  • Transaction rollbacks erase logs. If you log inside a DB::transaction() on the same connection, the log row rolls back with the business data — you lose exactly the records you need when debugging a failed transaction. Use a separate connection, or log after the transaction commits.
  • Unbounded growth is the default. Nothing in the package prunes the table. Ship the migration with indexes on level_name, created_at, and (if the driver supports it) the JSON context column, and schedule deletion from day one. Missing the created_at index is the usual cause of a slow admin log viewer.
  • Column widths and payload size. message and serialized context may be truncated or rejected depending on column type; long stack traces and large context arrays can silently lose the tail. Trim context at the call site (Log::info($msg, ['id' => $id])) rather than dumping whole models.
  • Volume shock after switching. Moving from daily files to a table frequently multiplies write load on the primary DB and can dwarf your table statistics. Benchmark at production LOG_LEVEL and consider debug writes only in non-production.
  • Queue workers amplify writes. A noisy job retried 100 times produces 100 rows per attempt. Set per-channel levels for workers (LOG_LEVEL=warning on queue processes) rather than globally.
  • Debugging missing rows. If nothing lands, check in this order: the channel is actually active (LOG_CHANNEL/stack), level threshold allows the record, the migration ran on the configured connection, and the driver name in config/logging.php matches what the package registers. php artisan about and config('logging.channels.database') confirm what's resolved.
  • Extension points. The published config is the primary lever (connection, table, level, and any except/context options); for anything beyond that — custom columns, enrichment — subclass or decorate the handler rather than editing vendor code, and note that config keys are not covered by semver the way the public API is. Re-diff the published config after minor upgrades.
  • Laravel 13 support (v1.1.0) is constraint-only. No behaviour change, but if you pin packages tightly, confirm your laravel/framework and monolog/monolog constraints resolve together — Monolog major bumps have historically been the real compatibility hurdle for logging packages, not the framework version.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings