exs/error-bundle
Symfony 2.x bundle that logs all 4xx and 5xx HTTP errors and exceptions to the database, capturing useful debugging details for each one. Pair it with a cron-based monitor to spot error spikes and alert the dev team, enabling rapid fixes before small issues become outages. Quick to install via Compo...
Confirm you actually have a Symfony 2.x app. This bundle is frozen at 2015: it targets Symfony 2, app/console, YAML service definitions, and a pre-DoctrineBundle-2 API. On Symfony 4+ Composer will refuse to resolve it; on Symfony 3 you'll have friction; don't force it with --ignore-platform-reqs on a live project.
Install:
composer require 'exs/error-bundle:~1.0@dev'
Wire it up (three files, per the README):
app/AppKernel.php → new EXS\ErrorBundle\EXSErrorBundle() (add it last in $bundles; enable in prod only, or at least disable in test).app/config/routing.yml → import the bundle's routing so any built-in listing/view route resolves.app/console doctrine:schema:update --force (or generate a migration — see Gotchas).Where to look first, in this order:
Resources/config/services.yml — the listener service and its kernel.event_listener tag with event: kernel.exception and its priority. This is the whole package.Entity/ — the error record (fields you'll query and index).DependencyInjection/Configuration.php + the Extension class — the knobs you can tune (ignored status codes, maybe notification email).try/catch around persistence before you trust it in production.First use case: throw a 404 (app/console router:debug → hit a bad URL), then SELECT * FROM <error_table> ORDER BY id DESC LIMIT 5;. If a row appears, the loop works. Then hit the same URL 50 times and watch it fill up — that tells you immediately why you need the filtering/aggregation patterns below.
Alerting cron (the intended use case). The bundle only stores; the "alert" half is yours. A 5-minute cron that counts recent rows and pings Slack/email:
// bin/error-watch.php, bootstrapped with the app kernel
$since = new \DateTime('-15 minutes');
$counts = $em->getRepository(Error::class)->createQueryBuilder('e')
->select('e.statusCode, COUNT(e.id) AS c')
->where('e.createdAt >= :since')->setParameter('since', $since)
->groupBy('e.statusCode')->getQuery()->getArrayResult();
// alert if 5xx count > threshold, or total > threshold
Aggregate, don't append. If the table has a fingerprint-style column (class + message + route), do INSERT ... ON DUPLICATE KEY UPDATE hits = hits + 1, last_seen = NOW(). If it only stores raw rows, add a fingerprint column yourself and normalise on read. Raw-row logging is what makes this bundle unbounded and noisy.
Only log what matters. 404s from bots (/wp-login.php, /.env, favicon) will drown the table. Configure ignored status codes / path patterns if the bundle exposes them; otherwise subclass/decorate the listener and early-return on 404 for /wp-*, /\.git, and static assets. Keep 5xx and uncaught 500s at full fidelity.
Pair it with Monolog, don't replace it with it. The DB row is for trends and counts; the stack trace, request payload, and user id belong in a log file or an error service. Keep fingers_crossed/prod handlers as-is and treat the bundle as a metrics sink.
Respect environment. Gate registration on %kernel.debug% === false so dev/test pages don't write throwaway rows, and so your functional tests don't need the table.
Port it forward when you upgrade. The entire mechanism is ~40 lines on Symfony 5+:
class ErrorLogSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents(): array
{ return [KernelEvents::EXCEPTION => ['onException', 0]]; }
// persist HttpException status, else 500
}
Take the Entity and the query patterns from this bundle, drop the Symfony 2 plumbing. That is realistically the highest-value use of this repository in a modern codebase.
kernel.exception and either loop or mask the original error. Wrap persistence in try/catch, check $em->isOpen() (consider resetManager()), and fall back to error_log().-128). Your listener must run before that to see the raw exception. Verify with app/console debug:event-dispatcher kernel.exception after clearing cache — if you see nothing, the tag didn't compile.created_at index (grouping queries will table-scan without it) and a nightly DELETE WHERE created_at < NOW() - INTERVAL 90 DAY. Also cap row size — request URIs are unbounded (attackers send 8 KB paths).doctrine:schema:update --force on production, especially since the bundle predates migrations being standard.~1.0@dev constraint may clone dev-master; pin the commit if you must ship it. composer show exs/error-bundle and check composer why-not when resolution fails — usually it's symfony/symfony: ^2.x vs your ^6.src/ is a defensible and safer choice than an unmaintained transitive dependency.How can I help you explore Laravel packages today?