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.plugins.util.PluginManager; 040import org.apache.logging.log4j.core.config.plugins.util.PluginType; 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.NetUtils; 046import org.apache.logging.log4j.core.util.ReflectionUtil; 047import org.apache.logging.log4j.status.StatusLogger; 048import org.apache.logging.log4j.util.LoaderUtil; 049import org.apache.logging.log4j.util.PropertiesUtil; 050import org.apache.logging.log4j.util.Strings; 051 052/** 053 * Factory class for parsed {@link Configuration} objects from a configuration file. 054 * ConfigurationFactory allows the configuration implementation to be 055 * dynamically chosen in 1 of 3 ways: 056 * <ol> 057 * <li>A system property named "log4j.configurationFactory" can be set with the 058 * name of the ConfigurationFactory to be used.</li> 059 * <li> 060 * {@linkplain #setConfigurationFactory(ConfigurationFactory)} can be called 061 * with the instance of the ConfigurationFactory to be used. This must be called 062 * before any other calls to Log4j.</li> 063 * <li> 064 * A ConfigurationFactory implementation can be added to the classpath and configured as a plugin in the 065 * {@link #CATEGORY ConfigurationFactory} category. The {@link Order} annotation should be used to configure the 066 * factory to be the first one inspected. See 067 * {@linkplain org.apache.logging.log4j.core.config.xml.XmlConfigurationFactory} for an example.</li> 068 * </ol> 069 * 070 * If the ConfigurationFactory that was added returns null on a call to 071 * getConfiguration then any other ConfigurationFactories found as plugins will 072 * be called in their respective order. DefaultConfiguration is always called 073 * last if no configuration has been returned. 074 */ 075public abstract class ConfigurationFactory extends ConfigurationBuilderFactory { 076 /** 077 * Allow the ConfigurationFactory class to be specified as a system property. 078 */ 079 public static final String CONFIGURATION_FACTORY_PROPERTY = "log4j.configurationFactory"; 080 081 /** 082 * Allow the location of the configuration file to be specified as a system property. 083 */ 084 public static final String CONFIGURATION_FILE_PROPERTY = "log4j.configurationFile"; 085 086 /** 087 * Plugin category used to inject a ConfigurationFactory {@link org.apache.logging.log4j.core.config.plugins.Plugin} 088 * class. 089 * 090 * @since 2.1 091 */ 092 public static final String CATEGORY = "ConfigurationFactory"; 093 094 /** 095 * Allow subclasses access to the status logger without creating another instance. 096 */ 097 protected static final Logger LOGGER = StatusLogger.getLogger(); 098 099 /** 100 * File name prefix for test configurations. 101 */ 102 protected static final String TEST_PREFIX = "log4j2-test"; 103 104 /** 105 * File name prefix for standard configurations. 106 */ 107 protected static final String DEFAULT_PREFIX = "log4j2"; 108 109 /** 110 * The name of the classloader URI scheme. 111 */ 112 private static final String CLASS_LOADER_SCHEME = "classloader"; 113 114 /** 115 * The name of the classpath URI scheme, synonymous with the classloader URI scheme. 116 */ 117 private static final String CLASS_PATH_SCHEME = "classpath"; 118 119 private static volatile List<ConfigurationFactory> factories = null; 120 121 private static ConfigurationFactory configFactory = new Factory(); 122 123 protected final StrSubstitutor substitutor = new StrSubstitutor(new Interpolator()); 124 125 private static final Lock LOCK = new ReentrantLock(); 126 127 /** 128 * Returns the ConfigurationFactory. 129 * @return the ConfigurationFactory. 130 */ 131 public static ConfigurationFactory getInstance() { 132 // volatile works in Java 1.6+, so double-checked locking also works properly 133 //noinspection DoubleCheckedLocking 134 if (factories == null) { 135 LOCK.lock(); 136 try { 137 if (factories == null) { 138 final List<ConfigurationFactory> list = new ArrayList<>(); 139 final String factoryClass = PropertiesUtil.getProperties().getStringProperty(CONFIGURATION_FACTORY_PROPERTY); 140 if (factoryClass != null) { 141 addFactory(list, factoryClass); 142 } 143 final PluginManager manager = new PluginManager(CATEGORY); 144 manager.collectPlugins(); 145 final Map<String, PluginType<?>> plugins = manager.getPlugins(); 146 final List<Class<? extends ConfigurationFactory>> ordered = 147 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 ConfigurationSource source = null; 397 try { 398 source = getInputFromUri(NetUtils.toURI(configLocationStr)); 399 } catch (final Exception ex) { 400 // Ignore the error and try as a String. 401 LOGGER.catching(Level.DEBUG, ex); 402 } 403 if (source == null) { 404 final ClassLoader loader = LoaderUtil.getThreadContextClassLoader(); 405 source = getInputFromString(configLocationStr, loader); 406 } 407 if (source != null) { 408 for (final ConfigurationFactory factory : getFactories()) { 409 final String[] types = factory.getSupportedTypes(); 410 if (types != null) { 411 for (final String type : types) { 412 if (type.equals("*") || configLocationStr.endsWith(type)) { 413 final Configuration config = factory.getConfiguration(source); 414 if (config != null) { 415 return config; 416 } 417 } 418 } 419 } 420 } 421 } 422 } else { 423 for (final ConfigurationFactory factory : getFactories()) { 424 final String[] types = factory.getSupportedTypes(); 425 if (types != null) { 426 for (final String type : types) { 427 if (type.equals("*")) { 428 final Configuration config = factory.getConfiguration(name, configLocation); 429 if (config != null) { 430 return config; 431 } 432 } 433 } 434 } 435 } 436 } 437 } else { 438 // configLocation != null 439 final String configLocationStr = configLocation.toString(); 440 for (final ConfigurationFactory factory : getFactories()) { 441 final String[] types = factory.getSupportedTypes(); 442 if (types != null) { 443 for (final String type : types) { 444 if (type.equals("*") || configLocationStr.endsWith(type)) { 445 final Configuration config = factory.getConfiguration(name, configLocation); 446 if (config != null) { 447 return config; 448 } 449 } 450 } 451 } 452 } 453 } 454 455 Configuration config = getConfiguration(true, name); 456 if (config == null) { 457 config = getConfiguration(true, null); 458 if (config == null) { 459 config = getConfiguration(false, name); 460 if (config == null) { 461 config = getConfiguration(false, null); 462 } 463 } 464 } 465 if (config != null) { 466 return config; 467 } 468 LOGGER.error("No log4j2 configuration file found. Using default configuration: logging only errors to the console."); 469 return new DefaultConfiguration(); 470 } 471 472 private Configuration getConfiguration(final boolean isTest, final String name) { 473 final boolean named = Strings.isNotEmpty(name); 474 final ClassLoader loader = LoaderUtil.getThreadContextClassLoader(); 475 for (final ConfigurationFactory factory : getFactories()) { 476 String configName; 477 final String prefix = isTest ? TEST_PREFIX : DEFAULT_PREFIX; 478 final String [] types = factory.getSupportedTypes(); 479 if (types == null) { 480 continue; 481 } 482 483 for (final String suffix : types) { 484 if (suffix.equals("*")) { 485 continue; 486 } 487 configName = named ? prefix + name + suffix : prefix + suffix; 488 489 final ConfigurationSource source = getInputFromResource(configName, loader); 490 if (source != null) { 491 return factory.getConfiguration(source); 492 } 493 } 494 } 495 return null; 496 } 497 498 @Override 499 public String[] getSupportedTypes() { 500 return null; 501 } 502 503 @Override 504 public Configuration getConfiguration(final ConfigurationSource source) { 505 if (source != null) { 506 final String config = source.getLocation(); 507 for (final ConfigurationFactory factory : getFactories()) { 508 final String[] types = factory.getSupportedTypes(); 509 if (types != null) { 510 for (final String type : types) { 511 if (type.equals("*") || config != null && config.endsWith(type)) { 512 final Configuration c = factory.getConfiguration(source); 513 if (c != null) { 514 LOGGER.debug("Loaded configuration from {}", source); 515 return c; 516 } 517 LOGGER.error("Cannot determine the ConfigurationFactory to use for {}", config); 518 return null; 519 } 520 } 521 } 522 } 523 } 524 LOGGER.error("Cannot process configuration, input source is null"); 525 return null; 526 } 527 } 528 529 static List<ConfigurationFactory> getFactories() { 530 return factories; 531 } 532}