Exception handling best practices
If an exception is not written to the exception.log
file with the exception model as context, it is not recognized and analyzed correctly in New Relic or other PSR-3 monolog-compatible log storage. Logging only a part of the exception (or logging it to the wrong file) leads to bugs in production when exceptions are overlooked.
Correct exception handling
The following checklist provides examples to demonstrate correct exception handling.
Write to the exception log
Write to the exception log using the following pattern, regardless of further actions, unless there is a compelling reason not to.
try {
$this->productRepository->getById($sku);
} catch (Exception $e) {
$this->logger->critical($e);
}
This approach automatically saves the $e->getMessage
to the log message and the $e
object to the context, following the PSR-3 context standard. This is done in \Magento\Framework\Logger\Monolog::addRecord
.
Mute signals
Mute signals by not logging exceptions that are part of the intended operations flow. No follow-up action is necessary when the exception is encountered, so it does not need to be logged and analyzed when it occurs. Add a comment indicating the reason for muting signals and that it is intentional. Combine with phpcs:ignore
.
try {
$this->productRepository->deleteById($sku);
} catch (NoSuchEntityException $e) { // phpcs:ignore Magento2.CodeAnalysis.EmptyBlock.DetectedCatch
// Product already removed
}
Downgrade exceptions
Downgrade exceptions by following the PSR-3 context standard.
try {
$this->productRepository->getById($sku);
} catch (Exception $e) {
$this->logger->debug($e->getMessage(), ['exception' => $e]);
}
Logging always comes first
As a best practice logging always comes first in the code to prevent cases where another exception or fatal error is thrown before writing to the log.
try {
$this->productRepository->getById($sku);
} catch (Exception $e) {
$this->logger->critical($e);
$this->alternativeProcedure();
}
Log messages and the entire exception trace
Log messages and the entire exception trace by following the PSR-3 context standard.
try {
$this->productRepository->getById($sku);
} catch (Exception $e) {
$this->logger->critical($e->getMessage(), ['exception' => $e, 'trace' => $e->getTrace()]);
}
Incorrect exception handling
The following examples demonstrate incorrect exception handling.
Logic before logging
Logic before logging can lead to another exception or fatal error, which prevents the exception from being logged and should be replaced by correct example.
try {
$this->productRepository->deleteById($sku);
} catch (NoSuchEntityException $e) {
$this->alternativeProcedure();
$this->logger->critical($e);
}
Empty catch
Empty catch
blocks can be a sign of unintended muting and should be replaced by the correct example.
try {
$this->productRepository->deleteById($sku);
} catch (NoSuchEntityException $e) {
}
Double localization
If the caught localized exception is not translated yet, resolve the problem at the place where the exception is thrown the first time.
try {
$this->productRepository->getById($sku);
} catch (LocalizedException $e) {
throw new LocalizedException(__($e->getMessage()));
}
Log messages and trace to different log files
The following code incorrectly logs the stack trace for an exception as a string to a log file.
try {
$this->productRepository->getById($sku);
} catch (\Exception $e) {
$this->logger->error($e->getMessage());
$this->logger->debug($e->getTraceAsString());
}
This approach introduces line breaks in the message, which is not compliant with PSR-3. The exception, including stack trace, must be part of the message context to ensure that it is saved correctly with the message in New Relic or other PSR-3 monolog-compatible log storage.
Fix this problem by replacing the code following the correct examples shown in Write to the exception log or Downgrade exceptions.
Downgrade exceptions without context
The exception is downgraded to an error, which does not allow an object to be passed, but only a string, hence the getMessage()
. This causes the trace to be lost and should be replaced by the correct examples shown in Write to the exception log or Downgrade exceptions.
try {
$this->productRepository->getById($sku);
} catch (\Exception $e) {
$this->logger->error($e->getMessage());
}
Log only the message to the exception log
Instead of passing the object $e
, only $e->getMessage()
is passed. This causes the trace to be lost and should be replaced by the correct examples shown Write to the exception log or Downgrade exceptions.
try {
$this->productRepository->getById($sku);
} catch (\Exception $e) {
$this->logger->critical($e->getMessage());
}
Missing // phpcs:ignore Magento2.CodeAnalysis.EmptyBlock.DetectedCatch
Omitting the phpcs:ignore
line triggers a warning in PHPCS and should not pass your CI. This should be replaced by the correct example shown in Mute signals.
try {
$this->productRepository->deleteById($sku);
} catch (NoSuchEntityException $e) {
// Product already removed
}