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