From d365886753f785bd58118c03510696318ea47941 Mon Sep 17 00:00:00 2001 From: Alex Jones Date: Sun, 12 Nov 2023 15:00:43 -0600 Subject: [PATCH] feat: log analyzer (#744) * feat: wip log analyzer Signed-off-by: Alex Jones * chore: turns off log by default Signed-off-by: Alex Jones * chore: turns off log by default Signed-off-by: Alex Jones --------- Signed-off-by: Alex Jones Co-authored-by: Thomas Schuetz <38893055+thschue@users.noreply.github.com> --- cmd/filters/add.go | 7 +++ pkg/analyzer/analyzer.go | 1 + pkg/analyzer/log.go | 116 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 124 insertions(+) create mode 100644 pkg/analyzer/log.go diff --git a/cmd/filters/add.go b/cmd/filters/add.go index 8b1ae80fb7..2be912b476 100644 --- a/cmd/filters/add.go +++ b/cmd/filters/add.go @@ -45,6 +45,13 @@ var addCmd = &cobra.Command{ for _, filter := range availableFilters { if filter == f { foundFilter = true + + // WARNING: This is to enable users correctly understand implications + // of enabling logs + if filter == "Log" { + color.Yellow("Warning: by enabling logs, you will be sending potentially sensitive data to the AI backend.") + } + break } } diff --git a/pkg/analyzer/analyzer.go b/pkg/analyzer/analyzer.go index 64d13ed226..0c6ac9f2fa 100644 --- a/pkg/analyzer/analyzer.go +++ b/pkg/analyzer/analyzer.go @@ -49,6 +49,7 @@ var additionalAnalyzerMap = map[string]common.IAnalyzer{ "HorizontalPodAutoScaler": HpaAnalyzer{}, "PodDisruptionBudget": PdbAnalyzer{}, "NetworkPolicy": NetworkPolicyAnalyzer{}, + "Log": LogAnalyzer{}, } func ListFilters() ([]string, []string, []string) { diff --git a/pkg/analyzer/log.go b/pkg/analyzer/log.go new file mode 100644 index 0000000000..e0ea087657 --- /dev/null +++ b/pkg/analyzer/log.go @@ -0,0 +1,116 @@ +/* +Copyright 2023 The K8sGPT Authors. +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 analyzer + +import ( + "fmt" + "regexp" + "strings" + + "github.com/k8sgpt-ai/k8sgpt/pkg/common" + "github.com/k8sgpt-ai/k8sgpt/pkg/util" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +var ( + errorPattern = regexp.MustCompile(`(error|exception|fail)`) + tailLines = int64(100) +) + +type LogAnalyzer struct { +} + +func (LogAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) { + + kind := "Log" + + AnalyzerErrorsMetric.DeletePartialMatch(map[string]string{ + "analyzer_name": kind, + }) + + // search all namespaces for pods that are not running + list, err := a.Client.GetClient().CoreV1().Pods(a.Namespace).List(a.Context, metav1.ListOptions{}) + if err != nil { + return nil, err + } + var preAnalysis = map[string]common.PreAnalysis{} + // Iterate through each pod + + for _, pod := range list.Items { + var failures []common.Failure + podName := pod.Name + podLogOptions := v1.PodLogOptions{ + TailLines: &tailLines, + } + + podLogs, err := a.Client.Client.CoreV1().Pods(pod.Namespace).GetLogs(podName, &podLogOptions).DoRaw(a.Context) + if err != nil { + failures = append(failures, common.Failure{ + Text: fmt.Sprintf("Error %s from Pod %s", err.Error(), pod.Name), + Sensitive: []common.Sensitive{ + { + Unmasked: pod.Name, + Masked: util.MaskString(pod.Name), + }, + }, + }) + + } else { + rawlogs := string(podLogs) + if errorPattern.MatchString(rawlogs) { + failures = append(failures, common.Failure{ + Text: printErrorLines(pod.Name, pod.Namespace, rawlogs, errorPattern), + Sensitive: []common.Sensitive{ + { + Unmasked: pod.Name, + Masked: util.MaskString(pod.Name), + }, + }, + }) + } + } + if len(failures) > 0 { + preAnalysis[fmt.Sprintf("%s/%s", pod.Namespace, pod.Name)] = common.PreAnalysis{ + FailureDetails: failures, + Pod: pod, + } + AnalyzerErrorsMetric.WithLabelValues(kind, pod.Name, pod.Namespace).Set(float64(len(failures))) + } + } + for key, value := range preAnalysis { + currentAnalysis := common.Result{ + Kind: "Pod", + Name: key, + Error: value.FailureDetails, + } + parent, _ := util.GetParent(a.Client, value.Pod.ObjectMeta) + currentAnalysis.ParentObject = parent + a.Results = append(a.Results, currentAnalysis) + } + + return a.Results, nil +} +func printErrorLines(podName, namespace, logs string, errorPattern *regexp.Regexp) string { + // Split the logs into lines + logLines := strings.Split(logs, "\n") + + // Check each line for errors and print the lines containing errors + for _, line := range logLines { + if errorPattern.MatchString(line) { + return line + } + } + return "" +}