Skip to content

Conversation

@gregw
Copy link
Contributor

@gregw gregw commented Oct 26, 2025

We can fix issue #13883 with this, but a) it is a bit verbose and b) I'm sure there are other spots in the code.

Utility method maybe ?

@gregw gregw requested review from joakime and olamy October 26, 2025 21:39
@gregw gregw linked an issue Oct 26, 2025 that may be closed by this pull request
last, state, lockedStateString(), BufferUtil.toDetailString(closeContent), closedCallback, wake);
else
LOG.debug("onWriteComplete({},{}) {}->{} c={} cb={} w={}",
last, failure, state, lockedStateString(), BufferUtil.toDetailString(closeContent), closedCallback, wake, failure);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for failure as the last parameter as it's already in second position, as this will make 8 objects for 7 placeholders.
I remember one impl was checking parameters type and if exception doing something different (but we cannot rely on logging impl)

Something to try possibly is the fluent log api, not sure if this displays the stacktrace of Throwable though, as it might depend on the final logging implementation but worth to try:


      LoggingEventBuilder loggingEventBuilder = LOG.atDebug();
      if(failure != null) loggingEventBuilder = loggingEventBuilder.setCause(failure);
      loggingEventBuilder.log("onWriteComplete({},null) {}->{} c={} cb={} w={}",
              last, state, lockedStateString(), BufferUtil.toDetailString(closeContent), closedCallback, wake);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The difference is whether the stack trace is logged though, isn't it?
{} will take the toString only, so we know the exception type and message, while putting it in as a last param makes the logger render the full Throwable nicely.

Of course it depends on your logging intentions.

@gregw gregw requested a review from olamy October 27, 2025 20:22
olamy
olamy previously approved these changes Oct 27, 2025
Copy link
Contributor

@joakime joakime left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The LOG.atDebug().setCause(x)... code is only necessary when you are dealing with code that might have a null Throwable.

The following code blocks are 100% supported by slf4j and log4j2 when the Throwable is not null.

LOG.debug("message", x);
LOG.debug("message with {} params {}", param1, param2, x);

Every code block that is in a catch (Throwable) doesn't need to use the LOG.atDebug().setCause(x)... technique, as the Throwable is guaranteed to be not null.
You can undo about 90%+ of the change you just made in this PR.

{
if (LOG.isDebugEnabled())
LOG.debug(x.getMessage(), x);
LOG.atDebug().setCause(x).log(x.getMessage());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not necessary here, you are in a catch block already.

{
if (LOG.isDebugEnabled())
LOG.debug("Could not initialize {}", processor, x);
LOG.atDebug().setCause(x).log("Could not initialize {}", processor);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not necessary here, you are in a catch block already.
The Throwable will exist.

{
if (LOG.isDebugEnabled())
LOG.debug("Connection creation failed {}", reserved, x);
LOG.atDebug().setCause(x).log("Connection creation failed {}", reserved);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not necessary here, you are in a catch block already.
The Throwable will exist.

{
if (LOG.isDebugEnabled())
LOG.debug("Authentication failed", x);
LOG.atDebug().setCause(x).log("Authentication failed");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not necessary here, you are in a catch block already.
The Throwable will exist.

{
if (LOG.isDebugEnabled())
LOG.debug("Unable to send headers on exchange {}", exchange, x);
LOG.atDebug().setCause(x).log("Unable to send headers on exchange {}", exchange);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not necessary here, you are in a catch block already.
The Throwable will exist.

{
if (LOG.isDebugEnabled())
LOG.debug("failure while notifying listener {}", listener, x);
LOG.atDebug().setCause(x).log("failure while notifying listener {}", listener);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not necessary here, you are in a catch block already.
The Throwable will exist.

{
if (LOG.isDebugEnabled())
LOG.debug("failure while notifying listener {}", listener, x);
LOG.atDebug().setCause(x).log("failure while notifying listener {}", listener);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not necessary here, you are in a catch block already.
The Throwable will exist.

{
if (LOG.isDebugEnabled())
LOG.debug("failure while notifying listener {}", listener, x);
LOG.atDebug().setCause(x).log("failure while notifying listener {}", listener);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not necessary here, you are in a catch block already.
The Throwable will exist.

{
if (LOG.isDebugEnabled())
LOG.debug("failure while notifying listener {}", listener, x);
LOG.atDebug().setCause(x).log("failure while notifying listener {}", listener);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not necessary here, you are in a catch block already.
The Throwable will exist.

{
if (LOG.isDebugEnabled())
LOG.debug("failure while notifying listener {}", listener, x);
LOG.atDebug().setCause(x).log("failure while notifying listener {}", listener);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not necessary here, you are in a catch block already.
The Throwable will exist.

Copy link
Contributor

@sbordet sbordet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with @joakime that we should only use the builder style only when necessary.

@gregw
Copy link
Contributor Author

gregw commented Oct 28, 2025

@joakime @sbordet I was thinking of only using the builder style when it was strictly needed, but then that is an NP-complete problem to fully calculate. So in the end, I thought it was just best to use the fluent style whenever we want to add a cause. It will then be the default pattern we use and we don't have to think about what happens if the throwable is null.

@gregw gregw requested review from joakime and sbordet October 28, 2025 20:41
@gregw gregw marked this pull request as ready for review October 28, 2025 20:44
Copy link
Contributor

@sbordet sbordet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@joakime @sbordet I was thinking of only using the builder style when it was strictly needed, but then that is an NP-complete problem to fully calculate.

I think you're exaggerating. There are probably just a handful of places outside of catch blocks or fail-style methods such as fail(Throwable) that we need to fix.

And I think a warning is way too severe of an event to emit for a DEBUG log that has an extra parameter that is null and won't actually log anything either way, so I'd categorize this as a LOG4J weirdness for which we should not really sweat too much.

We certainly do not want to have a utility wrapper also for logging!

@gregw
Copy link
Contributor Author

gregw commented Oct 29, 2025

@joakime @sbordet I was thinking of only using the builder style when it was strictly needed, but then that is an NP-complete problem to fully calculate.

I think you're exaggerating. There are probably just a handful of places outside of catch blocks or fail-style methods such as fail(Throwable) that we need to fix.

Well if we used some kind of @NotNull annotation, then this would not be an issue.

But what is wrong with using the fluent style anyway, as I think it is clearer than having to count "{}" to see if the last arg is treated as a cause or just an arg.

@gregw gregw requested a review from sbordet October 29, 2025 20:49
@joakime
Copy link
Contributor

joakime commented Oct 29, 2025

But what is wrong with using the fluent style anyway, as I think it is clearer than having to count "{}" to see if the last arg is treated as a cause or just an arg.

IntelliJ tells you this btw.
Not enough (or too many) {}, it's an error.
If there is 1 more param than {} then the last one better be a Throwable, otherwise it's an error.

@gregw
Copy link
Contributor Author

gregw commented Oct 29, 2025

But what is wrong with using the fluent style anyway, as I think it is clearer than having to count "{}" to see if the last arg is treated as a cause or just an arg.

IntelliJ tells you this btw. Not enough (or too many) {}, it's an error. If there is 1 more param than {} then the last one better be a Throwable, otherwise it's an error.

@joakime No it doesn't. It tells you if you have the number wrong. But it does not tell you which of the two correct interpretations you are using. Last arg can be a cause or an arg depending on the number of {}

@joakime
Copy link
Contributor

joakime commented Oct 29, 2025

@joakime No it doesn't. It tells you if you have the number wrong. But it does not tell you which of the two correct interpretations you are using. Last arg can be a cause or an arg depending on the number of {}

If you use the Throwable as the {}...
Note the colorization of the {}, on my color scheme this means there is no associated method parameter that fits this {}.

Screenshot from 2025-10-29 16-05-54

If you have a parameter, and attempt to use {} on it...
Note the colorization of the {}, the left hand one has an associated method parameter, the second one doesn't.

Screenshot from 2025-10-29 16-06-17

The proper usage. (no error)

Screenshot from 2025-10-29 16-06-09

@sbordet sbordet moved this to 👀 In review in Jetty 12.1.4 Oct 29, 2025
@gregw
Copy link
Contributor Author

gregw commented Oct 31, 2025

@joakime No it doesn't. It tells you if you have the number wrong. But it does not tell you which of the two correct interpretations you are using. Last arg can be a cause or an arg depending on the number of {}

If you use the Throwable as the {}... Note the colorization of the {}, on my color scheme this means there is no associated method parameter that fits this {}.

I don't agree with intellij's colorization in this case. LOG.debug("whoops {}", ex); is a perfectly valid usage of the API. It is a little ambiguous, hence the reason this PR is a good idea. Using the fluent API removes the ambiguity if a passed Throwable is a cause or an argument.

…883-logNullThrowable

# Conflicts:
#	jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java
Comment on lines +760 to +762
(_enterScopeSetClassloaderFailed.compareAndSet(false, true) ? LOG.atWarn() : LOG.atDebug())
.setCause(x)
.log("Error setting a context classloader on thread {}", Thread.currentThread());
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@joakime @sbordet Also with the fluent style, code like this is much nicer as it avoids duplicating the log call!

@joakime
Copy link
Contributor

joakime commented Oct 31, 2025

I don't agree with intellij's colorization in this case. LOG.debug("whoops {}", ex); is a perfectly valid usage of the API. It is a little ambiguous, hence the reason this PR is a good idea. Using the fluent API removes the ambiguity if a passed Throwable is a cause or an argument.

There's no ambiguity, the API for LOG.debug("whoops {}", ex) is for the method Logger.debug(String msg, Throwable t)

There are no argument parameters or what you call "cause" in that use case.

Here's the docs for how Throwable is treated in various logging libraries.

@joakime
Copy link
Contributor

joakime commented Oct 31, 2025

Be gentle with fluent style, it will cause more object creation.

@gregw
Copy link
Contributor Author

gregw commented Oct 31, 2025

@joakime you are just re-enforcing my point that there is ambiguity in that API because they have both debug(String, Throwable) and debug(String, Object) and debug(String, Object...) !

Best to avoid that ambiguity (be it perceived or actual).

The fluent API allows for a clear statement of intent. The cost is a trivial even wrapper, which will not significantly change object allocation.

@gregw
Copy link
Contributor Author

gregw commented Nov 2, 2025

@sbordet Can I get this reviewed before you go away. I think these changes are good:

  • consistently use the same API for handling causes
  • There is no visual or real ambiguity about if a throwable should be logged as a string or a cause
  • trivial additional builder cost only if debug is turned on

Copy link
Contributor

@sbordet sbordet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not thrilled, but also there are some obvious goodnesses, so LGTM.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: 👀 In review

Development

Successfully merging this pull request may close these issues.

Invalid logger usage in HttpOutput

6 participants