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.List;
22  import java.util.Map;
23  import java.util.concurrent.locks.Lock;
24  import java.util.concurrent.locks.ReentrantLock;
25  
26  import org.apache.logging.log4j.LogManager;
27  import org.apache.logging.log4j.Logger;
28  import org.apache.logging.log4j.core.LogEvent;
29  import org.apache.logging.log4j.core.config.plugins.Plugin;
30  import org.apache.logging.log4j.core.lookup.AbstractLookup;
31  import org.apache.logging.log4j.core.lookup.StrLookup;
32  import org.apache.logging.log4j.status.StatusLogger;
33  import org.apache.logging.log4j.util.LoaderUtil;
34  import org.apache.logging.log4j.util.Strings;
35  
36  import io.fabric8.kubernetes.api.model.Container;
37  import io.fabric8.kubernetes.api.model.ContainerStatus;
38  import io.fabric8.kubernetes.api.model.Namespace;
39  import io.fabric8.kubernetes.api.model.Pod;
40  import io.fabric8.kubernetes.client.Config;
41  import io.fabric8.kubernetes.client.KubernetesClient;
42  
43  
44  /**
45   * Retrieve various Kubernetes attributes. Supported keys are:
46   *  accountName, containerId, containerName, clusterName, host, hostIp, labels, labels.app,
47   *  labels.podTemplateHash, masterUrl, namespaceId, namespaceName, podId, podIp, podName,
48   *  imageId, imageName.
49   */
50  @Plugin(name = "k8s", category = StrLookup.CATEGORY)
51  public class KubernetesLookup extends AbstractLookup {
52  
53      private static final Logger LOGGER = StatusLogger.getLogger();
54      private static final String HOSTNAME = "HOSTNAME";
55      private static final String SPRING_ENVIRONMENT_KEY = "SpringEnvironment";
56  
57      private static volatile KubernetesInfo kubernetesInfo;
58      private static final Lock initLock = new ReentrantLock();
59      private static final boolean isSpringIncluded =
60              LoaderUtil.isClassAvailable("org.apache.logging.log4j.spring.cloud.config.client.SpringEnvironmentHolder")
61                      || LoaderUtil.isClassAvailable("org.apache.logging.log4j.spring.boot.SpringEnvironmentHolder");
62      private Pod pod;
63      private Namespace namespace;
64      private URL masterUrl;
65  
66      public KubernetesLookup() {
67          this.pod = null;
68          this.namespace = null;
69          this.masterUrl = null;
70          initialize();
71      }
72  
73      KubernetesLookup(Pod pod, Namespace namespace, URL masterUrl) {
74          this.pod = pod;
75          this.namespace = namespace;
76          this.masterUrl = masterUrl;
77          initialize();
78      }
79      private boolean initialize() {
80          if (kubernetesInfo == null || (isSpringIncluded && !kubernetesInfo.isSpringActive)) {
81              initLock.lock();
82              try {
83                  boolean isSpringActive = isSpringActive();
84                  if (kubernetesInfo == null || (!kubernetesInfo.isSpringActive && isSpringActive)) {
85                      KubernetesInfo info = new KubernetesInfo();
86                      KubernetesClient client = null;
87                      info.isSpringActive = isSpringActive;
88                      if (pod == null) {
89                          client = new KubernetesClientBuilder().createClient();
90                          if (client != null) {
91                              pod = getCurrentPod(System.getenv(HOSTNAME), client);
92                              info.masterUrl = client.getMasterUrl();
93                              if (pod != null) {
94                                  info.namespace = pod.getMetadata().getNamespace();
95                                  namespace = client.namespaces().withName(info.namespace).get();
96                              }
97                          } else {
98                              LOGGER.warn("Kubernetes is not available for access");
99                          }
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 }