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