Skip to content

Fix Log4j1 to SLF4J migration of mapped diagnostic context (MDC): convert non-String values to Strings and migrate Hashtable to Map. - #296

Merged
timtebeek merged 6 commits into
openrewrite:mainfrom
motlin:log4j1-mdc-retype-fixes
Jul 3, 2026
Merged

timtebeek merged 6 commits into
openrewrite:mainfrom
motlin:log4j1-mdc-retype-fixes

Conversation

@motlin

@motlin motlin commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

What's changed?

Added Log4j 1.x MDC migration support to the SLF4J recipes:

  • Wraps non-String MDC.put(String, Object) values with String.valueOf(...) before migrating to SLF4J.
  • Converts MDC.getContext() to MDC.getCopyOfContextMap().
  • Retypes affected Hashtable declarations/returns/assignments to Map<String, String>.

What's your motivation?

Log4j 1.x and SLF4J differ in MDC API types: Log4j accepts arbitrary object values and returns Hashtable from getContext(), while SLF4J expects String values and returns a Map. A direct type migration can leave uncompilable code.

Anything in particular you'd like reviewers to focus on?

Working on the Log4j 1.x recipes can be tricky because they work through Log4j 2.x as a stepping stone. I want to make sure I'm not breaking anything for Log4j 2.x users, but don't use it myself.

Anyone you would like to review specifically?

@timtebeek probably?

…vert non-String values to Strings and migrate Hashtable to Map.

Co-authored-by: mdepaula <dipaula.matheus01@gmail.com>
@timtebeek

Copy link
Copy Markdown
Member

Code review — high effort (self-review of this branch)

Reviewed the two new recipes + YAML/CSV wiring + tests. Six findings, most severe first.

Correctness

  1. returnsGetContext() descends into nested/local classes — Log4j1MdcGetContextToCopyOfContextMap.java (returnsGetContext). Only visitLambda/visitNewClass short-circuit, so a return MDC.getContext() inside a local/nested class wrongly flags the enclosing Hashtable-returning method for retyping.

    Hashtable outer() {
        class Local { Hashtable inner() { return MDC.getContext(); } }
        return someOtherHashtable; // outer() gets retyped to Map<String,String> — wrong
    }
  2. Parameter-sync loop clobbers unrelated Map generics — visitMethodDeclaration. Runs on every method in any getContext-using file and rewrites the method-type entry of any Map-typed parameter to the raw shallow MAP_TYPE, dropping <K,V>. An unrelated void handle(Map<String,Integer> config) loses its generics. It also drops <String,String> even on the intended retyped parameter.

  3. Qualified field assignment missed — the assignment pre-scan only handles J.Identifier LHS, so this.field = MDC.getContext() (a J.FieldAccess) never retypes the field, leaving Map assigned to a Hashtable field (won't compile, even in the composite).

  4. Multi-variable sibling widening — retype triggers on the whole declaration if any variable qualifies, so Hashtable a = MDC.getContext(), other = new Hashtable(); silently widens other to Map, breaking later Hashtable-specific calls on it.

  5. Cross-file caller breakage (plausible) — retyping a non-override public method signature isn't propagated to callers in files the recipe never visits (no getContext call → UsesMethod precondition skips them). The description only documents the override case.

Cleanup

  1. Hand-rolled type expression (plausible) — mapStringString manually assembles J.ParameterizedType/JContainer/Space instead of the idiomatic JavaTemplate path sibling recipes use; fragile and reimplements framework logic.

Checked and cleared: @Getter final String display/description style matches all sibling recipes; composite fixpoint/cycle ordering and recipes.csv count deltas are consistent; log4j 1.2 MDC signatures are correct. Standalone recipe emitting org.apache.log4j.MDC.getCopyOfContextMap() until composed with ChangeType is by design (documented).

- returnsGetContext(): stop descending into nested/local class declarations
  so a return inside a nested class no longer retypes the enclosing method
- parameter-sync: only rewrite method-type entries still typed Hashtable,
  leaving unrelated Map parameters' (parameterized) attribution intact
- assignment pre-scan: handle this.field = MDC.getContext() (J.FieldAccess)
  so field declarations assigned via qualified access are retyped

Add regression tests for each.
@timtebeek

timtebeek commented Jul 1, 2026 •

Copy link
Copy Markdown
Member

Fixes pushed (db48d83)

Addressed the three clear correctness findings, with a regression test each:

  • 1 nested-class returns — returnsGetContext() now also short-circuits J.ClassDeclaration, so a return MDC.getContext() inside a nested/local class no longer retypes the enclosing method. Test: returnInNestedClassDoesNotRetypeEnclosingMethod.
  • 2 parameter-sync over-broad — the method-type sync now only rewrites entries still typed Hashtable whose source parameter is now Map, so unrelated Map<K,V> parameters keep their parameterized attribution. Test: unrelatedMapParameterIsLeftAlone.
  • 3 this.field = missed — the assignment pre-scan now handles J.FieldAccess targets, so a field assigned via this.field = MDC.getContext() is retyped. Test: retypeFieldAssignedViaThis. (Minor: the now-unused java.util.Hashtable import is left behind because the this.field reference keeps stale attribution — harmless, pinned in the test.)

Deferred, with rationale:

  • 4 multi-variable sibling widening — a correct fix requires splitting Hashtable a = MDC.getContext(), other = new Hashtable(); into two declarations (one type can't cover both). Narrow edge case; leaving as a known limitation for now.
  • 5 cross-file caller breakage — inherent to a single-file, UsesMethod-gated recipe; callers in unvisited files can't be reached. This is the same class of manual-fix caveat the description already flags for signature changes.
  • 6 hand-rolled mapStringString — works and is tested; building a standalone parameterized type expression via JavaTemplate in declaration-type position is awkward and riskier than the current LST construction. Left as-is.

All 20 tests in the four affected suites pass.

timtebeek added 2 commits July 3, 2026 11:17
- Pre-scan and returnsGetContext accumulate via .reduce(tree, seed)
  instead of visiting a pre-allocated mutable holder
- Return the retyped node directly instead of reassign-then-return in
  visitVariableDeclarations and the return-type retype
- Extract syncRetypedParameters so the parameter sync is its own
  immediate-return helper, keeping both method-declaration retypes as
  early returns without dropping the both-apply case

@timtebeek timtebeek left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks a lot for the help here! Took a bit of time to trickle through some small improvements, but looks ok otherwise!

@github-project-automation github-project-automation Bot moved this from In Progress to Ready to Review in OpenRewrite Jul 3, 2026
@timtebeek timtebeek added the recipe Recipe Requested label Jul 3, 2026
@timtebeek
timtebeek merged commit 0677d12 into openrewrite:main Jul 3, 2026
1 check passed
@github-project-automation github-project-automation Bot moved this from Ready to Review to Done in OpenRewrite Jul 3, 2026
@motlin
motlin deleted the log4j1-mdc-retype-fixes branch July 3, 2026 13:06
@motlin

motlin commented Jul 3, 2026

Copy link
Copy Markdown
Contributor Author

Thank you again! It looks like you're using a helpful Openrewrite AI skill. I wonder if it's public? I'd like to catch as much as I can before raising PRs.

mergify Bot added a commit to robfrank/linklift that referenced this pull request Jul 17, 2026
…rom 3.29.1 to 3.30.0 [skip ci]

Bumps [org.openrewrite.recipe:rewrite-logging-frameworks](https://github.com/openrewrite/rewrite-logging-frameworks) from 3.29.1 to 3.30.0.
Release notes

*Sourced from [org.openrewrite.recipe:rewrite-logging-frameworks's releases](https://github.com/openrewrite/rewrite-logging-frameworks/releases).*

> 3.30.0
> ------
>
> What's Changed
> --------------
>
> * Allow StringFormatToParameterizedLogging to migrate format strings without specifiers. by [`@​motlin`](https://github.com/motlin) in [openrewrite/rewrite-logging-frameworks#295](https://redirect.github.com/openrewrite/rewrite-logging-frameworks/pull/295)
> * Fix Log4j1 to SLF4J migration of mapped diagnostic context (MDC): convert non-String values to Strings and migrate Hashtable to Map. by [`@​motlin`](https://github.com/motlin) in [openrewrite/rewrite-logging-frameworks#296](https://redirect.github.com/openrewrite/rewrite-logging-frameworks/pull/296)
> * Suppress OWASP findings for intentional log4j 1.x dependency by [`@​timtebeek`](https://github.com/timtebeek) in [openrewrite/rewrite-logging-frameworks#297](https://redirect.github.com/openrewrite/rewrite-logging-frameworks/pull/297)
>
> **Full Changelog**: <openrewrite/rewrite-logging-frameworks@v3.29.2...v3.30.0>
>
> 3.29.2
> ------
>
> What's Changed
> --------------
>
> * OpenRewrite v8.86.0: <https://github.com/openrewrite/rewrite>
>
> **Full Changelog**: <openrewrite/rewrite-logging-frameworks@v3.29.1...v3.29.2>


Commits

* [`c357a72`](openrewrite/rewrite-logging-frameworks@c357a72) OpenRewrite recipe best practices
* [`3f0e4a0`](openrewrite/rewrite-logging-frameworks@3f0e4a0) Add OWASP suppression for intentional log4j 1.x dependency ([#297](https://redirect.github.com/openrewrite/rewrite-logging-frameworks/issues/297))
* [`0677d12`](openrewrite/rewrite-logging-frameworks@0677d12) Fix Log4j1 to SLF4J migration of mapped diagnostic context (MDC): convert non...
* [`aab44dd`](openrewrite/rewrite-logging-frameworks@aab44dd) OpenRewrite recipe best practices
* [`068d1bf`](openrewrite/rewrite-logging-frameworks@068d1bf) Allow StringFormatToParameterizedLogging to migrate format strings without sp...
* [`1883eb7`](openrewrite/rewrite-logging-frameworks@1883eb7) Remove out-of-date OWASP suppressions
* See full diff in [compare view](openrewrite/rewrite-logging-frameworks@v3.29.1...v3.30.0)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

recipe Recipe Requested

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

2 participants