This is an automated email from the ASF dual-hosted git repository.
jamesbognar pushed a commit to branch docs
in repository https://gitbox.apache.org/repos/asf/juneau.git
The following commit(s) were added to refs/heads/docs by this push:
new fc55468b04 TODO-368: JUL/Spring control plane for REST debug logging
fc55468b04 is described below
commit fc55468b044aa7177f5b09407421a074081c9a1d
Author: James Bognar <[email protected]>
AuthorDate: Sun Aug 16 06:52:31 2026 -0400
TODO-368: JUL/Spring control plane for REST debug logging
- Emit REST debug records at stable INFO level; tier (INFO/DEBUG/TRACE)
controls message detail only.
- Add proxy-safe logger naming (LoggerNaming.userClassName) and wire
RestContext default logger derivation.
- Add Spring Boot auto-config (JuneauRestLoggingAutoConfiguration) with
reset-resistant Logback->JUL LevelChangePropagator, provided logback-classic,
optional OSGi import + manifest assertion, and no-Logback fallback.
- Integration coverage for resource/op-level level propagation; docs +
migration + release-note updates.
---
pages/release-notes/10.0.0.md | 7 ++++-
.../topics/10.32.RestServerLoggingAndDebugging.md | 33 ++++++++++++++++++++--
pages/topics/12.00.JuneauRestServerSpringboot.md | 29 +++++++++++++++++++
pages/topics/27.V10MigrationGuide.md | 7 ++++-
4 files changed, 71 insertions(+), 5 deletions(-)
diff --git a/pages/release-notes/10.0.0.md b/pages/release-notes/10.0.0.md
index bfd1c5dd13..d2f559bcfc 100644
--- a/pages/release-notes/10.0.0.md
+++ b/pages/release-notes/10.0.0.md
@@ -476,9 +476,12 @@ for the full removed-symbol mapping.
- **One signal.** A resource's own `java.util.logging.Logger` level is the
sole control for request/response debug
capture — no annotations, no per-request headers, no separate config keys.
Verbosity is cumulative: `INFO` =
- basic, `FINE` = + headers, `FINEST` = + bodies, one `LogRecord` per request
at the highest enabled tier.
+ basic, `FINE` = + headers, `FINEST` = + bodies, one `LogRecord` per request
(stable emitted level: `INFO`).
- **Per-operation granularity** falls out of ordinary JUL parent-inheritance
via a hierarchical child logger
(`resourceClass.getName() + "." + methodName`) — elevate one method to
`FINEST` without touching the rest.
+- **Spring Boot propagation path.** For real Logback backends,
`logging.level.*` now propagates into JUL by default
+ (`juneau.rest.logging.propagate-levels=true`), with an additive default
posture
+ (`juneau.rest.logging.propagate-levels.reset-jul=false`) and silent no-op
fallback for non-Logback backends.
- **New `RestDebugFormatter` SPI** (`org.apache.juneau.rest.server.logging`) —
a per-tier formatter interface
(`formatBasic`/`formatHeaders`/`formatBody`) with a shipped
`BasicRestDebugFormatter` default. It's the only
public extension point; the two-phase capture pipeline underneath it is
internal. Resolution precedence:
@@ -493,6 +496,8 @@ for the full removed-symbol mapping.
- **Marshall-layer `debug`** (recursion detection + richer serialization
exceptions) is unaffected in behavior —
`RestRequest.isDebug()` still exists, now derived and read-only, `true` when
the resolved logger is
`FINE`-or-finer.
+- **Operator caveat.** Stable-`INFO` emission means DEBUG/TRACE-selected
headers and bodies flow through INFO sinks;
+ keep broad DEBUG/TRACE raises scoped until payload-hardening controls are in
place.
See the new [Logging / Debugging](/docs/topics/RestServerLoggingAndDebugging)
topic page for the full model.
diff --git a/pages/topics/10.32.RestServerLoggingAndDebugging.md
b/pages/topics/10.32.RestServerLoggingAndDebugging.md
index a57f014ec2..9f4201cd0a 100644
--- a/pages/topics/10.32.RestServerLoggingAndDebugging.md
+++ b/pages/topics/10.32.RestServerLoggingAndDebugging.md
@@ -14,7 +14,7 @@ that class's endpoints, at three cumulative tiers.
The resolved JUL logger's level is the sole control.
- **Cumulative tiers.** `INFO` = basic request/response logging, `FINE` = adds
headers, `FINEST` = adds bodies.
Each tier includes everything the tier below it logs.
-- **One log record per request**, emitted at the resolved tier, through the
resolved logger.
+- **One log record per request**, always emitted at JUL `INFO`. The resolved
tier controls content only.
- **Secure by default.** Sensitive headers are redacted and bodies are capped,
both overridable — see
[Secure by default](#secure-by-default-redaction--body-cap) below.
@@ -34,6 +34,10 @@ this is a plain <a
href="https://docs.oracle.com/en/java/javase/17/docs/api/java
hierarchy, ordinary JUL parent-inheritance rules apply — an operation logger
with no level of its own inherits the
resource logger's level.
+Default resource logger naming normalizes common proxy-shaped classes (Spring
CGLIB, ByteBuddy, JDK proxy-style,
+Hibernate/Mockito proxy infixes) back to the user class name, while preserving
ordinary nested class names unchanged.
+This avoids logger-name churn when resources are wrapped by runtime proxies.
+
```java
// logging.properties
com.example.MyResource.level = FINE
@@ -102,6 +106,9 @@ makes its *operations* log full detail; it does not
retroactively add header/bod
| `FINE` | Basic **+** request/response headers. |
| `FINEST` | Basic + headers **+** request/response bodies. |
+`FINER` resolves through the `FINE` tier (headers), while `CONFIG` resolves
through `INFO` (basic line only), matching
+JUL `isLoggable(...)` semantics used by the pipeline.
+
The tier is re-derived at request completion via `isLoggable(...)` against the
resolved logger, and is stable for
the life of the request. Content is cumulative — `FINEST` always includes
everything `FINE` and `INFO` log, in one
`LogRecord`.
@@ -118,7 +125,7 @@ customization surface is the
[`RestDebugFormatter`](#the-restdebugformatter-spi)
happens at all, so requests below `FINEST` pay no buffering overhead.
- **Phase B (request completion):** the tier is re-derived, the resolved
`RestDebugFormatter` is invoked
cumulatively (`formatBasic` always, `+ formatHeaders` at `FINE`-or-finer, `+
formatBody` at `FINEST`), header
- redaction is applied, and exactly one `LogRecord` is emitted at the resolved
level through the resolved logger.
+ redaction is applied, and exactly one `LogRecord` is emitted at `INFO`
through the resolved logger.
- **Bounded capture.** The caching wrapper buffers at most the configured body
cap (default 8 KB), not the entire
stream, so memory use is bounded even for large uploads/downloads.
- **Edge case:** if the logger's level is changed *during* a request (between
Phase A and Phase B), the two phases
@@ -220,10 +227,30 @@ try (var c =
RichLogger.getLogger(MyResource.class).captureEvents(Level.FINEST))
```
`captureEvents(Level.FINEST)` is the right threshold for
`.debug()`/`logLevel(FINEST)` mock flows, since debug
-records are emitted at `FINEST`. Pair this with the mock request's
`logLevel(Level)` helper to set the resolved
+records are emitted at `INFO`, and the capture threshold is `>=`-based.
`captureEvents(Level.INFO)` is the precise
+threshold; `captureEvents(Level.FINEST)` still captures these records for
existing tests. Pair this with the mock
+request's `logLevel(Level)` helper to set the resolved
logger level for one call (and restore it afterward) — the mock equivalent of
editing `logging.properties` without
introducing a separate boolean debug flag.
+## Spring/back-end notes
+
+- Spring Boot + Logback can propagate `logging.level.*` into JUL through
`LevelChangePropagator` when enabled.
+- The propagator's inherit-then-parent-change synchronization hole still
applies: a JUL child logger set to inherit
+ can remain stale after a later parent-level change.
+- For nested resource class names that contain `$`, `logging.level.<fqcn>`
property keys may need escaping in Spring
+ property sources because `$...` can be interpreted as a placeholder.
+- Backend fallback matrix:
+ - Logback: auto-propagation support.
+ - Log4j2 + `log4j-jul`: JUL facade path works.
+ - Log4j2 without `log4j-jul`: no JUL back-propagation.
+ - Plain JUL / other SLF4J backends: configure JUL directly.
+
+:::danger
+Stable-INFO emission means DEBUG/TRACE-selected headers and bodies now travel
through INFO sinks. Before dedicated
+payload-hardening controls are enabled, avoid broad DEBUG/TRACE raises on
production resource loggers.
+:::
+
## Migrating from pre-10.0
If you're coming from a pre-10.0 REST debug/logging setup —
`@Rest(debug=...)`, `@Debug`, `DebugEnablement`,
diff --git a/pages/topics/12.00.JuneauRestServerSpringboot.md
b/pages/topics/12.00.JuneauRestServerSpringboot.md
index 9def257c49..e58b4f6d46 100644
--- a/pages/topics/12.00.JuneauRestServerSpringboot.md
+++ b/pages/topics/12.00.JuneauRestServerSpringboot.md
@@ -66,6 +66,35 @@ the same property space Spring uses — `application.yaml` /
`application.proper
command-line arguments, and any custom Spring `PropertySource`. Spring's
active profiles are also surfaced
to Juneau's `juneau.profiles.active` config-profile key. See the cross-links
below for details.
+## Logback level propagation to JUL
+
+When Logback is the active backend, `juneau-rest-server-springboot`
auto-installs a JUL `LevelChangePropagator`
+listener by default (`juneau.rest.logging.propagate-levels=true`,
`matchIfMissing=true`) so `logging.level.*`
+changes are reflected into JUL-backed REST debug detail controls.
+
+- Installation is guarded at runtime:
+ - if Logback classes are absent, no install is attempted;
+ - if `ILoggerFactory` is not a Logback `LoggerContext`, it silently no-ops.
+- Disable propagation with `juneau.rest.logging.propagate-levels=false`.
+- The installer is additive and idempotent (it reuses any existing
`LevelChangePropagator` subtype, and installs
+ at most one reset-resistant listener).
+
+### `reset-jul` posture
+
+`juneau.rest.logging.propagate-levels.reset-jul` defaults to `false`
(recommended).
+
+- `false` (default): pre-existing JUL logger levels/handlers are preserved.
+- `true`: process-global reset/reapply behavior from Logback's propagator is
enabled.
+
+When `reset-jul=true`, be explicit about the side effects:
+
+- non-root explicit JUL logger levels are cleared;
+- explicit Logback levels are then propagated back into JUL;
+- JUL-only `logging.properties` entries not mirrored by explicit Logback
levels do not survive.
+
+For operator-controlled durability across explicit Logback resets, you can
also install a Logback XML
+`<contextListener>` directly.
+
## See also
- [Spring Boot Overview](/docs/topics/SpringBootOverview) — base classes and a
worked sample application.
diff --git a/pages/topics/27.V10MigrationGuide.md
b/pages/topics/27.V10MigrationGuide.md
index 6511824f63..ea276b53cc 100644
--- a/pages/topics/27.V10MigrationGuide.md
+++ b/pages/topics/27.V10MigrationGuide.md
@@ -116,7 +116,7 @@ Single-type imports follow the same rename, e.g.:
| Class-level `@RestInit public void init(RestContext.Builder b) { ... }` hook
— the framework added the in-flight `RestContext.Builder` to the resource's
bean store so any `@RestInit` method that declared a `RestContext.Builder`
parameter received it and could imperatively configure the resource-level
context (`builder.path(...)`, `builder.children(...)`, `builder.encoders(...)`,
etc.). | **Removed.** The class-level Builder-injection protocol is gone —
`RestContext.Builder` is no longe [...]
| Custom annotation appliers — user code that subclassed the internal
`AnnotationApplier<Rest, RestContext.Builder>` (or `AnnotationApplier<RestOp,
RestOpContext.Builder>`) to extend the annotation-processing pass (the
`apply(AnnotationInfo<A>, B builder)` hook invoked once per annotation during
context construction). | **Removed.** The builder-based apply-pass is gone;
`RestAnnotation.Apply` (`RestContextApply`) is now a package-private nested
class inside `RestContext` and is not exten [...]
| Custom `RestAnnotation.create(...)` / `RestOpAnnotation.create(...)`
builder-of-builders patterns — programmatic construction of `@Rest` / `@RestOp`
annotation proxies used to feed synthetic annotations into the builder
apply-pass (common in test fixtures and extension libraries). | The annotation
proxy builders still exist for test use (`RestAnnotation.create()` /
`RestOpAnnotation.create()` are still available via annotation-test helpers),
but they no longer feed into a builder apply [...]
-| All prior REST debug/logging mechanisms, in order of vintage: `@Rest(debug)`
/ `@RestOp(debug)` plain-string attributes; the nested `@Debug` annotation
(`value()`/`on()`/`format()`/`level()`/`config()`) and its
`debugDefault`/`debugEnablement`/`debugOn` siblings;
`DebugEnablement`/`BasicDebugEnablement`/`DebugConfig`/`DebugRule`; and the
`CallLogger`/`CallLoggerRule`/`BasicCallLogger` logging SPI (including the
`juneau.restLogger.*` config keys). | **Removed — replaced by a single JUL
[...]
+| All prior REST debug/logging mechanisms, in order of vintage: `@Rest(debug)`
/ `@RestOp(debug)` plain-string attributes; the nested `@Debug` annotation
(`value()`/`on()`/`format()`/`level()`/`config()`) and its
`debugDefault`/`debugEnablement`/`debugOn` siblings;
`DebugEnablement`/`BasicDebugEnablement`/`DebugConfig`/`DebugRule`; and the
`CallLogger`/`CallLoggerRule`/`BasicCallLogger` logging SPI (including the
`juneau.restLogger.*` config keys). | **Removed — replaced by a single JUL
[...]
## REST Debug/Logging Redesign (JUL-Level-Driven `RestDebugFormatter`)
@@ -140,6 +140,11 @@ rest-server-only type. It has been **deleted with no
replacement in 10.0**.
core marshalling artifact most projects already depend on, not an opt-in
rest-server module.
:::
+For Spring Boot users: Logback propagation to JUL is default-on for real
Logback backends
+(`juneau.rest.logging.propagate-levels=true`), additive by default
(`...reset-jul=false`), and silently no-ops for
+other backends. See
[juneau-rest-server-springboot](/docs/topics/JuneauRestServerSpringboot) for
fallback behavior
+and the process-global side effects of opting into `reset-jul=true`.
+
| Removed | Replacement |
|---|---|
| `@Rest(debug=...)` / `@RestOp(debug=...)` and the nested `@Debug` annotation
(`value()`, `on()`, `format()`, `level()`, `config()`) | No annotation
replacement. Set your resource's JUL logger level (`INFO`/`FINE`/`FINEST`)
instead. |