|
22 | 22 | import java.util.Collection;
|
23 | 23 | import java.util.HashMap;
|
24 | 24 | import java.util.Map;
|
25 |
| -import java.util.Optional; |
26 | 25 | import java.util.WeakHashMap;
|
27 | 26 | import java.util.concurrent.locks.Lock;
|
28 | 27 | import java.util.concurrent.locks.ReadWriteLock;
|
|
31 | 30 | import java.util.stream.Collectors;
|
32 | 31 | import java.util.stream.Stream;
|
33 | 32 | import org.apache.logging.log4j.core.Logger;
|
| 33 | +import org.apache.logging.log4j.core.LoggerContext; |
34 | 34 | import org.apache.logging.log4j.message.MessageFactory;
|
35 | 35 | import org.apache.logging.log4j.status.StatusLogger;
|
36 | 36 | import org.jspecify.annotations.NullMarked;
|
37 | 37 | import org.jspecify.annotations.Nullable;
|
38 | 38 |
|
39 | 39 | /**
|
40 |
| - * Convenience class used by {@link org.apache.logging.log4j.core.LoggerContext} |
| 40 | + * A registry of {@link Logger}s namespaced by name and message factory. |
| 41 | + * This class is internally used by {@link LoggerContext}. |
41 | 42 | * <p>
|
42 |
| - * We don't use {@link org.apache.logging.log4j.spi.LoggerRegistry} from the Log4j API to keep Log4j Core independent |
43 |
| - * from the version of the Log4j API at runtime. |
| 43 | + * We don't use {@linkplain org.apache.logging.log4j.spi.LoggerRegistry the registry from Log4j API} to keep Log4j Core independent from the version of Log4j API at runtime. |
| 44 | + * This also allows Log4j Core to evolve independently from Log4j API. |
44 | 45 | * </p>
|
| 46 | + * |
45 | 47 | * @since 2.25.0
|
46 | 48 | */
|
47 | 49 | @NullMarked
|
@@ -70,11 +72,15 @@ public InternalLoggerRegistry() {}
|
70 | 72 | requireNonNull(messageFactory, "messageFactory");
|
71 | 73 | readLock.lock();
|
72 | 74 | try {
|
73 |
| - return Optional.of(loggerRefByNameByMessageFactory) |
74 |
| - .map(loggerRefByNameByMessageFactory -> loggerRefByNameByMessageFactory.get(messageFactory)) |
75 |
| - .map(loggerRefByName -> loggerRefByName.get(name)) |
76 |
| - .map(WeakReference::get) |
77 |
| - .orElse(null); |
| 75 | + final Map<String, WeakReference<Logger>> loggerRefByName = |
| 76 | + loggerRefByNameByMessageFactory.get(messageFactory); |
| 77 | + if (loggerRefByName != null) { |
| 78 | + final WeakReference<Logger> loggerRef = loggerRefByName.get(name); |
| 79 | + if (loggerRef != null) { |
| 80 | + return loggerRef.get(); |
| 81 | + } |
| 82 | + } |
| 83 | + return null; |
78 | 84 | } finally {
|
79 | 85 | readLock.unlock();
|
80 | 86 | }
|
@@ -147,43 +153,51 @@ public Logger computeIfAbsent(
|
147 | 153 | return logger;
|
148 | 154 | }
|
149 | 155 |
|
| 156 | + // Intentionally moving the logger creation outside the write lock, because: |
| 157 | + // |
| 158 | + // - Logger instantiation is expensive (causes contention on the write-lock) |
| 159 | + // |
| 160 | + // - User code might have circular code paths, though through different threads. |
| 161 | + // Consider `T1[ILR:computeIfAbsent] -> ... -> T1[Logger::new] -> ... -> T2[ILR::computeIfAbsent]`. |
| 162 | + // Hence, having logger instantiation while holding a write lock might cause deadlocks: |
| 163 | + // https://github.com/apache/logging-log4j2/issues/3252 |
| 164 | + // https://github.com/apache/logging-log4j2/issues/3399 |
| 165 | + // |
| 166 | + // - Creating loggers without a lock, allows multiple threads to create loggers in parallel, which also improves |
| 167 | + // performance. |
| 168 | + // |
| 169 | + // Since all loggers with the same parameters are equivalent, we can safely return the logger from the |
| 170 | + // thread that finishes first. |
| 171 | + Logger newLogger = loggerSupplier.apply(name, messageFactory); |
| 172 | + |
| 173 | + // Report name and message factory mismatch if there are any |
| 174 | + final String loggerName = newLogger.getName(); |
| 175 | + final MessageFactory loggerMessageFactory = newLogger.getMessageFactory(); |
| 176 | + if (!loggerName.equals(name) || !loggerMessageFactory.equals(messageFactory)) { |
| 177 | + StatusLogger.getLogger() |
| 178 | + .error( |
| 179 | + "Newly registered logger with name `{}` and message factory `{}`, is requested to be associated with a different name `{}` or message factory `{}`.\n" |
| 180 | + + "Effectively the message factory of the logger will be used and the other one will be ignored.\n" |
| 181 | + + "This generally hints a problem at the logger context implementation.\n" |
| 182 | + + "Please report this using the Log4j project issue tracker.", |
| 183 | + loggerName, |
| 184 | + loggerMessageFactory, |
| 185 | + name, |
| 186 | + messageFactory); |
| 187 | + } |
| 188 | + |
150 | 189 | // Write lock slow path: Insert the logger
|
151 | 190 | writeLock.lock();
|
152 | 191 | try {
|
153 |
| - |
154 |
| - // See if the logger is created by another thread in the meantime |
155 |
| - final Map<String, WeakReference<Logger>> loggerRefByName = |
156 |
| - loggerRefByNameByMessageFactory.computeIfAbsent(messageFactory, ignored -> new HashMap<>()); |
157 |
| - WeakReference<Logger> loggerRef = loggerRefByName.get(name); |
158 |
| - if (loggerRef != null && (logger = loggerRef.get()) != null) { |
159 |
| - return logger; |
| 192 | + Map<String, WeakReference<Logger>> loggerRefByName = loggerRefByNameByMessageFactory.get(messageFactory); |
| 193 | + // noinspection Java8MapApi (avoid the allocation of lambda passed to `Map::computeIfAbsent`) |
| 194 | + if (loggerRefByName == null) { |
| 195 | + loggerRefByNameByMessageFactory.put(messageFactory, loggerRefByName = new HashMap<>()); |
160 | 196 | }
|
161 |
| - |
162 |
| - // Create the logger |
163 |
| - logger = loggerSupplier.apply(name, messageFactory); |
164 |
| - |
165 |
| - // Report name and message factory mismatch if there are any |
166 |
| - final String loggerName = logger.getName(); |
167 |
| - final MessageFactory loggerMessageFactory = logger.getMessageFactory(); |
168 |
| - if (!loggerMessageFactory.equals(messageFactory)) { |
169 |
| - StatusLogger.getLogger() |
170 |
| - .error( |
171 |
| - "Newly registered logger with name `{}` and message factory `{}`, is requested to be associated with a different name `{}` or message factory `{}`.\n" |
172 |
| - + "Effectively the message factory of the logger will be used and the other one will be ignored.\n" |
173 |
| - + "This generally hints a problem at the logger context implementation.\n" |
174 |
| - + "Please report this using the Log4j project issue tracker.", |
175 |
| - loggerName, |
176 |
| - loggerMessageFactory, |
177 |
| - name, |
178 |
| - messageFactory); |
179 |
| - // Register logger under alternative keys |
180 |
| - loggerRefByNameByMessageFactory |
181 |
| - .computeIfAbsent(loggerMessageFactory, ignored -> new HashMap<>()) |
182 |
| - .putIfAbsent(loggerName, new WeakReference<>(logger)); |
| 197 | + final WeakReference<Logger> loggerRef = loggerRefByName.get(name); |
| 198 | + if (loggerRef == null || (logger = loggerRef.get()) == null) { |
| 199 | + loggerRefByName.put(name, new WeakReference<>(logger = newLogger)); |
183 | 200 | }
|
184 |
| - |
185 |
| - // Insert the logger |
186 |
| - loggerRefByName.put(name, new WeakReference<>(logger)); |
187 | 201 | return logger;
|
188 | 202 | } finally {
|
189 | 203 | writeLock.unlock();
|
|
0 commit comments