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