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