rezzza/model-violation-logger-bundle
A Symfony bundle that captures model validation violations and logs them through a configurable logger. It helps developers audit failed validations, trace data issues in production, and keep a record of model errors without writing custom logging code.
This bundle is a thin Symfony bridge between the Validator component and Monolog: it gives you a service that walks a ConstraintViolationListInterface and writes each violation to a logger channel so that domain-rule violations are observable at runtime instead of only visible in a form's error output.
Minimal setup:
composer require rezzza/model-violation-logger-bundle. The current patch is v1.1.12, a backward-compatible release (bug fix + one named constructor, no breaking changes, no migration steps). Pin with a caret (^1.1) rather than an exact version, but verify the tag's composer.json symfony/* constraints against your framework version before committing — the package sits in maintenance mode and its Symfony constraints have not kept pace with 6.x/7.x.AppKernel::registerBundles(); on Symfony 4+ you must add it to config/bundles.php manually, and you will likely need to enable it in a legacy services.yml rather than relying on autowiring.DependencyInjection/Configuration.php and Resources/config/services.xml in vendor/rezzza/model-violation-logger-bundle. The whole package is a handful of classes; five minutes of reading tells you more than any documentation, because there is essentially none.bin/console debug:container | grep -i violation, then inject that service wherever you validate models.First use case: a command or API controller validates a DTO/entity, and instead of silently dropping the violations on the floor you pass the ConstraintViolationListInterface to the logger service so the offending property paths, messages and (optionally) the invalid value land in var/log or your aggregator with the request context attached.
If the bundle's service definitions do not load cleanly on a recent Symfony, do not fight it — copy the two or three classes into your own App\Validator namespace and register them as ordinary services. The new ViolationList::fromConstraintViolationList() named constructor is worth knowing about even then: it is the intended way to turn a Symfony ConstraintViolationListInterface into the bundle's own ViolationList value object, which is what you need if you want to compare or dedupe violations rather than just log them.
Wrap validation in a domain service. Rather than sprinkling $validator->validate() calls through controllers, create an application service that converts, logs and then throws:
final class ValidatingCommandBus
{
public function __construct(
private ValidatorInterface $validator,
private ViolationLoggerInterface $violationLogger, // the bundle service
) {}
public function handle(object $command): void
{
$violations = $this->validator->validate($command);
if (count($violations) > 0) {
$this->violationLogger->log($violations, ['command' => $command::class]);
throw new ValidationFailedException($violations);
}
// dispatch...
}
}
This keeps the bundle's API surface to a single call site, which matters a lot for a package you may need to replace.
Convert once, work with the bundle's list. When you need to do more than log — dedupe across a retry, compare two validation runs, or attach a stable fingerprint to a log entry — build the bundle's own list via the new ViolationList::fromConstraintViolationList($violations) and operate on Violation objects. This is the cleanest use of v1.1.12 and avoids ad-hoc array munging in your call sites.
Deduplicate with equals(), now inheritance-aware. v1.1.12 fixes Violation::equals() so a violation whose class extends another compares correctly against its parent/child instances. This matters when you validate polymorphic payloads (e.g. a DiscountCommand hierarchy) and want to collapse repeated violations before logging. Example use: skip re-logging a violation you already logged earlier in the same request/retry by comparing against a small in-memory ViolationList.
Log-then-throw, not log-and-continue. Violation logging is most useful as forensic evidence on the failure path. Log at warning or error immediately before throwing; do not log at debug for every validation attempt, or your log volume will explode on form-heavy pages.
Use a dedicated Monolog channel. If the bundle accepts a channel name via config, route it to model_violations and give that channel its own handler (e.g. a separate file or a Sentry/ELK sink). Mixing model violations into the main app channel makes them impossible to alert on.
Attach request/tenant context. Wrap the logger in a decorator that merges request_id, user_id, and the current route into the context array passed down. This is the single highest-value addition — a violation message without knowing which request produced it is nearly useless in production.
Pair with form and API layers. In a form-based app, hook the same decorator into a kernel.exception listener that catches your ValidationFailedException; in an API, let the exception listener serialize the violations back to the client while the logger has already recorded them server-side.
Integration tip for legacy codebases: if you only need the logging behavior, an event subscriber on kernel.exception that inspects exceptions carrying a violation list can replace this bundle entirely. Consider that before adding a dependency whose release cadence is patch-level at best.
composer why-not before assuming a modern Symfony install will resolve.equals() fix is a behavior correction for subclass comparisons, and the named constructor is additive. Your upgrade risk is near zero, but run your validation tests if you rely on equality.equals() semantics changed for subclasses. Because the method is now inheritance-aware, a Violation subclass can compare equal to a parent-class instance. If you key any dedupe map, cache or "already logged" flag on equals(), re-verify that distinct-but-related violations are still counted separately where you need them to be.Resources/config/services.xml being loaded by the extension. If debug:container shows nothing, the extension is not being discovered — check the bundle class name and getContainerExtension().null or the default logger silently sends violations to app. Verify in dev by checking the channel name in the log line, not by assuming the config took effect.ConstraintViolation::getMessage() is the raw message key unless you call setLocale() on the validator or pass a translator. If your logs show This value should not be blank. in English while your users see French, that is the raw key, not a bug in the bundle.getInvalidValue() can dump passwords, tokens, or PII. If the bundle logs the invalid value, override or disable that behavior in production, or filter the list before handing it over.fromConstraintViolationList() copies the list, so do not call it inside tight loops over large collections. Convert once per validation run, then reuse.composer.lock safety net. Ecosystem feedback is thin, so nobody has reported the bugs you may hit. Run the validator tests against your Symfony version before committing.LoggerInterface (or the bundle service itself) is the least invasive way to add context, sampling or redaction.level: debug temporarily and run a single controller action; if nothing appears, the violations are most likely empty because validate() was called on a plain DTO without annotations/attributes loaded — a validator configuration problem, not a logging one.^1.1 and keep the bundle service behind a single call site.How can I help you explore Laravel packages today?