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 }