joseluisll commented on PR #8903:
URL: https://github.com/apache/hadoop/pull/8903#issuecomment-6562292429
Full investigation behind this change: `HADOOP-19972-slf4j.md`, checked on
the `jetty-phase-c` branch of #8704 at `907cfc03d9a`. It also covers Hadoop's
own distribution, the shaded clients and some Jetty 12 compatibility notes.
Contains content generated by Claude.
<details>
<summary>HADOOP-19972-slf4j.md</summary>
# HADOOP-19972: Jetty 12.1.14 and slf4j 1.7.36
Checked on `origin/jetty-phase-c` at `907cfc03d9a`, on 2026-10-31.
## Verdict
The question was whether Jetty 12, recalled as being built for slf4j 2,
would drop log events or fail on Hadoop's slf4j 1.7.36.
**That claim is half right, and there is one real problem, but not the one
it predicted.**
- **The claim was right about the declaration.** Jetty 12.1.14 is built
against slf4j 2.0.17.
- **Jetty does not need slf4j 2.** It calls only API that exists in 1.7.36.
In Hadoop's own distribution, Jetty's events are not dropped, nothing fails,
and `/logLevel` works.
- **There is a real regression for Maven consumers of the non-shaded
artifacts.** Jetty's declaration gives them a new path to `slf4j-api:2.0.17`.
Depending on declaration order, a working client ends up with
`slf4j-api:2.0.17` and `slf4j-reload4j:1.7.36`. slf4j 2 ignores that binding,
so **all logging on the classpath goes to NOP**, Hadoop's included, not just
Jetty's. On trunk the same projects log normally.
**What should change:**
1. Keep slf4j 1.7.36 for HADOOP-19972.
2. Fix the regression in **HADOOP-20049**, a subtask of HADOOP-19912 created
on 2026-10-31, landing before HADOOP-19972 or together with it. See "What
should change" below.
3. Add the harness observations listed below.
Moving to slf4j 2 is not needed for Jetty 12. If anyone wants it, it needs
its own ticket.
## Findings
### 1. What Jetty declares
`org.eclipse.jetty:jetty-project:12.1.14` sets
`<slf4j.version>2.0.17</slf4j.version>` and manages `org.slf4j:slf4j-api` at
that version.
| Pom | slf4j declaration |
|---|---|
| `jetty-util`, `jetty-http`, `jetty-io`, `jetty-server`, `jetty-session`,
`jetty-security`, `jetty-xml`, `jetty-client`, `jetty-alpn-client` |
`org.slf4j:slf4j-api` compile, not optional, 2.0.17 through the parent |
| `jetty-ee8-nested`, `jetty-ee8-servlet`, `jetty-ee8-webapp`,
`jetty-ee8-security`, `jetty-ee-webapp` | same |
| `jetty-ee8-websocket-jetty-client`, `-jetty-server`, `-servlet`,
`jetty-websocket-core-common` | same |
| `jetty-bom`, `jetty-ee8-bom` | no `slf4j-api` entry (`jetty-bom` lists
only `jetty-slf4j-impl`) |
| Most of the above | `org.eclipse.jetty:jetty-slf4j-impl`, test scope only |
| `jetty-slf4j-impl:12.1.14` | `slf4j-api`, compile. Not on any Hadoop
classpath |
The published jars also state 2.x in their metadata. Each has OSGi
`Import-Package: org.slf4j;version="[2.0,3.0)"`, and `module-info` has
`requires org.slf4j` (`requires transitive` in several).
Neither applies to Hadoop, which runs on the classpath. In any case,
slf4j-api 1.7.36 has `Automatic-Module-Name: org.slf4j`, so the module name
would resolve.
### 2. What resolves in Hadoop
| Scope | `slf4j-api` | Binding |
|---|---|---|
| `hadoop-common` | 1.7.36 compile | `slf4j-reload4j` 1.7.36 compile |
| `hadoop-yarn-server-resourcemanager` | 1.7.36 compile | `slf4j-reload4j`
1.7.36 test |
| `hadoop-client-runtime` | 1.7.36 runtime | none (`reload4j` 1.2.22
optional) |
| `hadoop-client-minicluster` | 1.7.36 compile | none |
| `share/hadoop/common/lib` | `slf4j-api-1.7.36.jar` |
`slf4j-reload4j-1.7.36.jar`, `reload4j-1.2.22.jar`, plus
`jul-to-slf4j-1.7.36.jar` |
No slf4j 2 jar is in the distribution, and `jetty-slf4j-impl` is not there
either. Inside the build, `hadoop-project` dependency management pins 1.7.36.
**Nothing enforces an slf4j version.** The `depcheck` enforcer execution
bans `org.slf4j:slf4j-log4j12` and `log4j:log4j`, and it has
`DependencyConvergence`, but both act on Hadoop's own modules, where 1.7.36 is
managed. They cannot see what a consumer resolves. The shaded-client invariants
only allow `slf4j-api` to stay unshaded.
### 3. Bytecode
I checked every `org/slf4j` class and member reference against the real
`slf4j-api-1.7.36.jar`, walking superinterfaces. The scan parsed the class
files and loaded no code.
| Check | Result |
|---|---|
| 24 Jetty 12.1.14 jars, 2,196 classes | **0 unresolved references** |
| Classes referenced | Only `org/slf4j/Logger` and `org/slf4j/LoggerFactory`
|
| Methods used | `getLogger(Class)`, `getLogger(String)`, `isXEnabled()`,
`trace`/`debug`/`info`/`warn`/`error`, `getName()` |
| Raw constant-pool search, 25 jars with `jetty-servlet-api`, 2,282 classes
| No `LoggingEventBuilder`, `SLF4JServiceProvider`, `atInfo`/`atDebug`/`atWarn`
and so on, and no `org.slf4j` strings |
| `META-INF/services` | No slf4j provider files in any of the 24 jars |
| Shaded `hadoop-client-runtime` and `hadoop-client-minicluster` | 0
unresolved against 1.7.36 |
**Negative control:** the same scanner run on `jetty-slf4j-impl-12.1.14.jar`
reports `org/slf4j/spi/SLF4JServiceProvider`, the one real 2-only reference. It
also reports `Level.ordinal()`, a false positive, because the scanner does not
follow `java.lang.Enum`. So it can find what it looks for, and that jar is not
on Hadoop's classpath.
There are no slf4j 2-only hits for Hadoop's code paths to reach.
### 4. Observed behaviour
**A real `HttpServer2`.** The same `JettyLogProbe` ran on both distributions
with the real launcher classpath, `slf4j-reload4j` binding and a file appender.
| | Trunk (Jetty 9.4.58) | `jetty-phase-c` (Jetty 12.1.14) |
|---|---|---|
| Logger class for Jetty | `org.slf4j.impl.Reload4jLoggerAdapter` | same |
| INFO at startup | yes (`org.eclipse.jetty.server.Server: jetty-9.4.58...`)
| yes (`...: jetty-12.1.14; built: ...`) |
| WARN from a throwing servlet | `org.eclipse.jetty.server.HttpChannel:
/boom` | `org.eclipse.jetty.ee8.nested.HttpChannel: /boom` |
| DEBUG after `/logLevel` set it | yes | yes, 1,240 log lines in total
against 1,203 on trunk |
| Access log (`http.requests.probe`) | yes | yes |
| `SLF4J:` messages on stderr or in the log | none | none |
**Real daemons.** A NameNode and a ResourceManager ran from each
distribution, using the distribution's own `log4j.properties` and `bin/hdfs
--daemon start`.
| | Trunk | `jetty-phase-c` |
|---|---|---|
| NameNode: `curl /imagetransfer` | `WARN
org.eclipse.jetty.server.HttpChannel: handleException /imagetransfer ...` |
`WARN org.eclipse.jetty.ee8.nested.HttpChannel: handleException /imagetransfer
...` |
| NameNode: `/fsck` | WARN, same logger pattern | WARN, same logger pattern |
| `hadoop daemonlog -setlevel ... org.eclipse.jetty DEBUG` | 13,565 NameNode
and 13,444 RM DEBUG lines | 13,402 and 13,228 |
| `daemonlog` reset to INFO | Effective level INFO | Effective level INFO |
| `.out` files | no `SLF4J` lines | no `SLF4J` lines |
The one count gap I followed up was at shutdown: the RM logs 3 context
`Stopped` lines on trunk and none on `jetty-phase-c`. That is Jetty's own
change: `ContextHandler` in 12.1.14 has no `Stopped` log literal. It does not
indicate lost events.
### 5. Hadoop's log settings
| File | Entry | Status |
|---|---|---|
| `hadoop-kms/src/main/conf/kms-log4j.properties:41` |
`log4j.logger.org.eclipse.jetty=INFO` | Still applies. log4j is hierarchical,
so it covers `org.eclipse.jetty.ee8.*` |
| `hadoop-yarn-services-api/.../log4j-server.properties:76` |
`log4j.logger.org.mortbay.log=DEBUG` | Stale and does nothing, but it is the
same on trunk |
| `hadoop-common/src/main/conf/log4j.properties` | No Jetty entries; Jetty
inherits the root logger | Works, as the daemon runs show |
With a KMS-shaped entry (`org.eclipse.jetty=WARN`), no Jetty INFO lines
appeared, the 3 WARNs did, and `/logLevel` reported `org.eclipse.jetty.ee8` at
effective level WARN. I tested this shape with the probe, not with a running
KMS.
`/logLevel` reads and sets `org.eclipse.jetty`, `.server`, `.ee8` and
`.ee8.nested`. Setting `org.eclipse.jetty.ee8` to ERROR suppressed the next
`/boom` WARN, leaving 3 WARNs. Trunk logs 4, because its logger is not under
`.ee8`.
**Logger names did change.** An operator's fine-grained settings stop
matching, though repository config is unaffected. Announce this in the release
note:
| Jetty 9.4 | Jetty 12 ee8 |
|---|---|
| `org.eclipse.jetty.server.HttpChannel` |
`org.eclipse.jetty.ee8.nested.HttpChannel` and
`org.eclipse.jetty.server.internal.HttpChannelState` |
| `org.eclipse.jetty.server.HttpConnection` |
`org.eclipse.jetty.server.internal.HttpConnection` |
| `org.eclipse.jetty.server.session` | `org.eclipse.jetty.session.*`,
`org.eclipse.jetty.ee8.nested.SessionHandler` |
| `org.eclipse.jetty.servlet.*` | `org.eclipse.jetty.ee8.servlet.*` |
| `org.eclipse.jetty.util.log` (`Logging initialized ... Slf4jLog`) | Gone;
the line no longer appears |
### 6. Downstream
#### The regression
Fresh consumer projects with default Maven mediation, on trunk-era snapshots
and on `jetty-phase-c`:
| Consumer's dependencies, in order | Trunk | `jetty-phase-c` |
|---|---|---|
| `hadoop-yarn-client`, then `hadoop-client` | 1.7.36, Reload4j | **api
2.0.17 + binding 1.7.36: NOP** |
| `hadoop-hdfs`, then `hadoop-client` | 1.7.36, Reload4j | **api 2.0.17 +
binding 1.7.36: NOP** |
| `hadoop-client`, then `hadoop-yarn-client` | 1.7.36, Reload4j | 1.7.36,
Reload4j |
| `hadoop-yarn-client`, then `hadoop-common` | 1.7.36, Reload4j | 1.7.36,
Reload4j |
| `hadoop-hdfs`, then `hadoop-common` | 1.7.36, Reload4j | 1.7.36, Reload4j |
| `hadoop-yarn-client` alone | 1.7.36, Reload4j | api 2.0.17: NOP (but
cannot run, no `hadoop-common`) |
| `hadoop-hdfs` alone | no slf4j at all | api 2.0.17: NOP (but cannot run,
no `hadoop-common`) |
The first two rows are working clients: `YarnClient.createYarnClient()`
succeeds and every logger is `NOPLogger`. The only sign is this at startup, on
stderr:
```
SLF4J(W): No SLF4J providers were found.
SLF4J(W): Defaulting to no-operation (NOP) logger implementation
SLF4J(W): Class path contains SLF4J bindings targeting slf4j-api versions
1.7.x or earlier.
SLF4J(W): Ignoring binding found at
[...slf4j-reload4j-1.7.36.jar!/org/slf4j/impl/StaticLoggerBinder.class]
```
With `hadoop-hdfs` then `hadoop-client`, `HttpServer2` also runs with no
output: 0 log lines, 0 access log lines, and `/logLevel` answers `Sorry,
setting log level is only supported for log4j loggers`.
**Cause.** In Jetty 9.4, `slf4j-api` is `provided` and optional, so it never
reached consumers. In Jetty 12 it is compile scope. `hadoop-yarn-client`,
`hadoop-hdfs` and `hadoop-hdfs-nfs` declare Jetty directly, do not declare
`slf4j-api`, and keep `hadoop-common` at `provided`. So Jetty's 2.0.17 sits at
depth 3, for example:
```
consumer -> hadoop-yarn-client -> jetty-ee8-websocket-jetty-client ->
slf4j-api:2.0.17
```
Hadoop's own `slf4j-api:1.7.36` path through `hadoop-client` is also at
depth 3, so the first one declared wins. A Hadoop parent's dependency
management does not reach a consumer's view of Jetty's transitive dependencies.
The binding has no such competitor, so it stays at 1.7.36.
**How widely it applies.** I resolved all 89 published Hadoop jar artifacts
as sole dependencies. Exactly three moved to 2.0.17: `hadoop-hdfs`,
`hadoop-hdfs-nfs`, `hadoop-yarn-client`. The same three, and no others, flip
when each of the 89 is declared before `hadoop-client`. Other orderings and
larger dependency sets were not swept. I do not know how common these orderings
are in real projects. Projects that manage slf4j themselves are unaffected.
#### Already broken on trunk, and not caused by Jetty
| Consumer | Result on both sides |
|---|---|
| `hadoop-common` + `slf4j-api:2.0.17` only | NOP |
| `hadoop-common` + `slf4j-api:2.0.17` + `slf4j-reload4j:2.0.17` | Logging
works, Jetty INFO and WARN reach log4j, but **`/logLevel` refuses**: `Sorry,
setting log level is only supported for log4j loggers` |
The second row is because `GenericsUtil.java:37` hard-codes
`org.slf4j.impl.Reload4jLoggerAdapter`, and slf4j-reload4j 2 moved that class
to `org.slf4j.reload4j`.
`hadoop-azure` and `hadoop-cloud-storage` resolve `slf4j-api:2.0.9` through
`arrow-vector`, which is also on trunk and unrelated to Jetty.
#### Shaded clients
| | `hadoop-client-runtime` | `hadoop-client-minicluster` |
|---|---|---|
| slf4j classes in the jar | 0 | 0 |
| Relocated slf4j | No | No |
| `dependency-reduced-pom` | `slf4j-api:1.7.36`, runtime | Through
`hadoop-client-runtime` |
| Jetty | Relocated to `org.apache.hadoop.shaded.org.eclipse.jetty` | Same |
slf4j is left for the consumer, as on trunk. Their fresh-consumer resolution
is 1.7.36 on both sides, so the regression does not touch them.
A real slf4j 2 consumer (`slf4j-api:2.0.17`, `slf4j-reload4j:2.0.17`) ran
`MiniDFSCluster` from `hadoop-client-minicluster` on both sides. The web UI
answered, and Jetty's INFO and WARN events reached log4j under
`org.apache.hadoop.shaded.org.eclipse.jetty.*`. That shaded prefix is on trunk
too, so a consumer's `org.eclipse.jetty` setting does not match it on either
side.
## What should change
**Keep 1.7.36.** Jetty works correctly on it, and the bytecode proves that
for all of Jetty, not just the paths the probe exercised.
**Fix the regression in HADOOP-20049, a subtask of HADOOP-19912.** It must
not land after HADOOP-19972, or trunk carries the regression in between. The
form validated below is an exclusion on the 12.1.14 entries, but only
`jetty-server`, `jetty-util` and `jetty-http` exist before HADOOP-19972; that
ticket creates the other 5, and Jetty 9.4 has no compile dependency on slf4j,
so the exclusion does nothing until then. The subtask therefore has two options:
- **Land first, declaring `slf4j-api`** in `hadoop-hdfs`, `hadoop-hdfs-nfs`
and `hadoop-yarn-client`. This needs no change to HADOOP-19972. **Chosen, in
apache/hadoop#8903.** Tested against `jetty-phase-c` artifacts with edited pom
copies. The commit compiles on Jetty 9.4; it has not been built together with
Jetty 12.
- **Stack on HADOOP-19972 with the exclusion** and merge the two together.
Also works, and passed a full build, but it cannot land first.
Both gave the same result against an unfixed control. Declared before
`hadoop-client`, the 3 artifacts that resolved `slf4j-api` 2.0.17 unfixed
resolve 1.7.36 with either fix, and the other 86 of the 89 stay as they were.
The real consumers go from NOP to Reload4j, and `hadoop-hdfs` then
`hadoop-client` goes from 0 log lines to 906. One difference: with the
declaration, `hadoop-hdfs` and `hadoop-hdfs-nfs` alone resolve `slf4j-api`
1.7.36, where trunk resolves none.
For the exclusion, in `hadoop-project/pom.xml`, add this to the 8 managed
Jetty entries with an `org.eclipse.jetty*` group: `jetty-server`, `jetty-http`,
`jetty-util`, `jetty-ee8-nested`, `jetty-ee8-servlet`, `jetty-ee8-webapp`,
`jetty-ee8-websocket-jetty-server`, `jetty-ee8-websocket-jetty-client`.
```xml
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</exclusion>
</exclusions>
```
`jetty-ee8-websocket-jetty-server` already has an `<exclusions>` block, so
add to it.
The exclusion is one place, and it puts these modules back to their trunk
state. Coverage of both options was tested only in the orderings listed here.
**Validated on a scratch copy only; the exclusion is not committed
anywhere:**
| Check, with the exclusion | Result |
|---|---|
| All 89 artifacts as sole dependency | No `slf4j-api` 2.0.17 left. Only the
`arrow-vector` 2.0.9 cases remain |
| `hadoop-yarn-client`, then `hadoop-client` | 1.7.36, Reload4j,
`YarnClientImpl` created |
| `hadoop-hdfs`, then `hadoop-client` | 1.7.36, Reload4j, Jetty INFO, WARN,
DEBUG and access log all present |
| `hadoop-hdfs` alone | No slf4j, as on trunk (`hadoop-hdfs-nfs` likewise) |
| Full `package -Pdist -DskipTests` build | BUILD SUCCESS, all 120 modules,
0 compile errors. `share/hadoop/common/lib` file list identical. Only
`slf4j-api-1.7.36.jar` and `slf4j-reload4j-1.7.36.jar` in the distribution |
**Move to slf4j 2 only as a separate ticket, and only if someone wants it
for its own sake.** It would change the binding every downstream gets, and it
first needs `GenericsUtil` fixed, or `/logLevel` stops working in the daemons.
Jetty 12 gives no reason to do it now.
## What the harness should record
Add both to step 3. Neither is covered by `logscan.py`, which looks only for
linkage and provider errors.
**A. Consumer logging binding, in `downstream-deps.sh`.** For every project,
record this tuple:
```
<artifact> <scenario> slf4j-api=<version> binding=<artifact:version|none>
factory=<LoggerFactory.getILoggerFactory() class name>
```
- **Fail** when `factory` is `org.slf4j.helpers.NOPLoggerFactory` on the
candidate and not on the baseline, or when the `slf4j-api` major version
differs from the binding's.
- **Add a scenario** `then-hadoop-client`: the Hadoop artifact, then
`hadoop-client`. The existing `plain` scenario already shows
`hadoop-yarn-client` going from 1.7.36 to 2.0.17, but only the new scenario
shows a client that works and logs nothing.
- **Add `hadoop-hdfs-nfs`** to `ARTIFACTS`.
- **Do not allowlist** an `org.slf4j:slf4j-api` version change in
`expected-downstream-differences.txt`.
**B. Jetty log delivery, in the e2e runs, per daemon with an HTTP server:**
1. The log contains `INFO org.eclipse.jetty.server.Server:
jetty-<jetty.version>`.
2. `GET /logLevel?log=org.eclipse.jetty` reports `Log Class:
org.slf4j.impl.Reload4jLoggerAdapter`.
3. After `hadoop daemonlog -setlevel <addr> org.eclipse.jetty DEBUG` and one
request, the count of `DEBUG org.eclipse.jetty` lines is above 0. Setting it
back to INFO reports `Effective Level: INFO`.
4. On the NameNode, `GET /imagetransfer` gives HTTP 410 and exactly one
`WARN org.eclipse.jetty.ee8.nested.HttpChannel: handleException /imagetransfer`
(`org.eclipse.jetty.server.HttpChannel` on the Jetty 9 baseline).
5. No line matching `^SLF4J(\(W\))?:` in any `.log` or `.out`. Add that
pattern to `logscan.py`'s `PATTERN`.
Compare presence, not line counts: DEBUG volume differs between Jetty
versions, and the logger names and message text change as listed in section 5.
## Side findings, unrelated to slf4j
None of these is fixed here. They belong with the HADOOP-19972 compatibility
notes.
- **Access log format.** An absent `Referer` or `User-Agent` logs as `""` on
Jetty 12, and as `"-"` on trunk. Log parsers may care.
- **Bad requests in the access log.** A request rejected for a malformed
header is logged on `jetty-phase-c` (`"GET /bad HTTP/1.1" 400`) and not on
trunk.
- **`/%c0`.** The NameNode answers 400 on `jetty-phase-c`, and 404 on trunk.
- **Missing `/static` directory.** Jetty 12 refuses a missing base resource,
and `HttpServer2` handles that for `/logs` but not for `/static`. A missing
`<webapps>/static` stops `start()` with `Base Resource is not valid`. Both
`webapps` directories in the distribution have `static`, so this only affects
code embedding `HttpServer2` with its own webapps directory. My own probe hit
it first.
## Not verified
- Real downstream projects (Spark, Hive, HBase, Tez). I used synthetic
consumers only, and how common the bad ordering is remains unknown.
- A logback consumer. The slf4j 2 runs used `slf4j-reload4j:2.0.17`.
- A running KMS or HttpFS daemon, a NodeManager, and TLS or Kerberos
daemons. The NameNode, ResourceManager and probe paths were plain HTTP.
- Unit tests with the exclusion applied. Only the build and the checks above
ran.
- Baselines: the probe and daemon runs used a build of `84f77af53c9`
(`PREPARATION` merged with trunk, `hadoop-project/pom.xml` identical to
`upstream/trunk`). The Maven consumer baseline used the `3.6.0-SNAPSHOT`
artifacts in `~/.m2`, from early October, on Jetty 9.4.58 and slf4j 1.7.36.
Neither is exactly `upstream/trunk`.
## How this was run
Everything ran in a scratch worktree at `907cfc03d9a`, built with Java 17
and `install -Pdist -DskipTests` into a private Maven repo layered over
`~/.m2`, so the shared repo was not written. No tests were run through Maven.
</details>
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]