Problem/Motivation
Currently, Error::logException() does not log the chain of "previous" exceptions.
This means if one or more layers in the application catch an exception to then wrap it into a different exception type, the inner layers do not arrive in the log.
From what I read this would be available in the monolog/monolog package.
But it is not provided by default in Drupal core with e.g. dblog.
As a consequence, when developer for core or contrib, I am discouraged to wrap exceptions at domain boundaries.
(I would not want to add a dependency to monolog when I am just contributing)
Steps to reproduce
Proposed resolution
Explore ways to log previous exceptions, which can already work in Drupal core.
Either with a custom solution or by using monolog.
(tbh I have not deeply looked into monolog, first we have to agree if logging previous exceptions is something we want)
Comments
Comment #2
donquixote commentedSandbox module: https://www.drupal.org/sandbox/donquixote/3582548
It modifies the stack trace output so that we see a "tree" stack trace like this, which combines the main stack trace with those from previous exceptions.
It works as a decorator, but I have not fully figured out how to universally apply it to all loggers.
Anyway in core we would do something slightly differently.
(EDIT: I am posting here a simplified version that does not cause line wrapping)
(EDIT: Posting a simpler, but completely made-up example)
Comment #3
sourav_paulI took a look at the issue and the sandbox module, and I think the smallest core-friendly approach is to fix this at the point where Drupal builds the exception log context.
My understanding of the problem is:
Right now, when an exception wraps another exception, Drupal only logs the outer exception trace. That means the original cause can disappear from the log, even though it is still available through getPrevious().
Because of that, wrapping exceptions at service/domain boundaries becomes less useful for developers, since the logs lose important debugging information.
Proposed resolution:
1. Keep Error::logException() as-is.
It is already just a thin wrapper, so there is probably no need to add more logic there.
2. Update Error::decodeException() to include previous exceptions in @backtrace_string.
This seems like the most natural place, because decodeException() already prepares the logging context from a Throwable.
3. Keep the existing placeholder and logging flow unchanged.
In other words, continue using @backtrace_string, so existing callers do not need to change anything.
4. Do not make this dblog-specific.
dblog already displays @backtrace_string correctly, so if we improve the value produced by Error::decodeException(), all core logging backends can benefit.
5. Add test coverage for nested exceptions.
At minimum, this should include:
- a unit test for Error::decodeException()
- a test confirming the logged output contains previous exceptions
N.B: If decodeException() becomes too large, I think a small private helper inside Error would be fine.
So in short: the likely core fix is to improve Error::decodeException(), keep the current API shape, and make sure previous exceptions are included in the generated backtrace string.
Comment #4
donquixote commentedThanks for the feedback @sourav_paul!
Excactly.
My concern here would be that this behavior is not pluggable.
The most default and to-be-expected behavior is to just put ->getTraceAsString() which it does currently.
If we replace it with a custom "creative" backtrace printer, these low-level static methods become quite opinionated.
At this point we would want to provide some kind of pluggability.
The logger service should maybe not depend on config (TBD), but it can still be altered by modifying service definitions.
The current version of that module adds this decorator to every logger via service provider.
So yes, it should not be dblog specific.
But maybe it should be compatible with monolog module.
Comment #5
sourav_paulThanks, that concern makes sense to me.
I agree that putting one custom/creative backtrace printer directly into Error::decodeException() could make this low-level behavior too opinionated and not easily overridable.
So I think a more core-friendly approach would be:
1. Keep Error::logException() / Error::decodeException() responsible for building the standard log context.
2. Add pluggability at the logger/factory layer, so @backtrace_string can be reformatted when an exception is present.
3. Let core provide a simple default implementation that includes previous exceptions in plain text.
4. Allow contrib/site code to swap that formatter/decorator via service definitions.
5. Make sure the integration point works with Monolog as well, since Monolog replaces logger.factory.
[used AI for clarity & format and to make the approach concise.]
Comment #6
smustgrave commentedAs the last comment appears to be AI generated I’m going to post here https://www.drupal.org/docs/develop/issues/issue-procedures-and-etiquett...
Comment #7
sourav_paulThanks @smustgrave, Yes I've used AI to concise my thought with clarity.
Comment #8
sourav_paulI was aware of AI policy: https://www.drupal.org/docs/develop/issues/issue-procedures-and-etiquett...
but at that time forget to mentioned.