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