oh/form-error-log-bundle
A Symfony bundle that records form validation errors for easier debugging and monitoring. It captures error details from failed submissions so you can trace issues, inspect patterns, and keep user-facing responses clean.
This is a Symfony bundle, not a Laravel package. If you're coming from Laravel, you cannot composer require it into a Laravel app and expect it to work — you'd either need a Symfony application or replicate the pattern (a listener on validation failure that writes to a dedicated log channel).
For a Symfony app:
composer require oh/form-error-log-bundleconfig/bundles.php (Flex may do this automatically):
Oh\FormErrorLogBundle\OhFormErrorLogBundle::class => ['all' => true],
DependencyInjection/Configuration.php and Resources/config/services.xml in the vendor directory. Those two files tell you the exact config keys, the logger service ID, and which form events are hooked. Don't guess from the README alone.dev/staging, submit an intentionally invalid form, and confirm entries appear in your configured Monolog channel. Expected config shape (verify against Configuration.php):
# config/packages/oh_form_error_log.yaml
oh_form_error_log:
channel: form_errors
level: warning
config/packages/monolog.yaml so form errors go to a separate file rather than muddying app logs.The first practical win: instead of grepping var/log/dev.log for "This value is not valid", you get a structured record with the form class, field path, and message — per submission.
Dedicated Monolog channel per environment. Route form-error logs to var/log/form_errors.log in staging and a sampled handler in production. Validation failures are user-error noise, not application errors — keep them out of your alerting channel.
Subscriber-level filtering. The bundle's subscriber likely fires on FormEvents::POST_SUBMIT and inspects $form->isSubmitted() && !$form->isValid(). If you need more control (ignore specific form types, only log first N errors, skip login/registration forms), decorate or replace the subscriber rather than patching vendor code:
final class FilteredFormErrorLogger
{
public function __construct(private LoggerInterface $logger) {}
public function onPostSubmit(FormEvent $event): void
{
$form = $event->getForm();
if ($form->isRoot() || !$form->isSubmitted() || $form->isValid()) {
return;
}
foreach ($form->getErrors(true) as $error) {
$this->logger->warning('form_error', [
'form' => $form->getConfig()->getType()->getInnerType()::class,
'field' => $error->getOrigin()?->getName(),
'message'=> $error->getMessage(),
]);
}
}
}
Structured context, not string concatenation. For day-to-day usefulness you want queryable fields: form class, error origin, error message, request ID/correlation ID, user ID (if available), and environment. Monolog's JSON formatter + a log aggregator makes this searchable.
Pair it with a request-scoped correlation ID. A Monolog processor that injects a request ID makes form-error logs joinable to the same request's other logs — far more useful than isolated entries.
Testing hook. In a functional test, replace the logger with a spy and assert a record was emitted for an invalid submission. In a Laravel port, do the equivalent with Log::spy() inside a FormRequest failure test.
Laravel equivalent if you're not on Symfony. There's no drop-in; the same idea is ~15 lines in App\Exceptions\Handler or a ValidationException listener:
Event::listen(ValidationException::class, function ($e) {
Log::channel('form_errors')->warning('form_error', [
'route' => request()->route()?->getName(),
'errors'=> $e->errors(),
]);
});
Use this as the reference model for what the Symfony bundle is doing.
...Bundle) and the Resources/config layout are Symfony conventions. Installing it in Laravel will fail service registration. Treat it as a reference implementation, not a dependency.$form->getErrors(true) on a parent and again on each child produces the same message multiple times. Decide whether you log per root form or per field origin, and stick to one. If the bundle logs both, add a dedupe guard in a decorator.$form->getData() blindly. Password fields, API keys, and PII routinely live in form models. Log field names and error messages; scrub or omit values.error-level events. Log at warning or info. Logging every failed login at error will page someone at 3 a.m. and destroy your signal-to-noise ratio.test env, form errors during fixtures/tests will spam logs; in production, a bot hammering a public form can fill disks. Use fingers_crossed or a sampling handler.composer.json, read the full source before relying on it (it's small), and be prepared to fork or replace with your own ~30-line subscriber. Don't build critical alerting workflows on top of it.bin/console debug:container --tag=kernel.event_subscriber | grep -i form and bin/console debug:event-dispatcher Oh\\FormErrorLogBundle\\... will confirm whether the listener is actually registered. If nothing logs, the event subscription or the Monolog channel config is almost always the cause.vendor/ — a composer update will erase it.How can I help you explore Laravel packages today?