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.File;
020 import java.io.FileInputStream;
021 import java.io.FileNotFoundException;
022 import java.io.IOException;
023 import java.io.InputStream;
024 import java.net.MalformedURLException;
025 import java.net.URI;
026 import java.net.URISyntaxException;
027 import java.net.URL;
028 import java.util.ArrayList;
029 import java.util.Collection;
030 import java.util.Collections;
031 import java.util.List;
032 import java.util.Map;
033 import java.util.concurrent.locks.Lock;
034 import java.util.concurrent.locks.ReentrantLock;
035
036 import org.apache.logging.log4j.Level;
037 import org.apache.logging.log4j.Logger;
038 import org.apache.logging.log4j.core.config.plugins.util.PluginManager;
039 import org.apache.logging.log4j.core.config.plugins.util.PluginType;
040 import org.apache.logging.log4j.core.lookup.Interpolator;
041 import org.apache.logging.log4j.core.lookup.StrSubstitutor;
042 import org.apache.logging.log4j.core.util.FileUtils;
043 import org.apache.logging.log4j.core.util.Loader;
044 import org.apache.logging.log4j.core.util.ReflectionUtil;
045 import org.apache.logging.log4j.status.StatusLogger;
046 import org.apache.logging.log4j.util.LoaderUtil;
047 import org.apache.logging.log4j.util.PropertiesUtil;
048
049 /**
050 * Factory class for parsed {@link Configuration} objects from a configuration file.
051 * ConfigurationFactory allows the configuration implementation to be
052 * dynamically chosen in 1 of 3 ways:
053 * <ol>
054 * <li>A system property named "log4j.configurationFactory" can be set with the
055 * name of the ConfigurationFactory to be used.</li>
056 * <li>
057 * {@linkplain #setConfigurationFactory(ConfigurationFactory)} can be called
058 * with the instance of the ConfigurationFactory to be used. This must be called
059 * before any other calls to Log4j.</li>
060 * <li>
061 * A ConfigurationFactory implementation can be added to the classpath and configured as a plugin in the
062 * {@link #CATEGORY ConfigurationFactory} category. The {@link Order} annotation should be used to configure the
063 * factory to be the first one inspected. See
064 * {@linkplain org.apache.logging.log4j.core.config.xml.XmlConfigurationFactory} for an example.</li>
065 * </ol>
066 *
067 * If the ConfigurationFactory that was added returns null on a call to
068 * getConfiguration the any other ConfigurationFactories found as plugins will
069 * be called in their respective order. DefaultConfiguration is always called
070 * last if no configuration has been returned.
071 */
072 public abstract class ConfigurationFactory {
073 /**
074 * Allow the ConfigurationFactory class to be specified as a system property.
075 */
076 public static final String CONFIGURATION_FACTORY_PROPERTY = "log4j.configurationFactory";
077
078 /**
079 * Allow the location of the configuration file to be specified as a system property.
080 */
081 public static final String CONFIGURATION_FILE_PROPERTY = "log4j.configurationFile";
082
083 /**
084 * Plugin category used to inject a ConfigurationFactory {@link org.apache.logging.log4j.core.config.plugins.Plugin}
085 * class.
086 *
087 * @since 2.1
088 */
089 public static final String CATEGORY = "ConfigurationFactory";
090
091 /**
092 * Allow subclasses access to the status logger without creating another instance.
093 */
094 protected static final Logger LOGGER = StatusLogger.getLogger();
095
096 /**
097 * File name prefix for test configurations.
098 */
099 protected static final String TEST_PREFIX = "log4j2-test";
100
101 /**
102 * File name prefix for standard configurations.
103 */
104 protected static final String DEFAULT_PREFIX = "log4j2";
105
106 /**
107 * The name of the classloader URI scheme.
108 */
109 private static final String CLASS_LOADER_SCHEME = "classloader";
110
111 /**
112 * The name of the classpath URI scheme, synonymous with the classloader URI scheme.
113 */
114 private static final String CLASS_PATH_SCHEME = "classpath";
115
116 private static volatile List<ConfigurationFactory> factories = null;
117
118 private static ConfigurationFactory configFactory = new Factory();
119
120 protected final StrSubstitutor substitutor = new StrSubstitutor(new Interpolator());
121
122 private static final Lock LOCK = new ReentrantLock();
123
124 /**
125 * Returns the ConfigurationFactory.
126 * @return the ConfigurationFactory.
127 */
128 public static ConfigurationFactory getInstance() {
129 // volatile works in Java 1.6+, so double-checked locking also works properly
130 //noinspection DoubleCheckedLocking
131 if (factories == null) {
132 LOCK.lock();
133 try {
134 if (factories == null) {
135 final List<ConfigurationFactory> list = new ArrayList<ConfigurationFactory>();
136 final String factoryClass = PropertiesUtil.getProperties().getStringProperty(CONFIGURATION_FACTORY_PROPERTY);
137 if (factoryClass != null) {
138 addFactory(list, factoryClass);
139 }
140 final PluginManager manager = new PluginManager(CATEGORY);
141 manager.collectPlugins();
142 final Map<String, PluginType<?>> plugins = manager.getPlugins();
143 final List<Class<? extends ConfigurationFactory>> ordered =
144 new ArrayList<Class<? extends ConfigurationFactory>>(plugins.size());
145 for (final PluginType<?> type : plugins.values()) {
146 try {
147 ordered.add(type.getPluginClass().asSubclass(ConfigurationFactory.class));
148 } catch (final Exception ex) {
149 LOGGER.warn("Unable to add class {}", type.getPluginClass(), ex);
150 }
151 }
152 Collections.sort(ordered, OrderComparator.getInstance());
153 for (final Class<? extends ConfigurationFactory> clazz : ordered) {
154 addFactory(list, clazz);
155 }
156 // see above comments about double-checked locking
157 //noinspection NonThreadSafeLazyInitialization
158 factories = Collections.unmodifiableList(list);
159 }
160 } finally {
161 LOCK.unlock();
162 }
163 }
164
165 LOGGER.debug("Using configurationFactory {}", configFactory);
166 return configFactory;
167 }
168
169 private static void addFactory(final Collection<ConfigurationFactory> list, final String factoryClass) {
170 try {
171 addFactory(list, LoaderUtil.loadClass(factoryClass).asSubclass(ConfigurationFactory.class));
172 } catch (final Exception ex) {
173 LOGGER.error("Unable to load class {}", factoryClass, ex);
174 }
175 }
176
177 private static void addFactory(final Collection<ConfigurationFactory> list,
178 final Class<? extends ConfigurationFactory> factoryClass) {
179 try {
180 list.add(ReflectionUtil.instantiate(factoryClass));
181 } catch (final Exception ex) {
182 LOGGER.error("Unable to create instance of {}", factoryClass.getName(), ex);
183 }
184 }
185
186 /**
187 * Set the configuration factory. This method is not intended for general use and may not be thread safe.
188 * @param factory the ConfigurationFactory.
189 */
190 public static void setConfigurationFactory(final ConfigurationFactory factory) {
191 configFactory = factory;
192 }
193
194 /**
195 * Reset the ConfigurationFactory to the default. This method is not intended for general use and may
196 * not be thread safe.
197 */
198 public static void resetConfigurationFactory() {
199 configFactory = new Factory();
200 }
201
202 /**
203 * Remove the ConfigurationFactory. This method is not intended for general use and may not be thread safe.
204 * @param factory The factory to remove.
205 */
206 public static void removeConfigurationFactory(final ConfigurationFactory factory) {
207 if (configFactory == factory) {
208 configFactory = new Factory();
209 }
210 }
211
212 protected abstract String[] getSupportedTypes();
213
214 protected boolean isActive() {
215 return true;
216 }
217
218 public abstract Configuration getConfiguration(ConfigurationSource source);
219
220 /**
221 * Returns the Configuration.
222 * @param name The configuration name.
223 * @param configLocation The configuration location.
224 * @return The Configuration.
225 */
226 public Configuration getConfiguration(final String name, final URI configLocation) {
227 if (!isActive()) {
228 return null;
229 }
230 if (configLocation != null) {
231 final ConfigurationSource source = getInputFromUri(configLocation);
232 if (source != null) {
233 return getConfiguration(source);
234 }
235 }
236 return null;
237 }
238
239 /**
240 * Returns the Configuration obtained using a given ClassLoader.
241 *
242 * @param name The configuration name.
243 * @param configLocation A URI representing the location of the configuration.
244 * @param loader The default ClassLoader to use. If this is {@code null}, then the
245 * {@linkplain LoaderUtil#getThreadContextClassLoader() default ClassLoader} will be used.
246 * @return The Configuration.
247 * @since 2.1
248 */
249 public Configuration getConfiguration(final String name, final URI configLocation, final ClassLoader loader) {
250 if (!isActive()) {
251 return null;
252 }
253 if (loader == null) {
254 return getConfiguration(name, configLocation);
255 }
256 if (isClassLoaderUri(configLocation)) {
257 final String path = extractClassLoaderUriPath(configLocation);
258 final ConfigurationSource source = getInputFromResource(path, loader);
259 if (source != null) {
260 final Configuration configuration = getConfiguration(source);
261 if (configuration != null) {
262 return configuration;
263 }
264 }
265 }
266 return getConfiguration(name, configLocation);
267 }
268
269 /**
270 * Load the configuration from a URI.
271 * @param configLocation A URI representing the location of the configuration.
272 * @return The ConfigurationSource for the configuration.
273 */
274 protected ConfigurationSource getInputFromUri(final URI configLocation) {
275 final File configFile = FileUtils.fileFromUri(configLocation);
276 if (configFile != null && configFile.exists() && configFile.canRead()) {
277 try {
278 return new ConfigurationSource(new FileInputStream(configFile), configFile);
279 } catch (final FileNotFoundException ex) {
280 LOGGER.error("Cannot locate file {}", configLocation.getPath(), ex);
281 }
282 }
283 if (isClassLoaderUri(configLocation)) {
284 final ClassLoader loader = LoaderUtil.getThreadContextClassLoader();
285 final String path = extractClassLoaderUriPath(configLocation);
286 final ConfigurationSource source = getInputFromResource(path, loader);
287 if (source != null) {
288 return source;
289 }
290 }
291 if (!configLocation.isAbsolute()) { // LOG4J2-704 avoid confusing error message thrown by uri.toURL()
292 LOGGER.error("File not found in file system or classpath: {}", configLocation.toString());
293 return null;
294 }
295 try {
296 return new ConfigurationSource(configLocation.toURL().openStream(), configLocation.toURL());
297 } catch (final MalformedURLException ex) {
298 LOGGER.error("Invalid URL {}", configLocation.toString(), ex);
299 } catch (final Exception ex) {
300 LOGGER.error("Unable to access {}", configLocation.toString(), ex);
301 }
302 return null;
303 }
304
305 private static boolean isClassLoaderUri(final URI uri) {
306 if (uri == null) {
307 return false;
308 }
309 final String scheme = uri.getScheme();
310 return scheme == null || scheme.equals(CLASS_LOADER_SCHEME) || scheme.equals(CLASS_PATH_SCHEME);
311 }
312
313 private static String extractClassLoaderUriPath(final URI uri) {
314 return uri.getScheme() == null ? uri.getPath() : uri.getSchemeSpecificPart();
315 }
316
317 /**
318 * Load the configuration from the location represented by the String.
319 * @param config The configuration location.
320 * @param loader The default ClassLoader to use.
321 * @return The InputSource to use to read the configuration.
322 */
323 protected ConfigurationSource getInputFromString(final String config, final ClassLoader loader) {
324 try {
325 final URL url = new URL(config);
326 return new ConfigurationSource(url.openStream(), FileUtils.fileFromUri(url.toURI()));
327 } catch (final Exception ex) {
328 final ConfigurationSource source = getInputFromResource(config, loader);
329 if (source == null) {
330 try {
331 final File file = new File(config);
332 return new ConfigurationSource(new FileInputStream(file), file);
333 } catch (final FileNotFoundException fnfe) {
334 // Ignore the exception
335 LOGGER.catching(Level.DEBUG, fnfe);
336 }
337 }
338 return source;
339 }
340 }
341
342 /**
343 * Retrieve the configuration via the ClassLoader.
344 * @param resource The resource to load.
345 * @param loader The default ClassLoader to use.
346 * @return The ConfigurationSource for the configuration.
347 */
348 protected ConfigurationSource getInputFromResource(final String resource, final ClassLoader loader) {
349 final URL url = Loader.getResource(resource, loader);
350 if (url == null) {
351 return null;
352 }
353 InputStream is = null;
354 try {
355 is = url.openStream();
356 } catch (final IOException ioe) {
357 LOGGER.catching(Level.DEBUG, ioe);
358 return null;
359 }
360 if (is == null) {
361 return null;
362 }
363
364 if (FileUtils.isFile(url)) {
365 try {
366 return new ConfigurationSource(is, FileUtils.fileFromUri(url.toURI()));
367 } catch (final URISyntaxException ex) {
368 // Just ignore the exception.
369 LOGGER.catching(Level.DEBUG, ex);
370 }
371 }
372 return new ConfigurationSource(is, url);
373 }
374
375 /**
376 * Default Factory.
377 */
378 private static class Factory extends ConfigurationFactory {
379
380 /**
381 * Default Factory Constructor.
382 * @param name The configuration name.
383 * @param configLocation The configuration location.
384 * @return The Configuration.
385 */
386 @Override
387 public Configuration getConfiguration(final String name, final URI configLocation) {
388
389 if (configLocation == null) {
390 final String config = this.substitutor.replace(
391 PropertiesUtil.getProperties().getStringProperty(CONFIGURATION_FILE_PROPERTY));
392 if (config != null) {
393 ConfigurationSource source = null;
394 try {
395 source = getInputFromUri(FileUtils.getCorrectedFilePathUri(config));
396 } catch (final Exception ex) {
397 // Ignore the error and try as a String.
398 LOGGER.catching(Level.DEBUG, ex);
399 }
400 if (source == null) {
401 final ClassLoader loader = LoaderUtil.getThreadContextClassLoader();
402 source = getInputFromString(config, loader);
403 }
404 if (source != null) {
405 for (final ConfigurationFactory factory : factories) {
406 final String[] types = factory.getSupportedTypes();
407 if (types != null) {
408 for (final String type : types) {
409 if (type.equals("*") || config.endsWith(type)) {
410 final Configuration c = factory.getConfiguration(source);
411 if (c != null) {
412 return c;
413 }
414 }
415 }
416 }
417 }
418 }
419 }
420 } else {
421 for (final ConfigurationFactory factory : factories) {
422 final String[] types = factory.getSupportedTypes();
423 if (types != null) {
424 for (final String type : types) {
425 if (type.equals("*") || configLocation.toString().endsWith(type)) {
426 final Configuration config = factory.getConfiguration(name, configLocation);
427 if (config != null) {
428 return config;
429 }
430 }
431 }
432 }
433 }
434 }
435
436 Configuration config = getConfiguration(true, name);
437 if (config == null) {
438 config = getConfiguration(true, null);
439 if (config == null) {
440 config = getConfiguration(false, name);
441 if (config == null) {
442 config = getConfiguration(false, null);
443 }
444 }
445 }
446 if (config != null) {
447 return config;
448 }
449 LOGGER.error("No log4j2 configuration file found. Using default configuration: logging only errors to the console.");
450 return new DefaultConfiguration();
451 }
452
453 private Configuration getConfiguration(final boolean isTest, final String name) {
454 final boolean named = name != null && name.length() > 0;
455 final ClassLoader loader = LoaderUtil.getThreadContextClassLoader();
456 for (final ConfigurationFactory factory : factories) {
457 String configName;
458 final String prefix = isTest ? TEST_PREFIX : DEFAULT_PREFIX;
459 final String [] types = factory.getSupportedTypes();
460 if (types == null) {
461 continue;
462 }
463
464 for (final String suffix : types) {
465 if (suffix.equals("*")) {
466 continue;
467 }
468 configName = named ? prefix + name + suffix : prefix + suffix;
469
470 final ConfigurationSource source = getInputFromResource(configName, loader);
471 if (source != null) {
472 return factory.getConfiguration(source);
473 }
474 }
475 }
476 return null;
477 }
478
479 @Override
480 public String[] getSupportedTypes() {
481 return null;
482 }
483
484 @Override
485 public Configuration getConfiguration(final ConfigurationSource source) {
486 if (source != null) {
487 final String config = source.getLocation();
488 for (final ConfigurationFactory factory : factories) {
489 final String[] types = factory.getSupportedTypes();
490 if (types != null) {
491 for (final String type : types) {
492 if (type.equals("*") || config != null && config.endsWith(type)) {
493 final Configuration c = factory.getConfiguration(source);
494 if (c != null) {
495 LOGGER.debug("Loaded configuration from {}", source);
496 return c;
497 }
498 LOGGER.error("Cannot determine the ConfigurationFactory to use for {}", config);
499 return null;
500 }
501 }
502 }
503 }
504 }
505 LOGGER.error("Cannot process configuration, input source is null");
506 return null;
507 }
508 }
509 }