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 846b54d20d feat: pluggable /loggers backends (Logback + Log4j2 
adapters via LogBackend SPI)
846b54d20d is described below

commit 846b54d20d108a83c76694b9ec3d1aa6bfbfa6e0
Author: James Bognar <[email protected]>
AuthorDate: Fri Jun 19 10:32:34 2026 -0400

    feat: pluggable /loggers backends (Logback + Log4j2 adapters via LogBackend 
SPI)
---
 pages/release-notes/10.0.0.md           | 12 ++++++++-
 pages/topics/16.12.ManagementSurface.md | 48 +++++++++++++++++++++++++++++----
 2 files changed, 54 insertions(+), 6 deletions(-)

diff --git a/pages/release-notes/10.0.0.md b/pages/release-notes/10.0.0.md
index 75f203bb08..e79b85f9f5 100644
--- a/pages/release-notes/10.0.0.md
+++ b/pages/release-notes/10.0.0.md
@@ -273,7 +273,7 @@ Juneau 10.0 adds a coherent, discoverable **Actuator-style 
management surface**
 New endpoints:
 
 - **`/info`** (`juneau-microservice`, 
`org.apache.juneau.microservice.management`) — renders the running 
application's `ManifestFile` main attributes (build/version/git metadata). 
Degrades to `{}` when no manifest is registered. Build-time git/version 
stamping goes in the consumer's application-jar build (a `git-commit-id` + 
`maven-jar-plugin` recipe is in the topic page).
-- **`/loggers`** (`juneau-rest-server`, 
`org.apache.juneau.rest.server.management`) — read all `java.util.logging` 
levels and (deny-by-default) set a level at runtime. **JUL-only in v1**; 
non-JUL backends (SLF4J/Logback/Log4j2) are tracked as a follow-on.
+- **`/loggers`** (`juneau-rest-server`, 
`org.apache.juneau.rest.server.management`) — read all logger levels and 
(deny-by-default) set a level at runtime. Drives `java.util.logging` by 
default; Logback / Log4j2 backends are an explicit opt-in (see the dedicated 
entry below).
 - **`/metrics`** (`juneau-rest-server-metrics-micrometer`) — renders a 
Prometheus scrape from a consumer-provided Micrometer `MeterRegistry` resolved 
from the bean store; degrades to `501` when no scrapeable registry is present. 
Never auto-registers a default registry.
 - **`/threaddump` + `/heapdump`** (`juneau-rest-server`) — server-agnostic 
`ThreadMXBean` thread dump and HotSpot `.hprof` heap dump, both 
**deny-by-default** on security grounds (opt in via `DumpsSettings`).
 
@@ -281,6 +281,16 @@ Exposure is **on-by-default-with-gating**: non-sensitive 
reads are exposed; muta
 
 See the new [Management Surface](/docs/topics/ManagementSurface) topic page 
for the endpoint catalog, the `BasicActuatorGroup` one-liner, the exposure 
policy, and the manifest-stamping recipe.
 
+#### `/loggers` non-JUL backends (Logback, Log4j2)
+
+The `/loggers` endpoint (above) now drives a pluggable **`LogBackend`** 
instead of being hardwired to `java.util.logging`. `juneau-rest-server` still 
ships only the JUL backend (`JulLogBackend`, the default) so the core stays 
dependency-free, and a new opt-in **`juneau-rest-server-management-logging`** 
module adds `LogbackLogBackend` + `Log4j2LogBackend` (the backends declared 
`provided`, so they never leak transitively).
+
+- **Explicit selection** (no classpath auto-detection, per the 
explicit-over-magic principle): declare the backend on the existing settings 
bean — `LoggersSettings.create().backend(new LogbackLogBackend()).build()`.
+- **Identical response shape across backends**: read reports the 
**configured** level (empty string = inherited from an ancestor; `ROOT` key for 
the root logger); set is **process-lifetime-only** (no config-file rewrite). 
Level names are backend-native; an invalid name fails `400`.
+- **Drives the concrete backend, not the SLF4J facade**: `LogbackLogBackend` 
for SLF4J→Logback, `Log4j2LogBackend` for Log4j2; an `slf4j-jdk14` binding is 
JUL, so the default backend already covers it. Custom backends implement 
`LogBackend` directly.
+
+This closes the 174b JUL-only `/loggers` limitation. See the extended 
[Management 
Surface](/docs/topics/ManagementSurface#loggers-backends-jul-logback-log4j2) 
topic page.
+
 #### Host-side mixin overrides — `@Rest(mixinDefs=@Mixin(...))`
 
 `@Rest(mixins=...)` (since 9.5.0) lets a mixin class declare its own `@Rest` 
settings that apply only to its endpoints — but the composition decision often 
belongs to the *host*. New in 10.0.0, the `@Rest(mixinDefs=@Mixin(...))` 
attribute lets a host declare a mixin **and** override selected `@Rest`-level 
settings for that mixin's endpoints, in one place, without editing or 
subclassing the mixin:
diff --git a/pages/topics/16.12.ManagementSurface.md 
b/pages/topics/16.12.ManagementSurface.md
index b1628920c4..5576aee262 100644
--- a/pages/topics/16.12.ManagementSurface.md
+++ b/pages/topics/16.12.ManagementSurface.md
@@ -12,14 +12,14 @@ Starting with **10.0.0**, Juneau ships a coherent, 
discoverable **Actuator-style
 | Endpoint | What it does | Module | Default exposure |
 | --- | --- | --- | --- |
 | `GET /info` | Application manifest / build / version / git metadata | 
`juneau-microservice` | **on** |
-| `GET /loggers`, `GET /loggers/{name}` | Read `java.util.logging` levels | 
`juneau-rest-server` | **on** |
-| `PUT`/`POST /loggers/{name}` | Set a JUL level at runtime | 
`juneau-rest-server` | **deny-by-default** |
+| `GET /loggers`, `GET /loggers/{name}` | Read logger levels (JUL default; 
Logback/Log4j2 opt-in) | `juneau-rest-server` | **on** |
+| `PUT`/`POST /loggers/{name}` | Set a logger level at runtime | 
`juneau-rest-server` | **deny-by-default** |
 | `GET /metrics` | Prometheus scrape from a Micrometer registry | 
`juneau-rest-server-metrics-micrometer` | on (501 if no registry) |
 | `GET /healthz`, `/readyz`, `/livez` | Health / readiness / liveness probes | 
`juneau-rest-server` | **on** |
 | `GET /threaddump` | `ThreadMXBean` thread dump | `juneau-rest-server` | 
**deny-by-default** |
 | `GET /heapdump` | HotSpot `.hprof` heap dump | `juneau-rest-server` | 
**deny-by-default** |
 
-Endpoints are split across modules by dependency: the pure-JUL `/loggers` and 
the JDK-only `/threaddump`/`/heapdump` need only the REST server; `/metrics` 
needs Micrometer; `/info` needs the microservice runtime that resolves the 
application `ManifestFile`.
+Endpoints are split across modules by dependency: `/loggers` (JUL backend) and 
the JDK-only `/threaddump`/`/heapdump` need only the REST server; non-JUL 
`/loggers` backends (Logback/Log4j2) need 
`juneau-rest-server-management-logging`; `/metrics` needs Micrometer; `/info` 
needs the microservice runtime that resolves the application `ManifestFile`.
 
 ## `BasicActuatorGroup` — one-liner assembly
 
@@ -153,6 +153,44 @@ MeterRegistry meterRegistry() {
 
 Mount the endpoint by mixing `MetricsMixin` into your host resource (the 
`juneau-rest-server-metrics-micrometer` module must be on the classpath).
 
-## `/loggers` is JUL-only in v1
+## `/loggers` backends (JUL, Logback, Log4j2)
 
-The `/loggers` endpoint reads and sets **`java.util.logging`** levels — the 
logging backend Juneau itself configures. Applications that route logging 
through SLF4J→Logback or Log4j2 will **not** have their levels changed by this 
endpoint in v1. Backend-aware level control (SLF4J/Logback/Log4j2) is tracked 
as a separate follow-on.
+The `/loggers` endpoint drives a **`LogBackend`**. By default that is 
**`java.util.logging`** (`JulLogBackend`) — the backend Juneau itself 
configures, and the only one bundled with `juneau-rest-server` (the core stays 
dependency-free). Applications that route logging through **SLF4J→Logback** or 
**Log4j2** select the matching backend explicitly.
+
+### Explicit backend selection (no auto-detection)
+
+Consistent with Juneau's explicit-over-magic stance, you **declare** the 
backend on a `LoggersSettings` bean — the endpoint never classpath-scans for, 
and then silently drives, a backend you didn't choose. The Logback / Log4j2 
adapters ship in the opt-in `juneau-rest-server-management-logging` module (the 
backends are declared `provided`, so they never leak transitively onto your 
classpath):
+
+```xml
+<dependency>
+    <groupId>org.apache.juneau</groupId>
+    <artifactId>juneau-rest-server-management-logging</artifactId>
+    <version>10.0.0</version>
+</dependency>
+<!-- plus your chosen backend at the version you want, e.g. 
ch.qos.logback:logback-classic -->
+```
+
+```java
+@Bean
+public LoggersSettings loggersSettings() {
+    return LoggersSettings.create()
+        .enableWrite()                          // opt in to the set-level 
endpoints (deny-by-default)
+        .backend(new LogbackLogBackend())       // or new Log4j2LogBackend() — 
explicit, no auto-detect
+        .build();
+}
+```
+
+When no `LoggersSettings` bean is registered (or none declares a backend), the 
endpoint drives JUL.
+
+### Same response shape across backends
+
+The endpoint's contract is identical regardless of backend:
+
+- **Read** reports each logger's **configured** level — the level set on the 
logger itself, or the empty string when it inherits from an ancestor (Log4j2 
`LoggerConfig`s always carry a concrete level, so the empty-string "inherited" 
marker only appears for JUL/Logback). The root logger is keyed `ROOT`.
+- **Set** is **process-lifetime-only** (JUL `Logger.setLevel`, Logback 
`Logger.setLevel`, Log4j2 `Configurator.setLevel`) — it does **not** rewrite 
the backend's configuration file. A blank/empty level clears the logger's own 
level so it inherits again.
+
+Level names are backend-native (JUL `FINE`/`INFO`/…; Logback & Log4j2 
`DEBUG`/`INFO`/`WARN`/…). An invalid level name fails with `400`.
+
+### SLF4J is a facade — pick the real backend
+
+SLF4J itself emits nothing; the levels live in the bound backend. Select the 
backend that actually emits: `LogbackLogBackend` for SLF4J→Logback, 
`Log4j2LogBackend` for Log4j2 (including SLF4J→Log4j2). When the SLF4J binding 
is `slf4j-jdk14` (SLF4J→JUL), the backend *is* JUL, so the default 
`JulLogBackend` is correct. Custom backends can implement `LogBackend` directly.

Reply via email to