This is an automated email from the ASF dual-hosted git repository.
rgoers pushed a commit to branch release-2.x
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git
The following commit(s) were added to refs/heads/release-2.x by this push:
new d10eadb LOG4J2-2815 - Allow Spring Boot applications to use composite
configuratons.
d10eadb is described below
commit d10eadbc2ab11f01ce4d04d7a6eb5a9a285c6e42
Author: Ralph Goers <[email protected]>
AuthorDate: Sat Apr 11 16:13:04 2020 -0700
LOG4J2-2815 - Allow Spring Boot applications to use composite configuratons.
---
.../log4j/core/config/ConfigurationFactory.java | 84 ++++++++++++++++++----
.../client/Log4j2CloudConfigLoggingSystem.java | 83 +++++++++++++++++++--
.../src/site/markdown/index.md | 12 ++++
src/changes/changes.xml | 3 +
4 files changed, 160 insertions(+), 22 deletions(-)
diff --git
a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationFactory.java
b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationFactory.java
index 28a1d67..d376df3 100644
---
a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationFactory.java
+++
b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationFactory.java
@@ -16,20 +16,6 @@
*/
package org.apache.logging.log4j.core.config;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.net.URI;
-import java.net.URL;
-import java.net.URLConnection;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
-
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.LoggerContext;
@@ -51,6 +37,23 @@ import org.apache.logging.log4j.util.LoaderUtil;
import org.apache.logging.log4j.util.PropertiesUtil;
import org.apache.logging.log4j.util.Strings;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLDecoder;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
/**
* Factory class for parsed {@link Configuration} objects from a configuration
file.
* ConfigurationFactory allows the configuration implementation to be
@@ -133,6 +136,8 @@ public abstract class ConfigurationFactory extends
ConfigurationBuilderFactory {
*/
private static final String CLASS_PATH_SCHEME = "classpath";
+ private static final String OVERRIDE_PARAM = "override";
+
private static volatile List<ConfigurationFactory> factories = null;
private static ConfigurationFactory configFactory = new Factory();
@@ -393,7 +398,7 @@ public abstract class ConfigurationFactory extends
ConfigurationBuilderFactory {
final String configLocationStr =
this.substitutor.replace(PropertiesUtil.getProperties()
.getStringProperty(CONFIGURATION_FILE_PROPERTY));
if (configLocationStr != null) {
- final String[] sources = configLocationStr.split(",");
+ String[] sources = parseConfigLocations(configLocationStr);
if (sources.length > 1) {
final List<AbstractConfiguration> configs = new
ArrayList<>();
for (final String sourceLocation : sources) {
@@ -430,6 +435,20 @@ public abstract class ConfigurationFactory extends
ConfigurationBuilderFactory {
}
}
} else {
+ String[] sources = parseConfigLocations(configLocation);
+ if (sources.length > 1) {
+ final List<AbstractConfiguration> configs = new
ArrayList<>();
+ for (final String sourceLocation : sources) {
+ final Configuration config =
getConfiguration(loggerContext, sourceLocation.trim());
+ if (config instanceof AbstractConfiguration) {
+ configs.add((AbstractConfiguration) config);
+ } else {
+ LOGGER.error("Failed to created configuration at
{}", sourceLocation);
+ return null;
+ }
+ }
+ return new CompositeConfiguration(configs);
+ }
// configLocation != null
final String configLocationStr = configLocation.toString();
for (final ConfigurationFactory factory : getFactories()) {
@@ -565,6 +584,41 @@ public abstract class ConfigurationFactory extends
ConfigurationBuilderFactory {
LOGGER.error("Cannot process configuration, input source is null");
return null;
}
+
+ private String[] parseConfigLocations(URI configLocations) {
+ final String[] uris = configLocations.toString().split("\\?");
+ final List<String> locations = new ArrayList<>();
+ if (uris.length > 1) {
+ locations.add(uris[0]);
+ final String[] pairs = configLocations.getQuery().split("&");
+ for (String pair : pairs) {
+ final int idx = pair.indexOf("=");
+ try {
+ final String key = idx > 0 ?
URLDecoder.decode(pair.substring(0, idx), "UTF-8") : pair;
+ if (key.equalsIgnoreCase(OVERRIDE_PARAM)) {
+ locations.add(URLDecoder.decode(pair.substring(idx
+ 1), "UTF-8"));
+ }
+ } catch (UnsupportedEncodingException ex) {
+ LOGGER.warn("Invalid query parameter in {}",
configLocations);
+ }
+ }
+ return locations.toArray(new String[0]);
+ }
+ return new String[] {uris[0]};
+ }
+
+ private String[] parseConfigLocations(String configLocations) {
+ final String[] uris = configLocations.split(",");
+ if (uris.length > 1) {
+ return uris;
+ }
+ try {
+ return parseConfigLocations(new URI(configLocations));
+ } catch (URISyntaxException ex) {
+ LOGGER.warn("Error parsing URI {}", configLocations);
+ }
+ return new String[] {configLocations};
+ }
}
static List<ConfigurationFactory> getFactories() {
diff --git
a/log4j-spring-cloud-config/log4j-spring-cloud-config-client/src/main/java/org/apache/logging/log4j/spring/cloud/config/client/Log4j2CloudConfigLoggingSystem.java
b/log4j-spring-cloud-config/log4j-spring-cloud-config-client/src/main/java/org/apache/logging/log4j/spring/cloud/config/client/Log4j2CloudConfigLoggingSystem.java
index 4f170e3..c41c0a8 100644
---
a/log4j-spring-cloud-config/log4j-spring-cloud-config-client/src/main/java/org/apache/logging/log4j/spring/cloud/config/client/Log4j2CloudConfigLoggingSystem.java
+++
b/log4j-spring-cloud-config/log4j-spring-cloud-config-client/src/main/java/org/apache/logging/log4j/spring/cloud/config/client/Log4j2CloudConfigLoggingSystem.java
@@ -17,25 +17,36 @@
package org.apache.logging.log4j.spring.cloud.config.client;
import java.io.File;
+import java.io.FileNotFoundException;
import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.MalformedURLException;
+import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLConnection;
+import java.net.URLDecoder;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
import javax.net.ssl.HttpsURLConnection;
import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.LoggerContext;
+import org.apache.logging.log4j.core.config.AbstractConfiguration;
+import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.ConfigurationFactory;
import org.apache.logging.log4j.core.config.ConfigurationSource;
+import org.apache.logging.log4j.core.config.composite.CompositeConfiguration;
import org.apache.logging.log4j.core.net.ssl.LaxHostnameVerifier;
import org.apache.logging.log4j.core.net.ssl.SslConfiguration;
import org.apache.logging.log4j.core.net.ssl.SslConfigurationFactory;
import org.apache.logging.log4j.core.util.AuthorizationProvider;
import org.apache.logging.log4j.core.util.BasicAuthorizationProvider;
import org.apache.logging.log4j.core.util.FileUtils;
+import org.apache.logging.log4j.status.StatusLogger;
import org.apache.logging.log4j.util.PropertiesUtil;
import org.springframework.boot.logging.LogFile;
import org.springframework.boot.logging.LoggingInitializationContext;
@@ -50,6 +61,8 @@ import org.springframework.util.ResourceUtils;
public class Log4j2CloudConfigLoggingSystem extends Log4J2LoggingSystem {
private static final String HTTPS = "https";
public static final String ENVIRONMENT_KEY = "SpringEnvironment";
+ private static final String OVERRIDE_PARAM = "override";
+ private static Logger LOGGER = StatusLogger.getLogger();
public Log4j2CloudConfigLoggingSystem(ClassLoader loader) {
super(loader);
@@ -104,9 +117,33 @@ public class Log4j2CloudConfigLoggingSystem extends
Log4J2LoggingSystem {
Assert.notNull(location, "Location must not be null");
try {
LoggerContext ctx = getLoggerContext();
- URL url = ResourceUtils.getURL(location);
- ConfigurationSource source = getConfigurationSource(url);
- ctx.start(ConfigurationFactory.getInstance().getConfiguration(ctx,
source));
+ String[] locations = parseConfigLocations(location);
+ if (locations.length == 1) {
+ final URL url = ResourceUtils.getURL(location);
+ final ConfigurationSource source = getConfigurationSource(url);
+ if (source != null) {
+
ctx.start(ConfigurationFactory.getInstance().getConfiguration(ctx, source));
+ }
+ } else {
+ final List<AbstractConfiguration> configs = new ArrayList<>();
+ for (final String sourceLocation : locations) {
+ final ConfigurationSource source =
getConfigurationSource(ResourceUtils.getURL(sourceLocation));
+ if (source != null) {
+ final Configuration config =
ConfigurationFactory.getInstance().getConfiguration(ctx, source);
+ if (config instanceof AbstractConfiguration) {
+ configs.add((AbstractConfiguration) config);
+ } else {
+ LOGGER.warn("Configuration at {} cannot be
combined in a CompositeConfiguration", sourceLocation);
+ return;
+ }
+ }
+ }
+ if (configs.size() > 1) {
+ ctx.start(new CompositeConfiguration(configs));
+ } else {
+ ctx.start(configs.get(0));
+ }
+ }
}
catch (Exception ex) {
throw new IllegalStateException(
@@ -120,6 +157,33 @@ public class Log4j2CloudConfigLoggingSystem extends
Log4J2LoggingSystem {
super.cleanUp();
}
+ private String[] parseConfigLocations(String configLocations) {
+ final String[] uris = configLocations.split("\\?");
+ final List<String> locations = new ArrayList<>();
+ if (uris.length > 1) {
+ locations.add(uris[0]);
+ try {
+ final URL url = new URL(configLocations);
+ final String[] pairs = url.getQuery().split("&");
+ for (String pair : pairs) {
+ final int idx = pair.indexOf("=");
+ try {
+ final String key = idx > 0 ?
URLDecoder.decode(pair.substring(0, idx), "UTF-8") : pair;
+ if (key.equalsIgnoreCase(OVERRIDE_PARAM)) {
+ locations.add(URLDecoder.decode(pair.substring(idx
+ 1), "UTF-8"));
+ }
+ } catch (UnsupportedEncodingException ex) {
+ LOGGER.warn("Bad data in configuration string: {}",
pair);
+ }
+ }
+ return locations.toArray(new String[0]);
+ } catch (MalformedURLException ex) {
+ LOGGER.warn("Unable to parse configuration URL {}",
configLocations);
+ }
+ }
+ return new String[] {uris[0]};
+ }
+
private ConfigurationSource getConfigurationSource(URL url) throws
IOException, URISyntaxException {
URLConnection urlConnection = url.openConnection();
AuthorizationProvider provider =
ConfigurationFactory.authorizationProvider(PropertiesUtil.getProperties());
@@ -134,10 +198,15 @@ public class Log4j2CloudConfigLoggingSystem extends
Log4J2LoggingSystem {
}
}
File file = FileUtils.fileFromUri(url.toURI());
- if (file != null) {
- return new ConfigurationSource(urlConnection.getInputStream(),
FileUtils.fileFromUri(url.toURI()));
- } else {
- return new ConfigurationSource(urlConnection.getInputStream(),
url, urlConnection.getLastModified());
+ try {
+ if (file != null) {
+ return new ConfigurationSource(urlConnection.getInputStream(),
FileUtils.fileFromUri(url.toURI()));
+ } else {
+ return new ConfigurationSource(urlConnection.getInputStream(),
url, urlConnection.getLastModified());
+ }
+ } catch (FileNotFoundException ex) {
+ LOGGER.info("Unable to locate file {}, ignoring.", url.toString());
+ return null;
}
}
diff --git
a/log4j-spring-cloud-config/log4j-spring-cloud-config-client/src/site/markdown/index.md
b/log4j-spring-cloud-config/log4j-spring-cloud-config-client/src/site/markdown/index.md
index f3bbebd..bb8892f 100644
---
a/log4j-spring-cloud-config/log4j-spring-cloud-config-client/src/site/markdown/index.md
+++
b/log4j-spring-cloud-config/log4j-spring-cloud-config-client/src/site/markdown/index.md
@@ -41,6 +41,18 @@ When referencing a configuration located in Spring Cloud
Config the configuratio
```
log4j.configurationFile=http://host.docker.internal:8888/ConfigService/sampleapp/default/master/log4j2.xml
```
+
+Log4j also supports Composite Configurations. The standard way to do that is
to concatentate the paths to the files in
+a comma separated string. Unfortunately, Spring validates the URL being
provided and commas are not allowed.
+Therefore, additional configurations must be supplied as "override" query
parametes.
+
+```
+log4j.configurationFile=http://host.docker.internal:8888/ConfigService/sampleapp/default/master/log4j2.xml
+?override=http://host.docker.internal:8888/ConfigService/sampleapp/default/master/log4j2-sampleapp.xml
+```
+Note that the location within the directory structure and how configuration
files are located is completely
+dependent on the searchPaths setting in the Spring Cloud Config server.
+
When running in a docker container host.docker.internal may be used as the
domain name to access an application
running on the same hose outside of the docker container. Note that in
accordance with Spring Cloud Config
practices but the application, profile, and label should be specified in the
url.
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 9c8c184..28ba55e 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -30,6 +30,9 @@
- "remove" - Removed
-->
<release version="2.13.2" date="2020-MM-DD" description="GA Release
2.13.2">
+ <action issue="LOG4J2-2815" dev="rgoers" type="update">
+ Allow Spring Boot applications to use composite configuratons.
+ </action>
<action issue="LOG4J2-1360" dev="rgoers" type="add" due-to="Kevin
Leturc">
Provide a Log4j implementation of System.Logger.
</action>