11/*
2- * Copyright (c) 2020, 2022 Oracle and/or its affiliates.
2+ * Copyright (c) 2020, 2025 Oracle and/or its affiliates.
33 *
44 * Licensed under the Apache License, Version 2.0 (the "License");
55 * you may not use this file except in compliance with the License.
1515 */
1616package io .helidon .logging .common ;
1717
18+ import java .util .HashMap ;
1819import java .util .List ;
20+ import java .util .Map ;
1921import java .util .Objects ;
2022import java .util .Optional ;
2123import java .util .ServiceLoader ;
24+ import java .util .function .Supplier ;
2225
2326import io .helidon .common .HelidonServiceLoader ;
2427import io .helidon .logging .common .spi .MdcProvider ;
2528
2629/**
2730 * Helidon MDC delegates values across all of the supported logging frameworks on the classpath.
31+ * <p>
32+ * Helidon permits adding MDC entries using {@code Supplier<String>} values as well as direct {@code String} values.
33+ * Although some logging implementations provide their own context maps (for example {@code ThreadContext} in Log4J and
34+ * {@code MDC} in SLF4J), they map MDC keys to {@code String} values, not to arbitrary objects that would accommodate
35+ * {@code Supplier<String>}. Therefore, Helidon not only propagates every {@code set} operation to the loaded MDC providers,
36+ * but also manages its own map of key/supplier pairs. Helidon resolves each lookup using that map
37+ * if possible, delegating a look-up to the loaded MDC providers only if there is no supplier for a key.
2838 */
2939public class HelidonMdc {
3040
3141 private static final List <MdcProvider > MDC_PROVIDERS = HelidonServiceLoader
3242 .builder (ServiceLoader .load (MdcProvider .class )).build ().asList ();
3343
44+ private static final ThreadLocal <Map <String , Supplier <String >>> SUPPLIERS = ThreadLocal .withInitial (HashMap ::new );
45+
3446 private HelidonMdc () {
3547 throw new UnsupportedOperationException ("This class cannot be instantiated" );
3648 }
@@ -45,19 +57,45 @@ public static void set(String key, String value) {
4557 MDC_PROVIDERS .forEach (provider -> provider .put (key , value ));
4658 }
4759
60+ /**
61+ * Propagate the value supplier to all {@link MdcProvider} instances registered.
62+ *
63+ * @param key entry key
64+ * @param valueSupplier supplier of the entry value
65+ */
66+ public static void set (String key , Supplier <String > valueSupplier ) {
67+ SUPPLIERS .get ().put (key , valueSupplier );
68+ MDC_PROVIDERS .forEach (provider -> provider .put (key , valueSupplier .get ()));
69+ }
70+
71+ /**
72+ * Sets a value supplier <em>without</em> immediately getting the value and propagating the value to
73+ * underlying logging implementations.
74+ * <p>
75+ * Normally, user code should use {@link #set(String, java.util.function.Supplier)} instead.
76+ *
77+ * @param key entry key
78+ * @param valueSupplier supplier of the entry value
79+ */
80+ public static void setDeferred (String key , Supplier <String > valueSupplier ) {
81+ SUPPLIERS .get ().put (key , valueSupplier );
82+ }
83+
4884 /**
4985 * Remove value with the specific key from all of the instances of {@link MdcProvider}.
5086 *
5187 * @param key key
5288 */
5389 public static void remove (String key ) {
90+ SUPPLIERS .get ().remove (key );
5491 MDC_PROVIDERS .forEach (provider -> provider .remove (key ));
5592 }
5693
5794 /**
5895 * Remove all of the entries bound to the current thread from the instances of {@link MdcProvider}.
5996 */
6097 public static void clear () {
98+ SUPPLIERS .get ().clear ();
6199 MDC_PROVIDERS .forEach (MdcProvider ::clear );
62100 }
63101
@@ -68,10 +106,29 @@ public static void clear() {
68106 * @return found value bound to key
69107 */
70108 public static Optional <String > get (String key ) {
71- return MDC_PROVIDERS .stream ()
72- .map (provider -> provider .get (key ))
73- .filter (Objects ::nonNull )
74- .findFirst ();
109+ /*
110+ User or 3rd-party code might have added values directly to the logger's own context store. So look in other
111+ providers if our data structure cannot resolve the key.
112+ */
113+ return SUPPLIERS .get ().containsKey (key )
114+ ? Optional .of (SUPPLIERS .get ().get (key ).get ())
115+ : MDC_PROVIDERS .stream ()
116+ .map (provider -> provider .get (key ))
117+ .filter (Objects ::nonNull )
118+ .findFirst ();
119+ }
120+
121+ static Map <String , Supplier <String >> suppliers () {
122+ return new HashMap <>(SUPPLIERS .get ());
123+ }
124+
125+ static void suppliers (Map <String , Supplier <String >> suppliers ) {
126+ SUPPLIERS .get ().clear ();
127+ SUPPLIERS .get ().putAll (suppliers );
128+ }
129+
130+ static void clearSuppliers () {
131+ SUPPLIERS .get ().clear ();
75132 }
76133
77134}
0 commit comments