001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache license, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the license for the specific language governing permissions and
015 * limitations under the license.
016 */
017 package org.apache.logging.log4j.core.config;
018
019 import java.io.ByteArrayOutputStream;
020 import java.io.IOException;
021 import java.io.InputStream;
022 import java.io.Serializable;
023 import java.util.ArrayList;
024 import java.util.Collection;
025 import java.util.Collections;
026 import java.util.HashSet;
027 import java.util.LinkedHashMap;
028 import java.util.List;
029 import java.util.Map;
030 import java.util.Set;
031 import java.util.concurrent.ConcurrentHashMap;
032 import java.util.concurrent.ConcurrentMap;
033 import java.util.concurrent.CopyOnWriteArrayList;
034
035 import org.apache.logging.log4j.Level;
036 import org.apache.logging.log4j.LogManager;
037 import org.apache.logging.log4j.core.Appender;
038 import org.apache.logging.log4j.core.Filter;
039 import org.apache.logging.log4j.core.Layout;
040 import org.apache.logging.log4j.core.LogEvent;
041 import org.apache.logging.log4j.core.appender.AsyncAppender;
042 import org.apache.logging.log4j.core.appender.ConsoleAppender;
043 import org.apache.logging.log4j.core.async.AsyncLoggerConfig;
044 import org.apache.logging.log4j.core.async.AsyncLoggerContextSelector;
045 import org.apache.logging.log4j.core.config.plugins.util.PluginBuilder;
046 import org.apache.logging.log4j.core.config.plugins.util.PluginManager;
047 import org.apache.logging.log4j.core.config.plugins.util.PluginType;
048 import org.apache.logging.log4j.core.filter.AbstractFilterable;
049 import org.apache.logging.log4j.core.impl.Log4jContextFactory;
050 import org.apache.logging.log4j.core.layout.PatternLayout;
051 import org.apache.logging.log4j.core.lookup.Interpolator;
052 import org.apache.logging.log4j.core.lookup.MapLookup;
053 import org.apache.logging.log4j.core.lookup.StrLookup;
054 import org.apache.logging.log4j.core.lookup.StrSubstitutor;
055 import org.apache.logging.log4j.core.net.Advertiser;
056 import org.apache.logging.log4j.core.selector.ContextSelector;
057 import org.apache.logging.log4j.core.util.Assert;
058 import org.apache.logging.log4j.core.util.Constants;
059 import org.apache.logging.log4j.core.util.Loader;
060 import org.apache.logging.log4j.core.util.NameUtil;
061 import org.apache.logging.log4j.spi.LoggerContextFactory;
062 import org.apache.logging.log4j.util.PropertiesUtil;
063
064 /**
065 * The base Configuration. Many configuration implementations will extend this class.
066 */
067 public abstract class AbstractConfiguration extends AbstractFilterable implements Configuration {
068
069 private static final long serialVersionUID = 1L;
070
071 private static final int BUF_SIZE = 16384;
072
073 /**
074 * The root node of the configuration.
075 */
076 protected Node rootNode;
077
078 /**
079 * Listeners for configuration changes.
080 */
081 protected final List<ConfigurationListener> listeners = new CopyOnWriteArrayList<ConfigurationListener>();
082
083 /**
084 * The ConfigurationMonitor that checks for configuration changes.
085 */
086 protected ConfigurationMonitor monitor = new DefaultConfigurationMonitor();
087
088 /**
089 * The Advertiser which exposes appender configurations to external systems.
090 */
091 private Advertiser advertiser = new DefaultAdvertiser();
092 private Node advertiserNode = null;
093 private Object advertisement;
094
095 /**
096 *
097 */
098 protected boolean isShutdownHookEnabled = true;
099 private String name;
100 private ConcurrentMap<String, Appender> appenders = new ConcurrentHashMap<String, Appender>();
101 private ConcurrentMap<String, LoggerConfig> loggers = new ConcurrentHashMap<String, LoggerConfig>();
102 private List<CustomLevelConfig> customLevels = Collections.emptyList();
103 private final ConcurrentMap<String, String> properties = new ConcurrentHashMap<String, String>();
104 private final StrLookup tempLookup = new Interpolator(properties);
105 private final StrSubstitutor subst = new StrSubstitutor(tempLookup);
106 private LoggerConfig root = new LoggerConfig();
107 private final ConcurrentMap<String, Object> componentMap = new ConcurrentHashMap<String, Object>();
108 protected final List<String> pluginPackages = new ArrayList<String>();
109 protected PluginManager pluginManager;
110 private final ConfigurationSource configurationSource;
111
112 /**
113 * Constructor.
114 */
115 protected AbstractConfiguration(final ConfigurationSource configurationSource) {
116 this.configurationSource = Assert.requireNonNull(configurationSource, "configurationSource is null");
117 componentMap.put(Configuration.CONTEXT_PROPERTIES, properties);
118 pluginManager = new PluginManager(Node.CATEGORY);
119 rootNode = new Node();
120 }
121
122 @Override
123 public ConfigurationSource getConfigurationSource() {
124 return configurationSource;
125 }
126
127 @Override
128 public List<String> getPluginPackages() {
129 return pluginPackages;
130 }
131
132 @Override
133 public Map<String, String> getProperties() {
134 return properties;
135 }
136
137 /**
138 * Initialize the configuration.
139 */
140 @Override
141 public void start() {
142 LOGGER.debug("Starting configuration {}", this);
143 this.setStarting();
144 pluginManager.collectPlugins(pluginPackages);
145 final PluginManager levelPlugins = new PluginManager(Level.CATEGORY);
146 levelPlugins.collectPlugins(pluginPackages);
147 final Map<String, PluginType<?>> plugins = levelPlugins.getPlugins();
148 if (plugins != null) {
149 for (final PluginType<?> type : plugins.values()) {
150 try {
151 // Cause the class to be initialized if it isn't already.
152 Loader.initializeClass(type.getPluginClass().getName(), type.getPluginClass().getClassLoader());
153 } catch (final Exception e) {
154 LOGGER.error("Unable to initialize {} due to {}", type.getPluginClass().getName(), e.getClass()
155 .getSimpleName(), e);
156 }
157 }
158 }
159 setup();
160 setupAdvertisement();
161 doConfigure();
162 final Set<LoggerConfig> alreadyStarted = new HashSet<LoggerConfig>();
163 for (final LoggerConfig logger : loggers.values()) {
164 logger.start();
165 alreadyStarted.add(logger);
166 }
167 for (final Appender appender : appenders.values()) {
168 appender.start();
169 }
170 if (!alreadyStarted.contains(root)) { // LOG4J2-392
171 root.start(); // LOG4J2-336
172 }
173 super.start();
174 LOGGER.debug("Started configuration {} OK.", this);
175 }
176
177 /**
178 * Tear down the configuration.
179 */
180 @Override
181 public void stop() {
182 this.setStopping();
183 LOGGER.trace("Stopping {}...", this);
184
185 // LOG4J2-392 first stop AsyncLogger Disruptor thread
186 final LoggerContextFactory factory = LogManager.getFactory();
187 if (factory instanceof Log4jContextFactory) {
188 final ContextSelector selector = ((Log4jContextFactory) factory).getSelector();
189 if (selector instanceof AsyncLoggerContextSelector) { // all loggers are async
190 // TODO until LOG4J2-493 is fixed we can only stop AsyncLogger once!
191 // but LoggerContext.setConfiguration will call config.stop()
192 // every time the configuration changes...
193 //
194 // Uncomment the line below after LOG4J2-493 is fixed
195 //AsyncLogger.stop();
196 //LOGGER.trace("AbstractConfiguration stopped AsyncLogger disruptor.");
197 }
198 }
199 // similarly, first stop AsyncLoggerConfig Disruptor thread(s)
200 final Set<LoggerConfig> alreadyStopped = new HashSet<LoggerConfig>();
201 int asyncLoggerConfigCount = 0;
202 for (final LoggerConfig logger : loggers.values()) {
203 if (logger instanceof AsyncLoggerConfig) {
204 // LOG4J2-520, LOG4J2-392:
205 // Important: do not clear appenders until after all AsyncLoggerConfigs
206 // have been stopped! Stopping the last AsyncLoggerConfig will
207 // shut down the disruptor and wait for all enqueued events to be processed.
208 // Only *after this* the appenders can be cleared or events will be lost.
209 logger.stop();
210 asyncLoggerConfigCount++;
211 alreadyStopped.add(logger);
212 }
213 }
214 if (root instanceof AsyncLoggerConfig & !alreadyStopped.contains(root)) { // LOG4J2-807
215 root.stop();
216 asyncLoggerConfigCount++;
217 alreadyStopped.add(root);
218 }
219 LOGGER.trace("AbstractConfiguration stopped {} AsyncLoggerConfigs.", asyncLoggerConfigCount);
220
221 // Stop the appenders in reverse order in case they still have activity.
222 final Appender[] array = appenders.values().toArray(new Appender[appenders.size()]);
223
224 // LOG4J2-511, LOG4J2-392 stop AsyncAppenders first
225 int asyncAppenderCount = 0;
226 for (int i = array.length - 1; i >= 0; --i) {
227 if (array[i] instanceof AsyncAppender) {
228 array[i].stop();
229 asyncAppenderCount++;
230 }
231 }
232 LOGGER.trace("AbstractConfiguration stopped {} AsyncAppenders.", asyncAppenderCount);
233
234 int appenderCount = 0;
235 for (int i = array.length - 1; i >= 0; --i) {
236 if (array[i].isStarted()) { // then stop remaining Appenders
237 array[i].stop();
238 appenderCount++;
239 }
240 }
241 LOGGER.trace("AbstractConfiguration stopped {} Appenders.", appenderCount);
242
243 int loggerCount = 0;
244 for (final LoggerConfig logger : loggers.values()) {
245 // clear appenders, even if this logger is already stopped.
246 logger.clearAppenders();
247
248 // AsyncLoggerConfigHelper decreases its ref count when an AsyncLoggerConfig is stopped.
249 // Stopping the same AsyncLoggerConfig twice results in an incorrect ref count and
250 // the shared Disruptor may be shut down prematurely, resulting in NPE or other errors.
251 if (alreadyStopped.contains(logger)) {
252 continue;
253 }
254 logger.stop();
255 loggerCount++;
256 }
257 LOGGER.trace("AbstractConfiguration stopped {} Loggers.", loggerCount);
258
259 // AsyncLoggerConfigHelper decreases its ref count when an AsyncLoggerConfig is stopped.
260 // Stopping the same AsyncLoggerConfig twice results in an incorrect ref count and
261 // the shared Disruptor may be shut down prematurely, resulting in NPE or other errors.
262 if (!alreadyStopped.contains(root)) {
263 root.stop();
264 }
265 super.stop();
266 if (advertiser != null && advertisement != null) {
267 advertiser.unadvertise(advertisement);
268 }
269 LOGGER.debug("Stopped {} OK", this);
270 }
271
272 @Override
273 public boolean isShutdownHookEnabled() {
274 return isShutdownHookEnabled;
275 }
276
277 protected void setup() {
278 }
279
280 protected Level getDefaultStatus() {
281 final String statusLevel = PropertiesUtil.getProperties().getStringProperty(Constants.LOG4J_DEFAULT_STATUS_LEVEL,
282 Level.ERROR.name());
283 try {
284 return Level.toLevel(statusLevel);
285 } catch (final Exception ex) {
286 return Level.ERROR;
287 }
288 }
289
290 protected void createAdvertiser(final String advertiserString, final ConfigurationSource configSource,
291 final byte[] buffer, final String contentType) {
292 if (advertiserString != null) {
293 final Node node = new Node(null, advertiserString, null);
294 final Map<String, String> attributes = node.getAttributes();
295 attributes.put("content", new String(buffer));
296 attributes.put("contentType", contentType);
297 attributes.put("name", "configuration");
298 if (configSource.getLocation() != null) {
299 attributes.put("location", configSource.getLocation());
300 }
301 advertiserNode = node;
302 }
303 }
304
305 private void setupAdvertisement() {
306 if (advertiserNode != null)
307 {
308 final String name = advertiserNode.getName();
309 final PluginType<?> type = pluginManager.getPluginType(name);
310 if (type != null)
311 {
312 final Class<? extends Advertiser> clazz = type.getPluginClass().asSubclass(Advertiser.class);
313 try {
314 advertiser = clazz.newInstance();
315 advertisement = advertiser.advertise(advertiserNode.getAttributes());
316 } catch (final InstantiationException e) {
317 LOGGER.error("InstantiationException attempting to instantiate advertiser: {}", name, e);
318 } catch (final IllegalAccessException e) {
319 LOGGER.error("IllegalAccessException attempting to instantiate advertiser: {}", name, e);
320 }
321 }
322 }
323 }
324
325 @SuppressWarnings("unchecked")
326 @Override
327 public <T> T getComponent(final String name) {
328 return (T) componentMap.get(name);
329 }
330
331 @Override
332 public void addComponent(final String name, final Object obj) {
333 componentMap.putIfAbsent(name, obj);
334 }
335
336 protected void doConfigure() {
337 if (rootNode.hasChildren() && rootNode.getChildren().get(0).getName().equalsIgnoreCase("Properties")) {
338 final Node first = rootNode.getChildren().get(0);
339 createConfiguration(first, null);
340 if (first.getObject() != null) {
341 subst.setVariableResolver((StrLookup) first.getObject());
342 }
343 } else {
344 final Map<String, String> map = this.getComponent(CONTEXT_PROPERTIES);
345 final StrLookup lookup = map == null ? null : new MapLookup(map);
346 subst.setVariableResolver(new Interpolator(lookup, pluginPackages));
347 }
348
349 boolean setLoggers = false;
350 boolean setRoot = false;
351 for (final Node child : rootNode.getChildren()) {
352 if (child.getName().equalsIgnoreCase("Properties")) {
353 if (tempLookup == subst.getVariableResolver()) {
354 LOGGER.error("Properties declaration must be the first element in the configuration");
355 }
356 continue;
357 }
358 createConfiguration(child, null);
359 if (child.getObject() == null) {
360 continue;
361 }
362 if (child.getName().equalsIgnoreCase("Appenders")) {
363 appenders = child.getObject();
364 } else if (child.isInstanceOf(Filter.class)) {
365 addFilter(child.getObject(Filter.class));
366 } else if (child.getName().equalsIgnoreCase("Loggers")) {
367 final Loggers l = child.getObject();
368 loggers = l.getMap();
369 setLoggers = true;
370 if (l.getRoot() != null) {
371 root = l.getRoot();
372 setRoot = true;
373 }
374 } else if (child.getName().equalsIgnoreCase("CustomLevels")) {
375 customLevels = child.getObject(CustomLevels.class).getCustomLevels();
376 } else if (child.isInstanceOf(CustomLevelConfig.class)) {
377 final List<CustomLevelConfig> copy = new ArrayList<CustomLevelConfig>(customLevels);
378 copy.add(child.getObject(CustomLevelConfig.class));
379 customLevels = copy;
380 } else {
381 LOGGER.error("Unknown object \"{}\" of type {} is ignored.", child.getName(),
382 child.getObject().getClass().getName());
383 }
384 }
385
386 if (!setLoggers) {
387 LOGGER.warn("No Loggers were configured, using default. Is the Loggers element missing?");
388 setToDefault();
389 return;
390 } else if (!setRoot) {
391 LOGGER.warn("No Root logger was configured, creating default ERROR-level Root logger with Console appender");
392 setToDefault();
393 // return; // LOG4J2-219: creating default root=ok, but don't exclude configured Loggers
394 }
395
396 for (final Map.Entry<String, LoggerConfig> entry : loggers.entrySet()) {
397 final LoggerConfig l = entry.getValue();
398 for (final AppenderRef ref : l.getAppenderRefs()) {
399 final Appender app = appenders.get(ref.getRef());
400 if (app != null) {
401 l.addAppender(app, ref.getLevel(), ref.getFilter());
402 } else {
403 LOGGER.error("Unable to locate appender {} for logger {}", ref.getRef(), l.getName());
404 }
405 }
406
407 }
408
409 setParents();
410 }
411
412 private void setToDefault() {
413 // TODO: reduce duplication between this method and DefaultConfiguration constructor
414 setName(DefaultConfiguration.DEFAULT_NAME);
415 final Layout<? extends Serializable> layout = PatternLayout.newBuilder()
416 .withPattern(DefaultConfiguration.DEFAULT_PATTERN)
417 .withConfiguration(this)
418 .build();
419 final Appender appender = ConsoleAppender.createDefaultAppenderForLayout(layout);
420 appender.start();
421 addAppender(appender);
422 final LoggerConfig root = getRootLogger();
423 root.addAppender(appender, null, null);
424
425 final String levelName = PropertiesUtil.getProperties().getStringProperty(DefaultConfiguration.DEFAULT_LEVEL);
426 final Level level = levelName != null && Level.getLevel(levelName) != null ?
427 Level.getLevel(levelName) : Level.ERROR;
428 root.setLevel(level);
429 }
430
431 /**
432 * Set the name of the configuration.
433 * @param name The name.
434 */
435 public void setName(final String name) {
436 this.name = name;
437 }
438
439 /**
440 * Returns the name of the configuration.
441 * @return the name of the configuration.
442 */
443 @Override
444 public String getName() {
445 return name;
446 }
447
448 /**
449 * Add a listener for changes on the configuration.
450 * @param listener The ConfigurationListener to add.
451 */
452 @Override
453 public void addListener(final ConfigurationListener listener) {
454 listeners.add(listener);
455 }
456
457 /**
458 * Remove a ConfigurationListener.
459 * @param listener The ConfigurationListener to remove.
460 */
461 @Override
462 public void removeListener(final ConfigurationListener listener) {
463 listeners.remove(listener);
464 }
465
466 /**
467 * Returns the Appender with the specified name.
468 * @param name The name of the Appender.
469 * @return the Appender with the specified name or null if the Appender cannot be located.
470 */
471 @Override
472 public Appender getAppender(final String name) {
473 return appenders.get(name);
474 }
475
476 /**
477 * Returns a Map containing all the Appenders and their name.
478 * @return A Map containing each Appender's name and the Appender object.
479 */
480 @Override
481 public Map<String, Appender> getAppenders() {
482 return appenders;
483 }
484
485 /**
486 * Adds an Appender to the configuration.
487 * @param appender The Appender to add.
488 */
489 @Override
490 public void addAppender(final Appender appender) {
491 appenders.putIfAbsent(appender.getName(), appender);
492 }
493
494 @Override
495 public StrSubstitutor getStrSubstitutor() {
496 return subst;
497 }
498
499 @Override
500 public void setConfigurationMonitor(final ConfigurationMonitor monitor) {
501 this.monitor = monitor;
502 }
503
504 @Override
505 public ConfigurationMonitor getConfigurationMonitor() {
506 return monitor;
507 }
508
509 @Override
510 public void setAdvertiser(final Advertiser advertiser) {
511 this.advertiser = advertiser;
512 }
513
514 @Override
515 public Advertiser getAdvertiser() {
516 return advertiser;
517 }
518
519 /**
520 * Associates an Appender with a LoggerConfig. This method is synchronized in case a Logger with the
521 * same name is being updated at the same time.
522 *
523 * Note: This method is not used when configuring via configuration. It is primarily used by
524 * unit tests.
525 * @param logger The Logger the Appender will be associated with.
526 * @param appender The Appender.
527 */
528 @Override
529 public synchronized void addLoggerAppender(final org.apache.logging.log4j.core.Logger logger,
530 final Appender appender) {
531 final String name = logger.getName();
532 appenders.putIfAbsent(appender.getName(), appender);
533 final LoggerConfig lc = getLoggerConfig(name);
534 if (lc.getName().equals(name)) {
535 lc.addAppender(appender, null, null);
536 } else {
537 final LoggerConfig nlc = new LoggerConfig(name, lc.getLevel(), lc.isAdditive());
538 nlc.addAppender(appender, null, null);
539 nlc.setParent(lc);
540 loggers.putIfAbsent(name, nlc);
541 setParents();
542 logger.getContext().updateLoggers();
543 }
544 }
545 /**
546 * Associates a Filter with a LoggerConfig. This method is synchronized in case a Logger with the
547 * same name is being updated at the same time.
548 *
549 * Note: This method is not used when configuring via configuration. It is primarily used by
550 * unit tests.
551 * @param logger The Logger the Fo;ter will be associated with.
552 * @param filter The Filter.
553 */
554 @Override
555 public synchronized void addLoggerFilter(final org.apache.logging.log4j.core.Logger logger, final Filter filter) {
556 final String name = logger.getName();
557 final LoggerConfig lc = getLoggerConfig(name);
558 if (lc.getName().equals(name)) {
559
560 lc.addFilter(filter);
561 } else {
562 final LoggerConfig nlc = new LoggerConfig(name, lc.getLevel(), lc.isAdditive());
563 nlc.addFilter(filter);
564 nlc.setParent(lc);
565 loggers.putIfAbsent(name, nlc);
566 setParents();
567 logger.getContext().updateLoggers();
568 }
569 }
570 /**
571 * Marks a LoggerConfig as additive. This method is synchronized in case a Logger with the
572 * same name is being updated at the same time.
573 *
574 * Note: This method is not used when configuring via configuration. It is primarily used by
575 * unit tests.
576 * @param logger The Logger the Appender will be associated with.
577 * @param additive True if the LoggerConfig should be additive, false otherwise.
578 */
579 @Override
580 public synchronized void setLoggerAdditive(final org.apache.logging.log4j.core.Logger logger,
581 final boolean additive) {
582 final String name = logger.getName();
583 final LoggerConfig lc = getLoggerConfig(name);
584 if (lc.getName().equals(name)) {
585 lc.setAdditive(additive);
586 } else {
587 final LoggerConfig nlc = new LoggerConfig(name, lc.getLevel(), additive);
588 nlc.setParent(lc);
589 loggers.putIfAbsent(name, nlc);
590 setParents();
591 logger.getContext().updateLoggers();
592 }
593 }
594
595 /**
596 * Remove an Appender. First removes any associations between LoggerConfigs and the Appender, removes
597 * the Appender from this appender list and then stops the appender. This method is synchronized in
598 * case an Appender with the same name is being added during the removal.
599 * @param name the name of the appender to remove.
600 */
601 public synchronized void removeAppender(final String name) {
602 for (final LoggerConfig logger : loggers.values()) {
603 logger.removeAppender(name);
604 }
605 final Appender app = appenders.remove(name);
606
607 if (app != null) {
608 app.stop();
609 }
610 }
611
612 /*
613 * (non-Javadoc)
614 * @see org.apache.logging.log4j.core.config.Configuration#getCustomLevels()
615 */
616 @Override
617 public List<CustomLevelConfig> getCustomLevels() {
618 return Collections.unmodifiableList(customLevels);
619 }
620
621 /**
622 * Locates the appropriate LoggerConfig for a Logger name. This will remove tokens from the
623 * package name as necessary or return the root LoggerConfig if no other matches were found.
624 * @param name The Logger name.
625 * @return The located LoggerConfig.
626 */
627 @Override
628 public LoggerConfig getLoggerConfig(final String name) {
629 if (loggers.containsKey(name)) {
630 return loggers.get(name);
631 }
632 String substr = name;
633 while ((substr = NameUtil.getSubName(substr)) != null) {
634 if (loggers.containsKey(substr)) {
635 return loggers.get(substr);
636 }
637 }
638 return root;
639 }
640
641 /**
642 * Returns the root Logger.
643 * @return the root Logger.
644 */
645 public LoggerConfig getRootLogger() {
646 return root;
647 }
648
649 /**
650 * Returns a Map of all the LoggerConfigs.
651 * @return a Map with each entry containing the name of the Logger and the LoggerConfig.
652 */
653 @Override
654 public Map<String, LoggerConfig> getLoggers() {
655 return Collections.unmodifiableMap(loggers);
656 }
657
658 /**
659 * Returns the LoggerConfig with the specified name.
660 * @param name The Logger name.
661 * @return The LoggerConfig or null if no match was found.
662 */
663 public LoggerConfig getLogger(final String name) {
664 return loggers.get(name);
665 }
666
667 /**
668 * Add a loggerConfig. The LoggerConfig must already be configured with Appenders, Filters, etc.
669 * After addLogger is called LoggerContext.updateLoggers must be called.
670 *
671 * @param name The name of the Logger.
672 * @param loggerConfig The LoggerConfig.
673 */
674 @Override
675 public synchronized void addLogger(final String name, final LoggerConfig loggerConfig) {
676 loggers.putIfAbsent(name, loggerConfig);
677 setParents();
678 }
679
680 /**
681 * Remove a LoggerConfig.
682 *
683 * @param name The name of the Logger.
684 */
685 @Override
686 public synchronized void removeLogger(final String name) {
687 loggers.remove(name);
688 setParents();
689 }
690
691 @Override
692 public void createConfiguration(final Node node, final LogEvent event) {
693 final PluginType<?> type = node.getType();
694 if (type != null && type.isDeferChildren()) {
695 node.setObject(createPluginObject(type, node, event));
696 } else {
697 for (final Node child : node.getChildren()) {
698 createConfiguration(child, event);
699 }
700
701 if (type == null) {
702 if (node.getParent() != null) {
703 LOGGER.error("Unable to locate plugin for {}", node.getName());
704 }
705 } else {
706 node.setObject(createPluginObject(type, node, event));
707 }
708 }
709 }
710
711 /**
712 * Invokes a static factory method to either create the desired object or to create a builder object that creates
713 * the desired object. In the case of a factory method, it should be annotated with
714 * {@link org.apache.logging.log4j.core.config.plugins.PluginFactory}, and each parameter should be annotated with
715 * an appropriate plugin annotation depending on what that parameter describes. Parameters annotated with
716 * {@link org.apache.logging.log4j.core.config.plugins.PluginAttribute} must be a type that can be converted from
717 * a string using one of the {@link org.apache.logging.log4j.core.config.plugins.convert.TypeConverter TypeConverters}.
718 * Parameters with {@link org.apache.logging.log4j.core.config.plugins.PluginElement} may be any plugin class or an
719 * array of a plugin class. Collections and Maps are currently not supported, although the factory method that is
720 * called can create these from an array.
721 *
722 * Plugins can also be created using a builder class that implements
723 * {@link org.apache.logging.log4j.core.util.Builder}. In that case, a static method annotated with
724 * {@link org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute} should create the builder class,
725 * and the various fields in the builder class should be annotated similarly to the method parameters. However,
726 * instead of using PluginAttribute, one should use
727 * {@link org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute} where the default value can be
728 * specified as the default field value instead of as an additional annotation parameter.
729 *
730 * In either case, there are also annotations for specifying a
731 * {@link org.apache.logging.log4j.core.config.Configuration}
732 * ({@link org.apache.logging.log4j.core.config.plugins.PluginConfiguration}) or a
733 * {@link org.apache.logging.log4j.core.config.Node}
734 * ({@link org.apache.logging.log4j.core.config.plugins.PluginNode}).
735 *
736 * Although the happy path works, more work still needs to be done to log incorrect
737 * parameters. These will generally result in unhelpful InvocationTargetExceptions.
738 *
739 * @param type the type of plugin to create.
740 * @param node the corresponding configuration node for this plugin to create.
741 * @param event the LogEvent that spurred the creation of this plugin
742 * @return the created plugin object or {@code null} if there was an error setting it up.
743 * @see org.apache.logging.log4j.core.config.plugins.util.PluginBuilder
744 * @see org.apache.logging.log4j.core.config.plugins.visitors.PluginVisitor
745 * @see org.apache.logging.log4j.core.config.plugins.convert.TypeConverter
746 */
747 private Object createPluginObject(final PluginType<?> type, final Node node, final LogEvent event) {
748 final Class<?> clazz = type.getPluginClass();
749
750 if (Map.class.isAssignableFrom(clazz)) {
751 try {
752 return createPluginMap(node);
753 } catch (final Exception e) {
754 LOGGER.warn("Unable to create Map for {} of class {}", type.getElementName(), clazz, e);
755 }
756 }
757
758 if (Collection.class.isAssignableFrom(clazz)) {
759 try {
760 return createPluginCollection(node);
761 } catch (final Exception e) {
762 LOGGER.warn("Unable to create List for {} of class {}", type.getElementName(), clazz, e);
763 }
764 }
765
766 return new PluginBuilder(type)
767 .withConfiguration(this)
768 .withConfigurationNode(node)
769 .forLogEvent(event)
770 .build();
771 }
772
773 private static Map<String, ?> createPluginMap(final Node node) {
774 final Map<String, Object> map = new LinkedHashMap<String, Object>();
775 for (final Node child : node.getChildren()) {
776 final Object object = child.getObject();
777 map.put(child.getName(), object);
778 }
779 return map;
780 }
781
782 private static Collection<?> createPluginCollection(final Node node) {
783 final List<Node> children = node.getChildren();
784 final Collection<Object> list = new ArrayList<Object>(children.size());
785 for (final Node child : children) {
786 final Object object = child.getObject();
787 list.add(object);
788 }
789 return list;
790 }
791
792 private void setParents() {
793 for (final Map.Entry<String, LoggerConfig> entry : loggers.entrySet()) {
794 final LoggerConfig logger = entry.getValue();
795 String name = entry.getKey();
796 if (!name.isEmpty()) {
797 final int i = name.lastIndexOf('.');
798 if (i > 0) {
799 name = name.substring(0, i);
800 LoggerConfig parent = getLoggerConfig(name);
801 if (parent == null) {
802 parent = root;
803 }
804 logger.setParent(parent);
805 } else {
806 logger.setParent(root);
807 }
808 }
809 }
810 }
811
812 /**
813 * Reads an InputStream using buffered reads into a byte array buffer. The given InputStream will remain open
814 * after invocation of this method.
815 *
816 * @param is the InputStream to read into a byte array buffer.
817 * @return a byte array of the InputStream contents.
818 * @throws IOException if the {@code read} method of the provided InputStream throws this exception.
819 */
820 protected static byte[] toByteArray(final InputStream is) throws IOException {
821 final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
822
823 int nRead;
824 final byte[] data = new byte[BUF_SIZE];
825
826 while ((nRead = is.read(data, 0, data.length)) != -1) {
827 buffer.write(data, 0, nRead);
828 }
829
830 return buffer.toByteArray();
831 }
832
833 }