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