View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
16   */
17  package org.apache.logging.log4j.kubernetes;
18  
19  import java.net.URL;
20  import java.nio.file.Paths;
21  import java.util.Map;
22  import java.util.concurrent.locks.Lock;
23  import java.util.concurrent.locks.ReentrantLock;
24  
25  import org.apache.logging.log4j.LogManager;
26  import org.apache.logging.log4j.Logger;
27  import org.apache.logging.log4j.core.LogEvent;
28  import org.apache.logging.log4j.core.config.plugins.Plugin;
29  import org.apache.logging.log4j.core.lookup.AbstractLookup;
30  import org.apache.logging.log4j.core.lookup.StrLookup;
31  import org.apache.logging.log4j.status.StatusLogger;
32  import org.apache.logging.log4j.util.LoaderUtil;
33  import org.apache.logging.log4j.util.Strings;
34  
35  import io.fabric8.kubernetes.api.model.Container;
36  import io.fabric8.kubernetes.api.model.ContainerStatus;
37  import io.fabric8.kubernetes.api.model.Namespace;
38  import io.fabric8.kubernetes.api.model.Pod;
39  import io.fabric8.kubernetes.client.Config;
40  import io.fabric8.kubernetes.client.KubernetesClient;
41  
42  
43  /**
44   * Retrieve various Kubernetes attributes. Supported keys are:
45   *  accountName, containerId, containerName, clusterName, host, hostIp, labels, labels.app,
46   *  labels.podTemplateHash, masterUrl, namespaceId, namespaceName, podId, podIp, podName,
47   *  imageId, imageName.
48   */
49  @Plugin(name = "k8s", category = StrLookup.CATEGORY)
50  public class KubernetesLookup extends AbstractLookup {
51  
52      private static final Logger LOGGER = StatusLogger.getLogger();
53      private static final String HOSTNAME = "HOSTNAME";
54      private static final String SPRING_ENVIRONMENT_KEY = "SpringEnvironment";
55  
56      private static volatile KubernetesInfo kubernetesInfo;
57      private static Lock initLock = new ReentrantLock();
58      private static boolean isSpringIncluded =
59              LoaderUtil.isClassAvailable("org.apache.logging.log4j.spring.cloud.config.client.SpringEnvironmentHolder");
60  
61      private boolean initialize() {
62          if (kubernetesInfo == null || (isSpringIncluded && !kubernetesInfo.isSpringActive)) {
63              initLock.lock();
64              boolean isSpringActive = isSpringActive();
65              if (kubernetesInfo == null || (!kubernetesInfo.isSpringActive && isSpringActive)) {
66                  try {
67                      KubernetesClient client = new KubernetesClientBuilder().createClient();
68                      if (client != null) {
69                          KubernetesInfo info = new KubernetesInfo();
70                          info.isSpringActive = isSpringActive;
71                          info.hostName = getHostname();
72                          Pod pod = getCurrentPod(info.hostName, client);
73                          if (pod != null) {
74                              info.app = pod.getMetadata().getLabels().get("app");
75                              final String app = info.app != null ? info.app : "";
76                              info.podTemplateHash = pod.getMetadata().getLabels().get("pod-template-hash");
77                              info.accountName = pod.getSpec().getServiceAccountName();
78                              info.clusterName = pod.getMetadata().getClusterName();
79                              info.hostIp = pod.getStatus().getHostIP();
80                              info.labels = pod.getMetadata().getLabels();
81                              info.podId = pod.getMetadata().getUid();
82                              info.podIp = pod.getStatus().getPodIP();
83                              info.podName = pod.getMetadata().getName();
84                              Container container = pod.getSpec().getContainers().stream()
85                                      .filter(c -> c.getName().equals(app)).findFirst().orElse(null);
86                              if (container != null) {
87                                  info.containerName = container.getName();
88                                  info.imageName = container.getImage();
89                              }
90                              info.masterUrl = client.getMasterUrl();
91                              info.namespace = pod.getMetadata().getNamespace();
92                              Namespace namespace = client.namespaces().withName(info.namespace).get();
93                              if (namespace != null) {
94                                  info.namespaceId = namespace.getMetadata().getUid();
95                              }
96                              ContainerStatus containerStatus = pod.getStatus().getContainerStatuses().stream()
97                                      .filter(cs -> cs.getName().equals(app)).findFirst().orElse(null);
98                              if (containerStatus != null) {
99                                  info.containerId = containerStatus.getContainerID();
100                                 info.imageId = containerStatus.getImageID();
101                             }
102                             kubernetesInfo = info;
103                         }
104                     }
105                 } finally {
106                     initLock.unlock();
107                 }
108             }
109         }
110         return kubernetesInfo != null;
111     }
112 
113     @Override
114     public String lookup(LogEvent event, String key) {
115         if (!initialize()) {
116             return null;
117         }
118         switch (key) {
119             case "accountName": {
120                 return kubernetesInfo.accountName;
121             }
122             case "containerId": {
123                 return kubernetesInfo.containerId;
124             }
125             case "containerName": {
126                 return kubernetesInfo.containerName;
127             }
128             case "clusterName": {
129                 return kubernetesInfo.clusterName;
130             }
131             case "host": {
132                 return kubernetesInfo.hostName;
133             }
134             case "hostIp": {
135                 return kubernetesInfo.hostIp;
136             }
137             case "labels": {
138                 return kubernetesInfo.labels.toString();
139             }
140             case "labels.app": {
141                 return kubernetesInfo.app;
142             }
143             case "labels.podTemplateHash": {
144                 return kubernetesInfo.podTemplateHash;
145             }
146             case "masterUrl": {
147                 return kubernetesInfo.masterUrl.toString();
148             }
149             case "namespaceId": {
150                 return kubernetesInfo.namespaceId;
151             }
152             case "namespaceName": {
153                 return kubernetesInfo.namespace;
154             }
155             case "podId": {
156                 return kubernetesInfo.podId;
157             }
158             case "podIp": {
159                 return kubernetesInfo.podIp;
160             }
161             case "podName": {
162                 return kubernetesInfo.podName;
163             }
164             case "imageId": {
165                 return kubernetesInfo.imageId;
166             }
167             case "imageName": {
168                 return kubernetesInfo.imageName;
169             }
170             default:
171                 return null;
172         }
173     }
174 
175     private String getHostname() {
176         return System.getenv(HOSTNAME);
177     }
178 
179     private Pod getCurrentPod(String hostName, KubernetesClient kubernetesClient) {
180         try {
181             if (isServiceAccount() && Strings.isNotBlank(hostName)) {
182                 return kubernetesClient.pods().withName(hostName).get();
183             }
184         } catch (Throwable t) {
185             LOGGER.debug("Unable to locate pod with name {}.", hostName);
186         }
187         return null;
188     }
189 
190     private boolean isServiceAccount() {
191         return Paths.get(Config.KUBERNETES_SERVICE_ACCOUNT_TOKEN_PATH).toFile().exists()
192                 && Paths.get(Config.KUBERNETES_SERVICE_ACCOUNT_CA_CRT_PATH).toFile().exists();
193     }
194 
195     private boolean isSpringActive() {
196         return isSpringIncluded && LogManager.getFactory() != null
197             && LogManager.getFactory().hasContext(KubernetesLookup.class.getName(), null, false)
198             && LogManager.getContext(false).getObject(SPRING_ENVIRONMENT_KEY) != null;
199     }
200 
201     private static class KubernetesInfo {
202         boolean isSpringActive;
203         String accountName;
204         String app;
205         String clusterName;
206         String containerId;
207         String containerName;
208         String hostName;
209         String hostIp;
210         String imageId;
211         String imageName;
212         Map<String, String> labels;
213         URL masterUrl;
214         String namespace;
215         String namespaceId;
216         String podId;
217         String podIp;
218         String podName;
219         String podTemplateHash;
220     }
221 }