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 2019/10/20 01:58:09 UTC

[logging-log4j2] branch master updated: LOG4J2-2710 - Allow Spring Boot application properties to be accessed in the Log4j 2 configuraiton. Add lower and upper case Lookups.

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 195134b  LOG4J2-2710 - Allow Spring Boot application properties to be accessed in the Log4j 2 configuraiton. Add lower and upper case Lookups.
195134b is described below

commit 195134b6cb8ae86fe7070c7ebe2c0beefebffc10
Author: Ralph Goers <rg...@apache.org>
AuthorDate: Sat Oct 19 18:57:48 2019 -0700

    LOG4J2-2710 - Allow Spring Boot application properties to be accessed in the Log4j 2 configuraiton. Add lower and upper case Lookups.
---
 .../apache/logging/log4j/spi/LoggerContext.java    | 52 ++++++++++++++++++++++
 .../apache/logging/log4j/core/LoggerContext.java   | 52 +++++++++++++++++++---
 .../logging/log4j/core/lookup/Interpolator.java    | 12 ++++-
 .../logging/log4j/core/lookup/LowerLookup.java     | 48 ++++++++++++++++++++
 .../logging/log4j/core/lookup/UpperLookup.java     | 48 ++++++++++++++++++++
 .../logging/log4j/core/lookup/CaseLookupTest.java  | 48 ++++++++++++++++++++
 .../client/Log4j2CloudConfigLoggingSystem.java     | 29 +++++++++---
 .../spring/cloud/config/client/SpringLookup.java   | 50 +++++++++++++++++++++
 .../src/main/config-repo/log4j2.xml                | 10 +++++
 src/changes/changes.xml                            |  4 ++
 src/site/asciidoc/manual/lookups.adoc              | 47 +++++++++++++++++++
 src/site/markdown/manual/cloud.md                  |  1 +
 12 files changed, 388 insertions(+), 13 deletions(-)

diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggerContext.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggerContext.java
index 88c3b7e..25b0071 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggerContext.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggerContext.java
@@ -30,6 +30,58 @@ public interface LoggerContext {
     Object getExternalContext();
 
     /**
+     * Retrieve an object by its name.
+     * @param key The object's key.
+     * @since 2.13.0
+     */
+    default Object getObject(String key) {
+        return null;
+    }
+
+    /**
+     * Store an object into the LoggerContext by name for later use.
+     * @param key The object's key.
+     * @param value The object.
+     * @return The previous object or null.
+     * @since 2.13.0
+     */
+    default Object putObject(String key, Object value) {
+        return null;
+    }
+
+    /**
+     * Store an object into the LoggerContext by name for later use if an object is not already stored with that key.
+     * @param key The object's key.
+     * @param value The object.
+     * @return The previous object or null.
+     * @since 2.13.0
+     */
+    default Object putObjectIfAbsent(String key, Object value) {
+        return null;
+    }
+
+    /**
+     * Remove an object if it is present.
+     * @param key The object's key.
+     * @return The object if it was present, null if it was not.
+     * @since 2.13.0
+     */
+    default Object removeObject(String key) {
+        return null;
+    }
+
+    /**
+     * Remove an object if it is present and the provided object is stored.
+     * @param key The object's key.
+     * @param value The object.
+     * @return The object if it was present, null if it was not.
+     * @since 2.13.0
+     */
+    default boolean removeObject(String key, Object value) {
+        return false;
+    }
+
+    /**
      * Returns an ExtendedLogger.
      * @param name The name of the Logger to return.
      * @return The logger with the specified name.
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/LoggerContext.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/LoggerContext.java
index d84ab2a..351b2ae 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/LoggerContext.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/LoggerContext.java
@@ -27,6 +27,7 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.TimeUnit;
@@ -93,7 +94,8 @@ public class LoggerContext extends AbstractLifeCycle
      * reference is updated.
      */
     private volatile Configuration configuration = new DefaultConfiguration();
-    private Object externalContext;
+    private static final String EXTERNAL_CONTEXT_KEY = "__EXTERNAL_CONTEXT_KEY__";
+    private ConcurrentMap<String, Object> externalMap = new ConcurrentHashMap<>();
     private String contextName;
     private volatile URI configLocation;
     private Cancellable shutdownCallback;
@@ -128,7 +130,11 @@ public class LoggerContext extends AbstractLifeCycle
      */
     public LoggerContext(final String name, final Object externalContext, final URI configLocn) {
         this.contextName = name;
-        this.externalContext = externalContext;
+        if (externalContext == null) {
+            externalMap.remove(EXTERNAL_CONTEXT_KEY);
+        } else {
+            externalMap.put(EXTERNAL_CONTEXT_KEY, externalContext);
+        }
         this.configLocation = configLocn;
     }
 
@@ -142,7 +148,11 @@ public class LoggerContext extends AbstractLifeCycle
      */
     public LoggerContext(final String name, final Object externalContext, final String configLocn) {
         this.contextName = name;
-        this.externalContext = externalContext;
+        if (externalContext == null) {
+            externalMap.remove(EXTERNAL_CONTEXT_KEY);
+        } else {
+            externalMap.put(EXTERNAL_CONTEXT_KEY, externalContext);
+        }
         if (configLocn != null) {
             URI uri;
             try {
@@ -372,7 +382,7 @@ public class LoggerContext extends AbstractLifeCycle
             configuration = NULL_CONFIGURATION;
             updateLoggers();
             ((LifeCycle) prev).stop(timeout, timeUnit);
-            externalContext = null;
+            externalMap.clear();
             LogManager.getFactory().removeContext(this);
         } finally {
             configLock.unlock();
@@ -419,13 +429,42 @@ public class LoggerContext extends AbstractLifeCycle
     	contextName = Objects.requireNonNull(name);
     }
 
+    @Override
+    public Object getObject(String key) {
+        return externalMap.get(key);
+    }
+
+    @Override
+    public Object putObject(String key, Object value) {
+        return externalMap.put(key, value);
+    }
+
+    @Override
+    public Object putObjectIfAbsent(String key, Object value) {
+        return externalMap.putIfAbsent(key, value);
+    }
+
+    @Override
+    public Object removeObject(String key) {
+        return externalMap.remove(key);
+    }
+
+    @Override
+    public boolean removeObject(String key, Object value) {
+        return externalMap.remove(key, value);
+    }
+
     /**
      * Sets the external context.
      *
      * @param context The external context.
      */
     public void setExternalContext(final Object context) {
-        this.externalContext = context;
+        if (context != null) {
+            this.externalMap.put(EXTERNAL_CONTEXT_KEY, context);
+        } else {
+            this.externalMap.remove(EXTERNAL_CONTEXT_KEY);
+        }
     }
 
     /**
@@ -435,7 +474,7 @@ public class LoggerContext extends AbstractLifeCycle
      */
     @Override
     public Object getExternalContext() {
-        return this.externalContext;
+        return this.externalMap.get(EXTERNAL_CONTEXT_KEY);
     }
 
     /**
@@ -637,6 +676,7 @@ public class LoggerContext extends AbstractLifeCycle
      * Reconfigures the context.
      */
     private void reconfigure(final URI configURI) {
+        Object externalContext = externalMap.get(EXTERNAL_CONTEXT_KEY);
         final ClassLoader cl = ClassLoader.class.isInstance(externalContext) ? (ClassLoader) externalContext : null;
         LOGGER.debug("Reconfiguration started for context[name={}] at URI {} ({}) with optional ClassLoader: {}",
                 contextName, configURI, this, cl);
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/Interpolator.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/Interpolator.java
index 854a762..10a63b0 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/Interpolator.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/Interpolator.java
@@ -40,6 +40,8 @@ public class Interpolator extends AbstractConfigurationAwareLookup {
 
     private static final String LOOKUP_KEY_DOCKER = "docker";
 
+    private static final String LOOKUP_KEY_SPRING = "spring";
+
     private static final String LOOKUP_KEY_JNDI = "jndi";
 
     private static final String LOOKUP_KEY_JVMRUNARGS = "jvmrunargs";
@@ -100,6 +102,8 @@ public class Interpolator extends AbstractConfigurationAwareLookup {
         strLookupMap.put("marker", new MarkerLookup());
         strLookupMap.put("java", new JavaLookup());
         strLookupMap.put("base64", new Base64StrLookup());
+        strLookupMap.put("lower", new LowerLookup());
+        strLookupMap.put("upper", new UpperLookup());
         // JNDI
         try {
             // [LOG4J2-703] We might be on Android
@@ -135,6 +139,12 @@ public class Interpolator extends AbstractConfigurationAwareLookup {
         } catch (final Exception ignored) {
             handleError(LOOKUP_KEY_DOCKER, ignored);
         }
+        try {
+            strLookupMap.put(LOOKUP_KEY_SPRING,
+                    Loader.newCheckedInstanceOf("org.apache.logging.log4j.spring.cloud.config.client.SpringLookup", StrLookup.class));
+        } catch (final Exception ignored) {
+            handleError(LOOKUP_KEY_SPRING, ignored);
+        }
     }
 
     public Map<String, StrLookup> getStrLookupMap() {
@@ -160,7 +170,7 @@ public class Interpolator extends AbstractConfigurationAwareLookup {
                         "available. If you want better web container support, please add the log4j-web JAR to your " +
                         "web archive or server lib directory.");
                 break;
-            case LOOKUP_KEY_DOCKER:
+            case LOOKUP_KEY_DOCKER: case LOOKUP_KEY_SPRING:
                 break;
             default:
                 LOGGER.error("Unable to create Lookup for {}", lookupKey, t);
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/LowerLookup.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/LowerLookup.java
new file mode 100644
index 0000000..366ff9a
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/LowerLookup.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.core.lookup;
+
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.plugins.Plugin;
+
+/**
+ * Converts values to lower case. The passed in "key" should be the value of another lookup.
+ */
+@Plugin(name = "lower", category = StrLookup.CATEGORY)
+public class LowerLookup implements StrLookup {
+
+    /**
+     * Converts the "key" to lower case.
+     * @param key  the key to be looked up, may be null
+     * @return The value associated with the key.
+     */
+    @Override
+    public String lookup(final String key) {
+        return key != null ? key.toLowerCase() : null;
+    }
+
+    /**
+     * Converts the "key" to lower case.
+     * @param event The current LogEvent.
+     * @param key  the key to be looked up, may be null
+     * @return The value associated with the key.
+     */
+    @Override
+    public String lookup(final LogEvent event, final String key) {
+        return lookup(key);
+    }
+}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/UpperLookup.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/UpperLookup.java
new file mode 100644
index 0000000..94737b9
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/UpperLookup.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.core.lookup;
+
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.plugins.Plugin;
+
+/**
+ * Converts values to upper case. The passed in "key" should be the value of another lookup.
+ */
+@Plugin(name = "upper", category = StrLookup.CATEGORY)
+public class UpperLookup implements StrLookup {
+
+    /**
+     * Converts the "key" to upper case.
+     * @param key  the key to be looked up, may be null
+     * @return The value associated with the key.
+     */
+    @Override
+    public String lookup(final String key) {
+        return key != null ? key.toUpperCase() : null;
+    }
+
+    /**
+     * Converts the "key" to upper case.
+     * @param event The current LogEvent.
+     * @param key  the key to be looked up, may be null
+     * @return The value associated with the key.
+     */
+    @Override
+    public String lookup(final LogEvent event, final String key) {
+        return lookup(key);
+    }
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/CaseLookupTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/CaseLookupTest.java
new file mode 100644
index 0000000..ae7d5ac
--- /dev/null
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/CaseLookupTest.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.core.lookup;
+
+import java.util.Calendar;
+
+import org.apache.logging.log4j.core.AbstractLogEvent;
+import org.apache.logging.log4j.core.LogEvent;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+/**
+ *
+ */
+public class CaseLookupTest {
+
+
+    @Test
+    public void testLookup() {
+        final String testStr = "JabberWocky";
+        final String lower = "jabberwocky";
+        final String upper = "JABBERWOCKY";
+        StrLookup lookup = new LowerLookup();
+        String value = lookup.lookup(null, testStr);
+        assertNotNull(value);
+        assertEquals(lower, value);
+        lookup = new UpperLookup();
+        value = lookup.lookup(null, testStr);
+        assertNotNull(value);
+        assertEquals(upper, value);
+    }
+}
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 459be37..5c4aea0 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
@@ -27,7 +27,6 @@ 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.ConfigurationFactory;
 import org.apache.logging.log4j.core.config.ConfigurationSource;
@@ -36,28 +35,40 @@ 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;
 import org.springframework.boot.logging.log4j2.Log4J2LoggingSystem;
-import org.springframework.core.io.ClassPathResource;
 import org.springframework.util.Assert;
 import org.springframework.util.ClassUtils;
 import org.springframework.util.ResourceUtils;
 
 /**
- *
+ * Override Spring's implementation of the Log4j 2 Logging System to properly support Spring Cloud Config.
  */
 public class Log4j2CloudConfigLoggingSystem extends Log4J2LoggingSystem {
-    private static final String FILE_PROTOCOL = "file";
     private static final String HTTPS = "https";
-    private Logger LOGGER = StatusLogger.getLogger();
+    public static final String ENVIRONMENT_KEY = "SpringEnvironment";
 
     public Log4j2CloudConfigLoggingSystem(ClassLoader loader) {
         super(loader);
     }
 
+    /**
+     * Set the environment into the ExternalContext field so that it can be obtained by SpringLookup when it
+     * is constructed. Spring will replace the ExternalContext field with a String once initialization is
+     * complete.
+     * @param initializationContext The initialization context.
+     * @param configLocation The configuration location.
+     * @param logFile the log file.
+     */
+    @Override
+    public void initialize(LoggingInitializationContext initializationContext, String configLocation, LogFile logFile) {
+        getLoggerContext().putObjectIfAbsent(ENVIRONMENT_KEY, initializationContext.getEnvironment());
+        super.initialize(initializationContext, configLocation, logFile);
+    }
+
+
     @Override
     protected String[] getStandardConfigLocations() {
         String[] locations = super.getStandardConfigLocations();
@@ -103,6 +114,12 @@ public class Log4j2CloudConfigLoggingSystem extends Log4J2LoggingSystem {
         }
     }
 
+    @Override
+    public void cleanUp() {
+        getLoggerContext().removeObject(ENVIRONMENT_KEY);
+        super.cleanUp();
+    }
+
     private ConfigurationSource getConfigurationSource(URL url) throws IOException, URISyntaxException {
         URLConnection urlConnection = url.openConnection();
         AuthorizationProvider provider = ConfigurationFactory.getAuthorizationProvider();
diff --git a/log4j-spring-cloud-config/log4j-spring-cloud-config-client/src/main/java/org/apache/logging/log4j/spring/cloud/config/client/SpringLookup.java b/log4j-spring-cloud-config/log4j-spring-cloud-config-client/src/main/java/org/apache/logging/log4j/spring/cloud/config/client/SpringLookup.java
new file mode 100644
index 0000000..53c97ea
--- /dev/null
+++ b/log4j-spring-cloud-config/log4j-spring-cloud-config-client/src/main/java/org/apache/logging/log4j/spring/cloud/config/client/SpringLookup.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.spring.cloud.config.client;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.lookup.StrLookup;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.springframework.core.env.Environment;
+
+/**
+ * Lookup for Spring properties.
+ */
+@Plugin(name = "spring", category = StrLookup.CATEGORY)
+public class SpringLookup implements StrLookup {
+
+    private final Environment environment;
+
+    public SpringLookup() {
+        Object obj = LogManager.getContext(false).getObject(Log4j2CloudConfigLoggingSystem.ENVIRONMENT_KEY);
+        environment = obj instanceof Environment ? (Environment) obj : null;
+    }
+
+    @Override
+    public String lookup(String key) {
+        if (environment != null) {
+            return environment.getProperty(key);
+        }
+        return null;
+    }
+
+    @Override
+    public String lookup(LogEvent event, String key) {
+        return lookup((key));
+    }
+}
diff --git a/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-server/src/main/config-repo/log4j2.xml b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-server/src/main/config-repo/log4j2.xml
index 84b9673..40d3098 100644
--- a/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-server/src/main/config-repo/log4j2.xml
+++ b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-server/src/main/config-repo/log4j2.xml
@@ -1,6 +1,9 @@
 <?xml version="1.0" encoding="UTF-8"?>
 
 <Configuration status="DEBUG" monitorInterval="0">
+  <Properties>
+    <Property name="spring.application.name">sampleapp</Property>
+  </Properties>
   <Appenders>
     <Console name="console" target="SYSTEM_OUT">
       <JsonLayout properties="true" compact="true" eventEol="true" stackTraceAsString="true">
@@ -20,6 +23,7 @@
         <KeyValuePair key="containerId" value="\${docker:containerId}"/>
         <KeyValuePair key="containerName" value="\${docker:containerName}"/>
         <KeyValuePair key="imageName" value="\${docker:imageName}"/>
+        <KeyValuePair key="application" value="\$\${lower:\${spring:spring.application.name}}"/>
       </JsonLayout>
     </Flume>
     <Flume name="avro" ignoreExceptions="false" batchSize="1" compress="false">
@@ -28,6 +32,7 @@
         <KeyValuePair key="containerId" value="\${docker:containerId}"/>
         <KeyValuePair key="containerName" value="\${docker:containerName}"/>
         <KeyValuePair key="imageName" value="\${docker:imageName}"/>
+        <KeyValuePair key="application" value="\$\${lower:\${spring:spring.application.name}}"/>
       </JsonLayout>
     </Flume>
     <Flume name="avroSyslog" ignoreExceptions="false" batchSize="100" compress="false">
@@ -38,6 +43,7 @@
           <KeyValuePair key="containerId" value="\${docker:containerId}"/>
           <KeyValuePair key="containerName" value="\${docker:containerName}"/>
           <KeyValuePair key="imageName" value="\${docker:imageName}"/>
+          <KeyValuePair key="application" value="\$\${lower:\${spring:spring.application.name}}"/>
         </LoggerFields>
       </RFC5424Layout>
     </Flume>
@@ -46,6 +52,7 @@
         <KeyValuePair key="containerId" value="\${docker:containerId}"/>
         <KeyValuePair key="containerName" value="\${docker:containerName}"/>
         <KeyValuePair key="imageName" value="\${docker:imageName}"/>
+        <KeyValuePair key="application" value="\$\${lower:\${spring:spring.application.name}}"/>
       </JsonLayout>
       <Property name="bootstrap.servers">host.docker.internal:9092</Property>
     </Kafka>
@@ -54,6 +61,7 @@
         <KeyValuePair key="containerId" value="\${docker:containerId}"/>
         <KeyValuePair key="containerName" value="\${docker:containerName}"/>
         <KeyValuePair key="imageName" value="\${docker:imageName}"/>
+        <KeyValuePair key="application" value="\$\${lower:\${spring:spring.application.name}}"/>
       </JsonLayout>
     </Socket>
     <RollingFile name="RollingFile" fileName="/var/log/sampleapp/app.log"
@@ -62,6 +70,7 @@
         <KeyValuePair key="containerId" value="\${docker:containerId}"/>
         <KeyValuePair key="containerName" value="\${docker:containerName}"/>
         <KeyValuePair key="imageName" value="\${docker:imageName}"/>
+        <KeyValuePair key="application" value="\$\${lower:\${spring:spring.application.name}}"/>
       </JsonLayout>
       <SizeBasedTriggeringPolicy size="10MB" />
       <DefaultRolloverStrategy max="5"/>
@@ -74,6 +83,7 @@
           <KeyValuePair key="containerId" value="\${docker:containerId}"/>
           <KeyValuePair key="containerName" value="\${docker:containerName}"/>
           <KeyValuePair key="imageName" value="\${docker:imageName}"/>
+          <KeyValuePair key="application" value="\$\${lower:\${spring:spring.application.name}}"/>
         </LoggerFields>
       </RFC5424Layout>
       <SizeBasedTriggeringPolicy size="10MB" />
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 59dbd6b..5ecb436 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -157,6 +157,10 @@
       </action>
     </release>
     <release version="2.13.0" date="2019-MM-DD" description="GA Release 2.13.0">
+      <action issue="LOG4J2-2710" dev="rgoers" type="add">
+        Allow Spring Boot application properties to be accessed in the Log4j 2 configuraiton. Add
+        lower and upper case Lookups.
+      </action>
       <action issue="LOG4J2-2709" dev="rgoers" type="update">
         Allow message portion of GELF layout to be formatted using a PatternLayout. Allow
         ThreadContext attributes to be explicitly included or excluded in the GelfLayout.
diff --git a/src/site/asciidoc/manual/lookups.adoc b/src/site/asciidoc/manual/lookups.adoc
index bf745c4..cea14ff 100644
--- a/src/site/asciidoc/manual/lookups.adoc
+++ b/src/site/asciidoc/manual/lookups.adoc
@@ -241,6 +241,21 @@ relative to the log4j configuration file.
 </File>
 ----
 
+[#LowerLookup]
+== Lower Lookup
+
+The LowerLookup converts the passed in argument to lower case. Presumably the value will be the
+result of a nested lookup.
+
+[source,xml]
+----
+<File name="Application" fileName="application.log">
+  <PatternLayout>
+    <pattern>%d %p %c{1.} [%t] $${lower:{${spring:spring.application.name}} %m%n</pattern>
+  </PatternLayout>
+</File>
+----
+
 [#AppMainArgsLookup]
 == Main Arguments Lookup (Application)
 
@@ -450,6 +465,23 @@ while "type" would have to be an item in the Map in a MapMessage.
 </Routing>
 ----
 
+[#SpringLookup]
+== Spring Boot Lookup
+
+The Spring Boot Lookup retrieves the values of Spring properties from the Spring configuration. This Lookup
+will return null values until Spring Boot initializes application logging.
+
+[source,xml]
+----
+<File name="Application" fileName="application.log">
+  <PatternLayout>
+    <pattern>%d %p %c{1.} [%t] $${spring:spring.application.name} %m%n</pattern>
+  </PatternLayout>
+</File>
+----
+
+This Lookup requires log4j-spring-cloud-config-client be included in the application.
+
 [#SystemPropertiesLookup]
 == System Properties Lookup
 
@@ -477,6 +509,21 @@ when the `logPath` system property is undefined, the default value
 </Appenders>
 ----
 
+[#UpperLookup]
+== Upper Lookup
+
+The LowerLookup converts the passed in argument to upper case. Presumably the value will be the
+result of a nested lookup.
+
+[source,xml]
+----
+<File name="Application" fileName="application.log">
+  <PatternLayout>
+    <pattern>%d %p %c{1.} [%t] $${upper:{${spring:spring.application.name}} %m%n</pattern>
+  </PatternLayout>
+</File>
+----
+
 [#WebLookup]
 == Web Lookup
 
diff --git a/src/site/markdown/manual/cloud.md b/src/site/markdown/manual/cloud.md
index a9c919f..9336914 100644
--- a/src/site/markdown/manual/cloud.md
+++ b/src/site/markdown/manual/cloud.md
@@ -133,6 +133,7 @@ one ip address associated with its DNS entry the socket appender will fail throu
         <ThreadContextIncludes>requestId,sessionId,loginId,userId,ipAddress,callingHost</ThreadContextIncludes>
         <MessagePattern>%d [%t] %-5p %X{requestId, sessionId, loginId, userId, ipAddress} %C{1.}.%M:%L - %m%n</MessagePattern>
         <KeyValuePair key="containerId" value="${docker:containerId:-}"/>
+        <KeyValuePair key="application" value="$${lower:${spring:spring.application.name:-spring}}"/>
       </GelfLayout>
     </Socket>