io.fabric8.kubernetes.log4j.lookup.KubernetesLookup Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of kubernetes-log4j Show documentation
Show all versions of kubernetes-log4j Show documentation
Provides a lookup to use Kubernetes attributes in a Log4j Core configuration.
The newest version!
/*
* Copyright (C) 2015 Red Hat, Inc.
*
* Licensed 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 io.fabric8.kubernetes.log4j.lookup;
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.ObjectMeta;
import io.fabric8.kubernetes.api.model.Pod;
import io.fabric8.kubernetes.api.model.PodSpec;
import io.fabric8.kubernetes.api.model.PodStatus;
import io.fabric8.kubernetes.client.KubernetesClient;
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 java.net.InetAddress;
import java.net.URL;
import java.net.UnknownHostException;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* Retrieves various attributes from the Kubernetes server.
*
* The supported keys are listed in the following table:
*
*
* Supported keys
*
* Key
* Description
*
*
* {@value ACCOUNT_NAME}
* the name of the account
*
*
* {@value ANNOTATIONS}
* the annotations of the Kubernetes pod
*
*
* {@value CONTAINER_ID}
* the id of the Kubernetes container
*
*
* {@value CONTAINER_NAME}
* the name of the Kubernetes container
*
*
* {@value HOST}
* the host name of the Kubernetes pod
*
*
* {@value HOST_IP}
* the IP of the Kubernetes pod
*
*
* {@value IMAGE_ID}
* the id of the Kubernetes container image
*
*
* {@value IMAGE_NAME}
* the name of the Kubernetes container image
*
*
* {@value LABELS}
* the labels of the Kubernetes pod
*
*
* "labels.<name>"
* the value of the "<name>" label of the Kubernetes pod
*
*
* {@value MASTER_URL}
* the master URL of the Kubernetes cluster
*
*
* {@value NAMESPACE_ANNOTATIONS}
* the annotations of the namespace
*
*
* {@value NAMESPACE_ID}
* the id of the namespace
*
*
* {@value NAMESPACE_LABELS}
* the labels of the namespace
*
*
* {@value NAMESPACE_NAME}
* the name of the namespace
*
*
* {@value POD_ID}
* the id of the pod
*
*
* {@value POD_IP}
* the IP of the pod
*
*
* {@value POD_NAME}
* the name of the pod
*
*
*/
@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";
/**
* Supported keys
*/
private static final String ACCOUNT_NAME = "accountName";
private static final String ANNOTATIONS = "annotations";
private static final String CONTAINER_ID = "containerId";
private static final String CONTAINER_NAME = "containerName";
private static final String HOST = "host";
private static final String HOST_IP = "hostIp";
private static final String IMAGE_ID = "imageId";
private static final String IMAGE_NAME = "imageName";
private static final String LABELS = "labels";
private static final String LABELS_PREFIX = "labels.";
private static final String MASTER_URL = "masterUrl";
private static final String NAMESPACE_ANNOTATIONS = "namespaceAnnotations";
private static final String NAMESPACE_ID = "namespaceId";
private static final String NAMESPACE_LABELS = "namespaceLabels";
private static final String NAMESPACE_NAME = "namespaceName";
private static final String POD_ID = "podId";
private static final String POD_IP = "podIp";
private static final String POD_NAME = "podName";
private static KubernetesInfo kubernetesInfo;
// Used in tests
static Path cgroupPath = ContainerUtil.CGROUP_PATH;
private static final ReadWriteLock LOCK = new ReentrantReadWriteLock();
private static final Lock READ_LOCK = LOCK.readLock();
private static final Lock WRITE_LOCK = LOCK.writeLock();
private static final boolean IS_SPRING_INCLUDED = LoaderUtil
.isClassAvailable("org.apache.logging.log4j.spring.cloud.config.client.SpringEnvironmentHolder")
|| LoaderUtil.isClassAvailable("org.apache.logging.log4j.spring.boot.SpringEnvironmentHolder");
// Vert.x uses Log4j as a logger, if the KubernetesLookup info is initialized when the plugin is instantiated
// it will cause an infinite loop since the Log4j factory will invoke the KubernetesLookup plugin,
// the Vert.x client will invoke the Log4j factory again, and so forth.
private volatile boolean initialized;
private Pod pod;
private Namespace namespace;
private URL masterUrl;
/**
* Default constructor, called by reflection.
*/
public KubernetesLookup() {
this.pod = null;
this.namespace = null;
this.masterUrl = null;
this.initialized = false;
}
KubernetesLookup(Pod pod, Namespace namespace, URL masterUrl) {
this.pod = pod;
this.namespace = namespace;
this.masterUrl = masterUrl;
this.initialized = false;
}
private static void initialize(KubernetesLookup lookup) {
KubernetesInfo kubernetesInfo = KubernetesLookup.kubernetesInfo;
if (kubernetesInfo == null || isSpringStatusChanged(kubernetesInfo)) {
WRITE_LOCK.lock();
try {
kubernetesInfo = KubernetesLookup.kubernetesInfo;
if (kubernetesInfo == null || isSpringStatusChanged(kubernetesInfo)) {
if (lookup.pod == null || lookup.namespace == null || lookup.masterUrl == null) {
tryInitializeFields(lookup);
}
// Retrieve the data from the fields
kubernetesInfo = new KubernetesInfo();
kubernetesInfo.isSpringActive = isSpringActive();
kubernetesInfo.masterUrl = lookup.masterUrl;
if (lookup.namespace != null) {
fillNamespaceData(lookup.namespace, kubernetesInfo);
}
if (lookup.pod != null) {
fillPodData(lookup.pod, kubernetesInfo);
}
KubernetesLookup.kubernetesInfo = kubernetesInfo;
}
} finally {
WRITE_LOCK.unlock();
}
}
}
private static boolean isSpringStatusChanged(KubernetesInfo kubernetesInfo) {
return IS_SPRING_INCLUDED
&& isSpringActive() != (kubernetesInfo != null && kubernetesInfo.isSpringActive);
}
private static boolean isSpringActive() {
return IS_SPRING_INCLUDED
&& LogManager.getFactory() != null
&& LogManager.getFactory().hasContext(KubernetesLookup.class.getName(), null, false)
&& LogManager.getContext(false).getObject(SPRING_ENVIRONMENT_KEY) != null;
}
/**
* Tries to initialize the fields of the lookup.
*/
private static void tryInitializeFields(KubernetesLookup lookup) {
KubernetesClient client = lookup.createClient();
if (client != null) {
if (lookup.pod == null) {
lookup.pod = getCurrentPod(client);
}
if (lookup.pod != null && lookup.namespace == null) {
lookup.namespace = getNamespace(client, lookup.pod);
}
if (lookup.masterUrl == null) {
lookup.masterUrl = client.getMasterUrl();
}
} else {
LOGGER.warn("Kubernetes is not available for access");
}
}
/**
* Creates a Kubernetes client used to retrieve K8S configuration.
*
* Used in tests to provide a mock client.
*
*
* @return A Kubernetes client.
*/
protected KubernetesClient createClient() {
return ClientBuilder.createClient();
}
private static Pod getCurrentPod(final KubernetesClient kubernetesClient) {
final String hostName = getHostName();
try {
if (hostName != null && !hostName.isEmpty()) {
return kubernetesClient.pods().withName(hostName).get();
}
} catch (Exception e) {
LOGGER.debug("Unable to locate pod with name {}.", hostName, e);
}
return null;
}
static String getHostName() {
String hostName = null;
try {
hostName = InetAddress.getLocalHost().getHostName();
} catch (final UnknownHostException ignored) {
// NOP
}
return hostName != null && !"localhost".equals(hostName) ? hostName : System.getenv(HOSTNAME);
}
private static Namespace getNamespace(KubernetesClient client, Pod pod) {
return client.namespaces()
.withName(pod.getMetadata().getNamespace())
.get();
}
private static void fillNamespaceData(Namespace namespace, KubernetesInfo kubernetesInfo) {
final ObjectMeta namespaceMetadata = namespace.getMetadata();
if (namespaceMetadata != null) {
kubernetesInfo.namespaceAnnotations = namespaceMetadata.getAnnotations();
kubernetesInfo.namespaceId = namespaceMetadata.getUid();
kubernetesInfo.namespaceLabels = namespaceMetadata.getLabels();
}
}
private static void fillPodData(Pod pod, KubernetesInfo kubernetesInfo) {
final ObjectMeta podMetadata = pod.getMetadata();
if (podMetadata != null) {
kubernetesInfo.annotations = podMetadata.getAnnotations();
kubernetesInfo.labels = podMetadata.getLabels();
kubernetesInfo.namespace = podMetadata.getNamespace();
kubernetesInfo.podId = podMetadata.getUid();
kubernetesInfo.podName = podMetadata.getName();
}
fillStatuses(pod, kubernetesInfo);
// The container name is filled as a result
String containerName = kubernetesInfo.containerName;
final PodSpec podSpec = pod.getSpec();
if (podSpec != null) {
kubernetesInfo.hostName = podSpec.getNodeName();
kubernetesInfo.accountName = podSpec.getServiceAccountName();
Container container = getContainer(podSpec, containerName);
if (container != null) {
kubernetesInfo.containerName = container.getName();
kubernetesInfo.imageName = container.getImage();
}
}
}
private static void fillStatuses(Pod pod, KubernetesInfo kubernetesInfo) {
final PodStatus podStatus = pod.getStatus();
if (podStatus != null) {
kubernetesInfo.hostIp = podStatus.getHostIP();
kubernetesInfo.podIp = podStatus.getPodIP();
ContainerStatus containerStatus = getContainerStatus(podStatus);
if (containerStatus != null) {
kubernetesInfo.containerId = containerStatus.getContainerID();
kubernetesInfo.imageId = containerStatus.getImageID();
kubernetesInfo.containerName = containerStatus.getName();
}
}
}
private static ContainerStatus getContainerStatus(PodStatus podStatus) {
List statuses = podStatus.getContainerStatuses();
switch (statuses.size()) {
case 0:
return null;
case 1:
return statuses.get(0);
default:
final String containerId = ContainerUtil.getContainerId(cgroupPath);
return containerId != null ? statuses.stream()
.filter(cs -> cs.getContainerID().contains(containerId))
.findFirst()
.orElse(null) : null;
}
}
private static Container getContainer(PodSpec podSpec, String containerName) {
final List containers = podSpec.getContainers();
switch (containers.size()) {
case 0:
return null;
case 1:
return containers.get(0);
default:
return containerName != null ? containers.stream()
.filter(c -> c.getName().equals(containerName))
.findFirst()
.orElse(null) : null;
}
}
@Override
public String lookup(final LogEvent event, final String key) {
synchronized (this) {
if (!initialized) {
initialize(this);
initialized = true;
}
}
KubernetesInfo info;
READ_LOCK.lock();
try {
info = kubernetesInfo;
} finally {
READ_LOCK.unlock();
}
if (key.startsWith(LABELS_PREFIX)) {
return info.labels != null ? info.labels.get(key.substring(LABELS_PREFIX.length())) : null;
}
switch (key) {
case ACCOUNT_NAME: {
return info.accountName;
}
case ANNOTATIONS: {
return info.annotations != null ? info.annotations.toString() : null;
}
case CONTAINER_ID: {
return info.containerId;
}
case CONTAINER_NAME: {
return info.containerName;
}
case HOST: {
return info.hostName;
}
case HOST_IP: {
return info.hostIp;
}
case LABELS: {
return info.labels != null ? info.labels.toString() : null;
}
case MASTER_URL: {
return info.masterUrl != null ? info.masterUrl.toString() : null;
}
case NAMESPACE_ANNOTATIONS: {
return info.namespaceAnnotations != null ? info.namespaceAnnotations.toString() : null;
}
case NAMESPACE_ID: {
return info.namespaceId;
}
case NAMESPACE_LABELS: {
return info.namespaceLabels != null ? info.namespaceLabels.toString() : null;
}
case NAMESPACE_NAME: {
return info.namespace;
}
case POD_ID: {
return info.podId;
}
case POD_IP: {
return info.podIp;
}
case POD_NAME: {
return info.podName;
}
case IMAGE_ID: {
return info.imageId;
}
case IMAGE_NAME: {
return info.imageName;
}
default:
return null;
}
}
/**
* For unit testing only.
*/
static void clear() {
kubernetesInfo = null;
cgroupPath = ContainerUtil.CGROUP_PATH;
}
private static class KubernetesInfo {
boolean isSpringActive;
String accountName;
Map annotations;
String containerId;
String containerName;
String hostName;
String hostIp;
String imageId;
String imageName;
Map labels;
URL masterUrl;
String namespace;
Map namespaceAnnotations;
String namespaceId;
Map namespaceLabels;
String podId;
String podIp;
String podName;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy