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>

Reply via email to