001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache license, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the license for the specific language governing permissions and 015 * limitations under the license. 016 */ 017package org.apache.logging.log4j.kubernetes; 018 019import java.net.URL; 020import java.nio.file.Paths; 021import java.util.List; 022import java.util.Map; 023import java.util.concurrent.locks.Lock; 024import java.util.concurrent.locks.ReentrantLock; 025 026import org.apache.logging.log4j.LogManager; 027import org.apache.logging.log4j.Logger; 028import org.apache.logging.log4j.core.LogEvent; 029import org.apache.logging.log4j.core.config.plugins.Plugin; 030import org.apache.logging.log4j.core.lookup.AbstractLookup; 031import org.apache.logging.log4j.core.lookup.StrLookup; 032import org.apache.logging.log4j.status.StatusLogger; 033import org.apache.logging.log4j.util.LoaderUtil; 034import org.apache.logging.log4j.util.Strings; 035 036import io.fabric8.kubernetes.api.model.Container; 037import io.fabric8.kubernetes.api.model.ContainerStatus; 038import io.fabric8.kubernetes.api.model.Namespace; 039import io.fabric8.kubernetes.api.model.Pod; 040import io.fabric8.kubernetes.client.Config; 041import io.fabric8.kubernetes.client.KubernetesClient; 042 043 044/** 045 * Retrieve various Kubernetes attributes. Supported keys are: 046 * accountName, containerId, containerName, clusterName, host, hostIp, labels, labels.app, 047 * labels.podTemplateHash, masterUrl, namespaceId, namespaceName, podId, podIp, podName, 048 * imageId, imageName. 049 */ 050@Plugin(name = "k8s", category = StrLookup.CATEGORY) 051public class KubernetesLookup extends AbstractLookup { 052 053 private static final Logger LOGGER = StatusLogger.getLogger(); 054 private static final String HOSTNAME = "HOSTNAME"; 055 private static final String SPRING_ENVIRONMENT_KEY = "SpringEnvironment"; 056 057 private static volatile KubernetesInfo kubernetesInfo; 058 private static final Lock initLock = new ReentrantLock(); 059 private static final boolean isSpringIncluded = 060 LoaderUtil.isClassAvailable("org.apache.logging.log4j.spring.cloud.config.client.SpringEnvironmentHolder") 061 || LoaderUtil.isClassAvailable("org.apache.logging.log4j.spring.boot.SpringEnvironmentHolder"); 062 private Pod pod; 063 private Namespace namespace; 064 private URL masterUrl; 065 066 public KubernetesLookup() { 067 this.pod = null; 068 this.namespace = null; 069 this.masterUrl = null; 070 initialize(); 071 } 072 073 KubernetesLookup(Pod pod, Namespace namespace, URL masterUrl) { 074 this.pod = pod; 075 this.namespace = namespace; 076 this.masterUrl = masterUrl; 077 initialize(); 078 } 079 private boolean initialize() { 080 if (kubernetesInfo == null || (isSpringIncluded && !kubernetesInfo.isSpringActive)) { 081 initLock.lock(); 082 try { 083 boolean isSpringActive = isSpringActive(); 084 if (kubernetesInfo == null || (!kubernetesInfo.isSpringActive && isSpringActive)) { 085 KubernetesInfo info = new KubernetesInfo(); 086 KubernetesClient client = null; 087 info.isSpringActive = isSpringActive; 088 if (pod == null) { 089 client = new KubernetesClientBuilder().createClient(); 090 if (client != null) { 091 pod = getCurrentPod(System.getenv(HOSTNAME), client); 092 info.masterUrl = client.getMasterUrl(); 093 if (pod != null) { 094 info.namespace = pod.getMetadata().getNamespace(); 095 namespace = client.namespaces().withName(info.namespace).get(); 096 } 097 } else { 098 LOGGER.warn("Kubernetes is not available for access"); 099 } 100 } else { 101 info.masterUrl = masterUrl; 102 } 103 if (pod != null) { 104 if (namespace != null) { 105 info.namespaceId = namespace.getMetadata().getUid(); 106 info.namespaceAnnotations = namespace.getMetadata().getAnnotations(); 107 info.namespaceLabels = namespace.getMetadata().getLabels(); 108 } 109 info.app = pod.getMetadata().getLabels().get("app"); 110 info.hostName = pod.getSpec().getNodeName(); 111 info.annotations = pod.getMetadata().getAnnotations(); 112 final String app = info.app != null ? info.app : ""; 113 info.podTemplateHash = pod.getMetadata().getLabels().get("pod-template-hash"); 114 info.accountName = pod.getSpec().getServiceAccountName(); 115 info.clusterName = pod.getMetadata().getClusterName(); 116 info.hostIp = pod.getStatus().getHostIP(); 117 info.labels = pod.getMetadata().getLabels(); 118 info.podId = pod.getMetadata().getUid(); 119 info.podIp = pod.getStatus().getPodIP(); 120 info.podName = pod.getMetadata().getName(); 121 ContainerStatus containerStatus = null; 122 List<ContainerStatus> statuses = pod.getStatus().getContainerStatuses(); 123 if (statuses.size() == 1) { 124 containerStatus = statuses.get(0); 125 } else if (statuses.size() > 1) { 126 String containerId = ContainerUtil.getContainerId(); 127 if (containerId != null) { 128 containerStatus = statuses.stream() 129 .filter(cs -> cs.getContainerID().contains(containerId)) 130 .findFirst().orElse(null); 131 } 132 } 133 final String containerName; 134 if (containerStatus != null) { 135 info.containerId = containerStatus.getContainerID(); 136 info.imageId = containerStatus.getImageID(); 137 containerName = containerStatus.getName(); 138 } else { 139 containerName = null; 140 } 141 Container container = null; 142 List<Container> containers = pod.getSpec().getContainers(); 143 if (containers.size() == 1) { 144 container = containers.get(0); 145 } else if (containers.size() > 1 && containerName != null) { 146 container = containers.stream().filter(c -> c.getName().equals(containerName)) 147 .findFirst().orElse(null); 148 } 149 if (container != null) { 150 info.containerName = container.getName(); 151 info.imageName = container.getImage(); 152 } 153 154 kubernetesInfo = info; 155 } 156 } 157 } finally { 158 initLock.unlock(); 159 } 160 } 161 return kubernetesInfo != null; 162 } 163 164 @Override 165 public String lookup(LogEvent event, String key) { 166 if (kubernetesInfo == null) { 167 return null; 168 } 169 switch (key) { 170 case "accountName": { 171 return kubernetesInfo.accountName; 172 } 173 case "annotations": { 174 return kubernetesInfo.annotations.toString(); 175 } 176 case "containerId": { 177 return kubernetesInfo.containerId; 178 } 179 case "containerName": { 180 return kubernetesInfo.containerName; 181 } 182 case "clusterName": { 183 return kubernetesInfo.clusterName; 184 } 185 case "host": { 186 return kubernetesInfo.hostName; 187 } 188 case "hostIp": { 189 return kubernetesInfo.hostIp; 190 } 191 case "labels": { 192 return kubernetesInfo.labels.toString(); 193 } 194 case "labels.app": { 195 return kubernetesInfo.app; 196 } 197 case "labels.podTemplateHash": { 198 return kubernetesInfo.podTemplateHash; 199 } 200 case "masterUrl": { 201 return kubernetesInfo.masterUrl.toString(); 202 } 203 case "namespaceAnnotations": { 204 return kubernetesInfo.namespaceAnnotations.toString(); 205 } 206 case "namespaceId": { 207 return kubernetesInfo.namespaceId; 208 } 209 case "namespaceLabels": { 210 return kubernetesInfo.namespaceLabels.toString(); 211 } 212 case "namespaceName": { 213 return kubernetesInfo.namespace; 214 } 215 case "podId": { 216 return kubernetesInfo.podId; 217 } 218 case "podIp": { 219 return kubernetesInfo.podIp; 220 } 221 case "podName": { 222 return kubernetesInfo.podName; 223 } 224 case "imageId": { 225 return kubernetesInfo.imageId; 226 } 227 case "imageName": { 228 return kubernetesInfo.imageName; 229 } 230 default: 231 return null; 232 } 233 } 234 235 /** 236 * For unit testing only. 237 */ 238 void clearInfo() { 239 kubernetesInfo = null; 240 } 241 242 private String getHostname() { 243 return System.getenv(HOSTNAME); 244 } 245 246 private Pod getCurrentPod(String hostName, KubernetesClient kubernetesClient) { 247 try { 248 if (isServiceAccount() && Strings.isNotBlank(hostName)) { 249 return kubernetesClient.pods().withName(hostName).get(); 250 } 251 } catch (Throwable t) { 252 LOGGER.debug("Unable to locate pod with name {}.", hostName); 253 } 254 return null; 255 } 256 257 private boolean isServiceAccount() { 258 return Paths.get(Config.KUBERNETES_SERVICE_ACCOUNT_TOKEN_PATH).toFile().exists() 259 && Paths.get(Config.KUBERNETES_SERVICE_ACCOUNT_CA_CRT_PATH).toFile().exists(); 260 } 261 262 private boolean isSpringActive() { 263 return isSpringIncluded && LogManager.getFactory() != null 264 && LogManager.getFactory().hasContext(KubernetesLookup.class.getName(), null, false) 265 && LogManager.getContext(false).getObject(SPRING_ENVIRONMENT_KEY) != null; 266 } 267 268 private static class KubernetesInfo { 269 boolean isSpringActive; 270 String accountName; 271 Map<String, String> annotations; 272 String app; 273 String clusterName; 274 String containerId; 275 String containerName; 276 String hostName; 277 String hostIp; 278 String imageId; 279 String imageName; 280 Map<String, String> labels; 281 URL masterUrl; 282 String namespace; 283 Map<String, String> namespaceAnnotations; 284 String namespaceId; 285 Map<String, String> namespaceLabels; 286 String podId; 287 String podIp; 288 String podName; 289 String podTemplateHash; 290 } 291}