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 Devlogger Laravel Package

onamfc/laravel-devlogger

Laravel DevLogger stores application logs in the database with automatic exception catching, tagging, queue and request/user context, advanced filtering, and configurable retention cleanup. Easily configurable via environment variables.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and publish in three commands, then verify the publish actually landed files (this has been the package's most frequent bug class):

composer require onamfc/laravel-devlogger
php artisan vendor:publish --tag=devlogger-config
php artisan vendor:publish --tag=devlogger-migrations
php artisan migrate

After publishing, confirm config/devlogger.php exists and the migration shows up in database/migrations/. Migrations auto-load, so publishing them is only needed if you intend to edit the schema. Run php artisan migrate:status | grep devlogger before assuming the table is there.

Where to look first

  1. config/devlogger.php — every behaviour is an env-driven toggle; read it before wiring anything.
  2. DevLoggerPackage\Models\DeveloperLog — this is the query surface. Scopes like level(), open(), queue(), and dateRange() are your admin UI building blocks.
  3. DevLoggerPackage\Facades\DevLogger — the logging API. The README also uses a bare DevLogger:: alias; if that alias isn't registered in your app, use the fully-qualified facade or add it to config/app.php.

First use case: stop reading storage/logs/laravel.log when a job fails in production. Log one exception manually, then query it back:

use DevLoggerPackage\Facades\DevLogger;

try {
    $this->chargeCustomer($order);
} catch (\Throwable $e) {
    DevLogger::withTags(['billing', 'critical'])
        ->logException($e, ['order_id' => $order->id]);

    throw $e;
}

Then DeveloperLog::level('error')->open()->latest()->first() gives you the message, stack trace, URL, method, user, IP, and user agent in one row — no log-file grepping, no correlation IDs to invent.

Only after manual logging works should you turn on DEVLOGGER_AUTO_CATCH. That requires an extra wiring step that differs by Laravel version (see Gotchas), and if you enable it blind you'll get rows you didn't expect and no way to tell where they came from.

Implementation Patterns

Use it as a triage queue, not a log sink. The status and tag columns are the whole point. A workflow that holds up in practice:

// Nightly: surface unresolved errors, grouped
$open = DeveloperLog::level('error')->open()->dateRange(now()->subDays(7), now())->get();

// Someone picks one up
$log = DeveloperLog::find($id);
$log->addTags(['triaged', 'billing-team']);
$log->markAsClosed(auth()->id());   // stamps updated_by

// Reopened because it recurred
$log->markAsOpen();

markAsClosed() takes the acting user ID, so an admin panel gets an audit trail for free. Pair open()/closed() with withTrashed() if you want resolved-then-purged entries to stay visible in reports.

Chain context instead of dumping arrays. Queue, tags, and level are all first-class columns — putting them in context makes them unqueryable:

DevLogger::onQueue('reports')
    ->withTags(['report', 'daily'])
    ->info('Daily report generated', ['rows' => $count]);

Exception handling: pick one hook, not both. Choose the base-class swap (Laravel 9–11) or the manual report() integration, never both, or every exception is logged twice. The manual form is the safer default because it keeps your existing handler logic:

public function report(Throwable $exception)
{
    if ($this->shouldReport($exception) && config('devlogger.auto_catch_exceptions', true)) {
        DevLogger::logException($exception, [
            'url'    => request()->fullUrl(),
            'method' => request()->method(),
            'input'  => request()->except(['password', 'password_confirmation', '_token']),
        ]);
    }

    parent::report($exception);
}

Wrap the logException call in its own try/catch in high-availability code paths. Logging failures must never escalate a handled exception into a fatal one.

Treat retention as a deployment concern, not a default. The scheduler runs cleanup daily when a retention value is configured. In a high-traffic app where every unhandled exception writes a longtext stack trace, 30 days is a lot of rows. Set DEVLOGGER_RETENTION_DAYS=7 (or 14) and add a monitored schedule entry:

$schedule->command('devlogger:cleanup')->dailyAt('03:00')->onOneServer();

Run php artisan devlogger:cleanup --dry-run in staging first and compare the count against cleanupCandidates().

Separate the connection in production. Point DEVLOGGER_DB_CONNECTION at a second database if you can. Log writes then stop competing with application writes for the same connection pool, and you can truncate the log table without touching business data. This also sidesteps the transaction caveat below.

Configure fallback channels. DEVLOGGER_FALLBACK_CHANNELS is your safety net when the DB write fails. Point it at a plain stack/single channel, not another exotic handler — a fallback that itself throws turns a logging failure into a request failure.

Test against an in-memory database. The package's own release notes verify behaviour on SQLite in-memory. RefreshDatabase plus asserting on DeveloperLog rows is the most reliable test approach; there's no documented DevLogger::fake(), so assert on the model rather than spying on the facade.

Gotchas and Tips

Pin the version — specifically, nothing below v1.1.11 if you rely on cleanup. Before 1.1.11, devlogger:cleanup --days=N was parsed, printed, and then ignored: the command called cleanup() with no arguments and the service re-read retention_days. Worse, deletion went through the model, and DeveloperLog uses SoftDeletes, so "deleted" logs were only stamped deleted_at — disk usage never went down. v1.1.11 deletes via the query builder in 1000-row chunks (--chunk to tune). If you're upgrading, the first run will delete far more than usual; that's the backlog clearing, not a bug. Follow it with OPTIMIZE TABLE developer_logs on MySQL — DELETE does not return disk to the OS.

Laravel 12 does not auto-catch just because the env var is true. Setting DEVLOGGER_AUTO_CATCH=true changes nothing on its own; you must register the reporting callback inside ->withExceptions() in bootstrap/app.php. On Laravel 12 the callback also has to return true, and request() can be null in console/queue contexts — use request()?->fullUrl() exactly as shown. Forgetting the null-safe operator produces "Call to a member function on null" during queue exception handling, which is a genuinely confusing failure to debug.

Facade naming is inconsistent in the docs. Examples mix a bare DevLogger:: alias with DevLoggerPackage\Facades\DevLogger. If DevLogger::info() throws "Class not found," that's the alias, not a broken install. Standardise on the namespaced import in your codebase.

Never log inside a transaction you might roll back. The log row is written on the same connection, so it dies with the transaction — exactly the failures you most want a record of. Log after commit (or on a separate connection).

Schema::connection() with a null connection broke in Laravel 12. Fixed in v1.1.2/1.1.3, but if you're on an older version and DEVLOGGER_DB_CONNECTION=null, migrations fail. Either upgrade or set an explicit connection name.

Publishing has been unstable across 1.1.5–1.1.9. Multiple patch releases were devoted to incorrect path resolution for Packagist installs. After any composer update, re-check that config and migration files are present and that php artisan config:cache doesn't fail. In deployment scripts, publish before caching config.

excluded_paths is a blunt instrument. It filters by originating path, with vendor/ and storage/framework/ pre-seeded. It reduces noise from framework internals but won't stop your own application from generating thousands of duplicate rows from one repeating exception. Rate-limiting isn't built in — if you have a flapping cron job, retention and tags are your only mitigation. Consider tagging noisy subsystems and querying around them.

Beware log-triggered recursion. A failure writing to the DB invokes the fallback channel. If that channel is configured to a database handler, you've built a loop. Keep fallbacks file-based.

Maturity caveats worth stating plainly. 16 stars, zero dependents, and a changelog dominated by publish-path fixes and a cleanup command that didn't delete anything for several releases. Treat it as a small, single-maintainer package: pin exact versions, read the release notes before upgrading, and validate cleanup and exception-catching behaviour yourself in staging rather than trusting the defaults. The core feature — queryable exception rows with request and user context — works and is genuinely useful; the operational edges around it are where the surprises live.

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.
roundly-consulting/kubernetes-api-for-laravel
roundly-consulting/plausible-for-laravel
gvp/gvp-laravel-template
metasyncsite/laravel-metasync-client
roundly-consulting/onboarding-for-laravel
andydefer/laravel-notification
adachsoft/cli-process-tester
jhaskell13/laraflow
sebastienheyd/boilerplate-media-manager
ccast/tagixo-primix
pretendtrue/laravel-builder
sawmainek/laravel-scout-tntsearch-driver
tomshaw/laravel-calendar-table
dibakar/laravel-ownership
steadrun/laravel-monitor
labyrinthe/connect-laravel
elmogy/layan
drewdan/sentinel-client
dskripchenko/laravel-llm
fatryst/nuonuo