1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.logging.log4j.core;
18
19 import static org.apache.logging.log4j.core.util.ShutdownCallbackRegistry.SHUTDOWN_HOOK_MARKER;
20
21 import java.beans.PropertyChangeEvent;
22 import java.beans.PropertyChangeListener;
23 import java.io.File;
24 import java.net.URI;
25 import java.util.Collection;
26 import java.util.List;
27 import java.util.Objects;
28 import java.util.concurrent.ConcurrentHashMap;
29 import java.util.concurrent.ConcurrentMap;
30 import java.util.concurrent.CopyOnWriteArrayList;
31 import java.util.concurrent.TimeUnit;
32 import java.util.concurrent.locks.Lock;
33 import java.util.concurrent.locks.ReentrantLock;
34
35 import org.apache.logging.log4j.LogManager;
36 import org.apache.logging.log4j.core.config.Configuration;
37 import org.apache.logging.log4j.core.config.ConfigurationFactory;
38 import org.apache.logging.log4j.core.config.ConfigurationListener;
39 import org.apache.logging.log4j.core.config.ConfigurationSource;
40 import org.apache.logging.log4j.core.config.DefaultConfiguration;
41 import org.apache.logging.log4j.core.config.NullConfiguration;
42 import org.apache.logging.log4j.core.config.Reconfigurable;
43 import org.apache.logging.log4j.core.impl.Log4jLogEvent;
44 import org.apache.logging.log4j.core.impl.ThreadContextDataInjector;
45 import org.apache.logging.log4j.core.jmx.Server;
46 import org.apache.logging.log4j.core.util.Cancellable;
47 import org.apache.logging.log4j.core.util.ExecutorServices;
48 import org.apache.logging.log4j.core.util.Loader;
49 import org.apache.logging.log4j.core.util.NetUtils;
50 import org.apache.logging.log4j.core.util.ShutdownCallbackRegistry;
51 import org.apache.logging.log4j.message.MessageFactory;
52 import org.apache.logging.log4j.spi.AbstractLogger;
53 import org.apache.logging.log4j.spi.LoggerContextFactory;
54 import org.apache.logging.log4j.spi.LoggerContextShutdownAware;
55 import org.apache.logging.log4j.spi.LoggerContextShutdownEnabled;
56 import org.apache.logging.log4j.spi.LoggerRegistry;
57 import org.apache.logging.log4j.spi.Terminable;
58 import org.apache.logging.log4j.spi.ThreadContextMapFactory;
59 import org.apache.logging.log4j.util.PropertiesUtil;
60
61
62
63
64
65
66
67 public class LoggerContext extends AbstractLifeCycle
68 implements org.apache.logging.log4j.spi.LoggerContext, AutoCloseable, Terminable, ConfigurationListener,
69 LoggerContextShutdownEnabled {
70
71 static {
72 try {
73
74 Loader.loadClass(ExecutorServices.class.getName());
75 } catch (final Exception e) {
76 LOGGER.error("Failed to preload ExecutorServices class.", e);
77 }
78 }
79
80
81
82
83 public static final String PROPERTY_CONFIG = "config";
84
85 private static final Configuration NULL_CONFIGURATION = new NullConfiguration();
86
87 private final LoggerRegistry<Logger> loggerRegistry = new LoggerRegistry<>();
88 private final CopyOnWriteArrayList<PropertyChangeListener> propertyChangeListeners = new CopyOnWriteArrayList<>();
89 private volatile List<LoggerContextShutdownAware> listeners;
90
91
92
93
94
95 private volatile Configuration configuration = new DefaultConfiguration();
96 private static final String EXTERNAL_CONTEXT_KEY = "__EXTERNAL_CONTEXT_KEY__";
97 private ConcurrentMap<String, Object> externalMap = new ConcurrentHashMap<>();
98 private String contextName;
99 private volatile URI configLocation;
100 private Cancellable shutdownCallback;
101
102 private final Lock configLock = new ReentrantLock();
103
104
105
106
107
108
109 public LoggerContext(final String name) {
110 this(name, null, (URI) null);
111 }
112
113
114
115
116
117
118
119 public LoggerContext(final String name, final Object externalContext) {
120 this(name, externalContext, (URI) null);
121 }
122
123
124
125
126
127
128
129
130 public LoggerContext(final String name, final Object externalContext, final URI configLocn) {
131 this.contextName = name;
132 if (externalContext == null) {
133 externalMap.remove(EXTERNAL_CONTEXT_KEY);
134 } else {
135 externalMap.put(EXTERNAL_CONTEXT_KEY, externalContext);
136 }
137 this.configLocation = configLocn;
138 Thread runner = new Thread(new ThreadContextDataTask(), "Thread Context Data Task");
139 runner.setDaemon(true);
140 runner.start();
141 }
142
143
144
145
146
147
148
149
150
151 public LoggerContext(final String name, final Object externalContext, final String configLocn) {
152 this.contextName = name;
153 if (externalContext == null) {
154 externalMap.remove(EXTERNAL_CONTEXT_KEY);
155 } else {
156 externalMap.put(EXTERNAL_CONTEXT_KEY, externalContext);
157 }
158 if (configLocn != null) {
159 URI uri;
160 try {
161 uri = new File(configLocn).toURI();
162 } catch (final Exception ex) {
163 uri = null;
164 }
165 configLocation = uri;
166 } else {
167 configLocation = null;
168 }
169 Thread runner = new Thread(new ThreadContextDataTask(), "Thread Context Data Task");
170 runner.setDaemon(true);
171 runner.start();
172 }
173
174 @Override
175 public void addShutdownListener(LoggerContextShutdownAware listener) {
176 if (listeners == null) {
177 synchronized(this) {
178 if (listeners == null) {
179 listeners = new CopyOnWriteArrayList<LoggerContextShutdownAware>();
180 }
181 }
182 }
183 listeners.add(listener);
184 }
185
186 @Override
187 public List<LoggerContextShutdownAware> getListeners() {
188 return listeners;
189 }
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209 public static LoggerContext getContext() {
210 return (LoggerContext) LogManager.getContext();
211 }
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230 public static LoggerContext getContext(final boolean currentContext) {
231 return (LoggerContext) LogManager.getContext(currentContext);
232 }
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254 public static LoggerContext getContext(final ClassLoader loader, final boolean currentContext,
255 final URI configLocation) {
256 return (LoggerContext) LogManager.getContext(loader, currentContext, configLocation);
257 }
258
259 @Override
260 public void start() {
261 LOGGER.debug("Starting LoggerContext[name={}, {}]...", getName(), this);
262 if (PropertiesUtil.getProperties().getBooleanProperty("log4j.LoggerContext.stacktrace.on.start", false)) {
263 LOGGER.debug("Stack trace to locate invoker",
264 new Exception("Not a real error, showing stack trace to locate invoker"));
265 }
266 if (configLock.tryLock()) {
267 try {
268 if (this.isInitialized() || this.isStopped()) {
269 this.setStarting();
270 reconfigure();
271 if (this.configuration.isShutdownHookEnabled()) {
272 setUpShutdownHook();
273 }
274 this.setStarted();
275 }
276 } finally {
277 configLock.unlock();
278 }
279 }
280 LOGGER.debug("LoggerContext[name={}, {}] started OK.", getName(), this);
281 }
282
283
284
285
286
287
288 public void start(final Configuration config) {
289 LOGGER.debug("Starting LoggerContext[name={}, {}] with configuration {}...", getName(), this, config);
290 if (configLock.tryLock()) {
291 try {
292 if (this.isInitialized() || this.isStopped()) {
293 if (this.configuration.isShutdownHookEnabled()) {
294 setUpShutdownHook();
295 }
296 this.setStarted();
297 }
298 } finally {
299 configLock.unlock();
300 }
301 }
302 setConfiguration(config);
303 LOGGER.debug("LoggerContext[name={}, {}] started OK with configuration {}.", getName(), this, config);
304 }
305
306 private void setUpShutdownHook() {
307 if (shutdownCallback == null) {
308 final LoggerContextFactory factory = LogManager.getFactory();
309 if (factory instanceof ShutdownCallbackRegistry) {
310 LOGGER.debug(SHUTDOWN_HOOK_MARKER, "Shutdown hook enabled. Registering a new one.");
311 try {
312 final long shutdownTimeoutMillis = this.configuration.getShutdownTimeoutMillis();
313 this.shutdownCallback = ((ShutdownCallbackRegistry) factory).addShutdownCallback(new Runnable() {
314 @Override
315 public void run() {
316 @SuppressWarnings("resource")
317 final LoggerContext context = LoggerContext.this;
318 LOGGER.debug(SHUTDOWN_HOOK_MARKER, "Stopping LoggerContext[name={}, {}]",
319 context.getName(), context);
320 context.stop(shutdownTimeoutMillis, TimeUnit.MILLISECONDS);
321 }
322
323 @Override
324 public String toString() {
325 return "Shutdown callback for LoggerContext[name=" + LoggerContext.this.getName() + ']';
326 }
327 });
328 } catch (final IllegalStateException e) {
329 throw new IllegalStateException(
330 "Unable to register Log4j shutdown hook because JVM is shutting down.", e);
331 } catch (final SecurityException e) {
332 LOGGER.error(SHUTDOWN_HOOK_MARKER, "Unable to register shutdown hook due to security restrictions",
333 e);
334 }
335 }
336 }
337 }
338
339 @Override
340 public void close() {
341 stop();
342 }
343
344 @Override
345 public void terminate() {
346 stop();
347 }
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368 @Override
369 public boolean stop(final long timeout, final TimeUnit timeUnit) {
370 LOGGER.debug("Stopping LoggerContext[name={}, {}]...", getName(), this);
371 configLock.lock();
372 try {
373 if (this.isStopped()) {
374 return true;
375 }
376
377 this.setStopping();
378 try {
379 Server.unregisterLoggerContext(getName());
380 } catch (final LinkageError | Exception e) {
381
382 LOGGER.error("Unable to unregister MBeans", e);
383 }
384 if (shutdownCallback != null) {
385 shutdownCallback.cancel();
386 shutdownCallback = null;
387 }
388 final Configuration prev = configuration;
389 configuration = NULL_CONFIGURATION;
390 updateLoggers();
391 if (prev instanceof LifeCycle2) {
392 ((LifeCycle2) prev).stop(timeout, timeUnit);
393 } else {
394 prev.stop();
395 }
396 externalMap.clear();
397 LogManager.getFactory().removeContext(this);
398 } finally {
399 configLock.unlock();
400 this.setStopped();
401 }
402 if (listeners != null) {
403 for (LoggerContextShutdownAware listener : listeners) {
404 try {
405 listener.contextShutdown(this);
406 } catch (Exception ex) {
407
408 }
409 }
410 }
411 LOGGER.debug("Stopped LoggerContext[name={}, {}] with status {}", getName(), this, true);
412 return true;
413 }
414
415
416
417
418
419
420 public String getName() {
421 return contextName;
422 }
423
424
425
426
427
428
429 public Logger getRootLogger() {
430 return getLogger(LogManager.ROOT_LOGGER_NAME);
431 }
432
433
434
435
436
437
438
439 public void setName(final String name) {
440 contextName = Objects.requireNonNull(name);
441 }
442
443 @Override
444 public Object getObject(String key) {
445 return externalMap.get(key);
446 }
447
448 @Override
449 public Object putObject(String key, Object value) {
450 return externalMap.put(key, value);
451 }
452
453 @Override
454 public Object putObjectIfAbsent(String key, Object value) {
455 return externalMap.putIfAbsent(key, value);
456 }
457
458 @Override
459 public Object removeObject(String key) {
460 return externalMap.remove(key);
461 }
462
463 @Override
464 public boolean removeObject(String key, Object value) {
465 return externalMap.remove(key, value);
466 }
467
468
469
470
471
472
473 public void setExternalContext(final Object context) {
474 if (context != null) {
475 this.externalMap.put(EXTERNAL_CONTEXT_KEY, context);
476 } else {
477 this.externalMap.remove(EXTERNAL_CONTEXT_KEY);
478 }
479 }
480
481
482
483
484
485
486 @Override
487 public Object getExternalContext() {
488 return this.externalMap.get(EXTERNAL_CONTEXT_KEY);
489 }
490
491
492
493
494
495
496
497 @Override
498 public Logger getLogger(final String name) {
499 return getLogger(name, null);
500 }
501
502
503
504
505
506
507
508
509
510
511 public Collection<Logger> getLoggers() {
512 return loggerRegistry.getLoggers();
513 }
514
515
516
517
518
519
520
521
522
523 @Override
524 public Logger getLogger(final String name, final MessageFactory messageFactory) {
525
526 Logger logger = loggerRegistry.getLogger(name, messageFactory);
527 if (logger != null) {
528 AbstractLogger.checkMessageFactory(logger, messageFactory);
529 return logger;
530 }
531
532 logger = newInstance(this, name, messageFactory);
533 loggerRegistry.putIfAbsent(name, messageFactory, logger);
534 return loggerRegistry.getLogger(name, messageFactory);
535 }
536
537
538
539
540
541
542
543 @Override
544 public boolean hasLogger(final String name) {
545 return loggerRegistry.hasLogger(name);
546 }
547
548
549
550
551
552
553
554 @Override
555 public boolean hasLogger(final String name, final MessageFactory messageFactory) {
556 return loggerRegistry.hasLogger(name, messageFactory);
557 }
558
559
560
561
562
563
564
565 @Override
566 public boolean hasLogger(final String name, final Class<? extends MessageFactory> messageFactoryClass) {
567 return loggerRegistry.hasLogger(name, messageFactoryClass);
568 }
569
570
571
572
573
574
575
576 public Configuration getConfiguration() {
577 return configuration;
578 }
579
580
581
582
583
584
585
586 public void addFilter(final Filter filter) {
587 configuration.addFilter(filter);
588 }
589
590
591
592
593
594
595 public void removeFilter(final Filter filter) {
596 configuration.removeFilter(filter);
597 }
598
599
600
601
602
603
604
605 public Configuration setConfiguration(final Configuration config) {
606 if (config == null) {
607 LOGGER.error("No configuration found for context '{}'.", contextName);
608
609 return this.configuration;
610 }
611 configLock.lock();
612 try {
613 final Configuration prev = this.configuration;
614 config.addListener(this);
615
616 final ConcurrentMap<String, String> map = config.getComponent(Configuration.CONTEXT_PROPERTIES);
617
618 try {
619
620 map.computeIfAbsent("hostName", s -> NetUtils.getLocalHostname());
621 } catch (final Exception ex) {
622 LOGGER.debug("Ignoring {}, setting hostName to 'unknown'", ex.toString());
623 map.putIfAbsent("hostName", "unknown");
624 }
625 map.putIfAbsent("contextName", contextName);
626 config.start();
627 this.configuration = config;
628 updateLoggers();
629 if (prev != null) {
630 prev.removeListener(this);
631 prev.stop();
632 }
633
634 firePropertyChangeEvent(new PropertyChangeEvent(this, PROPERTY_CONFIG, prev, config));
635
636 try {
637 Server.reregisterMBeansAfterReconfigure();
638 } catch (final LinkageError | Exception e) {
639
640 LOGGER.error("Could not reconfigure JMX", e);
641 }
642
643 Log4jLogEvent.setNanoClock(configuration.getNanoClock());
644
645 return prev;
646 } finally {
647 configLock.unlock();
648 }
649 }
650
651 private void firePropertyChangeEvent(final PropertyChangeEvent event) {
652 for (final PropertyChangeListener listener : propertyChangeListeners) {
653 listener.propertyChange(event);
654 }
655 }
656
657 public void addPropertyChangeListener(final PropertyChangeListener listener) {
658 propertyChangeListeners.add(Objects.requireNonNull(listener, "listener"));
659 }
660
661 public void removePropertyChangeListener(final PropertyChangeListener listener) {
662 propertyChangeListeners.remove(listener);
663 }
664
665
666
667
668
669
670
671
672
673 public URI getConfigLocation() {
674 return configLocation;
675 }
676
677
678
679
680
681
682 public void setConfigLocation(final URI configLocation) {
683 this.configLocation = configLocation;
684 reconfigure(configLocation);
685 }
686
687
688
689
690 private void reconfigure(final URI configURI) {
691 Object externalContext = externalMap.get(EXTERNAL_CONTEXT_KEY);
692 final ClassLoader cl = ClassLoader.class.isInstance(externalContext) ? (ClassLoader) externalContext : null;
693 LOGGER.debug("Reconfiguration started for context[name={}] at URI {} ({}) with optional ClassLoader: {}",
694 contextName, configURI, this, cl);
695 final Configuration instance = ConfigurationFactory.getInstance().getConfiguration(this, contextName, configURI, cl);
696 if (instance == null) {
697 LOGGER.error("Reconfiguration failed: No configuration found for '{}' at '{}' in '{}'", contextName, configURI, cl);
698 } else {
699 setConfiguration(instance);
700
701
702
703
704 final String location = configuration == null ? "?" : String.valueOf(configuration.getConfigurationSource());
705 LOGGER.debug("Reconfiguration complete for context[name={}] at URI {} ({}) with optional ClassLoader: {}",
706 contextName, location, this, cl);
707 }
708 }
709
710
711
712
713
714
715 public void reconfigure() {
716 reconfigure(configLocation);
717 }
718
719 public void reconfigure(Configuration configuration) {
720 setConfiguration(configuration);
721 ConfigurationSource source = configuration.getConfigurationSource();
722 if (source != null) {
723 URI uri = source.getURI();
724 if (uri != null) {
725 configLocation = uri;
726 }
727 }
728 }
729
730
731
732
733 public void updateLoggers() {
734 updateLoggers(this.configuration);
735 }
736
737
738
739
740
741
742 public void updateLoggers(final Configuration config) {
743 final Configuration old = this.configuration;
744 for (final Logger logger : loggerRegistry.getLoggers()) {
745 logger.updateConfiguration(config);
746 }
747 firePropertyChangeEvent(new PropertyChangeEvent(this, PROPERTY_CONFIG, old, config));
748 }
749
750
751
752
753
754
755 @Override
756 public synchronized void onChange(final Reconfigurable reconfigurable) {
757 final long startMillis = System.currentTimeMillis();
758 LOGGER.debug("Reconfiguration started for context {} ({})", contextName, this);
759 initApiModule();
760 final Configuration newConfig = reconfigurable.reconfigure();
761 if (newConfig != null) {
762 setConfiguration(newConfig);
763 LOGGER.debug("Reconfiguration completed for {} ({}) in {} milliseconds.", contextName, this,
764 System.currentTimeMillis() - startMillis);
765 } else {
766 LOGGER.debug("Reconfiguration failed for {} ({}) in {} milliseconds.", contextName, this,
767 System.currentTimeMillis() - startMillis);
768 }
769 }
770
771 private void initApiModule() {
772 ThreadContextMapFactory.init();
773 }
774
775
776 protected Logger newInstance(final LoggerContext ctx, final String name, final MessageFactory messageFactory) {
777 return new Logger(ctx, name, messageFactory);
778 }
779
780 private class ThreadContextDataTask implements Runnable {
781
782 @Override
783 public void run() {
784 LOGGER.debug("Initializing Thread Context Data Service Providers");
785 ThreadContextDataInjector.initServiceProviders();
786 LOGGER.debug("Thread Context Data Service Provider initialization complete");
787 }
788 }
789 }