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