Skip to content

feat(introspection): auto-log explain/trace via System.Logger + render refresh#216

Merged
eschizoid merged 14 commits into
mainfrom
feat/introspection-logging
Jul 5, 2026
Merged

feat(introspection): auto-log explain/trace via System.Logger + render refresh#216
eschizoid merged 14 commits into
mainfrom
feat/introspection-logging

Conversation

@eschizoid

Copy link
Copy Markdown
Owner

Two things, one PR (per request).

1. explain() render refresh

OpticReport.toString now renders Mapped → Skipped → Transformations → Unused sources, with a blank line between sections, and a dropped field reads (ignored) (in both explain and trace). Same content, closer to the original design mockup. Golden tests updated. No API change.

2. Auto-log explain/trace via System.Logger (ADR-0014)

Mapper and ForwardMapper log their own introspection through java.lang.System.Logger — zero dependency (java.base), no module-info change, routes to whatever backend the app runs (JUL default / Logback / Log4j2 / JBoss Logging via a LoggerFinder):

  • DEBUG → explain() structure, once at construction (skipped for trail-less lifted shells).
  • TRACE → trace(input) value column, per forward(), built from the already-computed result (never re-running forward, which would recurse through trace).

Both use the lazy log(Level, Supplier<String>) overload, so the render is never computed unless the level is enabled — a single isLoggable check on the hot path when off.

Always-on, level-gated (not a flag, not an opt-in view): the log level is the switch, controllable per-mapper at runtime from config, no redeploy. Considered and rejected a tracing=true factory flag (compile-time, redundant with the level, loses runtime control) and a mapper.logged() view (extra public surface). Adds zero new public API — just an internal logger.

Type-pair logger names — io.github.eschizoid.telescope.mapper.<Source>.<Target>:

io.github.eschizoid.telescope.mapper.UserDto.User = TRACE   # one mapper, values per conversion
io.github.eschizoid.telescope.mapper               = OFF     # kill all of it

Navigation (Telescope) auto-logging is deferred: it's built fluently (no single construction point for a DEBUG explain) and doesn't retain Class<S> for a logger name. Telescope.explain()/trace() stay explicit-only.

Verified by a JUL-captured test (MapperLoggingTest): logs the explain structure at DEBUG + the value trace at TRACE, and is completely silent (nothing rendered) when the level is off.

Design decisions flagged for review

  • always-on-guarded vs opt-in — chose always-on (config-time control is the value).
  • DEBUG=explain@construction, TRACE=trace@forward.
  • type-pair logger names under a shared .mapper. root.
  • navigation deferred.

…r refresh

Logging (ADR-0014): Mapper and ForwardMapper log their own introspection through
java.base's System.Logger — zero dependency, no module-info change, routes to the app's
backend (JUL / Logback / Log4j2 …):
- DEBUG → explain() structure, once at construction (skipped for trail-less lifted shells).
- TRACE → trace(input) value column, per forward(), built from the already-computed result
  (never re-running forward, which would recurse through trace).
Both use the lazy Supplier overload, so nothing renders unless the level is enabled — a
single isLoggable check when off. Always-on, level-gated: the log level is the per-mapper
opt-in, controllable at runtime from config with no redeploy. Type-pair logger names
(io.github.eschizoid.telescope.mapper.<Source>.<Target>) so one mapper — or the whole
library by prefix — can be enabled from the app's logging config. Verified via a
JUL-captured test (logs at TRACE, silent when off). Navigation auto-logging is deferred
(a Telescope has no single construction point and doesn't retain Class<S> for a name).

Render refresh: OpticReport.toString now renders Mapped → Skipped → Transformations →
Unused sources with a blank line between sections, and a dropped field reads (ignored)
(in explain and trace). Same content, matches the original design mockup; golden tests
updated.
@codecov

codecov Bot commented Jul 5, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 85.71429% with 7 lines in your changes missing coverage. Please review.
✅ Project coverage is 79.59%. Comparing base (4f8411b) to head (6f814fa).
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
.../eschizoid/telescope/conversion/MappingTraces.java 45.45% 6 Missing ⚠️
...eschizoid/telescope/introspection/OpticReport.java 95.83% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff            @@
##               main     #216   +/-   ##
=========================================
  Coverage     79.59%   79.59%           
- Complexity     1831     1841   +10     
=========================================
  Files            80       80           
  Lines          5762     5783   +21     
  Branches       1210     1215    +5     
=========================================
+ Hits           4586     4603   +17     
- Misses          656      660    +4     
  Partials        520      520           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR updates Telescope’s introspection rendering to better match the original intended format and adds always-on (level-gated) introspection auto-logging for Mapper / ForwardMapper via java.lang.System.Logger, with ADR documentation and tests.

Changes:

  • Refresh OpticReport.toString() section ordering/spacing and rename dropped-field label from (dropped) to (ignored) (tests updated).
  • Add System.Logger-based DEBUG/TRACE auto-logging in Mapper and ForwardMapper (DEBUG=explain-on-construction, TRACE=value-trace-per-forward).
  • Add a JUL-captured contract test for the auto-logging behavior and document the decision in ADR-0014.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
docs/adr/0014-system-logger-auto-trace.md Adds ADR documenting the System.Logger auto-tracing decision and rationale.
core/src/test/java/io/github/eschizoid/telescope/introspection/OpticReportTest.java Updates golden expectations for new section ordering/blank lines and (ignored) label.
core/src/test/java/io/github/eschizoid/telescope/introspection/MapperTraceTest.java Updates trace wording/assertions to (ignored) for dropped fields.
core/src/test/java/io/github/eschizoid/telescope/introspection/MapperLoggingTest.java Adds contract tests validating DEBUG/TRACE auto-logging via JUL-backed System.Logger.
core/src/main/java/io/github/eschizoid/telescope/introspection/OpticReport.java Reorders render sections, adds blank lines between sections, renames DROPPED label to ignored.
core/src/main/java/io/github/eschizoid/telescope/conversion/MappingTraces.java Renames DROPPED label to ignored in value-trace rendering.
core/src/main/java/io/github/eschizoid/telescope/conversion/Mapper.java Introduces System.Logger and emits DEBUG explain + TRACE value-trace logs.
core/src/main/java/io/github/eschizoid/telescope/conversion/ForwardMapper.java Introduces System.Logger and emits DEBUG explain + TRACE value-trace logs.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

…e Supplier

Copilot: the log(Level, Supplier) overload skips invoking the supplier when the level is
off, but the capturing lambda is still allocated on every forward(). Gate both the DEBUG
(construction) and TRACE (forward) logs with an explicit isLoggable check — plus a
non-empty-trail check — BEFORE the Supplier is built, so the hot path is fully
allocation-free when off and trail-less shells never log an (empty optic) record. ADR
updated to describe the guard precisely.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.

Comment thread core/src/main/java/io/github/eschizoid/telescope/introspection/OpticReport.java Outdated
…eport

Reword per review: a report holds either mapping sections or navigation hops (never both),
so the last section's trailing blank is the final whitespace that stripTrailing() removes.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.

Comment thread core/src/main/java/io/github/eschizoid/telescope/introspection/OpticReport.java Outdated
… test

- OpticReport.toString now builds each non-empty part as a block and joins them with a
  blank line, instead of appending a trailing blank per section. Copilot: a report CAN
  hold both mapping rows and hops (a mapping telescope further navigated, e.g.
  map(A,B).field(B::x)), where the old approach stranded a non-trailing blank between the
  section and the hops. Joining spaces every case correctly with no stray blank. Pinned by
  a mixed-report golden test.
- MapperLoggingTest disables useParentHandlers on the captured JUL logger (and restores it)
  so records don't propagate to the root handlers — keeps the contract test isolated and CI
  logs quiet.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.

…bump README to 1.1.0

- ADR-0013 "What it looks like" examples updated to the shipped render (Mapped → Skipped →
  Transformations order, blank lines between sections, (ignored) wording).
- ADR-0014: dropped the cosmetic "render refresh" consequence — it's not an architectural
  decision; the render lives in code + the synced 0013 examples. Kept separate from 0013
  (extends it) per the point-in-time-record convention.
- README install snippets bumped 1.0.5 → 1.1.0 (latest release). migration-feedback.md's
  1.0.5 references are historical (which version a bug was fixed/reported in) — left as-is.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.

…port, coverage

Consolidates the round of fixes from the reviewer fleet plus the aligned-report
polish:

- Trace render is now total: a field whose toString() throws degrades to the
  (n/a) sentinel instead of escaping forward(). Enabling TRACE can no longer
  fail the conversion it only meant to observe (MappingTraces.render).
- OpticReport render aligns the left column across every section — the widest
  cell sets one shared width, so every marker, field, and the following arrow /
  reason paren land in the same column across Mapped, Skipped, Transformations,
  and Unused sources rather than four independently-padded blocks.
- Coverage: ForwardMapper logging, DEBUG-vs-TRACE level gating (structure at
  DEBUG, values only at TRACE), trail-less lifted shells silent even at ALL, and
  a throwing-toString conversion surviving TRACE. Four-section render golden.
- README install snippets bumped to 1.1.1; ADR-0013 render examples synced to
  the aligned layout.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated no new comments.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.

Comment thread docs/adr/0014-system-logger-auto-trace.md Outdated
…oot's Logback backend

Proves ADR-0014's promise end-to-end, not just against the JDK's default JUL
sink: in the org-chart Spring Boot demo, a mapper's explain/trace auto-logging
travels System.Logger → java.util.logging → jul-to-slf4j → SLF4J → Logback, and
a Logback ListAppender on the type-pair logger captures it.

Surfaces the real bridge behaviour honestly: jul-to-slf4j maps System.Logger
TRACE (JUL FINER) onto SLF4J DEBUG, so both lines render at DEBUG through the
default Spring Boot setup. The config threshold still separates them — DEBUG
fires structure only, TRACE additionally fires the per-forward values — which is
the knob adopters turn. The two tests pin that toggle through the real backend;
ADR-0014 gains an adopter note documenting the bridge level mapping.
eschizoid added 2 commits July 5, 2026 13:16
…mple-name logger naming

- mappingRender collapses alignment padding and asserts the mapped row in-order
  (✓ firstName → givenName) rather than as independent substrings.
- ADR-0014 spells out that logger names use simple class names and the
  same-simple-name-across-packages collision trade-off.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 3 comments.

Comment thread README.md
…gging

Documents the mapper introspection surface as a first-class MapStruct
differentiator: explain() (static structure, aligned render, typed OpticReport
slices for assertions), trace(input) (value column), and the System.Logger
auto-logging (DEBUG=explain at build, TRACE=trace per forward; type-pair logger
names; the jul-to-slf4j level-render nuance).

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.

Comment thread README.md Outdated
Comment thread docs/adr/0014-system-logger-auto-trace.md Outdated
…docs

Two threads in one pass:

Repo-wide convention sweep (describe behaviour in its own terms, not by
citation):
- Removed every "ADR-00NN" / "Enh N" reference from comments, javadoc, and
  @DisplayName across core, internal, codegen, lombok, benchmarks, and the
  org-chart demo — zero ADR references remain in any .java. Each site was
  reworded to keep its technical explanation intact.
- Removed the lone "Bug 2" reference in LambdaIntrospectionTest, describing the
  propertyOf null-guard fix in its own terms.

Copilot review follow-ups:
- Bumped the codegen / lombok / quarkus / spring-boot-starter README install
  snippets from 1.0.5 to 1.1.1 (were stale against the root README).
- Replaced the invalid `logger = LEVEL` config snippets in the README and
  ADR-0014 with valid, copy-pasteable logback.xml and Spring Boot
  application.properties forms.
- Documented inline (Mapper / ForwardMapper) that the type-pair logger names use
  simple class names by design — the same-simple-name-across-packages collision
  is an accepted trade-off for readable, greppable names.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 29 out of 29 changed files in this pull request and generated 1 comment.

Follow-up to the ADR-citation sweep — removes the "Phase A/B/C/D" jargon that
only made sense as pointers into the ADR-0006 rollout plan, describing each
concept in its own terms instead.

- Renamed the holder-path test fixtures and their tests: PhaseCSrc/PhaseCDst →
  HolderIsoSrc/HolderIsoDst, PhaseCPlainSrc/PhaseCPlainDst →
  HolderIsoPlainSrc/HolderIsoPlainDst, DeepMapPhaseCHolderTest →
  DeepMapHolderIsoReadTest, DeepMapPhaseDConstructTest →
  DeepMapHolderIsoConstructTest (the codegen holders rename with them).
- Reworded the remaining "Phase B/C/D" prose in comments, javadoc, @DisplayName,
  and assert messages across core, internal, benchmarks, and lombok — "Phase B"
  → the holder probe, "Phase C" → the structural-iso holder path, etc.
- Removed the docs/migration-feedback.md references from MigrationRegressionTest,
  describing the regressions as adopter-reported mapping bugs.

Zero ADR / Phase / feedback-doc / Bug-number tokens remain in any .java. Full
build green.
…st RuntimeException

MappingTraces.render() and readDotted() are debug aids that must never fail the
conversion they observe. They caught RuntimeException but let an Error escape —
a cyclic toString() (StackOverflowError) or a toString()/getter that throws
AssertionError would still break forward() at TRACE. Broaden both to Throwable,
rethrowing only OutOfMemoryError (a dying JVM, not the value's fault). The
throwing-toString test now throws an Error to pin the stronger guarantee.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 43 out of 43 changed files in this pull request and generated no new comments.

@eschizoid eschizoid merged commit 8ee96c2 into main Jul 5, 2026
4 checks passed
@eschizoid eschizoid deleted the feat/introspection-logging branch July 5, 2026 21:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants