Skip to content

Commit 3a76ec0

Browse files
feat: stream bootloader logs (#111)
1 parent 0a859d4 commit 3a76ec0

File tree

4 files changed

+81
-0
lines changed

4 files changed

+81
-0
lines changed

internal/cmd/local/k8s/client.go

+9
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ type Client interface {
7575
EventsWatch(ctx context.Context, namespace string) (watch.Interface, error)
7676

7777
LogsGet(ctx context.Context, namespace string, name string) (string, error)
78+
StreamPodLogs(ctx context.Context, namespace string, podName string, since time.Time) (io.ReadCloser, error)
7879
}
7980

8081
var _ Client = (*DefaultK8sClient)(nil)
@@ -325,3 +326,11 @@ func (d *DefaultK8sClient) LogsGet(ctx context.Context, namespace string, name s
325326
}
326327
return buf.String(), nil
327328
}
329+
330+
func (d *DefaultK8sClient) StreamPodLogs(ctx context.Context, namespace string, podName string, since time.Time) (io.ReadCloser, error) {
331+
req := d.ClientSet.CoreV1().Pods(namespace).GetLogs(podName, &corev1.PodLogOptions{
332+
Follow: true,
333+
SinceTime: &metav1.Time{Time: since},
334+
})
335+
return req.Stream(ctx)
336+
}

internal/cmd/local/k8s/k8stest/k8stest.go

+10
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package k8stest
22

33
import (
44
"context"
5+
"io"
6+
"time"
57

68
"github.com/airbytehq/abctl/internal/cmd/local/k8s"
79
v1 "k8s.io/api/apps/v1"
@@ -33,6 +35,7 @@ type MockClient struct {
3335
FnServiceGet func(ctx context.Context, namespace, name string) (*corev1.Service, error)
3436
FnEventsWatch func(ctx context.Context, namespace string) (watch.Interface, error)
3537
FnLogsGet func(ctx context.Context, namespace string, name string) (string, error)
38+
FnStreamPodLogs func(ctx context.Context, namespace, podName string, since time.Time) (io.ReadCloser, error)
3639
}
3740

3841
func (m *MockClient) DeploymentList(ctx context.Context, namespace string) (*v1.DeploymentList, error) {
@@ -166,3 +169,10 @@ func (m *MockClient) LogsGet(ctx context.Context, namespace string, name string)
166169
}
167170
return m.FnLogsGet(ctx, namespace, name)
168171
}
172+
173+
func (m *MockClient) StreamPodLogs(ctx context.Context, namespace string, podName string, since time.Time) (io.ReadCloser, error) {
174+
if m.FnStreamPodLogs == nil {
175+
panic("FnStreamPodLogs is not configured")
176+
}
177+
return m.FnStreamPodLogs(ctx, namespace, podName, since)
178+
}

internal/cmd/local/local/cmd.go

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
)
2121

2222
const (
23+
airbyteBootloaderPodName = "airbyte-abctl-airbyte-bootloader"
2324
airbyteChartName = "airbyte/airbyte"
2425
airbyteChartRelease = "airbyte-abctl"
2526
airbyteIngress = "ingress-abctl"

internal/cmd/local/local/install.go

+61
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
package local
22

33
import (
4+
"bufio"
45
"context"
56
"fmt"
67
"net/http"
78
"os"
89
"path/filepath"
10+
"regexp"
911
"strconv"
1012
"strings"
1113
"time"
@@ -378,6 +380,63 @@ func (c *Command) watchEvents(ctx context.Context) {
378380
}
379381
}
380382

383+
// 2024-09-10 20:16:24 WARN i.m.s.r.u.Loggers$Slf4JLogger(warn):299 - [273....
384+
var javaLogRx = regexp.MustCompile(`^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} \x1b\[(?:1;)?\d+m(?P<level>[A-Z]+)\x1b\[m (?P<msg>\S+ - .*)`)
385+
386+
func (c *Command) streamPodLogs(ctx context.Context, namespace, podName, prefix string, since time.Time) error {
387+
r, err := c.k8s.StreamPodLogs(ctx, namespace, podName, since)
388+
if err != nil {
389+
return err
390+
}
391+
defer r.Close()
392+
393+
level := pterm.Debug
394+
scanner := bufio.NewScanner(r)
395+
396+
for scanner.Scan() {
397+
398+
// skip java stacktrace noise
399+
if strings.HasPrefix(scanner.Text(), "\tat ") || strings.HasPrefix(scanner.Text(), "\t... ") {
400+
continue
401+
}
402+
403+
m := javaLogRx.FindSubmatch(scanner.Bytes())
404+
var msg string
405+
406+
if m != nil {
407+
msg = string(m[2])
408+
if string(m[1]) == "ERROR" {
409+
level = pterm.Error
410+
} else {
411+
level = pterm.Debug
412+
}
413+
} else {
414+
msg = scanner.Text()
415+
}
416+
417+
level.Printfln("%s: %s", prefix, msg)
418+
}
419+
return scanner.Err()
420+
}
421+
422+
func (c *Command) watchBootloaderLogs(ctx context.Context) {
423+
pterm.Debug.Printfln("start streaming bootloader logs")
424+
since := time.Now()
425+
426+
for {
427+
// Wait a few seconds on the first iteration, give the bootloaders some time to start.
428+
time.Sleep(5 * time.Second)
429+
430+
err := c.streamPodLogs(ctx, airbyteNamespace, airbyteBootloaderPodName, "airbyte-bootloader", since)
431+
if err == nil {
432+
break
433+
} else {
434+
pterm.Debug.Printfln("error streaming bootloader logs. will retry: %s", err)
435+
}
436+
}
437+
pterm.Debug.Printfln("done streaming bootloader logs")
438+
}
439+
381440
// now is used to filter out kubernetes events that happened in the past.
382441
// Kubernetes wants us to use the ResourceVersion on the event watch request itself, but that approach
383442
// is more complicated as it requires determining which ResourceVersion to initially provide.
@@ -398,6 +457,8 @@ func (c *Command) handleEvent(ctx context.Context, e *eventsv1.Event) {
398457
case strings.EqualFold(e.Type, "normal"):
399458
if strings.EqualFold(e.Reason, "backoff") {
400459
pterm.Warning.Println(e.Note)
460+
} else if e.Reason == "Started" && e.Regarding.Name == "airbyte-abctl-airbyte-bootloader" {
461+
go c.watchBootloaderLogs(ctx)
401462
} else {
402463
pterm.Debug.Println(e.Note)
403464
}

0 commit comments

Comments
 (0)