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

Dbal Logger Laravel Package

firehed/dbal-logger

A drop-in Doctrine DBAL logger that sends SQL queries and execution times to any PSR-3 logger, making database activity and slow queries easy to trace in PHP applications without extra instrumentation.

View on GitHub
Deep Wiki
Context7

Getting Started

Why you're here: DBAL 3 deprecated SQLLogger and DBAL 4 removed it, so query logging silently disappeared on upgrade. DBAL's own Doctrine\DBAL\Logging\Middleware does exist, but it only logs the SQL string — no bound parameters, no types, no duration, and no failure signal. This package reimplements the old SQLLogger shape on top of the middleware API, which is the whole point: you get startQuery($sql, $params, $types) / stopQuery() back, plus a debug() hook for parity with Symfony's old DbalLogger.

As of 3.0.0 there is a single logger interface — DbalLogger. The previous QueryLogger / DbalLogger split is gone, all interface methods carry native type declarations, and stopQuery() now receives the exception when a statement fails. That last one closes the "did it throw?" blind spot that made 2.x awkward to instrument. Requirement is now PHP 8.2+ (8.1 dropped). If you're on 2.x, read UPGRADING.md before bumping — the interface rename and the added stopQuery() parameter are source-breaking for any custom logger you wrote.

Install

composer require firehed/dbal-logger:^3.0

The surface is tiny (middleware + DbalLogger interface + a PSR-3 adapter), so read the README end-to-end — it's short — and skim src/ for exact class names and signatures in your pinned version. The README was rewritten for 3.0, so it now actually matches the code.

Register the middleware. It must be attached to the Configuration before the connection is built; setting it afterwards is a no-op.

use Doctrine\DBAL\Configuration;
use Doctrine\DBAL\DriverManager;

$config = new Configuration();
$config->setMiddlewares([
    new Firehed\DbalLogger\Middleware($logger),
]);

$conn = DriverManager::getConnection($params, $config);

First use case: log every query to a Monolog doctrine channel at debug, with params, and emit failures at warning. If you want something smaller, the second-most-common use case is a test-only logger that collects queries so you can assert "this endpoint issued exactly 3 queries" — and, now, "this endpoint issued 1 query and 1 failure."

Implementation Patterns

1. PSR-3 adapter for day-to-day logging. Wrap your Monolog logger in the package's PSR-3 adapter and log sql, params, types, and duration at debug. Keep it on a dedicated channel so you can enable it per-environment (monolog.channels: ['doctrine']) without drowning in app noise.

2. Custom logger for structured output. Implement DbalLogger yourself when you need JSON lines with duration_ms, rows_affected, connection, outcome (ok/error), and a normalized SQL fingerprint (strip literals, collapse whitespace) so you can GROUP BY in your log aggregator. This is where you get real value over DBAL's built-in middleware. In 3.0 the signatures are natively typed, so your implementation must match exactly — copy the interface verbatim rather than typing from memory.

3. Slow-query gate + failure gate. Track hrtime() in startQuery, compute in stopQuery, and only emit when over a threshold — or when an exception came back. This turns a noisy debug tool into something you can leave on in staging permanently, and it means failures are never sampled away. One implementation handles both branches; don't fork into two loggers.

4. Test assertion logger. A trivial implementation that pushes {sql, params} into an array, combined with an assertQueryCount()/assertQueried('from users') helper. In 3.0 you can also record the exception in stopQuery() and assert assertQueryFailed('from users'), which is much better than asserting on a swallowed exception. Cap the buffer (see gotchas) and clear it in tearDown.

5. Composing with other middleware. Middleware nest. If you're also running a profiler or retry middleware, decide deliberately whether the logger sits inside or outside: inside a retry middleware you'll log each attempt (and now each failed attempt, with its exception); outside you'll log one logical query. Log the same query twice and your dashboards lie — verify the nesting empirically once, then comment it in services.yaml.

6. Symfony wiring.

services:
    Firehed\DbalLogger\Middleware:
        arguments: ['@App\Doctrine\QueryLogger']
        tags: ['doctrine.middleware']

Tagging is supported by DoctrineBundle; no compiler pass needed. Clear the container cache after adding it, or you'll spend twenty minutes debugging a middleware that "isn't firing."

7. Selective logging. Filter inside startQuery (e.g. skip SELECT 1, skip health checks, skip a specific connection by injecting its name). Cheaper than filtering in the log pipeline and keeps volume down at the source. Remember to apply the same filter logic to the failure path, or skipped statements will still surface when they break the request.

8. Decorate, don't fork. The merged interface makes decoration cleaner than it was in 2.x: a TimingLogger wrapping the PSR-3 adapter, plus a FilteringLogger in front of that, gets you 90% of custom needs without touching vendored code. Each decorator implements DbalLogger, forwards to the inner one, and adds exactly one concern.

Gotchas and Tips

  • The exception path changed in 3.0 — handle it. stopQuery() now receives the exception on failure, so the stale-open-query problem you used to work around is solvable at the source: record start time keyed to a stack and drain it in stopQuery() regardless of outcome. But this is opt-in: if your implementation ignores the new parameter, you get exactly the 2.x behaviour. Test it explicitly — run a query against a missing table and confirm you get a closed entry with the exception attached, not an orphaned open one.
  • Migration friction on the interface merge. QueryLogger no longer exists. Any custom implementation, decorator, or type hint referencing it will fatal on upgrade — including ones buried in test helpers. Grep for QueryLogger before you bump, and check UPGRADING.md for the exact new signature rather than inferring it.
  • Native types are strict, not lenient. Implementations written against 2.x with loose or missing parameter/return types will now violate the interface. Same for anything passing arrays where a more specific type is expected. Update in one pass; PHP will tell you precisely where.
  • PHP 8.2 floor. Dropping 8.1 means you cannot upgrade this package on an app still pinned to 8.1, even if you never touch the logger directly. Coordinate the PHP bump in the same deploy window as the package bump.
  • Resource parameters. DBAL allows stream resources as parameters for LOBs. json_encode() on a resource yields garbage, and reading a stream that the driver already consumed can throw. Detect is_resource() and log '<stream>' instead.
  • Sensitive data. Params include password hashes, tokens, and PII. The old Symfony DbalLogger didn't redact either — don't inherit that bug. Redact by key name and by type (Types::BINARY/BLOB) before the record ever leaves the process. This applies doubly to the new failure path: exceptions often carry the offending values in their messages, so scrub $e->getMessage() too if you log it.
  • Log volume and buffering. In queue workers, Octane, or Messenger consumers, an in-memory collector grows unbounded. Cap it (ring buffer of ~1000) or flush per request/job.
  • Don't let the logger query the database. A logger that writes to a DB-backed Monolog handler will recurse — and in 3.0 the failure path makes that recursion more likely, not less, since error handlers tend to be chatty. Keep the channel file/stderr-only.
  • debug_backtrace() is expensive. If you log a call site per query, gate it behind an env flag — it's often 10–100× the cost of the query itself for simple statements.
  • Version pinning. Single-digit stars and no dependents means you are the QA department. Pin to ^3.0 but lock the exact minor, and accept that if the interface changes again you're patching one file. Given the code size, vendoring it into app/Doctrine/ remains defensible for a critical path — the 3.0 breaking change is the argument for it.
  • Nothing logs? Check in this order: middleware registered on the exact Configuration object used by the connection; container cache cleared; Monolog channel/level actually permitting debug (and warning/error for the failure path — they're separate levels, so a channel scoped to debug only will drop failures); DBAL 3 vs 4 in composer.lock (the middleware interface moved); that you didn't call setMiddlewares() after DriverManager::getConnection(); and — new in 3.0 — that your logger implements DbalLogger and not the removed QueryLogger.
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.
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console