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