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 253ada6 LOG4J2-2716 - Add the ability to lookup Kubernetes attributes
in the Log4j configuration. Allow Log4j properties to be retrieved from the
Spring environment if it is available.
253ada6 is described below
commit 253ada6ff2fb6c6a0dcfee8ed432372215f2ef78
Author: Ralph Goers <[email protected]>
AuthorDate: Mon Oct 28 11:03:02 2019 -0700
LOG4J2-2716 - Add the ability to lookup Kubernetes attributes in the Log4j
configuration. Allow Log4j properties to be retrieved from the Spring
environment if it is available.
---
.../java/org/apache/logging/log4j/LogManager.java | 23 ++-
.../logging/log4j/spi/LoggerContextFactory.java | 31 +++
.../apache/logging/log4j/util/PropertiesUtil.java | 151 ++++++++++++++
.../apache/logging/log4j/util/PropertySource.java | 28 ++-
.../log4j/core/impl/Log4jContextFactory.java | 21 ++
.../logging/log4j/core/lookup/Interpolator.java | 15 ++
.../log4j/core/osgi/BundleContextSelector.java | 73 +++++++
.../log4j/core/selector/BasicContextSelector.java | 12 ++
.../core/selector/ClassLoaderContextSelector.java | 50 +++++
.../log4j/core/selector/ContextSelector.java | 31 +++
.../log4j/core/selector/JndiContextSelector.java | 39 ++++
log4j-docker/src/site/markdown/index.md.vm | 2 +-
log4j-kubernetes/pom.xml | 207 +++++++++++++++++++
.../log4j/kubernetes/KubernetesClientBuilder.java | 67 +++++++
.../kubernetes/KubernetesClientProperties.java | 191 ++++++++++++++++++
.../logging/log4j/kubernetes/KubernetesLookup.java | 221 +++++++++++++++++++++
log4j-kubernetes/src/site/markdown/index.md.vm | 104 ++++++++++
log4j-kubernetes/src/site/site.xml | 52 +++++
...ingLookup.java => SpringEnvironmentHolder.java} | 40 ++--
.../spring/cloud/config/client/SpringLookup.java | 13 +-
...SpringLookup.java => SpringPropertySource.java} | 34 ++--
.../org.apache.logging.log4j.util.PropertySource} | 19 +-
.../Dockerfile | 2 +-
.../k8s/deploy.sh | 36 ++++
.../k8s/sampleapp-deployment.yaml | 44 ++++
.../k8s/undeploy.sh | 29 +++
.../pom.xml | 5 +
.../src/main/resources/application.yml | 5 +-
.../src/main/config-repo/log4j2.xml | 52 +++--
pom.xml | 10 +
src/changes/changes.xml | 4 +
src/site/markdown/manual/cloud.md | 21 ++
src/site/xdoc/manual/lookups.xml | 55 ++++-
33 files changed, 1606 insertions(+), 81 deletions(-)
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/LogManager.java
b/log4j-api/src/main/java/org/apache/logging/log4j/LogManager.java
index f2fdc72..51d783b 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/LogManager.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/LogManager.java
@@ -387,11 +387,32 @@ public class LogManager {
* @since 2.6
*/
public static void shutdown(final boolean currentContext) {
- shutdown(getContext(currentContext));
+ factory.shutdown(FQCN, null, currentContext, false);
}
/**
* Shutdown the logging system if the logging system supports it.
+ * This is equivalent to calling {@code
LogManager.shutdown(LogManager.getContext(currentContext))}.
+ *
+ * This call is synchronous and will block until shut down is complete.
+ * This may include flushing pending log events over network connections.
+ *
+ * @param currentContext if true a default LoggerContext (may not be the
LoggerContext used to create a Logger
+ * for the calling class) will be used.
+ * If false the LoggerContext appropriate for the caller of
this method is used. For
+ * example, in a web application if the caller is a class in
WEB-INF/lib then one LoggerContext may be
+ * used and if the caller is a class in the container's
classpath then a different LoggerContext may
+ * be used.
+ * @param allContexts if true all LoggerContexts that can be located will
be shutdown.
+ * @since 2.13.0
+ */
+ public static void shutdown(final boolean currentContext, final boolean
allContexts) {
+ factory.shutdown(FQCN, null, currentContext, allContexts);
+ }
+
+
+ /**
+ * Shutdown the logging system if the logging system supports it.
*
* This call is synchronous and will block until shut down is complete.
* This may include flushing pending log events over network connections.
diff --git
a/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggerContextFactory.java
b/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggerContextFactory.java
index 235ad0c..7855098 100644
---
a/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggerContextFactory.java
+++
b/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggerContextFactory.java
@@ -24,6 +24,37 @@ import java.net.URI;
public interface LoggerContextFactory {
/**
+ * Shuts down the LoggerContext.
+ * @param fqcn The fully qualified class name of the caller.
+ * @param loader The ClassLoader to use or null.
+ * @param currentContext If true shuts down the current Context, if false
shuts down the Context appropriate
+ * for the caller if a more appropriate Context can be determined.
+ * @param allContexts if true all LoggerContexts that can be located will
be shutdown.
+ * @since 2.13.0
+ */
+ default void shutdown(String fqcn, ClassLoader loader, boolean
currentContext, boolean allContexts) {
+ if (hasContext(fqcn, loader, currentContext)) {
+ LoggerContext ctx = getContext(fqcn, loader, null, currentContext);
+ if (ctx instanceof Terminable) {
+ ((Terminable) ctx).terminate();
+ }
+ }
+ }
+
+ /**
+ * Checks to see if a LoggerContext is installed. The default
implementation returns false.
+ * @param fqcn The fully qualified class name of the caller.
+ * @param loader The ClassLoader to use or null.
+ * @param currentContext If true returns the current Context, if false
returns the Context appropriate
+ * for the caller if a more appropriate Context can be determined.
+ * @return true if a LoggerContext has been installed, false otherwise.
+ * @since 2.13.0
+ */
+ default boolean hasContext(String fqcn, ClassLoader loader, boolean
currentContext) {
+ return false;
+ }
+
+ /**
* Creates a {@link LoggerContext}.
*
* @param fqcn The fully qualified class name of the caller.
diff --git
a/log4j-api/src/main/java/org/apache/logging/log4j/util/PropertiesUtil.java
b/log4j-api/src/main/java/org/apache/logging/log4j/util/PropertiesUtil.java
index 4feb207..8421629 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/util/PropertiesUtil.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/PropertiesUtil.java
@@ -19,6 +19,9 @@ package org.apache.logging.log4j.util;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
+import java.time.Duration;
+import java.time.temporal.ChronoUnit;
+import java.time.temporal.TemporalUnit;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -155,6 +158,24 @@ public final class PropertiesUtil {
}
/**
+ * Retrieves a property that may be prefixed by more than one string.
+ * @param prefixes The array of prefixes.
+ * @param key The key to locate.
+ * @param supplier The method to call to derive the default value. If the
value is null, null will be returned
+ * if no property is found.
+ * @return The value or null if it is not found.
+ * @since 2.13.0
+ */
+ public Boolean getBooleanProperty(final String[] prefixes, String key,
Supplier<Boolean> supplier) {
+ for (String prefix : prefixes) {
+ if (hasProperty(prefix + key)) {
+ return getBooleanProperty(prefix + key);
+ }
+ }
+ return supplier != null ? supplier.get() : null;
+ }
+
+ /**
* Gets the named property as a Charset value.
*
* @param name the name of the property to look up
@@ -232,6 +253,24 @@ public final class PropertiesUtil {
}
/**
+ * Retrieves a property that may be prefixed by more than one string.
+ * @param prefixes The array of prefixes.
+ * @param key The key to locate.
+ * @param supplier The method to call to derive the default value. If the
value is null, null will be returned
+ * if no property is found.
+ * @return The value or null if it is not found.
+ * @since 2.13.0
+ */
+ public Integer getIntegerProperty(final String[] prefixes, String key,
Supplier<Integer> supplier) {
+ for (String prefix : prefixes) {
+ if (hasProperty(prefix + key)) {
+ return getIntegerProperty(prefix + key, 0);
+ }
+ }
+ return supplier != null ? supplier.get() : null;
+ }
+
+ /**
* Gets the named property as a long.
*
* @param name the name of the property to look up
@@ -251,6 +290,77 @@ public final class PropertiesUtil {
}
/**
+ * Retrieves a property that may be prefixed by more than one string.
+ * @param prefixes The array of prefixes.
+ * @param key The key to locate.
+ * @param supplier The method to call to derive the default value. If the
value is null, null will be returned
+ * if no property is found.
+ * @return The value or null if it is not found.
+ * @since 2.13.0
+ */
+ public Long getLongProperty(final String[] prefixes, String key,
Supplier<Long> supplier) {
+ for (String prefix : prefixes) {
+ if (hasProperty(prefix + key)) {
+ return getLongProperty(prefix + key, 0);
+ }
+ }
+ return supplier != null ? supplier.get() : null;
+ }
+
+ /**
+ * Retrieves a Duration where the String is of the format nnn[unit] where
nnn represents an integer value
+ * and unit represents a time unit.
+ * @param name The property name.
+ * @param defaultValue The default value.
+ * @return The value of the String as a Duration or the default value,
which may be null.
+ * @since 2.13.0
+ */
+ public Duration getDurationProperty(final String name, Duration
defaultValue) {
+ final String prop = getStringProperty(name);
+ if (prop != null) {
+ return TimeUnit.getDuration(prop);
+ }
+ return defaultValue;
+ }
+
+ /**
+ * Retrieves a property that may be prefixed by more than one string.
+ * @param prefixes The array of prefixes.
+ * @param key The key to locate.
+ * @param supplier The method to call to derive the default value. If the
value is null, null will be returned
+ * if no property is found.
+ * @return The value or null if it is not found.
+ * @since 2.13.0
+ */
+ public Duration getDurationProperty(final String[] prefixes, String key,
Supplier<Duration> supplier) {
+ for (String prefix : prefixes) {
+ if (hasProperty(prefix + key)) {
+ return getDurationProperty(prefix + key, null);
+ }
+ }
+ return supplier != null ? supplier.get() : null;
+ }
+
+ /**
+ * Retrieves a property that may be prefixed by more than one string.
+ * @param prefixes The array of prefixes.
+ * @param key The key to locate.
+ * @param supplier The method to call to derive the default value. If the
value is null, null will be returned
+ * if no property is found.
+ * @return The value or null if it is not found.
+ * @since 2.13.0
+ */
+ public String getStringProperty(final String[] prefixes, String key,
Supplier<String> supplier) {
+ for (String prefix : prefixes) {
+ String result = getStringProperty(prefix + key);
+ if (result != null) {
+ return result;
+ }
+ }
+ return supplier != null ? supplier.get() : null;
+ }
+
+ /**
* Gets the named property as a String.
*
* @param name the name of the property to look up
@@ -387,6 +497,11 @@ public final class PropertiesUtil {
if (hasSystemProperty(key)) {
return System.getProperty(key);
}
+ for (final PropertySource source : sources) {
+ if (source.containsProperty(key)) {
+ return source.getProperty(key);
+ }
+ }
return tokenized.get(PropertySource.Util.tokenize(key));
}
@@ -463,4 +578,40 @@ public final class PropertiesUtil {
return getStringProperty("os.name", "").startsWith("Windows");
}
+ private enum TimeUnit {
+ NANOS("ns,nano,nanos,nanosecond,nanoseconds", ChronoUnit.NANOS),
+ MICROS("us,micro,micros,microsecond,microseconds", ChronoUnit.MICROS),
+ MILLIS("ms,milli,millis,millsecond,milliseconds", ChronoUnit.MILLIS),
+ SECONDS("s,second,seconds", ChronoUnit.SECONDS),
+ MINUTES("m,minute,minutes", ChronoUnit.MINUTES),
+ HOURS("h,hour,hours", ChronoUnit.HOURS),
+ DAYS("d,day,days", ChronoUnit.DAYS);
+
+ private final String[] descriptions;
+ private final ChronoUnit timeUnit;
+
+ TimeUnit(String descriptions, ChronoUnit timeUnit) {
+ this.descriptions = descriptions.split(",");
+ this.timeUnit = timeUnit;
+ }
+
+ ChronoUnit getTimeUnit() {
+ return this.timeUnit;
+ }
+
+ static Duration getDuration(String time) {
+ String value = time.trim();
+ TemporalUnit temporalUnit = ChronoUnit.MILLIS;
+ long timeVal = 0;
+ for (TimeUnit timeUnit : values()) {
+ for (String suffix : timeUnit.descriptions) {
+ if (value.endsWith(suffix)) {
+ temporalUnit = timeUnit.timeUnit;
+ timeVal = Long.parseLong(value.substring(0,
value.length() - suffix.length()));
+ }
+ }
+ }
+ return Duration.of(timeVal, temporalUnit);
+ }
+ }
}
diff --git
a/log4j-api/src/main/java/org/apache/logging/log4j/util/PropertySource.java
b/log4j-api/src/main/java/org/apache/logging/log4j/util/PropertySource.java
index aba2ccf..58c462c 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/util/PropertySource.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/PropertySource.java
@@ -45,7 +45,8 @@ public interface PropertySource {
*
* @param action action to perform on each key/value pair
*/
- void forEach(BiConsumer<String, String> action);
+ default void forEach(BiConsumer<String, String> action) {
+ }
/**
* Converts a list of property name tokens into a normal form. For
example, a list of tokens such as
@@ -54,7 +55,30 @@ public interface PropertySource {
* @param tokens list of property name tokens
* @return a normalized property name using the given tokens
*/
- CharSequence getNormalForm(Iterable<? extends CharSequence> tokens);
+ default CharSequence getNormalForm(Iterable<? extends CharSequence>
tokens) {
+ return null;
+ }
+
+ /**
+ * For PropertySources that cannot iterate over all the potential
properties this provides a direct lookup.
+ * @param key The key to search for.
+ * @return The value or null;
+ * @since 2.13.0
+ */
+ default String getProperty(String key) {
+ return null;
+ }
+
+
+ /**
+ * For PropertySources that cannot iterate over all the potential
properties this provides a direct lookup.
+ * @param key The key to search for.
+ * @return The value or null;
+ * @since 2.13.0
+ */
+ default boolean containsProperty(String key) {
+ return false;
+ }
/**
* Comparator for ordering PropertySource instances by priority.
diff --git
a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jContextFactory.java
b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jContextFactory.java
index 9aa1da5..fa56a9d 100644
---
a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jContextFactory.java
+++
b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jContextFactory.java
@@ -283,6 +283,27 @@ public class Log4jContextFactory implements
LoggerContextFactory, ShutdownCallba
return ctx;
}
+ @Override
+ public void shutdown(String fqcn, ClassLoader loader, boolean
currentContext, boolean allContexts) {
+ if (selector.hasContext(fqcn, loader, currentContext)) {
+ selector.shutdown(fqcn, loader, currentContext, allContexts);
+ }
+ }
+
+ /**
+ * Checks to see if a LoggerContext is installed.
+ * @param fqcn The fully qualified class name of the caller.
+ * @param loader The ClassLoader to use or null.
+ * @param currentContext If true returns the current Context, if false
returns the Context appropriate
+ * for the caller if a more appropriate Context can be determined.
+ * @return true if a LoggerContext has been installed, false otherwise.
+ * @since 3.0
+ */
+ @Override
+ public boolean hasContext(String fqcn, ClassLoader loader, boolean
currentContext) {
+ return selector.hasContext(fqcn, loader, currentContext);
+ }
+
/**
* Returns the ContextSelector.
* @return The ContextSelector.
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 400337a..328b21a 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_KUBERNETES = "kubernetes";
+
private static final String LOOKUP_KEY_SPRING = "spring";
private static final String LOOKUP_KEY_JNDI = "jndi";
@@ -144,6 +146,14 @@ public class Interpolator extends
AbstractConfigurationAwareLookup {
} catch (final Exception ignored) {
handleError(LOOKUP_KEY_SPRING, ignored);
}
+ try {
+ strLookupMap.put(LOOKUP_KEY_KUBERNETES,
+
Loader.newCheckedInstanceOf("org.apache.logging.log4j.kubernetes.KubernetesLookup",
StrLookup.class));
+ } catch (final Exception ignored) {
+ handleError(LOOKUP_KEY_KUBERNETES, ignored);
+ } catch (final NoClassDefFoundError error) {
+ handleError(LOOKUP_KEY_KUBERNETES, error);
+ }
}
public Map<String, StrLookup> getStrLookupMap() {
@@ -171,6 +181,11 @@ public class Interpolator extends
AbstractConfigurationAwareLookup {
break;
case LOOKUP_KEY_DOCKER: case LOOKUP_KEY_SPRING:
break;
+ case LOOKUP_KEY_KUBERNETES:
+ if (t instanceof NoClassDefFoundError) {
+ LOGGER.warn("Unable to create Kubernetes lookup due to
missing dependency: {}", t.getMessage());
+ }
+ break;
default:
LOGGER.error("Unable to create Lookup for {}", lookupKey, t);
}
diff --git
a/log4j-core/src/main/java/org/apache/logging/log4j/core/osgi/BundleContextSelector.java
b/log4j-core/src/main/java/org/apache/logging/log4j/core/osgi/BundleContextSelector.java
index 5e22922..8864a76 100644
---
a/log4j-core/src/main/java/org/apache/logging/log4j/core/osgi/BundleContextSelector.java
+++
b/log4j-core/src/main/java/org/apache/logging/log4j/core/osgi/BundleContextSelector.java
@@ -19,6 +19,7 @@ package org.apache.logging.log4j.core.osgi;
import java.lang.ref.WeakReference;
import java.net.URI;
import java.util.Objects;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.logging.log4j.core.LoggerContext;
@@ -39,6 +40,72 @@ import org.osgi.framework.FrameworkUtil;
public class BundleContextSelector extends ClassLoaderContextSelector {
@Override
+ public void shutdown(final String fqcn, final ClassLoader loader, final
boolean currentContext,
+ final boolean allContexts) {
+ LoggerContext ctx = null;
+ Bundle bundle = null;
+ if (currentContext) {
+ ctx = ContextAnchor.THREAD_CONTEXT.get();
+ ContextAnchor.THREAD_CONTEXT.remove();
+ }
+ if (ctx == null && loader instanceof BundleReference) {
+ bundle = ((BundleReference) loader).getBundle();
+ ctx = getLoggerContext(bundle);
+ removeLoggerContext(ctx);
+ }
+ if (ctx == null) {
+ final Class<?> callerClass = StackLocatorUtil.getCallerClass(fqcn);
+ if (callerClass != null) {
+ bundle = FrameworkUtil.getBundle(callerClass);
+ ctx = getLoggerContext(FrameworkUtil.getBundle(callerClass));
+ removeLoggerContext(ctx);
+ }
+ }
+ if (ctx == null) {
+ ctx = ContextAnchor.THREAD_CONTEXT.get();
+ ContextAnchor.THREAD_CONTEXT.remove();
+ }
+ if (ctx != null) {
+ ctx.stop(DEFAULT_STOP_TIMEOUT, TimeUnit.MILLISECONDS);
+ }
+ if (bundle != null && allContexts) {
+ final Bundle[] bundles = bundle.getBundleContext().getBundles();
+ for (final Bundle bdl : bundles) {
+ ctx = getLoggerContext(bdl);
+ if (ctx != null) {
+ ctx.stop(DEFAULT_STOP_TIMEOUT, TimeUnit.MILLISECONDS);
+ }
+ }
+ }
+ }
+ private LoggerContext getLoggerContext(final Bundle bundle) {
+ final String name = Objects.requireNonNull(bundle, "No Bundle
provided").getSymbolicName();
+ final AtomicReference<WeakReference<LoggerContext>> ref =
CONTEXT_MAP.get(name);
+ if (ref != null && ref.get() != null) {
+ return ref.get().get();
+ }
+ return null;
+ }
+
+ private void removeLoggerContext(LoggerContext context) {
+ CONTEXT_MAP.remove(context.getName());
+ }
+
+ @Override
+ public boolean hasContext(final String fqcn, final ClassLoader loader,
final boolean currentContext) {
+ if (currentContext && ContextAnchor.THREAD_CONTEXT.get() != null) {
+ return ContextAnchor.THREAD_CONTEXT.get().isStarted();
+ }
+ if (loader instanceof BundleReference) {
+ return hasContext(((BundleReference) loader).getBundle());
+ }
+ final Class<?> callerClass = StackLocatorUtil.getCallerClass(fqcn);
+ if (callerClass != null) {
+ return hasContext(FrameworkUtil.getBundle(callerClass));
+ }
+ return ContextAnchor.THREAD_CONTEXT.get() != null &&
ContextAnchor.THREAD_CONTEXT.get().isStarted();
+ }
+ @Override
public LoggerContext getContext(final String fqcn, final ClassLoader
loader, final boolean currentContext,
final URI configLocation) {
if (currentContext) {
@@ -60,6 +127,12 @@ public class BundleContextSelector extends
ClassLoaderContextSelector {
return lc == null ? getDefault() : lc;
}
+ private static boolean hasContext(final Bundle bundle) {
+ final String name = Objects.requireNonNull(bundle, "No Bundle
provided").getSymbolicName();
+ final AtomicReference<WeakReference<LoggerContext>> ref =
CONTEXT_MAP.get(name);
+ return ref != null && ref.get() != null && ref.get().get() != null &&
ref.get().get().isStarted();
+ }
+
private static LoggerContext locateContext(final Bundle bundle, final URI
configLocation) {
final String name = Objects.requireNonNull(bundle, "No Bundle
provided").getSymbolicName();
final AtomicReference<WeakReference<LoggerContext>> ref =
CONTEXT_MAP.get(name);
diff --git
a/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/BasicContextSelector.java
b/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/BasicContextSelector.java
index 502084d..aac0221 100644
---
a/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/BasicContextSelector.java
+++
b/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/BasicContextSelector.java
@@ -20,6 +20,7 @@ import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.concurrent.TimeUnit;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.impl.ContextAnchor;
@@ -32,6 +33,17 @@ public class BasicContextSelector implements ContextSelector
{
private static final LoggerContext CONTEXT = new LoggerContext("Default");
@Override
+ public void shutdown(String fqcn, ClassLoader loader, boolean
currentContext, boolean allContexts) {
+ ContextAnchor.THREAD_CONTEXT.get().stop(DEFAULT_STOP_TIMEOUT,
TimeUnit.MILLISECONDS);
+ }
+
+ @Override
+ public boolean hasContext(String fqcn, ClassLoader loader, boolean
currentContext) {
+ LoggerContext ctx = ContextAnchor.THREAD_CONTEXT.get();
+ return ctx != null && ctx.isStarted();
+ }
+
+ @Override
public LoggerContext getContext(final String fqcn, final ClassLoader
loader, final boolean currentContext) {
final LoggerContext ctx = ContextAnchor.THREAD_CONTEXT.get();
diff --git
a/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/ClassLoaderContextSelector.java
b/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/ClassLoaderContextSelector.java
index a581efb..ada4ae7 100644
---
a/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/ClassLoaderContextSelector.java
+++
b/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/ClassLoaderContextSelector.java
@@ -25,6 +25,7 @@ import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.logging.log4j.core.LoggerContext;
@@ -54,6 +55,26 @@ public class ClassLoaderContextSelector implements
ContextSelector, LoggerContex
new ConcurrentHashMap<>();
@Override
+ public void shutdown(final String fqcn, final ClassLoader loader, final
boolean currentContext,
+ final boolean allContexts) {
+ LoggerContext ctx = null;
+ if (currentContext) {
+ ctx = ContextAnchor.THREAD_CONTEXT.get();
+ } else if (loader != null) {
+ ctx = findContext(loader);
+ } else {
+ final Class<?> clazz = StackLocatorUtil.getCallerClass(fqcn);
+ if (clazz != null) {
+ ctx = findContext(clazz.getClassLoader());
+ }
+ ctx = ContextAnchor.THREAD_CONTEXT.get();
+ }
+ if (ctx != null) {
+ ctx.stop(DEFAULT_STOP_TIMEOUT, TimeUnit.MILLISECONDS);
+ }
+ }
+
+ @Override
public void contextShutdown(org.apache.logging.log4j.spi.LoggerContext
loggerContext) {
if (loggerContext instanceof LoggerContext) {
removeContext((LoggerContext) loggerContext);
@@ -61,6 +82,35 @@ public class ClassLoaderContextSelector implements
ContextSelector, LoggerContex
}
@Override
+ public boolean hasContext(final String fqcn, final ClassLoader loader,
final boolean currentContext) {
+ LoggerContext ctx;
+ if (currentContext) {
+ ctx = ContextAnchor.THREAD_CONTEXT.get();
+ } else if (loader != null) {
+ ctx = findContext(loader);
+ } else {
+ final Class<?> clazz = StackLocatorUtil.getCallerClass(fqcn);
+ if (clazz != null) {
+ ctx = findContext(clazz.getClassLoader());
+ } else {
+ ctx = ContextAnchor.THREAD_CONTEXT.get();
+ }
+ }
+ return ctx != null && ctx.isStarted();
+ }
+
+ private LoggerContext findContext(ClassLoader loaderOrNull) {
+ final ClassLoader loader = loaderOrNull != null ? loaderOrNull :
ClassLoader.getSystemClassLoader();
+ final String name = toContextMapKey(loader);
+ AtomicReference<WeakReference<LoggerContext>> ref =
CONTEXT_MAP.get(name);
+ if (ref != null) {
+ final WeakReference<LoggerContext> weakRef = ref.get();
+ return weakRef.get();
+ }
+ return null;
+ }
+
+ @Override
public LoggerContext getContext(final String fqcn, final ClassLoader
loader, final boolean currentContext) {
return getContext(fqcn, loader, currentContext, null);
}
diff --git
a/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/ContextSelector.java
b/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/ContextSelector.java
index 65c4dd7..a1a892c 100644
---
a/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/ContextSelector.java
+++
b/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/ContextSelector.java
@@ -18,6 +18,7 @@ package org.apache.logging.log4j.core.selector;
import java.net.URI;
import java.util.List;
+import java.util.concurrent.TimeUnit;
import org.apache.logging.log4j.core.LoggerContext;
@@ -26,6 +27,36 @@ import org.apache.logging.log4j.core.LoggerContext;
*/
public interface ContextSelector {
+ long DEFAULT_STOP_TIMEOUT = 50;
+
+ /**
+ * Shuts down the LoggerContext.
+ * @param fqcn The fully qualified class name of the caller.
+ * @param loader The ClassLoader to use or null.
+ * @param currentContext If true returns the current Context, if false
returns the Context appropriate
+ * @param allContexts if true all LoggerContexts that can be located will
be shutdown.
+ * @since 2.13.0
+ */
+ default void shutdown(final String fqcn, final ClassLoader loader, final
boolean currentContext,
+ final boolean allContexts) {
+ if (hasContext(fqcn, loader, currentContext)) {
+ getContext(fqcn, loader,
currentContext).stop(DEFAULT_STOP_TIMEOUT, TimeUnit.MILLISECONDS);
+ }
+ }
+
+ /**
+ * Checks to see if a LoggerContext is installed. The default
implementation returns false.
+ * @param fqcn The fully qualified class name of the caller.
+ * @param loader The ClassLoader to use or null.
+ * @param currentContext If true returns the current Context, if false
returns the Context appropriate
+ * for the caller if a more appropriate Context can be determined.
+ * @return true if a LoggerContext has been installed, false otherwise.
+ * @since 2.13.0
+ */
+ default boolean hasContext(String fqcn, ClassLoader loader, boolean
currentContext) {
+ return false;
+ }
+
/**
* Returns the LoggerContext.
* @param fqcn The fully qualified class name of the caller.
diff --git
a/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/JndiContextSelector.java
b/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/JndiContextSelector.java
index b054e9a..09bd0a6 100644
---
a/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/JndiContextSelector.java
+++
b/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/JndiContextSelector.java
@@ -23,6 +23,7 @@ import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.TimeUnit;
import javax.naming.NamingException;
import org.apache.logging.log4j.core.LoggerContext;
@@ -93,6 +94,33 @@ public class JndiContextSelector implements
NamedContextSelector {
private static final StatusLogger LOGGER = StatusLogger.getLogger();
@Override
+ public void shutdown(String fqcn, ClassLoader loader, boolean
currentContext, boolean allContexts) {
+ LoggerContext ctx = ContextAnchor.THREAD_CONTEXT.get();
+ if (ctx == null) {
+ String loggingContextName = getContextName();
+ if (loggingContextName != null) {
+ ctx = CONTEXT_MAP.get(loggingContextName);
+ }
+ }
+ if (ctx != null) {
+ ctx.stop(DEFAULT_STOP_TIMEOUT, TimeUnit.MILLISECONDS);
+ }
+ }
+
+ @Override
+ public boolean hasContext(String fqcn, ClassLoader loader, boolean
currentContext) {
+ LoggerContext ctx = ContextAnchor.THREAD_CONTEXT.get();
+ if (ctx == null) {
+ String loggingContextName = getContextName();
+ if (loggingContextName == null) {
+ return false;
+ }
+ ctx = CONTEXT_MAP.get(loggingContextName);
+ }
+ return ctx != null && ctx.isStarted();
+ }
+
+ @Override
public LoggerContext getContext(final String fqcn, final ClassLoader
loader, final boolean currentContext) {
return getContext(fqcn, loader, currentContext, null);
}
@@ -117,6 +145,17 @@ public class JndiContextSelector implements
NamedContextSelector {
return loggingContextName == null ? CONTEXT :
locateContext(loggingContextName, null, configLocation);
}
+ private String getContextName() {
+ String loggingContextName = null;
+
+ try (final JndiManager jndiManager = JndiManager.getDefaultManager()) {
+ loggingContextName =
jndiManager.lookup(Constants.JNDI_CONTEXT_NAME);
+ } catch (final NamingException ne) {
+ LOGGER.error("Unable to lookup {}", Constants.JNDI_CONTEXT_NAME,
ne);
+ }
+ return loggingContextName;
+ }
+
@Override
public LoggerContext locateContext(final String name, final Object
externalContext, final URI configLocation) {
if (name == null) {
diff --git a/log4j-docker/src/site/markdown/index.md.vm
b/log4j-docker/src/site/markdown/index.md.vm
index 969e8e5..6ce02e5 100644
--- a/log4j-docker/src/site/markdown/index.md.vm
+++ b/log4j-docker/src/site/markdown/index.md.vm
@@ -48,7 +48,7 @@ Attributes may be accessed by adding
```
$D$container
```
-to the configuration. Note that docker variables are only resolved once during
logging initializaton so they
+to the configuration. Note that docker variables are only resolved once during
logging initialization so they
shouldn't be referenced with more than one '$' character.
$h2 Requirements
diff --git a/log4j-kubernetes/pom.xml b/log4j-kubernetes/pom.xml
new file mode 100644
index 0000000..4d8db5c
--- /dev/null
+++ b/log4j-kubernetes/pom.xml
@@ -0,0 +1,207 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j</artifactId>
+ <version>2.13.0-SNAPSHOT</version>
+ <relativePath>../</relativePath>
+ </parent>
+ <artifactId>log4j-kubernetes</artifactId>
+ <packaging>jar</packaging>
+ <name>Apache Log4j Kubernetes Library</name>
+ <description>Apache Log4j Kubernetes Support</description>
+ <properties>
+ <log4jParentDir>${basedir}/..</log4jParentDir>
+ <docLabel>Log4j Kubernetes Library Documentation</docLabel>
+ <projectDir>/kubernetes</projectDir>
+ <maven.compiler.source>1.8</maven.compiler.source>
+ <maven.compiler.target>1.8</maven.compiler.target>
+ <module.name>org.apache.logging.log4j.kubernetes</module.name>
+ </properties>
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-core</artifactId>
+ </dependency>
+ <!-- Kubernetes Client -->
+ <dependency>
+ <groupId>io.fabric8</groupId>
+ <artifactId>kubernetes-client</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-toolchains-plugin</artifactId>
+ <version>1.1</version>
+ <executions>
+ <execution>
+ <goals>
+ <goal>toolchain</goal>
+ </goals>
+ </execution>
+ </executions>
+ <configuration>
+ <toolchains>
+ <jdk>
+ <version>[8, )</version>
+ </jdk>
+ </toolchains>
+ </configuration>
+ </plugin>
+ <!-- Include the standard NOTICE and LICENSE -->
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-remote-resources-plugin</artifactId>
+ <executions>
+ <execution>
+ <goals>
+ <goal>process</goal>
+ </goals>
+ <configuration>
+ <skip>false</skip>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <forkCount>1</forkCount>
+ <reuseForks>false</reuseForks>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ <reporting>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-changes-plugin</artifactId>
+ <version>${changes.plugin.version}</version>
+ <reportSets>
+ <reportSet>
+ <reports>
+ <report>changes-report</report>
+ </reports>
+ </reportSet>
+ </reportSets>
+ <configuration>
+ <issueLinkTemplate>%URL%/show_bug.cgi?id=%ISSUE%</issueLinkTemplate>
+ <useJql>true</useJql>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-checkstyle-plugin</artifactId>
+ <version>${checkstyle.plugin.version}</version>
+ <configuration>
+
<!--<propertiesLocation>${vfs.parent.dir}/checkstyle.properties</propertiesLocation>
-->
+ <configLocation>${log4jParentDir}/checkstyle.xml</configLocation>
+
<suppressionsLocation>${log4jParentDir}/checkstyle-suppressions.xml</suppressionsLocation>
+ <enableRulesSummary>false</enableRulesSummary>
+ <propertyExpansion>basedir=${basedir}</propertyExpansion>
+
<propertyExpansion>licensedir=${log4jParentDir}/checkstyle-header.txt</propertyExpansion>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-javadoc-plugin</artifactId>
+ <version>${javadoc.plugin.version}</version>
+ <configuration>
+ <bottom><![CDATA[<p align="center">Copyright ©
{inceptionYear}-{currentYear} {organizationName}. All Rights Reserved.<br />
+ Apache Logging, Apache Log4j, Log4j, Apache, the Apache feather
logo, the Apache Logging project logo,
+ and the Apache Log4j logo are trademarks of The Apache Software
Foundation.</p>]]></bottom>
+ <!-- module link generation is completely broken in the javadoc
plugin for a multi-module non-aggregating
+ project -->
+ <detectOfflineLinks>false</detectOfflineLinks>
+ <linksource>true</linksource>
+ </configuration>
+ <reportSets>
+ <reportSet>
+ <id>non-aggregate</id>
+ <reports>
+ <report>javadoc</report>
+ </reports>
+ </reportSet>
+ </reportSets>
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>findbugs-maven-plugin</artifactId>
+ <version>${findbugs.plugin.version}</version>
+ <configuration>
+ <fork>true</fork>
+ <jvmArgs>-Duser.language=en</jvmArgs>
+ <threshold>Normal</threshold>
+ <effort>Default</effort>
+
<excludeFilterFile>${log4jParentDir}/findbugs-exclude-filter.xml</excludeFilterFile>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jxr-plugin</artifactId>
+ <version>${jxr.plugin.version}</version>
+ <reportSets>
+ <reportSet>
+ <id>non-aggregate</id>
+ <reports>
+ <report>jxr</report>
+ </reports>
+ </reportSet>
+ <reportSet>
+ <id>aggregate</id>
+ <reports>
+ <report>aggregate</report>
+ </reports>
+ </reportSet>
+ </reportSets>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-pmd-plugin</artifactId>
+ <version>${pmd.plugin.version}</version>
+ <configuration>
+ <targetJdk>${maven.compiler.target}</targetJdk>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>net.sourceforge.maven-taglib</groupId>
+ <artifactId>maven-taglib-plugin</artifactId>
+ <version>2.4</version>
+ </plugin>
+ </plugins>
+ </reporting>
+</project>
+
diff --git
a/log4j-kubernetes/src/main/java/org/apache/logging/log4j/kubernetes/KubernetesClientBuilder.java
b/log4j-kubernetes/src/main/java/org/apache/logging/log4j/kubernetes/KubernetesClientBuilder.java
new file mode 100644
index 0000000..414f9e7
--- /dev/null
+++
b/log4j-kubernetes/src/main/java/org/apache/logging/log4j/kubernetes/KubernetesClientBuilder.java
@@ -0,0 +1,67 @@
+/*
+ * 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.kubernetes;
+
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.status.StatusLogger;
+
+import io.fabric8.kubernetes.client.Config;
+import io.fabric8.kubernetes.client.ConfigBuilder;
+import io.fabric8.kubernetes.client.DefaultKubernetesClient;
+import io.fabric8.kubernetes.client.KubernetesClient;
+
+/**
+ * Builds a Kubernetes Client.
+ */
+public class KubernetesClientBuilder {
+
+ private static final Logger LOGGER = StatusLogger.getLogger();
+
+ public KubernetesClient createClient() {
+ return new DefaultKubernetesClient(kubernetesClientConfig());
+ }
+
+ private Config kubernetesClientConfig() {
+ Config base = Config.autoConfigure(null);
+ KubernetesClientProperties props = new
KubernetesClientProperties(base);
+ Config properties = new ConfigBuilder(base)
+ .withApiVersion(props.getApiVersion())
+ .withCaCertData(props.getCaCertData())
+ .withCaCertFile(props.getCaCertFile())
+ .withClientCertData(props.getClientCertData())
+ .withClientCertFile(props.getClientCertFile())
+ .withClientKeyAlgo(props.getClientKeyAlgo())
+ .withClientKeyData(props.getClientKeyData())
+ .withClientKeyFile(props.getClientKeyFile())
+ .withClientKeyPassphrase(props.getClientKeyPassphrase())
+ .withConnectionTimeout(props.getConnectionTimeout())
+ .withHttpProxy(props.getHttpProxy())
+ .withHttpsProxy(props.getHttpsProxy())
+ .withMasterUrl(props.getMasterUrl())
+ .withNamespace(props.getNamespace())
+ .withNoProxy(props.getNoProxy())
+ .withPassword(props.getPassword())
+ .withProxyPassword(props.getProxyPassword())
+ .withProxyUsername(props.getProxyUsername())
+ .withRequestTimeout(props.getRequestTimeout())
+ .withRollingTimeout(props.getRollingTimeout())
+ .withTrustCerts(props.isTrustCerts())
+ .withUsername(props.getUsername())
+ .build();
+ return properties;
+ }
+}
diff --git
a/log4j-kubernetes/src/main/java/org/apache/logging/log4j/kubernetes/KubernetesClientProperties.java
b/log4j-kubernetes/src/main/java/org/apache/logging/log4j/kubernetes/KubernetesClientProperties.java
new file mode 100644
index 0000000..61f50e0
--- /dev/null
+++
b/log4j-kubernetes/src/main/java/org/apache/logging/log4j/kubernetes/KubernetesClientProperties.java
@@ -0,0 +1,191 @@
+/*
+ * 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.kubernetes;
+
+import java.time.Duration;
+
+import org.apache.logging.log4j.util.PropertiesUtil;
+
+import io.fabric8.kubernetes.client.Config;
+
+/**
+ * Obtains properties used to configure the Kubernetes client.
+ */
+public class KubernetesClientProperties {
+
+ private static final String[] PREFIXES = {"log4j2.kubernetes.client.",
"spring.cloud.kubernetes.client."};
+ private static final String API_VERSION = "apiVersion";
+ private static final String CA_CERT_FILE = "caCertFile";
+ private static final String CA_CERT_DATA = "caCertData";
+ private static final String CLIENT_CERT_FILE = "clientCertFile";
+ private static final String CLIENT_CERT_DATA = "clientCertData";
+ private static final String CLIENT_KEY_FILE = "clientKeyFile";
+ private static final String CLIENT_KEY_DATA = "cientKeyData";
+ private static final String CLIENT_KEY_ALGO = "clientKeyAlgo";
+ private static final String CLIENT_KEY_PASSPHRASE = "clientKeyPassphrase";
+ private static final String CONNECTION_TIMEOUT = "connectionTimeout";
+ private static final String HTTP_PROXY = "httpProxy";
+ private static final String HTTPS_PROXY = "httpsProxy";
+ private static final String LOGGING_INTERVAL = "loggingInterval";
+ private static final String MASTER_URL = "masterUrl";
+ private static final String NAMESPACE = "namespace";
+ private static final String NO_PROXY = "noProxy";
+ private static final String PASSWORD = "password";
+ private static final String PROXY_USERNAME = "proxyUsername";
+ private static final String PROXY_PASSWORD = "proxyPassword";
+ private static final String REQUEST_TIMEOUT = "requestTimeout";
+ private static final String ROLLING_TIMEOUT = "rollingTimeout";
+ private static final String TRUST_CERTS = "trustCerts";
+ private static final String USERNAME = "username";
+ private static final String WATCH_RECONNECT_INTERVAL =
"watchReconnectInterval";
+ private static final String WATCH_RECONNECT_LIMIT = "watchReconnectLimit";
+
+ private PropertiesUtil props = PropertiesUtil.getProperties();
+ private final Config base;
+
+ public KubernetesClientProperties(Config base) {
+ this.base = base;
+ }
+
+
+ public String getApiVersion() {
+ return props.getStringProperty(PREFIXES, API_VERSION,
base::getApiVersion);
+ }
+ public String getCaCertFile() {
+ return props.getStringProperty(PREFIXES, CA_CERT_FILE,
base::getCaCertFile);
+ }
+
+ public String getCaCertData() {
+ return props.getStringProperty(PREFIXES, CA_CERT_DATA,
base::getCaCertData);
+ }
+
+ public String getClientCertFile() {
+ return props.getStringProperty(PREFIXES, CLIENT_CERT_FILE,
base::getClientCertFile);
+ }
+
+ public String getClientCertData() {
+ return props.getStringProperty(PREFIXES, CLIENT_CERT_DATA,
base::getClientCertData);
+ }
+
+ public String getClientKeyFile() {
+ return props.getStringProperty(PREFIXES, CLIENT_KEY_FILE,
base::getClientKeyFile);
+ }
+
+ public String getClientKeyData() {
+ return props.getStringProperty(PREFIXES, CLIENT_KEY_DATA,
base::getClientKeyData);
+ }
+
+ public String getClientKeyAlgo() {
+ return props.getStringProperty(PREFIXES, CLIENT_KEY_ALGO,
base::getClientKeyAlgo);
+ }
+
+ public String getClientKeyPassphrase() {
+ return props.getStringProperty(PREFIXES, CLIENT_KEY_PASSPHRASE,
base::getClientKeyPassphrase);
+ }
+
+ public int getConnectionTimeout() {
+ Duration timeout = props.getDurationProperty(PREFIXES,
CONNECTION_TIMEOUT, null);
+ if (timeout != null) {
+ return (int) timeout.toMillis();
+ }
+ return base.getConnectionTimeout();
+ }
+
+ public String getHttpProxy() {
+ return props.getStringProperty(PREFIXES, HTTP_PROXY,
base::getHttpProxy);
+ }
+
+ public String getHttpsProxy() {
+ return props.getStringProperty(PREFIXES, HTTPS_PROXY,
base::getHttpsProxy);
+ }
+
+ public int getLoggingInterval() {
+ Duration interval = props.getDurationProperty(PREFIXES,
LOGGING_INTERVAL, null);
+ if (interval != null) {
+ return (int) interval.toMillis();
+ }
+ return base.getLoggingInterval();
+ }
+
+ public String getMasterUrl() {
+ return props.getStringProperty(PREFIXES, MASTER_URL,
base::getMasterUrl);
+ }
+
+ public String getNamespace() {
+ return props.getStringProperty(PREFIXES, NAMESPACE,
base::getNamespace);
+ }
+
+ public String[] getNoProxy() {
+ String result = props.getStringProperty(PREFIXES, NO_PROXY, null);
+ if (result != null) {
+ return result.replace("\\s", "").split(",");
+ }
+ return base.getNoProxy();
+ }
+
+ public String getPassword() {
+ return props.getStringProperty(PREFIXES, PASSWORD, base::getPassword);
+ }
+
+ public String getProxyUsername() {
+ return props.getStringProperty(PREFIXES, PROXY_USERNAME,
base::getProxyUsername);
+ }
+
+ public String getProxyPassword() {
+ return props.getStringProperty(PREFIXES, PROXY_PASSWORD,
base::getProxyPassword);
+ }
+
+ public int getRequestTimeout() {
+ Duration interval = props.getDurationProperty(PREFIXES,
REQUEST_TIMEOUT, null);
+ if (interval != null) {
+ return (int) interval.toMillis();
+ }
+ return base.getRequestTimeout();
+ }
+
+ public long getRollingTimeout() {
+ Duration interval = props.getDurationProperty(PREFIXES,
ROLLING_TIMEOUT, null);
+ if (interval != null) {
+ return interval.toMillis();
+ }
+ return base.getRollingTimeout();
+ }
+
+ public Boolean isTrustCerts() {
+ return props.getBooleanProperty(PREFIXES, TRUST_CERTS,
base::isTrustCerts);
+ }
+
+ public String getUsername() {
+ return props.getStringProperty(PREFIXES, USERNAME, base::getUsername);
+ }
+
+ public int getWatchReconnectInterval() {
+ Duration interval = props.getDurationProperty(PREFIXES,
WATCH_RECONNECT_INTERVAL, null);
+ if (interval != null) {
+ return (int) interval.toMillis();
+ }
+ return base.getWatchReconnectInterval();
+ }
+
+ public int getWatchReconnectLimit() {
+ Duration interval = props.getDurationProperty(PREFIXES,
WATCH_RECONNECT_LIMIT, null);
+ if (interval != null) {
+ return (int) interval.toMillis();
+ }
+ return base.getWatchReconnectLimit();
+ }
+}
diff --git
a/log4j-kubernetes/src/main/java/org/apache/logging/log4j/kubernetes/KubernetesLookup.java
b/log4j-kubernetes/src/main/java/org/apache/logging/log4j/kubernetes/KubernetesLookup.java
new file mode 100644
index 0000000..44aa834
--- /dev/null
+++
b/log4j-kubernetes/src/main/java/org/apache/logging/log4j/kubernetes/KubernetesLookup.java
@@ -0,0 +1,221 @@
+/*
+ * 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.kubernetes;
+
+import java.net.URL;
+import java.nio.file.Paths;
+import java.util.Map;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.core.lookup.AbstractLookup;
+import org.apache.logging.log4j.core.lookup.StrLookup;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.apache.logging.log4j.util.LoaderUtil;
+import org.apache.logging.log4j.util.Strings;
+
+import io.fabric8.kubernetes.api.model.Container;
+import io.fabric8.kubernetes.api.model.ContainerStatus;
+import io.fabric8.kubernetes.api.model.Namespace;
+import io.fabric8.kubernetes.api.model.Pod;
+import io.fabric8.kubernetes.client.Config;
+import io.fabric8.kubernetes.client.KubernetesClient;
+
+
+/**
+ * Retrieve various Kubernetes attributes. Supported keys are:
+ * accountName, containerId, containerName, clusterName, host, hostIp,
labels, labels.app,
+ * labels.podTemplateHash, masterUrl, namespaceId, namespaceName, podId,
podIp, podName,
+ * imageId, imageName.
+ */
+@Plugin(name = "k8s", category = StrLookup.CATEGORY)
+public class KubernetesLookup extends AbstractLookup {
+
+ private static final Logger LOGGER = StatusLogger.getLogger();
+ private static final String HOSTNAME = "HOSTNAME";
+ private static final String SPRING_ENVIRONMENT_KEY = "SpringEnvironment";
+
+ private static volatile KubernetesInfo kubernetesInfo;
+ private static Lock initLock = new ReentrantLock();
+ private static boolean isSpringIncluded =
+
LoaderUtil.isClassAvailable("org.apache.logging.log4j.spring.cloud.config.client.SpringEnvironmentHolder");
+
+ private boolean initialize() {
+ if (kubernetesInfo == null || (isSpringIncluded &&
!kubernetesInfo.isSpringActive)) {
+ initLock.lock();
+ boolean isSpringActive = isSpringActive();
+ if (kubernetesInfo == null || (!kubernetesInfo.isSpringActive &&
isSpringActive)) {
+ try {
+ KubernetesClient client = new
KubernetesClientBuilder().createClient();
+ if (client != null) {
+ KubernetesInfo info = new KubernetesInfo();
+ info.isSpringActive = isSpringActive;
+ info.hostName = getHostname();
+ Pod pod = getCurrentPod(info.hostName, client);
+ if (pod != null) {
+ info.app =
pod.getMetadata().getLabels().get("app");
+ final String app = info.app != null ? info.app :
"";
+ info.podTemplateHash =
pod.getMetadata().getLabels().get("pod-template-hash");
+ info.accountName =
pod.getSpec().getServiceAccountName();
+ info.clusterName =
pod.getMetadata().getClusterName();
+ info.hostIp = pod.getStatus().getHostIP();
+ info.labels = pod.getMetadata().getLabels();
+ info.podId = pod.getMetadata().getUid();
+ info.podIp = pod.getStatus().getPodIP();
+ info.podName = pod.getMetadata().getName();
+ Container container =
pod.getSpec().getContainers().stream()
+ .filter(c ->
c.getName().equals(app)).findFirst().orElse(null);
+ if (container != null) {
+ info.containerName = container.getName();
+ info.imageName = container.getImage();
+ }
+ info.masterUrl = client.getMasterUrl();
+ info.namespace = pod.getMetadata().getNamespace();
+ Namespace namespace =
client.namespaces().withName(info.namespace).get();
+ if (namespace != null) {
+ info.namespaceId =
namespace.getMetadata().getUid();
+ }
+ ContainerStatus containerStatus =
pod.getStatus().getContainerStatuses().stream()
+ .filter(cs ->
cs.getName().equals(app)).findFirst().orElse(null);
+ if (containerStatus != null) {
+ info.containerId =
containerStatus.getContainerID();
+ info.imageId = containerStatus.getImageID();
+ }
+ kubernetesInfo = info;
+ }
+ }
+ } finally {
+ initLock.unlock();
+ }
+ }
+ }
+ return kubernetesInfo != null;
+ }
+
+ @Override
+ public String lookup(LogEvent event, String key) {
+ if (!initialize()) {
+ return null;
+ }
+ switch (key) {
+ case "accountName": {
+ return kubernetesInfo.accountName;
+ }
+ case "containerId": {
+ return kubernetesInfo.containerId;
+ }
+ case "containerName": {
+ return kubernetesInfo.containerName;
+ }
+ case "clusterName": {
+ return kubernetesInfo.clusterName;
+ }
+ case "host": {
+ return kubernetesInfo.hostName;
+ }
+ case "hostIp": {
+ return kubernetesInfo.hostIp;
+ }
+ case "labels": {
+ return kubernetesInfo.labels.toString();
+ }
+ case "labels.app": {
+ return kubernetesInfo.app;
+ }
+ case "labels.podTemplateHash": {
+ return kubernetesInfo.podTemplateHash;
+ }
+ case "masterUrl": {
+ return kubernetesInfo.masterUrl.toString();
+ }
+ case "namespaceId": {
+ return kubernetesInfo.namespaceId;
+ }
+ case "namespaceName": {
+ return kubernetesInfo.namespace;
+ }
+ case "podId": {
+ return kubernetesInfo.podId;
+ }
+ case "podIp": {
+ return kubernetesInfo.podIp;
+ }
+ case "podName": {
+ return kubernetesInfo.podName;
+ }
+ case "imageId": {
+ return kubernetesInfo.imageId;
+ }
+ case "imageName": {
+ return kubernetesInfo.imageName;
+ }
+ default:
+ return null;
+ }
+ }
+
+ private String getHostname() {
+ return System.getenv(HOSTNAME);
+ }
+
+ private Pod getCurrentPod(String hostName, KubernetesClient
kubernetesClient) {
+ try {
+ if (isServiceAccount() && Strings.isNotBlank(hostName)) {
+ return kubernetesClient.pods().withName(hostName).get();
+ }
+ } catch (Throwable t) {
+ LOGGER.debug("Unable to locate pod with name {}.", hostName);
+ }
+ return null;
+ }
+
+ private boolean isServiceAccount() {
+ return
Paths.get(Config.KUBERNETES_SERVICE_ACCOUNT_TOKEN_PATH).toFile().exists()
+ &&
Paths.get(Config.KUBERNETES_SERVICE_ACCOUNT_CA_CRT_PATH).toFile().exists();
+ }
+
+ private boolean isSpringActive() {
+ return isSpringIncluded && LogManager.getFactory() != null
+ &&
LogManager.getFactory().hasContext(KubernetesLookup.class.getName(), null,
false)
+ && LogManager.getContext(false).getObject(SPRING_ENVIRONMENT_KEY)
!= null;
+ }
+
+ private static class KubernetesInfo {
+ boolean isSpringActive;
+ String accountName;
+ String app;
+ String clusterName;
+ String containerId;
+ String containerName;
+ String hostName;
+ String hostIp;
+ String imageId;
+ String imageName;
+ Map<String, String> labels;
+ URL masterUrl;
+ String namespace;
+ String namespaceId;
+ String podId;
+ String podIp;
+ String podName;
+ String podTemplateHash;
+ }
+}
diff --git a/log4j-kubernetes/src/site/markdown/index.md.vm
b/log4j-kubernetes/src/site/markdown/index.md.vm
new file mode 100644
index 0000000..d51e757
--- /dev/null
+++ b/log4j-kubernetes/src/site/markdown/index.md.vm
@@ -0,0 +1,104 @@
+<!-- vim: set syn=markdown : -->
+<!--
+ 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.
+-->
+#set($dollar = '$')
+#set($h1='#')
+#set($h2='##')
+
+$h1 Log4j Kubernetes Support
+
+Log4j supports Kubernetes by providing a Lookup to retrieve container
information.
+
+$h2 Accessing Kubernetes
+
+The Log4j Kubernetes support requires access to the Docker REST interface. In
many cases the REST service
+can be accessed automatically. If needed the Kubernetes client can be
configured any of the standard
+Log4j configuration locations or via the Spring Boot configuration. Note,
however, that since Spring Boot
+causes logging to initialize 3 times and since the Spring environment is only
available during the last
+Log4j initialization Spring properties will only be available to Log4j in the
last initialization.
+
+$h2 Lookup Attributes
+
+Log4j Kubernetes provides access to the following container attributes:
+
+* accountName - The service account name.
+* clusterName - The name of the cluster the application is running in.
+* containerId - The full id assigned to the container.
+* containerName - The name assigned to the container.
+* host - The name of the host.
+* hostIp - The host's ip address.
+* imageId - The id assigned to the image.
+* imageName - The name assigned to the image.
+* labels - All labels formatted in a list.
+* labels.app - The application name.
+* labels.podTemplateHash - The pod's template hash value.
+* masterUrl - The url needed to access the API server.
+* namespaceId - The id of the namespace the various kubernetes components are
located within.
+* namespaceName - The namespace the various kubernetes components are located
within.
+* podId - The pod's id number.
+* podIp - The pod's ip address.
+* podName - The name of the pod.
+
+#set( $D = '${' )
+#set( $container = 'k8s:containerId}')
+Attributes may be accessed by adding
+```
+$D$container
+```
+to the configuration. Note that kubernetes variables are only resolved once
during logging initialization so they
+shouldn't be referenced with more than one '$' character.
+
+$h2 Configuration
+
+Much of the configuration needed to access the Kubernetes API server is
provided automatically by Kubernetes.
+However, it is not uncommon to need to provide the url required to access the
Kubernetes API server or the
+namespace the application is assigned to. The properties below may either be
configured using the Log4j
+variable names and located by Log4j's normal property resolution mechansim or
Log4j will resolve the
+spring properties when the application is running in Spring Boot and the
Spring Environment has been created.
+Note that Spring Boot initializes logging 3 times and only the last will have
a Spring Environment present.
+
+| Log4j Property Name | Spring Property Name | Default | Description |
+|------------------------ |----------------------:|----------:|------------:|
+| log4j2.kubernetes.client.apiVersion |
spring.cloud.kubernetes.client.apiVersion | v1 | Kubernetes API Version |
+| log4j2.kubernetes.client.caCertData |
spring.cloud.kubernetes.client.caCertData | | Kubernetes API CACertData |
+| log4j2.kubernetes.client.caCertFile |
spring.cloud.kubernetes.client.caCertFile | | Kubernetes API CACertFile |
+| log4j2.kubernetes.client.clientCertData |
spring.cloud.kubernetes.client.clientCertData | | Kubernetes API ClientCertData
|
+| log4j2.kubernetes.client.clientCertFile |
spring.cloud.kubernetes.client.clientCertFile | | Kubernetes API ClientCertFile
|
+| log4j2.kubernetes.client.clientKeyAlgo |
spring.cloud.kubernetes.client.clientKeyAlgo | RSA | Kubernetes API
ClientKeyAlgo |
+| log4j2.kubernetes.client.clientKeyData |
spring.cloud.kubernetes.client.clientKeyData | | Kubernetes API ClientKeyData |
+| log4j2.kubernetes.client.clientKeyFile |
spring.cloud.kubernetes.client.clientKeyFile | | Kubernetes API ClientKeyFile |
+| log4j2.kubernetes.client.clientKeyPassPhrase |
spring.cloud.kubernetes.client.clientKeyPassphrase | changeit | Kubernetes API
ClientKeyPassphrase |
+| log4j2.kubernetes.client.connectionTimeout |
spring.cloud.kubernetes.client.connectionTimeout | 10s | Connection timeout |
+| log4j2.kubernetes.client.httpProxy |
spring.cloud.kubernetes.client.http-proxy | | |
+| log4j2.kubernetes.client.httpsProxy |
spring.cloud.kubernetes.client.https-proxy | | |
+| log4j2.kubernetes.client.loggingInberval |
spring.cloud.kubernetes.client.loggingInterval | 20s | Logging interval |
+| log4j2.kubernetes.client.masterUrl |
spring.cloud.kubernetes.client.masterUrl | kubernetes.default.svc | Kubernetes
API Master Node URL |
+| log4j2.kubernetes.client.namespacce |
spring.cloud.kubernetes.client.namespace | default | Kubernetes Namespace |
+| log4j2.kubernetes.client.noProxy | spring.cloud.kubernetes.client.noProxy |
| |
+| log4j2.kubernetes.client.password | spring.cloud.kubernetes.client.password
| | Kubernetes API Password |
+| log4j2.kubernetes.client.proxyPassword |
spring.cloud.kubernetes.client.proxyPassword | | |
+| log4j2.kubernetes.client.proxyUsername |
spring.cloud.kubernetes.client.proxyUsername | | |
+| log4j2.kubernetes.client.requestTimeout |
spring.cloud.kubernetes.client.requestTimeout | 10s | Request timeout |
+| log4j2.kubernetes.client.rollingTimeout |
spring.cloud.kubernetes.client.rollingTimeout | 900s | Rolling timeout |
+| log4j2.kubernetes.client.trustCerts |
spring.cloud.kubernetes.client.trustCerts | false | Kubernetes API Trust
Certificates |
+| log4j2.kubernetes.client.username | spring.cloud.kubernetes.client.username
| | Kubernetes API Username |
+| log4j2.kubernetes.client.watchReconnectInterval |
spring.cloud.kubernetes.client.watchReconnectInterval | 1s | Reconnect Interval
|
+| log4j2.kubernetes.client.watchReconnectLimit |
spring.cloud.kubernetes.client.watchReconnectLimit | -1 | Reconnect Interval
limit retries |
+
+$h2 Requirements
+Log4j Kubernetes requires Log4j Core, Log4j API and a minimum of Java 8.
+For more information, see [Runtime Dependencies](../runtime-dependencies.html).
diff --git a/log4j-kubernetes/src/site/site.xml
b/log4j-kubernetes/src/site/site.xml
new file mode 100644
index 0000000..7322f3b
--- /dev/null
+++ b/log4j-kubernetes/src/site/site.xml
@@ -0,0 +1,52 @@
+<!--
+ 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.
+
+-->
+<project name="Log4j Docker Support"
+ xmlns="http://maven.apache.org/DECORATION/1.4.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/DECORATION/1.4.0
http://maven.apache.org/xsd/decoration-1.4.0.xsd">
+ <body>
+ <links>
+ <item name="Apache" href="http://www.apache.org/" />
+ <item name="Logging Services" href="http://logging.apache.org/"/>
+ <item name="Log4j" href="../index.html"/>
+ </links>
+
+ <!-- Component-specific reports -->
+ <menu ref="reports"/>
+
+ <!-- Overall Project Info -->
+ <menu name="Log4j Project Information" img="icon-info-sign">
+ <item name="Dependencies" href="../dependencies.html" />
+ <item name="Dependency Convergence"
href="../dependency-convergence.html" />
+ <item name="Dependency Management" href="../dependency-management.html"
/>
+ <item name="Project Team" href="../team-list.html" />
+ <item name="Mailing Lists" href="../mail-lists.html" />
+ <item name="Issue Tracking" href="../issue-tracking.html" />
+ <item name="Project License" href="../license.html" />
+ <item name="Source Repository" href="../source-repository.html" />
+ <item name="Project Summary" href="../project-summary.html" />
+ </menu>
+
+ <menu name="Log4j Project Reports" img="icon-cog">
+ <item name="Changes Report" href="../changes-report.html" />
+ <item name="JIRA Report" href="../jira-report.html" />
+ <item name="Surefire Report" href="../surefire-report.html" />
+ <item name="RAT Report" href="../rat-report.html" />
+ </menu>
+ </body>
+</project>
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/SpringEnvironmentHolder.java
similarity index 52%
copy from
log4j-spring-cloud-config/log4j-spring-cloud-config-client/src/main/java/org/apache/logging/log4j/spring/cloud/config/client/SpringLookup.java
copy to
log4j-spring-cloud-config/log4j-spring-cloud-config-client/src/main/java/org/apache/logging/log4j/spring/cloud/config/client/SpringEnvironmentHolder.java
index 9d62dda..60fa511 100644
---
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/SpringEnvironmentHolder.java
@@ -16,35 +16,33 @@
*/
package org.apache.logging.log4j.spring.cloud.config.client;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.lookup.StrLookup;
import org.springframework.core.env.Environment;
/**
- * Lookup for Spring properties.
+ * Provides access to the Spring Environment.
*/
-@Plugin(name = "spring", category = StrLookup.CATEGORY)
-public class SpringLookup implements StrLookup {
+public class SpringEnvironmentHolder {
- private final Environment environment;
+ private volatile Environment environment;
+ private Lock lock = new ReentrantLock();
- 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);
+ protected Environment getEnvironment() {
+ if (environment == null && LogManager.getFactory() != null &&
LogManager.getFactory().hasContext(SpringEnvironmentHolder.class.getName(),
null, false)) {
+ lock.lock();
+ try {
+ if (environment == null) {
+ Object obj =
LogManager.getContext(false).getObject(Log4j2CloudConfigLoggingSystem.ENVIRONMENT_KEY);
+ environment = obj instanceof Environment ? (Environment)
obj : null;
+ }
+ } finally {
+ lock.unlock();
+ }
}
- return null;
- }
-
- @Override
- public String lookup(LogEvent event, String key) {
- return lookup((key));
+ return environment;
}
}
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
index 9d62dda..d8e74eb 100644
---
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
@@ -16,7 +16,6 @@
*/
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.config.plugins.Plugin;
import org.apache.logging.log4j.core.lookup.StrLookup;
@@ -26,19 +25,17 @@ 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 class SpringLookup extends SpringEnvironmentHolder implements StrLookup
{
public SpringLookup() {
- Object obj =
LogManager.getContext(false).getObject(Log4j2CloudConfigLoggingSystem.ENVIRONMENT_KEY);
- environment = obj instanceof Environment ? (Environment) obj : null;
+ getEnvironment();
}
@Override
public String lookup(String key) {
- if (environment != null) {
- return environment.getProperty(key);
+ Environment env = getEnvironment();
+ if (env != null) {
+ return env.getProperty(key);
}
return null;
}
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/SpringPropertySource.java
similarity index 59%
copy from
log4j-spring-cloud-config/log4j-spring-cloud-config-client/src/main/java/org/apache/logging/log4j/spring/cloud/config/client/SpringLookup.java
copy to
log4j-spring-cloud-config/log4j-spring-cloud-config-client/src/main/java/org/apache/logging/log4j/spring/cloud/config/client/SpringPropertySource.java
index 9d62dda..8ffed65 100644
---
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/SpringPropertySource.java
@@ -16,27 +16,27 @@
*/
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.config.plugins.Plugin;
-import org.apache.logging.log4j.core.lookup.StrLookup;
+import org.apache.logging.log4j.util.PropertySource;
import org.springframework.core.env.Environment;
/**
- * Lookup for Spring properties.
+ * Returns properties from Spring.
*/
-@Plugin(name = "spring", category = StrLookup.CATEGORY)
-public class SpringLookup implements StrLookup {
+public class SpringPropertySource extends SpringEnvironmentHolder implements
PropertySource {
- private final Environment environment;
-
- public SpringLookup() {
- Object obj =
LogManager.getContext(false).getObject(Log4j2CloudConfigLoggingSystem.ENVIRONMENT_KEY);
- environment = obj instanceof Environment ? (Environment) obj : null;
+ /**
+ * System properties take precendence followed by properties in Log4j
properties files. Spring properties
+ * follow.
+ * @return This PropertySource's priority.
+ */
+ @Override
+ public int getPriority() {
+ return -50;
}
@Override
- public String lookup(String key) {
+ public String getProperty(String key) {
+ Environment environment = getEnvironment();
if (environment != null) {
return environment.getProperty(key);
}
@@ -44,7 +44,11 @@ public class SpringLookup implements StrLookup {
}
@Override
- public String lookup(LogEvent event, String key) {
- return lookup((key));
+ public boolean containsProperty(String key) {
+ Environment environment = getEnvironment();
+ if (environment != null) {
+ return environment.containsProperty(key);
+ }
+ return false;
}
}
diff --git
a/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/Dockerfile
b/log4j-spring-cloud-config/log4j-spring-cloud-config-client/src/main/resources/META-INF/services/org.apache.logging.log4j.util.PropertySource
similarity index 67%
copy from
log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/Dockerfile
copy to
log4j-spring-cloud-config/log4j-spring-cloud-config-client/src/main/resources/META-INF/services/org.apache.logging.log4j.util.PropertySource
index 7c54173..01b50a9 100644
---
a/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/Dockerfile
+++
b/log4j-spring-cloud-config/log4j-spring-cloud-config-client/src/main/resources/META-INF/services/org.apache.logging.log4j.util.PropertySource
@@ -1,4 +1,3 @@
-#
# 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.
@@ -13,20 +12,4 @@
# 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.
-#
-# Alpine Linux with OpenJDK
-#FROM openjdk:8-jdk-alpine
-FROM openjdk:11-jdk-slim
-
-ARG build_version
-ENV BUILD_VERSION=${build_version}
-RUN mkdir /service
-
-ADD ./target/sampleapp.jar /service/
-WORKDIR /service
-
-#EXPOSE 8080 5005
-EXPOSE 8080
-
-#CMD java
"-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=*:5005" -jar
sampleapp.jar
-CMD java -jar -Xmx2G sampleapp.jar
+org.apache.logging.log4j.spring.cloud.config.client.SpringPropertySource
diff --git
a/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/Dockerfile
b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/Dockerfile
index 7c54173..ce218d3 100644
---
a/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/Dockerfile
+++
b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/Dockerfile
@@ -29,4 +29,4 @@ WORKDIR /service
EXPOSE 8080
#CMD java
"-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=*:5005" -jar
sampleapp.jar
-CMD java -jar -Xmx2G sampleapp.jar
+ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar -Xmx1G sampleapp.jar"]
diff --git
a/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/k8s/deploy.sh
b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/k8s/deploy.sh
new file mode 100755
index 0000000..9f67de2
--- /dev/null
+++
b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/k8s/deploy.sh
@@ -0,0 +1,36 @@
+#!/usr/bin/env bash
+#
+#
+# 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.
+#
+
+echo "Building, (re)creating, starting, and attaching to containers for a
service."
+
+imageName=sampleapp
+containerName=sampleapp-container
+networkName=docker_sampleapp
+debug_port=5005
+#debug_expose="-p $debug_port:$debug_port"
+exposed_ports="-p 8080:8090 $debug_expose"
+
+mvn clean package -DskipTests=true
+docker build --no-cache -t $imageName -f Dockerfile .
+
+docker tag $imageName localhost:5000/$imageName
+docker push localhost:5000/$imageName
+kubectl apply -f k8s/sampleapp-deployment.yaml
\ No newline at end of file
diff --git
a/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/k8s/sampleapp-deployment.yaml
b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/k8s/sampleapp-deployment.yaml
new file mode 100644
index 0000000..fe5d2b7
--- /dev/null
+++
b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/k8s/sampleapp-deployment.yaml
@@ -0,0 +1,44 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: sampleapp
+ labels:
+ app: sampleapp
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: sampleapp
+ template:
+ metadata:
+ labels:
+ app: sampleapp
+ spec:
+ containers:
+ - name: sampleapp
+ image: localhost:5000/sampleapp:latest
+ imagePullPolicy: Always
+ ports:
+ - containerPort: 8080
+ - containerPort: 5005
+ env:
+ - name: JAVA_OPTS
+ value: "-Delastic.search.host=host.docker.internal"
+---
+apiVersion: v1
+kind: Service
+metadata:
+ name: sampleapp
+spec:
+ type: NodePort
+ selector:
+ app: sampleapp
+ ports:
+ - protocol: TCP
+ port: 8080
+ nodePort: 30011
+ name: http
+ - protocol: TCP
+ port: 5005
+ nodePort: 30012
+ name: debug
diff --git
a/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/k8s/undeploy.sh
b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/k8s/undeploy.sh
new file mode 100755
index 0000000..c5255c9
--- /dev/null
+++
b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/k8s/undeploy.sh
@@ -0,0 +1,29 @@
+#!/usr/bin/env bash
+#
+#
+# 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.
+#
+
+echo "Building, (re)creating, starting, and attaching to containers for a
service."
+
+imageName=sampleapp
+containerName=sampleapp-container
+networkName=docker_sampleapp
+
+kubectl delete deploy/$imageName svc/$imageName
+
diff --git
a/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/pom.xml
b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/pom.xml
index f937445..8a7178f 100644
---
a/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/pom.xml
+++
b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/pom.xml
@@ -106,6 +106,11 @@
<artifactId>log4j-docker</artifactId>
<version>${project.version}</version>
</dependency>
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-kubernetes</artifactId>
+ <version>${project.version}</version>
+ </dependency>
<!-- Required for Async Loggers -->
<dependency>
<groupId>com.lmax</groupId>
diff --git
a/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/src/main/resources/application.yml
b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/src/main/resources/application.yml
index 71ca238..7e9838e 100644
---
a/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/src/main/resources/application.yml
+++
b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/src/main/resources/application.yml
@@ -27,4 +27,7 @@ spring:
addresses: rabbit
port: 5672
username: guest
- password: guest
\ No newline at end of file
+ password: guest
+
+log4j2:
+ debug: true
\ No newline at end of file
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 416dbd1..2e9dcfa 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
@@ -16,17 +16,12 @@
limitations under the License.
-->
-<Configuration status="ERROR" monitorInterval="0">
+<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">
- <KeyValuePair key="application"
value="$\${spring:spring.application.name}"/>
- </JsonLayout>
- </Console>
- <Flume name="flume" ignoreExceptions="false" type="Embedded"
compress="false">
+ <!--<Flume name="flume" ignoreExceptions="false" type="Embedded"
compress="false">
<Property name="channel.type">memory</Property>
<Property name="channel.capacity">100000</Property>
<Property name="channel.transactionCapacity">5000</Property>
@@ -105,20 +100,53 @@
</RFC5424Layout>
<SizeBasedTriggeringPolicy size="10MB" />
<DefaultRolloverStrategy max="5"/>
- </RollingFile>
+ </RollingFile>-->
+ <Socket name="Elastic" host="\${sys:elastic.search.host:-localhost}"
port="12222" protocol="tcp" bufferedIo="true" ignoreExceptions="false">
+ <GelfLayout includeStackTrace="true" host="${hostName}"
includeThreadContext="true" includeNullDelimiter="true"
+ compressionType="OFF">
+
<ThreadContextIncludes>requestId,sessionId,loginId,userId,ipAddress,corpAcctNumber,callingHost,ohBehalfOf,onBehalfOfAccount</ThreadContextIncludes>
+ <MessagePattern>%d [%t] %-5p %X{requestId, sessionId, loginId, userId,
ipAddress, corpAcctNumber} %C{1.}.%M:%L - %m%n</MessagePattern>
+ <KeyValuePair key="docker.containerId"
value="\${docker:containerId:-}"/>
+ <KeyValuePair key="application"
value="$\${lower:\${spring:spring.application.name}}"/>
+ <KeyValuePair key="kubernetes.serviceAccountName"
value="\${k8s:accountName:-}"/>
+ <KeyValuePair key="kubernetes.containerId"
value="\${k8s:containerId:-}"/>
+ <KeyValuePair key="kubernetes.containerName"
value="\${k8s:containerName:-}"/>
+ <KeyValuePair key="kubernetes.host" value="\${k8s:host:-}"/>
+ <KeyValuePair key="kubernetes.labels.app"
value="\${k8s:labels.app:-}"/>
+ <KeyValuePair key="kubernetes.labels.pod-template-hash"
value="\${k8s:labels.podTemplateHash:-}"/>
+ <KeyValuePair key="kubernetes.master_url" value="\${k8s:masterUrl:-}"/>
+ <KeyValuePair key="kubernetes.namespaceId"
value="\${k8s:namespaceId:-}"/>
+ <KeyValuePair key="kubernetes.namespaceName"
value="\${k8s:namespaceName:-}"/>
+ <KeyValuePair key="kubernetes.podID" value="\${k8s:podId:-}"/>
+ <KeyValuePair key="kubernetes.podIP" value="\${k8s:podIp:-}"/>
+ <KeyValuePair key="kubernetes.podName" value="\${k8s:podName:-}"/>
+ <KeyValuePair key="kubernetes.imageId" value="\${k8s:imageId:-}"/>
+ <KeyValuePair key="kubernetes.imageName" value="\${k8s:imageName:-}"/>
+ </GelfLayout>
+ </Socket>
+ <Console name="Console" target="SYSTEM_OUT">
+ <RFC5424Layout enterpriseNumber="50177" includeMDC="true"
mdcId="RequestContext" appName="SalesforceGateway"
+ mdcPrefix="" newLine="true"
mdcIncludes="requestId,sessionId,loginId,userId,ipAddress,corpAcctNumber"/>
+ </Console>
+ <Failover name="log4j" primary="Elastic">
+ <Failovers>
+ <AppenderRef ref="Console"/>
+ </Failovers>
+ </Failover>
</Appenders>
+
<Loggers>
<Logger name="org.apache.kafka" level="warn" additivity="false">
- <AppenderRef ref="console"/>
+ <AppenderRef ref="log4j"/>
</Logger>
<Logger name="org.apache.flume" level="warn" additivity="false">
- <AppenderRef ref="console"/>
+ <AppenderRef ref="log4j"/>
</Logger>
<Logger name="org.apache.avro" level="warn" additivity="false">
- <AppenderRef ref="console"/>
+ <AppenderRef ref="log4j"/>
</Logger>
<Root level="DEBUG">
- <AppenderRef ref="console"/>
+ <AppenderRef ref="log4j"/>
</Root>
</Loggers>
</Configuration>
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index f409792..7f6e9d1 100644
--- a/pom.xml
+++ b/pom.xml
@@ -192,6 +192,7 @@
<jackson1Version>1.9.13</jackson1Version>
<jackson2Version>2.9.9</jackson2Version>
<springVersion>3.2.18.RELEASE</springVersion>
+ <kubernetes-client.version>4.6.1</kubernetes-client.version>
<flumeVersion>1.7.0</flumeVersion> <!-- Version 1.8.0 requires Java 8 -->
<disruptorVersion>3.4.2</disruptorVersion>
<conversantDisruptorVersion>1.2.10</conversantDisruptorVersion> <!--
Version 1.2.11 requires Java 8 -->
@@ -728,6 +729,11 @@
<version>${springVersion}</version>
</dependency>
<dependency>
+ <groupId>io.fabric8</groupId>
+ <artifactId>kubernetes-client</artifactId>
+ <version>${kubernetes-client.version}</version>
+ </dependency>
+ <dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<version>2.3.5</version>
@@ -1209,6 +1215,7 @@
<exclude>**/README.md</exclude>
<exclude>RELEASE-NOTES.md</exclude>
<exclude>**/*.yml</exclude>
+ <exclude>**/*.yaml</exclude>
<exclude>**/*.json</exclude>
<excllude>**/images/*.drawio</excllude>
<exclude>**/fluent-bit.conf</exclude>
@@ -1341,6 +1348,7 @@
<exclude>**/README.md</exclude>
<exclude>RELEASE-NOTES.md</exclude>
<exclude>**/*.yml</exclude>
+ <exclude>**/*.yaml</exclude>
<exclude>**/*.json</exclude>
<excllude>**/images/*.drawio</excllude>
<exclude>**/fluent-bit.conf</exclude>
@@ -1389,6 +1397,7 @@
<module>log4j-appserver</module>
<module>log4j-osgi</module>
<module>log4j-docker</module>
+ <module>log4j-kubernetes</module>
<module>log4j-spring-cloud-config</module>
</modules>
<profiles>
@@ -1508,6 +1517,7 @@
<exclude>**/README.md</exclude>
<exclude>RELEASE-NOTES.md</exclude>
<exclude>**/*.yml</exclude>
+ <exclude>**/*.yaml</exclude>
<exclude>**/*.json</exclude>
<excllude>**/images/*.drawio</excllude>
<exclude>**/fluent-bit.conf</exclude>
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 8159bd2..9e79004 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -30,6 +30,10 @@
- "remove" - Removed
-->
<release version="2.13.0" date="2019-MM-DD" description="GA Release
2.13.0">
+ <action issue="LOG4J2-2716" dev="rgoers" type="add">
+ Add the ability to lookup Kubernetes attributes in the Log4j
configuration. Allow Log4j properties to
+ be retrieved from the Spring environment if it is available.
+ </action>
<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.
diff --git a/src/site/markdown/manual/cloud.md
b/src/site/markdown/manual/cloud.md
index d0c7a52..e71753e 100644
--- a/src/site/markdown/manual/cloud.md
+++ b/src/site/markdown/manual/cloud.md
@@ -133,6 +133,20 @@ one ip address associated with its DNS entry the socket
appender will fail throu
<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}}"/>
+ <KeyValuePair key="kubernetes.serviceAccountName"
value="${k8s:accountName:-}"/>
+ <KeyValuePair key="kubernetes.containerId"
value="${k8s:containerId:-}"/>
+ <KeyValuePair key="kubernetes.containerName"
value="${k8s:containerName:-}"/>
+ <KeyValuePair key="kubernetes.host" value="${k8s:host:-}"/>
+ <KeyValuePair key="kubernetes.labels.app" value="${k8s:labels.app:-}"/>
+ <KeyValuePair key="kubernetes.labels.pod-template-hash"
value="${k8s:labels.podTemplateHash:-}"/>
+ <KeyValuePair key="kubernetes.master_url" value="${k8s:masterUrl:-}"/>
+ <KeyValuePair key="kubernetes.namespaceId"
value="${k8s:namespaceId:-}"/>
+ <KeyValuePair key="kubernetes.namespaceName"
value="${k8s:namespaceName:-}"/>
+ <KeyValuePair key="kubernetes.podID" value="${k8s:podId:-}"/>
+ <KeyValuePair key="kubernetes.podIP" value="${k8s:podIp:-}"/>
+ <KeyValuePair key="kubernetes.podName" value="${k8s:podName:-}"/>
+ <KeyValuePair key="kubernetes.imageId" value="${k8s:imageId:-}"/>
+ <KeyValuePair key="kubernetes.imageName" value="${k8s:imageName:-}"/>
</GelfLayout>
</Socket>
@@ -218,6 +232,13 @@ attributes in the formatted log event as described at
provides similar functionality via the [Docker
Lookup](lookups.html#DockerLookup). More information on
Log4j's Docker support may also be found at
[Log4j-Docker](../log4j-docker/index.html).
+## Integration with Kubernetes
+
+Applications managed by Kubernetes can bypass the Docker/Kubernetes logging
infrastructure and log directly to
+either a sidecar forwarder or a logging aggragator cluster while still
including all the kubernetes
+attributes by using the Log4j 2 [Kubernetes
Lookup](lookups.html#KubernetesLookup). More information on
+Log4j's Kubernetes support may also be found at
[Log4j-Kubernetes](../log4j-kubernetes/index.html).
+
## Appender Performance
The numbers in the table below represent how much time in seconds was required
for the application to
call logger.debug 100,000 times. These numbers only include the time taken to
deliver to the specifically
diff --git a/src/site/xdoc/manual/lookups.xml b/src/site/xdoc/manual/lookups.xml
index 4c10ed0..40322bd 100644
--- a/src/site/xdoc/manual/lookups.xml
+++ b/src/site/xdoc/manual/lookups.xml
@@ -76,7 +76,7 @@
Log4j Docker provides access to the following container attributes:
<table>
<tr><td>containerId</td><td>The full id assigned to the
container.</td></tr>
- <tr><td>containreName</td><td>The name assigned to the
container.</td></tr>
+ <tr><td>containerName</td><td>The name assigned to the
container.</td></tr>
<tr><td>imageId</td><td>The id assigned to the image.</td></tr>
<tr><td>imageName</td><td>The name assigned to the image.</td></tr>
<tr><td>shortContainerId</td><td>The first 12 characters of the
container id.</td></tr>
@@ -211,6 +211,59 @@
</p>
<p><strong>Java's JMX module is not available on Android or on
Google App Engine.</strong></p>
</subsection>
+ <a name="KubernetesLookup"/>
+ <subsection name="Kubernetes Lookup">
+ <p>
+ The KubernetesLookup can be used to lookup attributes from the
Kubernetes environment for the container
+ the application is running in.
+ </p>
+ Log4j Kubernetes provides access to the following container
attributes:
+ <table>
+ <tr><td>accountName</td><td>The service account name</td></tr>
+ <tr><td>clusterName</td><td>The name of the cluster the
application is deployed in</td></tr>
+ <tr><td>containerId</td><td>The full id assigned to the
container</td></tr>
+ <tr><td>containerName</td><td>The name assigned to the
container</td></tr>
+ <tr><td>host</td><td>The name assigned to the host operating
system</td></tr>
+ <tr><td>hostIp</td><td>The host's ip address</td></tr>
+ <tr><td>imageId</td><td>The id assigned to the container
image</td></tr>
+ <tr><td>imageName</td><td>The name assigned to the container
image</td></tr>
+ <tr><td>labels</td><td>All labels formatted in a list</td></tr>
+ <tr><td>labesl.app</td><td>The application name</td></tr>
+ <tr><td>labels.podTemplateHash</td><td>The pod's template hash
value</td></tr>
+ <tr><td>masterUrl</td><td>The URL used to access the API
server</td></tr>
+ <tr><td>namespaceId</td><td>The id of the namespace the various
kubernetes components are located within</td></tr>
+ <tr><td>namespaceName</td><td>The namespace the various kubernetes
components are located within</td></tr>
+ <tr><td>podId</td><td>The pod's ip number</td></tr>
+ <tr><td>podIp</td><td>The pod's ip address</td></tr>
+ <tr><td>podName</td><td>The name of the pod</td></tr>
+ </table>
+ <pre class="prettyprint linenums"><![CDATA[
+ <GelfLayout includeStackTrace="true" host="${hostName}"
includeThreadContext="true" includeNullDelimiter="true"
+ compressionType="OFF">
+
<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="docker.containerId"
value="${docker:containerId:-}"/>
+ <KeyValuePair key="application"
value="$${lower:${spring:spring.application.name}}"/>
+ <KeyValuePair key="kubernetes.serviceAccountName"
value="${k8s:accountName:-}"/>
+ <KeyValuePair key="kubernetes.clusterName"
value="${k8s:clusterName:-}/>
+ <KeyValuePair key="kubernetes.containerId"
value="${k8s:containerId:-}"/>
+ <KeyValuePair key="kubernetes.containerName"
value="${k8s:containerName:-}"/>
+ <KeyValuePair key="kubernetes.host" value="${k8s:host:-}"/>
+ <KeyValuePair key="kubernetes.labels.app" value="${k8s:labels.app:-}"/>
+ <KeyValuePair key="kubernetes.labels.pod-template-hash"
value="${k8s:labels.podTemplateHash:-}"/>
+ <KeyValuePair key="kubernetes.master_url" value="${k8s:masterUrl:-}"/>
+ <KeyValuePair key="kubernetes.namespaceId"
value="${k8s:namespaceId:-}"/>
+ <KeyValuePair key="kubernetes.namespaceName"
value="${k8s:namespaceName:-}"/>
+ <KeyValuePair key="kubernetes.podID" value="${k8s:podId:-}"/>
+ <KeyValuePair key="kubernetes.podIP" value="${k8s:podIp:-}"/>
+ <KeyValuePair key="kubernetes.podName" value="${k8s:podName:-}"/>
+ <KeyValuePair key="kubernetes.imageId" value="${k8s:imageId:-}"/>
+ <KeyValuePair key="kubernetes.imageName" value="${k8s:imageName:-}"/>
+ </GelfLayout>]]></pre>
+ <p>
+ This Lookup is subject to the configuration requirements listed at
<a href="../log4j-kubernetes/index.html">Log4j Kubernetes Support</a>
+ </p>
+ </subsection>
<a name="Log4jConfigLookup"/>
<subsection name="Log4j Configuration Location Lookup">
<p>