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 = new ArrayList<>(plugins.size()); 147 for (final PluginType<?> type : plugins.values()) { 148 try { 149 ordered.add(type.getPluginClass().asSubclass(ConfigurationFactory.class)); 150 } catch (final Exception ex) { 151 LOGGER.warn("Unable to add class {}", type.getPluginClass(), ex); 152 } 153 } 154 Collections.sort(ordered, OrderComparator.getInstance()); 155 for (final Class<? extends ConfigurationFactory> clazz : ordered) { 156 addFactory(list, clazz); 157 } 158 // see above comments about double-checked locking 159 //noinspection NonThreadSafeLazyInitialization 160 factories = Collections.unmodifiableList(list); 161 } 162 } finally { 163 LOCK.unlock(); 164 } 165 } 166 167 LOGGER.debug("Using configurationFactory {}", configFactory); 168 return configFactory; 169 } 170 171 private static void addFactory(final Collection<ConfigurationFactory> list, final String factoryClass) { 172 try { 173 addFactory(list, LoaderUtil.loadClass(factoryClass).asSubclass(ConfigurationFactory.class)); 174 } catch (final Exception ex) { 175 LOGGER.error("Unable to load class {}", factoryClass, ex); 176 } 177 } 178 179 private static void addFactory(final Collection<ConfigurationFactory> list, 180 final Class<? extends ConfigurationFactory> factoryClass) { 181 try { 182 list.add(ReflectionUtil.instantiate(factoryClass)); 183 } catch (final Exception ex) { 184 LOGGER.error("Unable to create instance of {}", factoryClass.getName(), ex); 185 } 186 } 187 188 /** 189 * Set the configuration factory. This method is not intended for general use and may not be thread safe. 190 * @param factory the ConfigurationFactory. 191 */ 192 public static void setConfigurationFactory(final ConfigurationFactory factory) { 193 configFactory = factory; 194 } 195 196 /** 197 * Reset the ConfigurationFactory to the default. This method is not intended for general use and may 198 * not be thread safe. 199 */ 200 public static void resetConfigurationFactory() { 201 configFactory = new Factory(); 202 } 203 204 /** 205 * Remove the ConfigurationFactory. This method is not intended for general use and may not be thread safe. 206 * @param factory The factory to remove. 207 */ 208 public static void removeConfigurationFactory(final ConfigurationFactory factory) { 209 if (configFactory == factory) { 210 configFactory = new Factory(); 211 } 212 } 213 214 protected abstract String[] getSupportedTypes(); 215 216 protected boolean isActive() { 217 return true; 218 } 219 220 public abstract Configuration getConfiguration(ConfigurationSource source); 221 222 /** 223 * Returns the Configuration. 224 * @param name The configuration name. 225 * @param configLocation The configuration location. 226 * @return The Configuration. 227 */ 228 public Configuration getConfiguration(final String name, final URI configLocation) { 229 if (!isActive()) { 230 return null; 231 } 232 if (configLocation != null) { 233 final ConfigurationSource source = getInputFromUri(configLocation); 234 if (source != null) { 235 return getConfiguration(source); 236 } 237 } 238 return null; 239 } 240 241 /** 242 * Returns the Configuration obtained using a given ClassLoader. 243 * 244 * @param name The configuration name. 245 * @param configLocation A URI representing the location of the configuration. 246 * @param loader The default ClassLoader to use. If this is {@code null}, then the 247 * {@linkplain LoaderUtil#getThreadContextClassLoader() default ClassLoader} will be used. 248 * @return The Configuration. 249 * @since 2.1 250 */ 251 public Configuration getConfiguration(final String name, final URI configLocation, final ClassLoader loader) { 252 if (!isActive()) { 253 return null; 254 } 255 if (loader == null) { 256 return getConfiguration(name, configLocation); 257 } 258 if (isClassLoaderUri(configLocation)) { 259 final String path = extractClassLoaderUriPath(configLocation); 260 final ConfigurationSource source = getInputFromResource(path, loader); 261 if (source != null) { 262 final Configuration configuration = getConfiguration(source); 263 if (configuration != null) { 264 return configuration; 265 } 266 } 267 } 268 return getConfiguration(name, configLocation); 269 } 270 271 /** 272 * Load the configuration from a URI. 273 * @param configLocation A URI representing the location of the configuration. 274 * @return The ConfigurationSource for the configuration. 275 */ 276 protected ConfigurationSource getInputFromUri(final URI configLocation) { 277 final File configFile = FileUtils.fileFromUri(configLocation); 278 if (configFile != null && configFile.exists() && configFile.canRead()) { 279 try { 280 return new ConfigurationSource(new FileInputStream(configFile), configFile); 281 } catch (final FileNotFoundException ex) { 282 LOGGER.error("Cannot locate file {}", configLocation.getPath(), ex); 283 } 284 } 285 if (isClassLoaderUri(configLocation)) { 286 final ClassLoader loader = LoaderUtil.getThreadContextClassLoader(); 287 final String path = extractClassLoaderUriPath(configLocation); 288 final ConfigurationSource source = getInputFromResource(path, loader); 289 if (source != null) { 290 return source; 291 } 292 } 293 if (!configLocation.isAbsolute()) { // LOG4J2-704 avoid confusing error message thrown by uri.toURL() 294 LOGGER.error("File not found in file system or classpath: {}", configLocation.toString()); 295 return null; 296 } 297 try { 298 return new ConfigurationSource(configLocation.toURL().openStream(), configLocation.toURL()); 299 } catch (final MalformedURLException ex) { 300 LOGGER.error("Invalid URL {}", configLocation.toString(), ex); 301 } catch (final Exception ex) { 302 LOGGER.error("Unable to access {}", configLocation.toString(), ex); 303 } 304 return null; 305 } 306 307 private static boolean isClassLoaderUri(final URI uri) { 308 if (uri == null) { 309 return false; 310 } 311 final String scheme = uri.getScheme(); 312 return scheme == null || scheme.equals(CLASS_LOADER_SCHEME) || scheme.equals(CLASS_PATH_SCHEME); 313 } 314 315 private static String extractClassLoaderUriPath(final URI uri) { 316 return uri.getScheme() == null ? uri.getPath() : uri.getSchemeSpecificPart(); 317 } 318 319 /** 320 * Load the configuration from the location represented by the String. 321 * @param config The configuration location. 322 * @param loader The default ClassLoader to use. 323 * @return The InputSource to use to read the configuration. 324 */ 325 protected ConfigurationSource getInputFromString(final String config, final ClassLoader loader) { 326 try { 327 final URL url = new URL(config); 328 return new ConfigurationSource(url.openStream(), FileUtils.fileFromUri(url.toURI())); 329 } catch (final Exception ex) { 330 final ConfigurationSource source = getInputFromResource(config, loader); 331 if (source == null) { 332 try { 333 final File file = new File(config); 334 return new ConfigurationSource(new FileInputStream(file), file); 335 } catch (final FileNotFoundException fnfe) { 336 // Ignore the exception 337 LOGGER.catching(Level.DEBUG, fnfe); 338 } 339 } 340 return source; 341 } 342 } 343 344 /** 345 * Retrieve the configuration via the ClassLoader. 346 * @param resource The resource to load. 347 * @param loader The default ClassLoader to use. 348 * @return The ConfigurationSource for the configuration. 349 */ 350 protected ConfigurationSource getInputFromResource(final String resource, final ClassLoader loader) { 351 final URL url = Loader.getResource(resource, loader); 352 if (url == null) { 353 return null; 354 } 355 InputStream is = null; 356 try { 357 is = url.openStream(); 358 } catch (final IOException ioe) { 359 LOGGER.catching(Level.DEBUG, ioe); 360 return null; 361 } 362 if (is == null) { 363 return null; 364 } 365 366 if (FileUtils.isFile(url)) { 367 try { 368 return new ConfigurationSource(is, FileUtils.fileFromUri(url.toURI())); 369 } catch (final URISyntaxException ex) { 370 // Just ignore the exception. 371 LOGGER.catching(Level.DEBUG, ex); 372 } 373 } 374 return new ConfigurationSource(is, url); 375 } 376 377 /** 378 * Default Factory. 379 */ 380 private static class Factory extends ConfigurationFactory { 381 382 /** 383 * Default Factory Constructor. 384 * @param name The configuration name. 385 * @param configLocation The configuration location. 386 * @return The Configuration. 387 */ 388 @Override 389 public Configuration getConfiguration(final String name, final URI configLocation) { 390 391 if (configLocation == null) { 392 final String configLocationStr = this.substitutor.replace(PropertiesUtil.getProperties() 393 .getStringProperty(CONFIGURATION_FILE_PROPERTY)); 394 if (configLocationStr != null) { 395 ConfigurationSource source = null; 396 try { 397 source = getInputFromUri(NetUtils.toURI(configLocationStr)); 398 } catch (final Exception ex) { 399 // Ignore the error and try as a String. 400 LOGGER.catching(Level.DEBUG, ex); 401 } 402 if (source == null) { 403 final ClassLoader loader = LoaderUtil.getThreadContextClassLoader(); 404 source = getInputFromString(configLocationStr, loader); 405 } 406 if (source != null) { 407 for (final ConfigurationFactory factory : getFactories()) { 408 final String[] types = factory.getSupportedTypes(); 409 if (types != null) { 410 for (final String type : types) { 411 if (type.equals("*") || configLocationStr.endsWith(type)) { 412 final Configuration config = factory.getConfiguration(source); 413 if (config != null) { 414 return config; 415 } 416 } 417 } 418 } 419 } 420 } 421 } else { 422 for (final ConfigurationFactory factory : getFactories()) { 423 final String[] types = factory.getSupportedTypes(); 424 if (types != null) { 425 for (final String type : types) { 426 if (type.equals("*")) { 427 final Configuration config = factory.getConfiguration(name, configLocation); 428 if (config != null) { 429 return config; 430 } 431 } 432 } 433 } 434 } 435 } 436 } else { 437 // configLocation != null 438 final String configLocationStr = configLocation.toString(); 439 for (final ConfigurationFactory factory : getFactories()) { 440 final String[] types = factory.getSupportedTypes(); 441 if (types != null) { 442 for (final String type : types) { 443 if (type.equals("*") || configLocationStr.endsWith(type)) { 444 final Configuration config = factory.getConfiguration(name, configLocation); 445 if (config != null) { 446 return config; 447 } 448 } 449 } 450 } 451 } 452 } 453 454 Configuration config = getConfiguration(true, name); 455 if (config == null) { 456 config = getConfiguration(true, null); 457 if (config == null) { 458 config = getConfiguration(false, name); 459 if (config == null) { 460 config = getConfiguration(false, null); 461 } 462 } 463 } 464 if (config != null) { 465 return config; 466 } 467 LOGGER.error("No log4j2 configuration file found. Using default configuration: logging only errors to the console."); 468 return new DefaultConfiguration(); 469 } 470 471 private Configuration getConfiguration(final boolean isTest, final String name) { 472 final boolean named = Strings.isNotEmpty(name); 473 final ClassLoader loader = LoaderUtil.getThreadContextClassLoader(); 474 for (final ConfigurationFactory factory : getFactories()) { 475 String configName; 476 final String prefix = isTest ? TEST_PREFIX : DEFAULT_PREFIX; 477 final String [] types = factory.getSupportedTypes(); 478 if (types == null) { 479 continue; 480 } 481 482 for (final String suffix : types) { 483 if (suffix.equals("*")) { 484 continue; 485 } 486 configName = named ? prefix + name + suffix : prefix + suffix; 487 488 final ConfigurationSource source = getInputFromResource(configName, loader); 489 if (source != null) { 490 return factory.getConfiguration(source); 491 } 492 } 493 } 494 return null; 495 } 496 497 @Override 498 public String[] getSupportedTypes() { 499 return null; 500 } 501 502 @Override 503 public Configuration getConfiguration(final ConfigurationSource source) { 504 if (source != null) { 505 final String config = source.getLocation(); 506 for (final ConfigurationFactory factory : getFactories()) { 507 final String[] types = factory.getSupportedTypes(); 508 if (types != null) { 509 for (final String type : types) { 510 if (type.equals("*") || config != null && config.endsWith(type)) { 511 final Configuration c = factory.getConfiguration(source); 512 if (c != null) { 513 LOGGER.debug("Loaded configuration from {}", source); 514 return c; 515 } 516 LOGGER.error("Cannot determine the ConfigurationFactory to use for {}", config); 517 return null; 518 } 519 } 520 } 521 } 522 } 523 LOGGER.error("Cannot process configuration, input source is null"); 524 return null; 525 } 526 } 527 528 static List<ConfigurationFactory> getFactories() { 529 return factories; 530 } 531}