Shopware 6 Hidden Gems #5: shopware.logger.* — turning down the noise
Fabian Blechschmidt
Open the error log of any grown Shopware shop and you’ll find the same suspects, hundreds of times a day: CHECKOUT__CUSTOMER_AUTH_BAD_CREDENTIALS because someone mistyped their password. CHECKOUT__CART_HASH_MISMATCH because a tab was open too long. 404s from bots probing for WordPress. None of these are your bugs — but they bury the one log line that is.
The result is the classic monitoring death spiral: too many alerts → alerts get ignored → the real incident gets ignored too. While digging through Configuration.php for this series I found that Shopware ships four config options to fix exactly this. They live under shopware.logger.*, the core uses them aggressively for itself, and the docs don’t mention any of them.
error_code_log_levels — remap severity per error code
The big one. Every ShopwareHttpException has an error code, and this option maps codes to Monolog levels:
# config/packages/shopware.yaml
shopware:
logger:
error_code_log_levels:
CHECKOUT__CUSTOMER_AUTH_BAD_CREDENTIALS: notice
CHECKOUT__CART_HASH_MISMATCH: notice
FRAMEWORK__ROUTE_NOT_FOUND: info
Under the hood, ErrorCodeLogLevelHandler decorates monolog.handler.main (services.xml:647). When a log record carries an exception whose error code is in the map, the record is rewritten with the new level before the real handler sees it (Framework/Log/Monolog/ErrorCodeLogLevelHandler.php:47-60). Nice touch: it even unwraps Symfony Messenger’s HandlerFailedException to look at the real exception inside — so the remap also works for errors thrown in queue workers.
And here’s the thing: Shopware already remaps 100 error codes by default (Framework/Resources/config/packages/shopware.yaml:405 ff.) — bad credentials, „customer already confirmed“, tax ID not found, and so on are all notice out of the box. Your additions merge into that list. If your monitoring still drowns, the mechanism is there; you just have to feed it your shop’s specific noise.
The workflow I use: grep a week of logs for ShopwareHttpException error codes, sort by frequency, and ask for each of the top ten „would I ever act on this?“ If not — notice.
cat *.log | grep -oP '(?<=\[object\] \()[A-Za-z\\]+(?=\(code:)' | sort | uniq -c | sort -rn
1498384 Symfony\\Component\\Console\\Exception\\RuntimeException
34333 Symfony\\Component\\Cache\\Exception\\InvalidArgumentException
15133 RuntimeException
8663 TypeError
6415 PDOException
6409 Doctrine\\DBAL\\Driver\\PDO\\Exception
3443 Symfony\\Component\\Messenger\\Exception\\TransportException
3322 Doctrine\\DBAL\\Exception\\ConnectionException
2690 Doctrine\\DBAL\\Exception\\TableNotFoundException
1601 Symfony\\Component\\Routing\\Exception\\RouteNotFoundException
854 Shopware\\Core\\Checkout\\Cart\\CartException
412 ErrorException
369 Doctrine\\DBAL\\Exception\\DriverException
99 Error
exclude_exception — drop them entirely
One level harsher: exceptions on this list don’t get logged at all.
shopware:
logger:
exclude_exception:
- League\OAuth2\Server\Exception\OAuthServerException
- Symfony\Component\HttpKernel\Exception\NotFoundHttpException
Those two (plus LanguageNotFoundException) are actually the default — which explains something that once cost me an afternoon: failed OAuth requests against the Admin API don’t appear in the log. Not because logging is broken, but because ExcludeExceptionHandler swallows the record by design (Framework/Log/Monolog/ExcludeExceptionHandler.php:30-37). If you’re debugging API authentication, temporarily take OAuthServerException off this list — that’s the gem within the gem.
Except „temporarily take it off the list“ is harder than it sounds. My first instinct was the obvious one — override it from project config:
# config/packages/shopware.yaml — this does NOT remove OAuthServerException
shopware:
logger:
exclude_exception:
- Symfony\Component\HttpKernel\Exception\NotFoundHttpException
- Shopware\Core\Framework\Routing\Exception\LanguageNotFoundException
Cleared cache, hit the endpoint again — still silent. bin/console debug:container --parameter=shopware.logger.exclude_exception showed why: OAuthServerException was still in the compiled list. The config node for exclude_exception is a plain arrayNode('exclude_exception')->prototype('scalar') (Framework/DependencyInjection/Configuration.php:373-375) without performNoDeepMerging(). Symfony’s config component treats that as mergeable: values from every loaded config file get appended into one list, they don’t replace each other. So your project override doesn’t swap the default list for yours — it just adds your entries alongside the vendor defaults, which are still very much in there.
Practically, that means exclude_exception and exclude_events are append-only from application config. You can silence more exceptions than the default; you cannot un-silence OAuthServerException, NotFoundHttpException, or LanguageNotFoundException without touching the vendor YAML directly (fine for a throwaway local debugging session, not something to ship) or writing a compiler pass that rewrites the shopware.logger.exclude_exception container parameter after Shopware’s extension has run. If you just want to confirm OAuth is failing at all during a debugging session, it’s often less friction to grep the access log for the 400s than to fight this parameter.
Note it also matches the exception class exactly (in_array on the FQCN, no inheritance check), so subclasses are not excluded automatically.
exclude_events — silence business events
Shopware’s business event logging writes flow-triggering events (order placed, mail sent, password recovery requested …) into the log. Per default two are excluded:
shopware:
logger:
exclude_events:
- user.recovery.request
- customer.recovery.request
Same decorator pattern (ExcludeFlowEventHandler). If some plugin fires a high-frequency custom event that floods your business_events channel, this is the off switch — one YAML line, no subscriber gymnastics.
enforce_throw_exception — the inverse option
The other three make logs quieter. This one makes failures louder. Several core services (the checkout gateway handlers, for example) route errors through ExceptionLogger::logOrThrowException() (Framework/Log/ExceptionLogger.php:21-28):
public function logOrThrowException(\Throwable $e, string $level = LogLevel::ERROR): void
{
$this->logger->log($level, $e->getMessage());
if ($this->enforceThrow || $this->environment !== 'prod') {
throw $e;
}
}
Read that carefully: in prod, these exceptions are logged and swallowed. In every other environment they throw. So there is a class of errors your staging environment surfaces loudly and your production hides in a log line. LOGGER_ENFORCE_THROW_EXCEPTION=1 flips production to fail-fast behavior — useful when you’re hunting one of these silent failures on a live system and want it to actually explode where you can see it.
Bonus: file_rotation_count
Shopware’s rotating file logger keeps 14 files by default. shopware.logger.file_rotation_count: 30 if compliance wants a month, : 3 if your disk is tight. Trivial, but again: you’d only know from the source.
TL;DR
Four undocumented knobs under shopware.logger.*: error_code_log_levels remaps noisy ShopwareHttpException codes to lower levels (100 codes are remapped by default, even inside queue workers), exclude_exception drops exceptions from logs entirely (and is why OAuth failures never show up — but it’s append-only from project config, so „remove it when debugging API auth“ means a temporary vendor patch or a compiler pass, not a YAML override), exclude_events silences business events, and enforce_throw_exception makes production throw instead of log-and-swallow. Your on-call rotation will notice the difference.
Next up: two state machine gems — generating workflow diagrams straight from your live shop, and the context state that lets you force „impossible“ order state transitions.
Got a favorite noisy error code? Drop me an email!
Found in Shopware 6.7.0.0: config nodes in Framework/DependencyInjection/Configuration.php:370-383 (note: no performNoDeepMerging() on exclude_exception/exclude_events, hence the append-only merge behavior), defaults in Framework/Resources/config/packages/shopware.yaml:395-505, handlers in Framework/Log/Monolog/ErrorCodeLogLevelHandler.php, ExcludeExceptionHandler.php, ExcludeFlowEventHandler.php (wired as monolog decorators in services.xml:640-655), fail-fast logic in Framework/Log/ExceptionLogger.php:21-28. OAuth exclusion behavior verified live against a running 6.7.0.0 instance, not just read from source.
Other articles from this category