Files
mats a0f558b43c
Build and Publish / Metadata (push) Successful in 10s
Build and Publish / BuildAndDeployAmd64 (push) Failing after 2m16s
fix notifications
2026-09-22 08:35:40 +02:00

88 lines
2.8 KiB
Go

package topology
import (
"bufio"
"context"
"sort"
"time"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// PodLog is what looking up a running pod behind a Deployment found.
type PodLog struct {
Pod string // empty unless Found
Lines []string // most recent log lines, oldest first
Found bool // a pod exists and was queried (Lines may still be empty: a quiet pod)
Err string // set when the lookup itself failed; Found is false
}
// podLogTimeout bounds one lookup so a slow or unreachable API server never
// stalls alert delivery.
const podLogTimeout = 5 * time.Second
// PodLogs finds a pod behind namespace/deployment and returns its last `tail`
// log lines. A Deployment with no live pods right now - scaled to 0, or mid
// rollout with none ready yet - comes back with Found=false and no Err: that
// absence is itself the answer, not a failure.
func (r *Registry) PodLogs(ctx context.Context, namespace, deployment string, tail int) PodLog {
if r == nil || r.client == nil {
return PodLog{}
}
ctx, cancel := context.WithTimeout(ctx, podLogTimeout)
defer cancel()
dep, err := r.client.AppsV1().Deployments(namespace).Get(ctx, deployment, metav1.GetOptions{})
if err != nil {
return PodLog{Err: err.Error()}
}
sel, err := metav1.LabelSelectorAsSelector(dep.Spec.Selector)
if err != nil {
return PodLog{Err: err.Error()}
}
pods, err := r.client.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{LabelSelector: sel.String()})
if err != nil {
return PodLog{Err: err.Error()}
}
live := make([]corev1.Pod, 0, len(pods.Items))
for _, p := range pods.Items {
if p.DeletionTimestamp == nil {
live = append(live, p)
}
}
if len(live) == 0 {
return PodLog{} // scaled to 0, or nothing ready yet
}
// Prefer a Running pod; among ties prefer the newest, most likely to hold
// the log lines behind whatever just triggered the alert.
sort.Slice(live, func(i, j int) bool {
ri, rj := live[i].Status.Phase == corev1.PodRunning, live[j].Status.Phase == corev1.PodRunning
if ri != rj {
return ri
}
return live[i].CreationTimestamp.After(live[j].CreationTimestamp.Time)
})
pod := live[0]
container := ""
if len(pod.Spec.Containers) > 0 {
container = pod.Spec.Containers[0].Name
}
tailLines := int64(tail)
stream, err := r.client.CoreV1().Pods(namespace).GetLogs(pod.Name, &corev1.PodLogOptions{Container: container, TailLines: &tailLines}).Stream(ctx)
if err != nil {
return PodLog{Pod: pod.Name, Found: true, Err: err.Error()}
}
defer stream.Close()
var lines []string
sc := bufio.NewScanner(stream)
sc.Buffer(make([]byte, 0, 64*1024), 1024*1024)
for sc.Scan() {
lines = append(lines, sc.Text())
}
if len(lines) > tail {
lines = lines[len(lines)-tail:]
}
return PodLog{Pod: pod.Name, Lines: lines, Found: true}
}