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