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