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

Form Error Log Bundle Laravel Package

lendable/form-error-log-bundle

A Symfony bundle that captures and logs form validation errors, so you can trace failed form submissions, debug validation issues, and monitor error patterns across your application.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a Symfony bundle, not a Laravel package — there is no service provider, no config publish step, and nothing under config/. Read it as a reference implementation of an idea, and plan to either (a) run it inside the Symfony app it was written for, or (b) port its ~40 lines into a Laravel listener. The whole package is essentially one event subscriber that hooks FormEvents::POST_SUBMIT on the root form, walks $form->getErrors(true), and writes each error to a PSR-3 logger.

The current release is v2.0.0, whose release notes mention only one thing: "PSR-4 replaces PSR-0 as the autoloading strategy." That is a packaging change, not a behaviour change — same classes, same namespace (Lendable\OhFormErrorLogBundle\…), same subscriber, same config key. Nothing in the day-to-day usage below changes between 1.x and 2.0. The major-version bump for an autoloading-only change is itself the signal: treat this as a low-traffic, low-maintenance package.

Minimal steps in a Symfony app:

composer require lendable/form-error-log-bundle:^2.0
// app/AppKernel.php (Symfony 2/3 era — this bundle predates bundles.php)
$bundles[] = new Lendable\OhFormErrorLogBundle\LendableOhFormErrorLogBundle();

Optional config, typically just the log level:

# app/config/config.yml
lendable_form_error_log:
    level: notice

Where to look first: the bundle's EventSubscriber / form-event listener class. It is short and is the entire feature. Note exactly what it puts in the log message versus the log context — that determines whether your downstream queries are usable. If you are pinning ^1.0 anywhere (a lockfile, a CI cache, a forked copy), read the v2 composer.json first: the autoload block and the on-disk src/ layout differ, but the class names do not.

First use case: usability triage. Route its output to a separate channel from application errors, let it run for a week, then answer "which three fields do users fail on most, and what does the message say?" That is the whole value proposition: validation errors are user friction, not bugs, and they are invisible if you only log exceptions.

If you are evaluating this for Laravel, the equivalent one-liner is a ValidationException handler or a failedValidation() override in a base FormRequest, logging $exception->errors() with the route name and field names as context. Same shape, same caveats.

Implementation Patterns

Dedicated channel, not the default logger. The single most important pattern: configure a Monolog channel (e.g. form_errors) with its own handler and its own retention. Mixing these into laravel.log / prod.log makes both the noise and the signal unusable.

monolog:
    channels: ['form_errors']
    handlers:
        form_errors:
            type: stream
            path: "%kernel.logs_dir%/form_errors.log"
            level: notice
            channels: ['form_errors']

Enrich with context you cannot get from the message. The bundle logs a message string; the useful fields are form name, field name, error message, route, and (carefully) a user or session hash. In a port, log an array:

// Laravel: app/Http/Requests/BaseFormRequest.php
protected function failedValidation(Validator $validator): void
{
    Log::channel('form_errors')->notice('form.validation_failed', [
        'form'   => class_basename(static::class),
        'route'  => request()->route()?->getName(),
        'fields' => array_keys($validator->errors()->toArray()),
        'count'  => $validator->errors()->count(),
    ]);

    parent::failedValidation($validator);
}

Log field names and error keys, not raw messages, if user input can end up inside them.

Turn logs into a counter, not a log. The real payoff is a dashboard. Either add a Monolog handler that increments a metric per error key, or parse the JSON log lines. "Errors per field per week" is a product signal; a text file nobody reads is not.

Scope by form. Not every form is interesting. Search boxes, filters, and multi-step wizards generate noise. In Symfony, decorate the subscriber and early-return for a denylist of form names; in Laravel, guard on static::class or the route name.

Test the instrumentation itself. A functional test that posts invalid data and asserts a log record was written protects you from silently losing the signal after a refactor:

Log::spy();
$this->post('/register', ['email' => 'not-an-email']);
Log::shouldHaveReceived('notice')->withArgs(fn ($m, $c) => $c['fields'] === ['email']);

For Symfony, extension points are the logger and the subscriber. Point the bundle's logger argument at your channel, or decorate the subscriber class to re-shape the message. There is no event dispatched you can listen to — the subscriber is the hook.

Gotchas and Tips

  • v2.0.0 modernizes autoloading and nothing else (as documented). PSR-0 → PSR-4 means the src/ tree is no longer nested per-namespace-segment. Consequences: any vendored copy, classmap, exclude-from-classmap, Composer autoload patch, or path-based exclusion referencing src/Lendable/OhFormErrorLogBundle/… breaks. Class names and namespaces are unchanged, so ordinary composer require/update consumers are unaffected. If you maintain a fork, rebase onto the v2 layout rather than merging it into a PSR-0 tree.
  • A major bump still doesn't prove framework support. The release notes describe only autoloading; there is no stated bump of the Symfony component constraints. Before adopting, open the v2 composer.json and check the symfony/* versions it allows. If they are still 2.7/^3.0-era, it will not install against Symfony 5+/6+/7+ or a modern Laravel (which ships Symfony 6/7 components) without --ignore-platform-reqs and code edits. Treat it as a spec, not a dependency.
  • Only sees Symfony Form submissions. If your app validates with the Validator service directly, or via API Platform/request DTOs, nothing fires. The bundle is blind to any path that isn't $form->submit().
  • getErrors(true) returns non-field errors with an empty origin. Global errors (CSRF, "form is invalid", cross-field rules) come back with $error->getOrigin()->getName() === ''. Handle that case explicitly or your top-error report will be dominated by a blank field name.
  • Level choice matters more than you think. error will page someone every time a user types a bad email. Use notice/info and alert only on rate changes, not presence.
  • Volume and PII. Error messages are user-shaped strings. If a message interpolates a value ("{{ value }} is not valid"), you are logging user input. Sample (1-in-N), hash identifiers, and set a short retention.
  • Multi-request flows double-count. A multi-step form that submits each step logs each step's failures, so a single abandoned signup can produce several lines. Group by session hash if you want per-user friction counts.
  • No built-in sampling, filtering, or rate limiting. Whatever you do about noise, you do by wrapping the logger or decorating the subscriber. v2.0.0 added none of this.
  • Upgrade checklist for 1.x users: bump the constraint, composer update lendable/form-error-log-bundle, run composer dump-autoload -o, then confirm your bundle registration namespace still resolves (Lendable\OhFormErrorLogBundle\LendableOhFormErrorLogBundle — unchanged) and that no deployed opcache/classmap artifact points at the old nested src/Lendable/... path.
  • Debugging "why is nothing logged": confirm the form goes through handleRequest()/submit(), confirm the error actually lands on a child form (getErrors(true) is recursive; getErrors() is not), and confirm the log level in config is at or below the level your handler accepts.
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.
teamzac/geocoder
ideatica/config-writer
keaton-php/pipe-dream
jacksonwebservices/laravel-user-suite
maksimru/conditional-laravel-debugbar
gismatul/nova-support-chat
gismatul/laravel-support-chat
networkrailbusinesssystems/oracle-api
alphabalex/messaging-made-easy
rawphp/warp
shootkiran/merobug
narakode/fineauth
numencode/wn-syncops-plugin
ibnnajjaar/mib-global-pay
ably/pubsub-server
mstfkhazaal/defender
mfonte/base62x
karnoweb/metapilot
tobya/bcsapiwrapper
swalker2/cpanel