Do not extract caller data in async worker when includeCallerData is false - #1060
Conversation
|
@seonwooj0810 Thank you for another thoughtful PR. May I ask which AI agent and AI model you are using? First, I must say that this PR shows that there is an existing problem deserving attention. It fixes a misconfiguration issue at the price of slight increase of complexity and does not completely solve the issue. For example, if AsyncAppender is configured before some other appender, say AppenderB, and AsyncAppender calls setAllowCallerDataExtraction(false) on an event, AppenderB will not extract caller data. It should be noted that this is also the case in the current code. AppenderB, will use the corrupt I think it is preferable to alert the user about the AsyncAppender misconfiguration at configuration time rather than try to incompletely correct the misconfiguration later at runtime. The detection of the discrepancy can be done using a similar technique to that in #1056 "Detect file collisions in SiftingAppender nested appenders". Alternatively, instead of just alerting about the discrepancy, we could automatically set |
|
Thanks for the careful read — and for the direct question, happy to answer it. Tooling. This is Claude Code (Anthropic's CLI agent), currently on Claude Opus 5, driven by a small harness of mine: it scouts issues, builds and tests a candidate fix locally, and drafts the PR text and follow-up replies. The account is mine and I'm accountable for everything submitted under it. If you'd like the disclosure in the PR description worded differently — or want these marked in some particular way for this project — say so and I'll follow that convention from here on. Substance. Agreed, and your sibling-appender example is the decisive one: with One design question before I rewrite, because following #1056 literally runs into an ordering problem.
(a) Register the analyser on the root (b) Do the check in I lean towards (b) for accuracy and for the non-XML case, but (a) is closer to what you suggested and keeps the public surface unchanged. Which would you prefer? I'll hold the rewrite until you say — either is a small change and I can push it quickly. |
|
@seonwooj0810 Thank you for your thoughtful reply. My suggestion to mimic #1056 "Detect file collisions in SiftingAppender nested appenders" was a bit misleading. Sorry about that. I think the Specifically, a new analyser (say This contradiction-detection approach is more general than the specific case of an appender nested inside an AsyncAppender, where the inner one wants caller data and the outer one does not. Does the above make sense? I’d love to hear what you think. |
|
Yes, the What I would build A new For each
This covers the document-order case that logback XML almost always follows: inner (pattern) appenders are declared first so they are in the map when the outer One question about the edge case If an inner appender is declared after its enclosing
I lean toward (a) for simplicity unless you see common configs that would miss the warning. Which do you prefer? Once you confirm the approach I will also drop the runtime flag ( |
|
Allow me go off on a tangent regarding order independence in configuration declarations. In the analysis phase, Thus, in the second processing phase, models are processed only after their dependencies have been processed. This holds independently of the location of their declarations in the XML. This guarantees that the model corresponding to an However, since This should not be a problem as long as For each
If multiple appenders have contradicting wishes, the algorithm above will emit multiple partial warnings. It would probably be cleaner to have Unfortunately, Does the above make sense? |
|
Yes, that makes complete sense. Let me summarize my understanding to confirm we're aligned: Bi-directional map check (order-independence):
This way, regardless of which AppenderModel the analyser visits first, the contradiction is always detected. Two-analyser split:
This is cleaner because the warning step runs exactly once, after all appender models have been analysed. I'll wait for the |
|
@seonwooj0810 Commits e22a528 and 500a46a fix the issue regarding postHandle calls of analyzers. |
…ownstream layout When AsyncAppender has includeCallerData=false (the default), caller data is extracted from the worker thread rather than the logging-call-site thread, yielding bogus '?' values for %C/%M/%L in any downstream layout. Detect this contradiction at dependency-analysis time via two new analysers: - CallerContradictionAnalyser (AppenderModel): records AsyncAppender instances with includeCallerData=false (or absent, the default) along with the appender names they reference, and records appenders whose layout pattern uses a caller-data converter (%C/%M/%L/%F/%l/%class/%method/%line/%file/%caller). - CallerContradictionWarnAnalyser (ConfigurationModel): in postHandle() — after all AppenderModels have been visited — performs the bi-directional check and emits a WARN for each async→child pair that forms a contradiction. Both analysers are registered in ModelClassToModelHandlerLinker. Three test cases cover: warning issued, no warning when includeCallerData=true, no warning when pattern has no caller-data converters. Fixes qos-ch#1059 Signed-off-by: seonwoo_jung <79202163+seonwooj0810@users.noreply.github.com>
cb94a08 to
3c76ed9
Compare
|
Thank you for the framework patches! I confirmed both commits are in upstream/master and rebased the branch. I've now replaced the runtime flag approach entirely with the
Both registered in Pushed as |
|
@seonwooj0810 Thank you for this PR. It seems to me that these logic of The fact that an appender is referenced by We did not take into account the case of the user setting I think we need to distinguish data gathered from AsyncAppender and data gather from regular appenders. Given a set S containing elements from the set {W_TRUE, W_FALSE, TRUE}, here is a method called contradiction(S) resulting in the string "GOOD" or "BAD". It follows the following logic:
import java.util.Set;
public enum Element {
W_TRUE, W_FALSE, TRUE
}
public class ContradictionChecker {
/**
* Returns "GOOD" or "BAD" according to the following rules:
* - Empty set → "GOOD"
* - Only TRUE (one or more) → "GOOD"
* - W_TRUE together with TRUE → "GOOD"
* - W_FALSE together with W_TRUE → "BAD"
* - W_FALSE together with TRUE → "BAD"
* - Only W_TRUE (no TRUE) → "BAD"
* - Only W_FALSE → "GOOD" (not forbidden by the stated rules)
*/
public static String contradiction(Set<Element> S) {
boolean hasWTrue = S.contains(Element.W_TRUE);
boolean hasWFalse = S.contains(Element.W_FALSE);
boolean hasTrue = S.contains(Element.TRUE);
// W_FALSE may not coexist with either W_TRUE or TRUE
if (hasWFalse && (hasWTrue || hasTrue)) {
return "BAD";
}
// W_TRUE is not allowed without at least one TRUE
if (hasWTrue && !hasTrue) {
return "BAD";
}
// All remaining cases are consistent
return "GOOD";
}
}Please let me know if the above makes sense. Also would you like to submit a PR for this? |
|
@seonwooj0810 Given that the release logback version 1.6.2 is being held by this item, I will go on and fix it without further ado. I should note that your PR has laid the necessary infrastructure to fix this issue. Thank you for that. |
|
Commit b1a80d6 adds |
Fixes #1059
Root cause
When
AsyncAppender.includeCallerDataisfalse, the caller (business) thread deliberately skips caller-data extraction inpreprocess. However, if a downstream layout references%C,%Mor%L, the pattern converters callLoggingEvent.getCallerData()from the async worker thread. SincecallerDataArrayis stillnull,getCallerData()lazily extracts it there — walking the worker thread's stack. This wastes CPU (the whole point ofincludeCallerData=falseis to avoid that cost) and produces bogus?values that have nothing to do with the real logging call site.Change
LoggingEvent.allowCallerDataExtractionflag (defaulttrue).getCallerData()only lazily extracts when the flag is set.AsyncAppender.preprocessclears the flag whenincludeCallerDataisfalse, so the worker thread no longer extracts caller data.The existing caller-data converters (
ClassOfCallerConverter,MethodOfCallerConverter,LineOfCallerConverter,CallerDataConverter) already treat absent caller data as not-available, so with the flag cleared they emit?without paying the extraction cost.getCallerData()could already returnnull(CallerData.extractreturnsnullfor anullthrowable), so no caller gains a new null case.Test evidence
Added
AsyncAppenderTest.notIncludingCallerDataPreventsExtractionEvenWhenLayoutRequestsIt: an async appender withincludeCallerData=falsefeeding anOutputStreamAppenderwhose layout is%L. It asserts the event has no caller data after processing and that the layout emits the not-available marker. The test fails before this change (hasCallerData()returnstrue— caller data was extracted on the worker thread) and passes after it.Verification done: built
logback-core/logback-classicand ran the fulllogback-classictest suite (443 tests, 0 failures/errors) plus the new regression test (JDK 21,maven.compiler.release=11); confirmed the new test fails without the production change and passes with it.This contribution was prepared with automated assistance and reviewed before submission.