Fix LAL segmentId/spanId extractor statements; delete dead DSL code and correct the kernel's docs - #13973
Merged
Merged
Conversation
Deletions, each verified to have no reference in Java, resources, SPI files,
codegen string literals or FreeMarker templates:
- DslContentHash, a byte-identical zero-caller twin of the live ContentHash
whose javadoc instructed the reader to consolidate toward the dead copy
- oal-rt's metrics-function registry (3 classes), a second divergent answer
to a problem MetricsHolder already solves
- LogAnalyzerFactory, sole occupant of its package
- MALCodegenHelper.RUN_VAR, MALExprCodegen.getVarCount, DslYamlLineIndex.empty
Three kernel classes carried javadoc asserting consumers that do not exist.
DSLClassLoaderManager claimed the MAL and LAL compile paths reach for its
singleton; LogDataDebugDump claimed core LALOutputBuilder implementations
render through it. Neither is referenced by any DSL module. A false rationale
in a shared kernel is a standing instruction to put more things there, so each
now states what the call sites actually support.
OALDebug, OALDebugRecorder and DebugHolderProvider now record why they live in
server-core when MAL's and LAL's equivalents live in their own modules:
dsl-debugging declares no oal-rt dependency, so they cannot move. The
asymmetry is forced by the module graph, not an oversight.
Fourteen comment sites left stale by #13972 are corrected, the worst being a
LALClassGenerator comment stating that no LineNumberTable is attached, eleven
lines above the code that attaches one and a test that asserts it.
MALClassGenerator now uses MALCodegenHelper.GATE_HOLDER_FQCN instead of
shadowing it with an identical local literal, and setYamlSource carries the
@deprecated its LAL and Hierarchy counterparts already had.
CLAUDE.md records why generated source is fully qualified: Javassist's
compiler has no import statement and resolves simple names only against
java.lang, so the no-inline-FQCN rule applies to hand-written Java only.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CMkDwMcr7139gZ95XCMQ6i
LALParser.g4 declares traceIdStatement, segmentIdStatement and spanIdStatement as extractor alternatives, and LALBlockCodegen already carried setSegmentId / setSpanId in its output-setter table, but LALScriptParser.visitExtractorStatement implemented a branch for only the first of the three. The remaining alternatives fell through to a line that assumed whatever was left had to be an ifStatement, so a rule writing "segmentId ..." failed at boot with a NullPointerException naming IfStatementContext -- for a rule line containing no `if`. Both statements now have their branch, and an extractor statement matching no branch reports its own rule line instead of throwing an NPE about a construct the author did not write. That fall-through is what let two of three trace statements stay unimplemented without anyone noticing. Existing log records are unaffected: LogBuilder copies trace id, segment id and span id straight from the log's metadata and skips that copy only when a rule has already set them -- which no shipped rule did, which is why nothing surfaced the gap. Dedicated coverage, none of which existed: reading all three fields through log.traceContext.*, writing all three from an extractor, and writing segmentId alone (the shape that threw). The fixture builder gained trace-segment-id and span-id inputs, and the execution test gained traceId / segmentId / spanId expectations -- the harness could not express any of this before, which is the reason METADATA_TRACE_GETTERS had never been executed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CMkDwMcr7139gZ95XCMQ6i
LALCodegenHelper.METADATA_GETTER_ALIASES was Map.of() with a single reader in LALValueCodegen, so get() always returned null and the alias branch could never execute. Its comment named a traceSegmentId -> segmentId alias as the motivating case; that mismatch no longer exists, because LogMetadata's nested TraceContext names the field traceSegmentId and Lombok generates getTraceSegmentId() to match. The map was empty because the problem was solved, not because work was left unfinished. Removing the branch makes getterName final; it was the only reassignment. A missing getter now fails the same way it already did for every other field, with an IllegalArgumentException naming the type and the getter. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CMkDwMcr7139gZ95XCMQ6i
mrproliu
approved these changes
Aug 11, 2026
wu-sheng
added a commit
that referenced
this pull request
Aug 11, 2026
The #13973 entry states that dsl-debugging now declares its server-core dependency directly rather than inheriting it transitively. It does not: that pom change was proposed, rejected on review as unnecessary -- server-core is foundational and never leaves the dependency tree -- and reverted, but the changelog sentence written before the revert was not removed with it. dsl-debugging/pom.xml contains no server-core declaration. Removing the sentence rather than adding the dependency, because the review's conclusion stands.
wu-sheng
added a commit
that referenced
this pull request
Aug 11, 2026
… and name the classify branches Three pieces of DSL-internals cleanup. No behaviour change. core/classloader held only DSL types, two of which say so in their names, and nothing outside the DSL and rule world imported it. It is now core/dsl/classloader. Catalog moves to core/dsl instead: a rule-file taxonomy is not a class-loading concern, and it is the package's most-used type, mostly as a RuleKey component in dsl-debugging. Nothing names this package in a string literal, a FreeMarker template or an SPI file, so the compiler catches every reference; ClassLoaderGc builds its unload-probe resource path from UnloadProbePayload.class.getName() rather than a constant, so it follows the move. Three copies of "define this generated class into the right loader" -- MAL's and LAL's generators and MeterSystem -- collapse into a static BytecodeClassDefiner.define. It lives on the interface rather than in a class of its own because the whole decision is whether the loader implements that interface; a second similarly-named type in the same package would be one concept with two names. The JDK 17 --add-opens rationale, previously restated in four places, now has one home and the other three point at it. MeterSystem's multi-catch drops | IOException, which define() wraps into CannotCompileException -- already an arm of the same catch, so the rollback and UnexpectedException wrap are unchanged. That arm was unreachable regardless: CtClass.toBytecode() writes to a ByteArrayOutputStream whose close() is a documented no-op, so the checked exception is a declaration artefact. Three of the four copies of the SW_DYNAMIC_CLASS_ENGINE_DEBUG dump-directory lookup become DslGeneratedFileWriter.resolveClassDumpDir. OAL keeps its own: its openEngineDebug flag is settable independently of the environment variable and two tests call setOpenEngineDebug(true), which the shared resolver cannot express without a second variant invented for one caller. /addOrUpdate chose its delta classifier with a MAL/LAL ternary, written when those were the only two engines, so its else meant LAL and would hand a third engine's rule to the LAL classifier. The decision is now three-valued and named (engineKindFor). An unknown catalog cannot reach it -- validate() rejects that earlier with invalid_catalog, using the same registry lookup -- so the third case means a registered engine with no delta classifier, which should fail loudly rather than parse as LAL. DSLScriptKey.isMalCatalog and isLalCatalog go with it: one caller between them. RuntimeRuleServiceEngineKindTest covers the dispatch, which had no test because it lived inside a private handler needing a DSLManager, storage and locks. DslSourceRef records a decision not to add a shared producer interface for rule coordinates. Two of the four DSLs could not implement one -- OAL's dispatcher deliberately wants a lineless ref, and Hierarchy has no per-rule object -- so the narrow entrance, a file and a line, stays the contract and each DSL keeps what it knows about its own rules. Also drops a changelog sentence merged with #13973 claiming dsl-debugging declares its server-core dependency directly. It does not; that pom change was proposed, rejected on review and reverted, but the sentence was not removed with it.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR carries two things: a LAL bug fix found while writing missing coverage, and the dead-code
and documentation cleanup that led to it.
Fix LAL
segmentIdandspanIdextractor statements, which never workedFound by writing the coverage below — the DSL surface existed, was documented by the grammar, and
had never been executed.
LALParser.g4declares three trace-writing extractor statements:LALScriptModel.FieldTypehas all three (TRACE_ID, SEGMENT_ID, SPAN_ID, TIMESTAMP), andLALBlockCodegen's output-setter table already carriedsetSegmentIdandsetSpanId— whichLogBuilderimplements. OnlyLALScriptParser.visitExtractorStatementwas missing two branches,and its
if-chain ended with an unchecked assumption:So a rule containing
segmentId parsed.seg as Stringfailed at boot withNullPointerException: Cannot invoke "LALParser$IfStatementContext.condition()" because "ctx" is null— naming a construct the rule does not contain. That fall-through is why two of the threestatements stayed unimplemented: the failure never pointed at them.
Both branches are added, and an unmatched extractor statement now reports its own rule line.
No existing behaviour changes.
LogBuilder.init()copies trace id, segment id and span iddirectly from the log's metadata, and skips that copy only when a rule already set them:
No shipped rule sets them, so every log record already gets these fields — which is exactly why
the gap went unnoticed.
Coverage, none of which existed
METADATA_TRACE_GETTERS(traceId/traceSegmentId/spanId) had no rule and no testexercising any entry, in either direction. The harness could not express it either — the fixture
builder only understood
trace-id, and the execution test had no expectation for these fields.Both are extended, then used:
trace-context-readlog.traceContext.*into tagstrace-write-all-threetrace-segment-overridesegmentIdalone — the exact shape that threwtrace-segment-overridefailed with the NPE before the parser fix and passes after, so the testsdemonstrably catch the defect rather than merely accompanying it.
Delete dead DSL code and correct the shared kernel's own documentation
Follow-up to #13972. No behaviour change: deletions of unreferenced code, plus comment and
javadoc corrections. 86 insertions, 506 deletions.
Dead code
Each verified to have no reference in Java, resources, SPI files, codegen string literals or
FreeMarker templates — the last two matter, because a class named only from a
.ftllooks dead toevery tool (
OALDebugis exactly that case, and is not touched here).DslContentHashContentHash, which has 29 call sites and a test. Its javadoc told the reader to consolidate toward the dead copyoal-rt/.../v2/registry/(3 classes)MetricsHolderalready does; zero references outside its own packageLogAnalyzerFactoryMALCodegenHelper.RUN_VAR,MALExprCodegen.getVarCount(),DslYamlLineIndex.empty()The kernel was asserting consumers it does not have
This is the part worth reviewing. Three classes in
server-corecarried javadoc justifying theirplacement by naming users that grep disproves:
DSLClassLoaderManager— "the meter / log compile paths reach forINSTANCE". Neitheranalyzer module references it. MAL and LAL accept a plain
ClassLoaderand narrow throughBytecodeClassDefiner, so neither ever names it.LogDataDebugDump— "Lives in core soLALOutputBuilderimplementations can render theircached
LogData". Zero core references; its one caller is inlog-analyzer.DslContentHash— "…can share one implementation" / "scheduled for consolidation in afollow-up". The follow-up never happened and nothing shared it.
A false rationale in a shared kernel is not a stale comment, it is a standing instruction to put
more things there. Each now states what the call sites support.
The converse was also missing:
OALDebug,OALDebugRecorderandDebugHolderProviderareOAL-specific and by symmetry belong in
oal-rt, where MAL's and LAL's equivalents live. Theycannot move —
dsl-debugging/pom.xmldeclares nooal-rtdependency, so the recorderimplementation would lose sight of them. Nothing said so, leaving it looking like carelessness.
Now recorded on all three.
Residue from #13972
Fourteen sites. The worst contradicts the code beside it:
LALClassGeneratorstated "NoLineNumberTable … an absent attribute reports an unknown line, which is honest" eleven lines
above the call that attaches one, and above a test from the same PR asserting every method carries
one.
git showconfirms it survived as a two-word substitution inside the very hunk that addedthose calls.
Also: a javadoc block orphaned when a method was inserted between it and its owner; a
{@link}toa method that PR deleted;
escapeJava/sanitizeName/COMPANION_SAM_LINE_IN_CLASSnamed incomments after being removed; a
log.debug("MAL YAML line index …")in a class now serving fourDSLs; and a hierarchy javadoc example missing the line number the code actually emits.
Two small corrections
MALClassGeneratornow usesMALCodegenHelper.GATE_HOLDER_FQCNinstead of shadowing it with anidentical local literal. The constant read as dead only because of the shadow; LAL uses its
equivalent at six sites.
setYamlSourcegains the@Deprecatedits LAL and Hierarchy counterparts already carry — it wasthe only one of the three giving callers no migration signal while its own delegate target was
deprecated.
CLAUDE.md: why generated source is fully qualified
Javassist's compiler has no
importstatement. It resolves a simple class name only againstClassPool.importedPackages, initialised tojava.langand nothing else; widening it needsClassPool.importPackage, which is pool-wide mutable state and is never called here. So generatedsource must fully qualify everything,
java.util.Mapincluded, and the project'sno-inline-fully-qualified-names rule applies to hand-written Java only. Shortening an FQCN inside a
codegen string or a
.ftlcompiles as Java and then fails at runtime — worth stating, since therule reads as universal.
CHANGESlog.