You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@logging.apache.org by rg...@apache.org on 2020/04/11 23:53:50 UTC

[logging-log4j2] branch master updated: LOG4J2-2815 - Allow Spring Boot applications to use composite configuratons.

This is an automated email from the ASF dual-hosted git repository.

rgoers pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git


The following commit(s) were added to refs/heads/master by this push:
     new 9d6ec84  LOG4J2-2815 - Allow Spring Boot applications to use composite configuratons.
9d6ec84 is described below

commit 9d6ec847402fb5567756a7ebc783feb5c0b30d01
Author: Ralph Goers <rg...@apache.org>
AuthorDate: Sat Apr 11 16:13:48 2020 -0700

    LOG4J2-2815 - Allow Spring Boot applications to use composite configuratons.
---
 .../log4j/core/config/ConfigurationFactory.java    | 56 +++++++++++++-
 .../client/Log4j2CloudConfigLoggingSystem.java     | 85 +++++++++++++++++++---
 .../src/site/markdown/index.md                     | 12 +++
 src/changes/changes.xml                            |  3 +
 4 files changed, 146 insertions(+), 10 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 6757f04..2db7a2d 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
@@ -19,9 +19,12 @@ package org.apache.logging.log4j.core.config;
 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;
@@ -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) {
@@ -431,6 +436,20 @@ public abstract class ConfigurationFactory extends ConfigurationBuilderFactory {
                 }
             } else {
                 // configLocation != null
+                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);
+                }
                 final String configLocationStr = configLocation.toString();
                 for (final ConfigurationFactory factory : getFactories()) {
                     final String[] types = factory.getSupportedTypes();
@@ -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 8da5bec..a029922 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
@@ -18,23 +18,33 @@ package org.apache.logging.log4j.spring.cloud.config.client;
 
 import javax.net.ssl.HttpsURLConnection;
 import java.io.File;
+import java.io.FileNotFoundException;
 import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.MalformedURLException;
 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 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.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;
@@ -49,6 +59,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);
@@ -103,13 +115,37 @@ 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(
-                "Could not initialize Log4J2 logging from " + location, ex);
+                    "Could not initialize Log4J2 logging from " + location, ex);
         }
     }
 
@@ -119,6 +155,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());
@@ -133,13 +196,17 @@ 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;
         }
     }
-
     private LoggerContext getLoggerContext() {
         return (LoggerContext) LogManager.getContext(false);
     }
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 6ca33b2..bce3a39 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -169,6 +169,9 @@
       </action>
     </release>
     <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>