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}