diff --git a/go.mod b/go.mod index fe4e224..e06dd54 100644 --- a/go.mod +++ b/go.mod @@ -30,6 +30,9 @@ require ( github.com/BurntSushi/toml v1.4.0 // indirect github.com/Microsoft/go-winio v0.5.1 // indirect github.com/alessio/shellescape v1.4.2 // indirect + github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect + github.com/charmbracelet/lipgloss v0.10.0 // indirect + github.com/charmbracelet/log v0.4.0 // indirect github.com/containerd/log v0.1.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/distribution/reference v0.6.0 // indirect @@ -40,6 +43,7 @@ require ( github.com/fatih/color v1.18.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect + github.com/go-logfmt/logfmt v0.6.0 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect @@ -57,25 +61,31 @@ require ( github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect + github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.15 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/term v0.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/morikuni/aec v1.0.0 // indirect + github.com/muesli/reflow v0.3.0 // indirect + github.com/muesli/termenv v0.15.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.0.2 // indirect github.com/pelletier/go-toml v1.9.5 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/rivo/uniseg v0.4.7 // indirect github.com/x448/float16 v0.8.4 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect go.opentelemetry.io/otel v1.28.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.27.0 // indirect go.opentelemetry.io/otel/metric v1.28.0 // indirect go.opentelemetry.io/otel/trace v1.28.0 // indirect + golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect golang.org/x/mod v0.21.0 // indirect golang.org/x/net v0.30.0 // indirect golang.org/x/oauth2 v0.21.0 // indirect diff --git a/go.sum b/go.sum index 805bc0a..4d3dee1 100644 --- a/go.sum +++ b/go.sum @@ -8,12 +8,18 @@ github.com/Microsoft/go-winio v0.5.1 h1:aPJp2QD7OOrhO5tQXqQoGSJc+DjDtWTGLOmNyAm6 github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/alessio/shellescape v1.4.2 h1:MHPfaU+ddJ0/bYWpgIeUnQUqKrlJ1S7BfEYPM4uEoM0= github.com/alessio/shellescape v1.4.2/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= +github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= +github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/charmbracelet/lipgloss v0.10.0 h1:KWeXFSexGcfahHX+54URiZGkBFazf70JNMtwg/AFW3s= +github.com/charmbracelet/lipgloss v0.10.0/go.mod h1:Wig9DSfvANsxqkRsqj6x87irdy123SR4dOXlKa91ciE= +github.com/charmbracelet/log v0.4.0 h1:G9bQAcx8rWA2T3pWvx7YtPTPwgqpk7D68BX21IRW8ZM= +github.com/charmbracelet/log v0.4.0/go.mod h1:63bXt/djrizTec0l11H20t8FDSvA4CRZJ1KH22MdptM= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= @@ -46,6 +52,8 @@ github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nos github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= +github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -107,6 +115,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= +github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= @@ -114,6 +124,9 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= @@ -125,6 +138,10 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= +github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= +github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= +github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= @@ -154,6 +171,10 @@ github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -207,6 +228,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc h1:mCRnTeVUjcrhlRmO0VK8a6k6Rrf6TF9htwo2pJVSjIU= golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= diff --git a/pkg/http/client.go b/pkg/http/client.go index 8b75045..b51fb4b 100644 --- a/pkg/http/client.go +++ b/pkg/http/client.go @@ -98,6 +98,7 @@ func (c *Client) Download(url string, path string) error { } // The progress use the same line so print a new line once it's finished downloading + // TODO (@NickLarsenNZ): Use a logger fmt.Println() // Rename the tmp file back to the original file @@ -125,10 +126,12 @@ func (wc *writeCounter) Write(p []byte) (int, error) { func (wc writeCounter) PrintProgress() { // Clear the line by using a character return to go back to the start and remove // the remaining characters by filling it with spaces + // TODO (@NickLarsenNZ): Use a logger fmt.Printf("\r%s", strings.Repeat(" ", 100)) // Return again and print current status of download // We use the humanize package to print the bytes in a meaningful way (e.g. 10 MB) + // TODO (@NickLarsenNZ): Use a logger fmt.Printf("\rDownloading (%s) %s complete", wc.Name, humanize.Bytes(wc.Total)) } diff --git a/pkg/kuttlctl/cmd/test.go b/pkg/kuttlctl/cmd/test.go index 65aa1c5..0df1552 100644 --- a/pkg/kuttlctl/cmd/test.go +++ b/pkg/kuttlctl/cmd/test.go @@ -84,6 +84,7 @@ For more detailed documentation, visit: https://kuttl.dev`, if _, err := os.Stat("kuttl-test.yaml"); err == nil { configPath = "kuttl-test.yaml" } else { + // TODO (@NickLarsenNZ): Use a logger log.Println("running without a 'kuttl-test.yaml' configuration") } } @@ -103,9 +104,11 @@ For more detailed documentation, visit: https://kuttl.dev`, case *harness.TestSuite: options = *ts case *unstructured.Unstructured: + // TODO (@NickLarsenNZ): Use a logger log.Println(fmt.Errorf("bad configuration in file %q", configPath)) } } else { + // TODO (@NickLarsenNZ): Use a logger log.Println(fmt.Errorf("unknown object type: %s", kind)) } } @@ -212,6 +215,7 @@ For more detailed documentation, visit: https://kuttl.dev`, } if len(args) != 0 { + // TODO (@NickLarsenNZ): Use a logger log.Println("kutt-test config testdirs is overridden with args: [", strings.Join(args, ", "), "]") options.TestDirs = args } @@ -220,6 +224,7 @@ For more detailed documentation, visit: https://kuttl.dev`, return errors.New("no test directories provided, please provide either --config or test directories on the command line") } if mockControllerFile != "" { + // TODO (@NickLarsenNZ): Use a logger, but we don't have access to the logger in the Harness log.Println("use of --control-plane-config is deprecated and no longer functions") } diff --git a/pkg/test/assert.go b/pkg/test/assert.go index 1fb35ac..633d3a2 100644 --- a/pkg/test/assert.go +++ b/pkg/test/assert.go @@ -47,11 +47,13 @@ func Assert(namespace string, timeout int, assertFiles ...string) error { } if len(testErrors) == 0 { + // TODO (@NickLarsenNZ): Use a logger fmt.Printf("assert is valid\n") return nil } for _, testError := range testErrors { + // TODO (@NickLarsenNZ): Use a logger fmt.Println(testError) } return errors.New("asserts not valid") @@ -94,11 +96,13 @@ func Errors(namespace string, timeout int, errorFiles ...string) error { } if len(testErrors) == 0 { + // TODO (@NickLarsenNZ): Use a logger fmt.Printf("error assert is valid\n") return nil } for _, testError := range testErrors { + // TODO (@NickLarsenNZ): Use a logger fmt.Println(testError) } return errors.New("error asserts not valid") diff --git a/pkg/test/case.go b/pkg/test/case.go index 6b082d0..7397037 100644 --- a/pkg/test/case.go +++ b/pkg/test/case.go @@ -60,11 +60,11 @@ type namespace struct { // DeleteNamespace deletes a namespace in Kubernetes after we are done using it. func (t *Case) DeleteNamespace(cl client.Client, ns *namespace) error { if !ns.AutoCreated { - t.Logger.Log("Skipping deletion of user-supplied namespace:", ns.Name) + t.Logger.LogWithArgs("Skipping deletion of user-supplied namespace", "namespace", ns.Name) return nil } - t.Logger.Log("Deleting namespace:", ns.Name) + t.Logger.LogWithArgs("Deleting namespace", "namespace", ns.Name) ctx := context.Background() if t.Timeout > 0 { @@ -83,7 +83,7 @@ func (t *Case) DeleteNamespace(cl client.Client, ns *namespace) error { } if err := cl.Delete(ctx, nsObj); k8serrors.IsNotFound(err) { - t.Logger.Logf("Namespace already cleaned up.") + t.Logger.LogWithArgs("Namespace already cleaned up", "namespace", ns.Name) } else if err != nil { return err } @@ -101,10 +101,10 @@ func (t *Case) DeleteNamespace(cl client.Client, ns *namespace) error { // CreateNamespace creates a namespace in Kubernetes to use for a test. func (t *Case) CreateNamespace(test *testing.T, cl client.Client, ns *namespace) error { if !ns.AutoCreated { - t.Logger.Log("Skipping creation of user-supplied namespace:", ns.Name) + t.Logger.LogWithArgs("Skipping creation of user-supplied namespace", "namespace", ns.Name) return nil } - t.Logger.Log("Creating namespace:", ns.Name) + t.Logger.LogWithArgs("Creating namespace", "namespace", ns.Name) ctx := context.Background() if t.Timeout > 0 { @@ -188,7 +188,7 @@ func (o byFirstTimestampCoreV1) Less(i, j int) bool { func (t *Case) CollectEvents(namespace string) { cl, err := t.Client(false) if err != nil { - t.Logger.Log("Failed to collect events for %s in ns %s: %v", t.Name, namespace, err) + t.Logger.ErrorWithArgs("Failed to collect events", "name", t.Name, "namespace", namespace, "error", err) return } @@ -211,14 +211,16 @@ func (t *Case) collectEventsBeta1(cl client.Client, namespace string) error { err := cl.List(context.TODO(), eventsList, client.InNamespace(namespace)) if err != nil { - t.Logger.Logf("Failed to collect events for %s in ns %s: %v", t.Name, namespace, err) + t.Logger.LogWithArgs("Failed to collect events", "name", t.Name, "namespace", namespace, "error", err) return err } events := eventsList.Items sort.Sort(byFirstTimestamp(events)) - t.Logger.Logf("%s events from ns %s:", t.Name, namespace) + // TODO (@NickLarsenNZ): Use LogWithArgs + t.Logger.Log(fmt.Sprintf("%s events from ns %s:", t.Name, namespace)) + // TODO (@NickLarsenNZ): make this log with a new group printEventsBeta1(events, t.Logger) return nil } @@ -228,14 +230,16 @@ func (t *Case) collectEventsV1(cl client.Client, namespace string) error { err := cl.List(context.TODO(), eventsList, client.InNamespace(namespace)) if err != nil { - t.Logger.Logf("Failed to collect events for %s in ns %s: %v", t.Name, namespace, err) + t.Logger.ErrorWithArgs("Failed to collect events", "name", t.Name, "namespace", namespace, "error", err) return err } events := eventsList.Items sort.Sort(byFirstTimestampV1(events)) - t.Logger.Logf("%s events from ns %s:", t.Name, namespace) + // TODO (@NickLarsenNZ): Use LogWithArgs + t.Logger.Log(fmt.Sprintf("%s events from ns %s:", t.Name, namespace)) + // TODO (@NickLarsenNZ): make this log with a new group printEventsV1(events, t.Logger) return nil } @@ -245,59 +249,89 @@ func (t *Case) collectEventsCoreV1(cl client.Client, namespace string) error { err := cl.List(context.TODO(), eventsList, client.InNamespace(namespace)) if err != nil { - t.Logger.Logf("Failed to collect events for %s in ns %s: %v", t.Name, namespace, err) + t.Logger.ErrorWithArgs("Failed to collect events", "name", t.Name, "namespace", namespace, "error", err) return err } events := eventsList.Items sort.Sort(byFirstTimestampCoreV1(events)) - t.Logger.Logf("%s events from ns %s:", t.Name, namespace) + // TODO (@NickLarsenNZ): Use LogWithArgs + t.Logger.Log(fmt.Sprintf("%s events from ns %s:", t.Name, namespace)) + // TODO (@NickLarsenNZ): make this log with a new group printEventsCoreV1(events, t.Logger) return nil } +// TODO (@NickLarsenNZ): make this log with a new group func printEventsBeta1(events []eventsbeta1.Event, logger testutils.Logger) { for _, e := range events { // time type regarding action reason note reportingController related - logger.Logf("%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s", + logger.LogWithArgs(fmt.Sprintf("Kubernetes Event (%s)", eventsbeta1.SchemeGroupVersion), + "creation_timestamp", e.ObjectMeta.CreationTimestamp, + "type", e.Type, + "regarding", shortString(&e.Regarding), + "action", e.Action, + "reason", e.Reason, + "note", e.Note, + "reporting_controller", e.ReportingController, - shortString(e.Related)) + "related", + shortString(e.Related), + ) } } +// TODO (@NickLarsenNZ): make this log with a new group func printEventsV1(events []eventsv1.Event, logger testutils.Logger) { for _, e := range events { // time type regarding action reason note reportingController related - logger.Logf("%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s", + logger.LogWithArgs(fmt.Sprintf("Kubernetes Event (%s)", eventsv1.SchemeGroupVersion), + "creation_timestamp", e.ObjectMeta.CreationTimestamp, + "type", e.Type, + "regarding", shortString(&e.Regarding), + "action", e.Action, + "reason", e.Reason, + "note", e.Note, + "reporting_controller", e.ReportingController, - shortString(e.Related)) + "related", + shortString(e.Related), + ) } } func printEventsCoreV1(events []corev1.Event, logger testutils.Logger) { for _, e := range events { // time type regarding action reason note reportingController related - logger.Logf("%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s", + logger.LogWithArgs(fmt.Sprintf("Kubernetes Event (%s)", corev1.SchemeGroupVersion), + "creation_timestamp", e.ObjectMeta.CreationTimestamp, + "type", e.Type, + "involved_object", shortString(&e.InvolvedObject), + "action", e.Action, + "reason", e.Reason, + "message", e.Message, + "reporting_controller", e.ReportingController, + "related", shortString(e.Related)) } } @@ -352,7 +386,7 @@ func (t *Case) Run(test *testing.T, ts *report.Testsuite) { for kc, c := range clients { if err = t.CreateNamespace(test, c, ns); k8serrors.IsAlreadyExists(err) { - t.Logger.Logf("namespace %q already exists, using kubeconfig %q", ns.Name, kc) + t.Logger.LogWithArgs("namespace already exists", "namespace", "namespace", ns.Name, kc) } else if err != nil { setupReport.Failure = report.NewFailure("failed to create test namespace", []error{err}) ts.AddTestcase(setupReport) @@ -371,7 +405,7 @@ func (t *Case) Run(test *testing.T, ts *report.Testsuite) { if testStep.Kubeconfig != "" { testStep.DiscoveryClient = newDiscoveryClient(testStep.Kubeconfig, testStep.Context) } - testStep.Logger = t.Logger.WithPrefix(testStep.String()) + testStep.Logger = t.Logger.WithGroup(testStep.String()) tc.Assertions += len(testStep.Asserts) tc.Assertions += len(testStep.Errors) @@ -383,7 +417,7 @@ func (t *Case) Run(test *testing.T, ts *report.Testsuite) { if err != nil { errs = append(errs, fmt.Errorf("failed to lazy-load kubeconfig: %w", err)) } else if err = t.CreateNamespace(test, cl, ns); k8serrors.IsAlreadyExists(err) { - t.Logger.Logf("namespace %q already exists", ns.Name) + t.Logger.LogWithArgs("namespace already exists", "namespace", ns.Name) } else if err != nil { errs = append(errs, fmt.Errorf("failed to create test namespace: %w", err)) } @@ -398,7 +432,7 @@ func (t *Case) Run(test *testing.T, ts *report.Testsuite) { caseErr := fmt.Errorf("failed in step %s", testStep.String()) tc.Failure = report.NewFailure(caseErr.Error(), errs) - test.Error(caseErr) + t.Logger.ErrorWithArgs("case error", "error", caseErr) for _, err := range errs { test.Error(err) } @@ -410,7 +444,8 @@ func (t *Case) Run(test *testing.T, ts *report.Testsuite) { } if funk.Contains(t.Suppress, "events") { - t.Logger.Logf("skipping kubernetes event logging") + // TODO (@NickLarsenNZ): Should be Debug, not Info + t.Logger.Log("skipping kubernetes event logging") } else { t.CollectEvents(ns.Name) } @@ -463,7 +498,7 @@ func (t *Case) CollectTestStepFiles() (map[int64][]string, error) { return nil, err } if index < 0 { - t.Logger.Log("Ignoring", file.Name(), "as it does not match file name regexp:", testStepRegex.String()) + t.Logger.LogWithArgs("ignoring file as it does not match file name regex", "filename", file.Name(), "regex", testStepRegex.String()) continue } @@ -523,6 +558,7 @@ func (t *Case) LoadTestSteps() error { Asserts: []client.Object{}, Apply: []client.Object{}, Errors: []client.Object{}, + Logger: t.Logger, // NOTE (@NickLarsenNZ): Pass the existing logger to the Step (else segfault) } for _, file := range files { diff --git a/pkg/test/harness.go b/pkg/test/harness.go index 0a37922..1dfc3b3 100644 --- a/pkg/test/harness.go +++ b/pkg/test/harness.go @@ -10,7 +10,6 @@ import ( "os/exec" "os/signal" "path/filepath" - "strings" "sync" "testing" "time" @@ -72,7 +71,7 @@ func (h *Harness) LoadTests(dir string) ([]*Case, error) { var tests []*Case timeout := h.GetTimeout() - h.T.Logf("going to run test suite with timeout of %d seconds for each step", timeout) + h.logger.LogWithArgs("going to run test suite with timeout for each step", "timeout_seconds", timeout) for _, file := range files { if !file.IsDir() { @@ -130,14 +129,14 @@ func (h *Harness) RunKIND() (*rest.Config, error) { // which means we do not stop that cluster. User will either need to switch to existing cluster or stop it. h.kind = nil msg := "KIND is already running, unable to start" - h.T.Log(msg) + h.logger.Log(msg) return nil, errors.New(msg) } kindCfg := &kindConfig.Cluster{} if h.TestSuite.KINDConfig != "" { - h.T.Logf("Loading KIND config from %s", h.TestSuite.KINDConfig) + h.logger.LogWithArgs("Loading KIND config", h.TestSuite.KINDConfig) var err error kindCfg, err = h.loadKindConfig(h.TestSuite.KINDConfig) if err != nil { @@ -155,7 +154,7 @@ func (h *Harness) RunKIND() (*rest.Config, error) { h.addNodeCaches(dockerClient, kindCfg) - h.T.Log("Starting KIND cluster") + h.logger.Log("Starting KIND cluster") if err := h.kind.Run(kindCfg); err != nil { return nil, err } @@ -173,7 +172,7 @@ func (h *Harness) RunKIND() (*rest.Config, error) { func (h *Harness) initTempPath() (err error) { if h.tempPath == "" { h.tempPath, err = os.MkdirTemp("", "kuttl") - h.T.Log("temp folder created", h.tempPath) + h.logger.LogWithArgs("temp folder created", "path", h.tempPath) } return err } @@ -198,11 +197,11 @@ func (h *Harness) addNodeCaches(dockerClient testutils.DockerClient, kindCfg *ki Name: fmt.Sprintf("%s-%d", h.TestSuite.KINDContext, index), }) if err != nil { - h.T.Log("error creating volume for node", err) + h.logger.LogWithArgs("error creating volume for node", "error", err) continue } - h.T.Log("node mount point", volume.Mountpoint) + h.logger.LogWithArgs("node mount point", "mount_path", volume.Mountpoint) kindCfg.Nodes[index].ExtraMounts = append(kindCfg.Nodes[index].ExtraMounts, kindConfig.Mount{ ContainerPath: "/var/lib/containerd", HostPath: volume.Mountpoint, @@ -220,9 +219,12 @@ func (h *Harness) RunTestEnv() (*rest.Config, error) { return nil, err } - h.T.Logf("started test environment (kube-apiserver and etcd) in %v, with following options:\n%s", + h.logger.LogWithArgs("started test environment (kube-apiserver and etcd) in %v, with following options:\n%s", + "elapsed_time", time.Since(started), - strings.Join(testenv.Environment.ControlPlane.GetAPIServer().Configure().AsStrings(nil), "\n")) + "options", + testenv.Environment.ControlPlane.GetAPIServer().Configure().AsStrings(nil), + ) h.env = testenv.Environment return testenv.Config, nil @@ -243,16 +245,16 @@ func (h *Harness) Config() (*rest.Config, error) { var err error switch { case h.TestSuite.Config != nil: - h.T.Log("running tests with passed rest config.") + h.logger.Log("running tests with passed rest config.") h.config = h.TestSuite.Config.RC case h.TestSuite.StartControlPlane: - h.T.Log("running tests with a mocked control plane (kube-apiserver and etcd).") + h.logger.Log("running tests with a mocked control plane (kube-apiserver and etcd).") h.config, err = h.RunTestEnv() case h.TestSuite.StartKIND: - h.T.Log("running tests with KIND.") + h.logger.Log("running tests with KIND.") h.config, err = h.RunKIND() default: - h.T.Log("running tests using configured kubeconfig.") + h.logger.Log("running tests using configured kubeconfig.") h.config, err = k8s.GetConfig() if err != nil { return nil, err @@ -275,7 +277,7 @@ func (h *Harness) Config() (*rest.Config, error) { if err := h.waitForFunctionalCluster(); err != nil { return nil, err } - h.T.Logf("Successful connection to cluster at: %s", h.config.Host) + h.logger.LogWithArgs("Successful connection to cluster", "hostname", h.config.Host) } // The creation of the "kubeconfig" is necessary for out of cluster execution of kubectl, @@ -360,7 +362,7 @@ func (h *Harness) DockerClient() (testutils.DockerClient, error) { func (h *Harness) RunTests() { // cleanup after running tests h.T.Cleanup(h.Stop) - h.T.Log("running tests") + h.logger.Log("running tests") testDirs := h.testPreProcessing() @@ -372,7 +374,7 @@ func (h *Harness) RunTests() { if err != nil { h.T.Fatal(err) } - h.T.Logf("testsuite: %s has %d tests", testDir, len(tempTests)) + h.logger.LogWithArgs("found testsuite", "path", testDir, "number_of_tests", len(tempTests)) // array of test cases tied to testsuite (by testdir) realTestSuite[testDir] = tempTests } @@ -391,7 +393,7 @@ func (h *Harness) RunTests() { // elapsed time calculations. t.Parallel() - test.Logger = testutils.NewTestLogger(t, test.Name) + test.Logger = h.GetLogger().WithGroup(test.Name) if err := test.LoadTestSteps(); err != nil { t.Fatal(err) @@ -404,7 +406,7 @@ func (h *Harness) RunTests() { } }) - h.T.Log("run tests finished") + h.logger.Log("run tests finished") } // testPreProcessing provides preprocessing bring all tests suites local if there are any refers to URLs @@ -418,7 +420,7 @@ func (h *Harness) testPreProcessing() []string { h.T.Fatal(err) } client := http.NewClient() - h.T.Logf("downloading %s", dir) + h.logger.LogWithArgs("downloading", "path", dir) // fresh temp dir created for each download to prevent overwriting folder, err := os.MkdirTemp(h.tempPath, filepath.Base(dir)) if err != nil { @@ -448,7 +450,7 @@ func (h *Harness) Run() { signal.Notify(sigchan, os.Interrupt) sig := <-sigchan h.Stop() - h.T.Log("failed with", sig) + h.logger.LogWithArgs("failed with", "signal", sig) os.Exit(-1) }() @@ -459,9 +461,12 @@ func (h *Harness) Run() { // Setup spins up the test env based on configuration // It can be used to start env which can than be modified prior to running tests, otherwise use Run(). func (h *Harness) Setup() { - rand.Seed(time.Now().UTC().UnixNano()) + // NOTE (@NickLarsenNZ): The logger needs to be set (else segfault). Ideally this should be done in a Harness constructor. + h.GetLogger() + + rand.New(rand.NewSource(time.Now().UTC().UnixNano())) h.report = report.NewSuiteCollection(h.TestSuite.Name) - h.T.Log("starting setup") + h.logger.Log("starting setup") cl, err := h.Client(false) if err != nil { @@ -512,7 +517,8 @@ func (h *Harness) Setup() { // Stop the test environment and clean up the harness. func (h *Harness) Stop() { - h.T.Log("cleaning up") + // h.logger.Log("cleaning up") + h.logger.Log("cleaning up") if h.managerStopCh != nil { close(h.managerStopCh) h.managerStopCh = nil @@ -521,26 +527,26 @@ func (h *Harness) Stop() { if h.kind != nil { logDir := filepath.Join(h.TestSuite.ArtifactsDir, fmt.Sprintf("kind-logs-%d", time.Now().Unix())) - h.T.Log("collecting cluster logs to", logDir) + h.logger.LogWithArgs("collecting cluster logs", "path", logDir) if err := h.kind.CollectLogs(logDir); err != nil { - h.T.Log("error collecting kind cluster logs", err) + h.logger.LogWithArgs("error collecting kind cluster", "error", err) } } if h.bgProcesses != nil { for _, p := range h.bgProcesses { - h.T.Logf("killing process %q", p) + h.logger.LogWithArgs("killing process", "pid", p) err := p.Process.Kill() if err != nil { - h.T.Logf("bg process: %q kill error %v", p, err) + h.logger.LogWithArgs("failed to kill background process", "pid", p, "error", err) } ps, err := p.Process.Wait() if err != nil { - h.T.Logf("bg process: %q kill wait error %v", p, err) + h.logger.LogWithArgs("failed to wait for background process to exit", "pid", p, "error", err) } if ps != nil { - h.T.Logf("bg process: %q exit code %v", p, ps.ExitCode()) + h.logger.LogWithArgs("background process exited", "pid", p, "exit_code", ps.ExitCode()) } } } @@ -550,34 +556,34 @@ func (h *Harness) Stop() { if h.TestSuite.SkipClusterDelete { cwd, err := os.Getwd() if err != nil { - h.T.Logf("issue getting work directory %v", err) + h.logger.LogWithArgs("failed to getting work directory", "path", err) } kubeconfig := filepath.Join(cwd, "kubeconfig") - h.T.Log("skipping cluster tear down") - h.T.Logf("to connect to the cluster, run: export KUBECONFIG=\"%s\"", kubeconfig) + h.logger.Log("skipping cluster tear down") + h.logger.Log(fmt.Sprintf("to connect to the cluster, run: export KUBECONFIG=\"%s\"", kubeconfig)) return } if h.env != nil { - h.T.Log("tearing down mock control plane") + h.logger.Log("tearing down mock control plane") if err := h.env.Stop(); err != nil { - h.T.Log("error tearing down mock control plane", err) + h.logger.LogWithArgs("error tearing down mock control plane", "error", err) } h.env = nil } - h.T.Logf("removing temp folder: %q", h.tempPath) + h.logger.LogWithArgs("removing temp folder", "path", h.tempPath) if err := os.RemoveAll(h.tempPath); err != nil { - h.T.Log("error removing temporary directory", err) + h.logger.LogWithArgs("error removing temporary directory", "error", err) } if h.kind != nil { - h.T.Log("tearing down kind cluster") + h.logger.Log("tearing down kind cluster") if err := h.kind.Stop(); err != nil { - h.T.Log("error tearing down kind cluster", err) + h.logger.LogWithArgs("error tearing down kind cluster", "error", err) } h.kind = nil @@ -635,7 +641,8 @@ func (h *Harness) loadKindConfig(path string) (*kindConfig.Cluster, error) { return nil, err } if !IsMinVersion(cluster.APIVersion) { - h.T.Logf("Warning: %q in %s is not a supported version.\n", cluster.APIVersion, path) + // TODO (@NickLarsenNZ): Should be a Warn, not Info + h.logger.Log(fmt.Sprintf("Warning: %q in %s is not a supported version.\n", cluster.APIVersion, path)) } return cluster, nil } diff --git a/pkg/test/kind_logger.go b/pkg/test/kind_logger.go index 66c31c1..595b4e2 100644 --- a/pkg/test/kind_logger.go +++ b/pkg/test/kind_logger.go @@ -1,6 +1,7 @@ package test import ( + "fmt" "strconv" "github.com/spf13/pflag" @@ -53,27 +54,33 @@ func (k kindLogger) V(level log.Level) log.InfoLogger { } func (k kindLogger) Warn(message string) { + // TODO (@NickLarsenNZ): Replace Logger.Log with a method for the correct level (eg: Warn) k.l.Log(message) } func (k kindLogger) Warnf(format string, args ...interface{}) { - k.l.Logf(format, args...) + // TODO (@NickLarsenNZ): Replace Logger.Log with a method for the correct level (eg: Warn) + k.l.Log(fmt.Sprintf(format, args...)) } func (k kindLogger) Error(message string) { + // TODO (@NickLarsenNZ): Replace Logger.Log with a method for the correct level (eg: Error) k.l.Log(message) } func (k kindLogger) Errorf(format string, args ...interface{}) { - k.l.Logf(format, args...) + // TODO (@NickLarsenNZ): Replace Logger.Log with a method for the correct level (eg: Error) + k.l.Log(fmt.Sprintf(format, args...)) } func (k kindLogger) Info(message string) { + // TODO (@NickLarsenNZ): Replace Logger.Log with a method for the correct level (eg: Info) k.l.Log(message) } func (k kindLogger) Infof(format string, args ...interface{}) { - k.l.Logf(format, args...) + // TODO (@NickLarsenNZ): Replace Logger.Log with a method for the correct level (eg: Info) + k.l.Log(fmt.Sprintf(format, args...)) } func (k kindLogger) Enabled() bool { diff --git a/pkg/test/step.go b/pkg/test/step.go index e3e2ffa..5ea8d27 100644 --- a/pkg/test/step.go +++ b/pkg/test/step.go @@ -218,7 +218,7 @@ func (s *Step) Create(test *testing.T, namespace string) []error { if updated { action = "updated" } - s.Logger.Log(testutils.ResourceID(obj), action) + s.Logger.LogWithArgs(testutils.ResourceID(obj), "action", action) } } @@ -437,7 +437,7 @@ func (s *Step) Check(namespace string, timeout int) []error { // 1. Apply all desired objects to Kubernetes. // 2. Wait for all of the states defined in the test step's asserts to be true.' func (s *Step) Run(test *testing.T, namespace string) []error { - s.Logger.Log("starting test step", s.String()) + s.Logger.LogWithArgs("starting test step", "step", s.String()) if err := s.DeleteExisting(namespace); err != nil { return []error{err} @@ -480,23 +480,24 @@ func (s *Step) Run(test *testing.T, namespace string) []error { // all is good if len(testErrors) == 0 { - s.Logger.Log("test step completed", s.String()) + s.Logger.LogWithArgs("test step completed", "step", s.String()) return testErrors } // test failure processing - s.Logger.Log("test step failed", s.String()) + s.Logger.ErrorWithArgs("test step failed", "step", s.String()) if s.Assert == nil { return testErrors } for _, collector := range s.Assert.Collectors { - s.Logger.Logf("collecting log output for %s", collector.String()) + // TODO (@NickLarsenNZ): Take a look at how collector logging is done + s.Logger.LogWithArgs("collecting log output for %s", "collector", collector.String()) if collector.Command() == nil { - s.Logger.Log("skipping invalid assertion collector") + s.Logger.LogWithArgs("skipping invalid assertion collector", "collector", collector.String()) continue } _, err := testutils.RunCommand(context.TODO(), namespace, *collector.Command(), s.Dir, s.Logger, s.Logger, s.Logger, s.Timeout, s.Kubeconfig) if err != nil { - s.Logger.Log("post assert collector failure: %s", err) + s.Logger.ErrorWithArgs("post assert collector failure", "error", err) } } s.Logger.Flush() @@ -630,7 +631,7 @@ func (s *Step) loadOrSkipFile(file string) (bool, []client.Object, error) { if selector.Empty() || selector.Matches(s.TestRunLabels) { continue } - fmt.Printf("Skipping file %q, label selector does not match test run labels.\n", file) + s.Logger.LogWithArgs("Skipping file, label selector does not match test run labels.", "path", file) shouldSkip = true } else { objects = append(objects, object) diff --git a/pkg/test/utils/kubernetes.go b/pkg/test/utils/kubernetes.go index e861a48..bc166d4 100644 --- a/pkg/test/utils/kubernetes.go +++ b/pkg/test/utils/kubernetes.go @@ -318,14 +318,17 @@ func (r *RetryStatusWriter) Patch(ctx context.Context, obj client.Object, patch func Scheme() *runtime.Scheme { schemeLock.Do(func() { if err := apis.AddToScheme(scheme.Scheme); err != nil { + // TODO (@NickLarsenNZ): Use a logger fmt.Printf("failed to add API resources to the scheme: %v", err) os.Exit(-1) } if err := apiextv1.AddToScheme(scheme.Scheme); err != nil { + // TODO (@NickLarsenNZ): Use a logger fmt.Printf("failed to add V1 API extension resources to the scheme: %v", err) os.Exit(-1) } if err := apiextv1beta1.AddToScheme(scheme.Scheme); err != nil { + // TODO (@NickLarsenNZ): Use a logger fmt.Printf("failed to add V1beta1 API extension resources to the scheme: %v", err) os.Exit(-1) } @@ -691,6 +694,7 @@ func InstallManifests(ctx context.Context, c client.Client, dClient discovery.Di action = "updated" } // TODO: use test logger instead of Go logger + // TODO (@NickLarsenNZ): As above log.Println(ResourceID(obj), action) newCrd := apiextv1.CustomResourceDefinition{ @@ -1156,7 +1160,7 @@ func RunCommand(ctx context.Context, namespace string, cmd harness.Command, cwd return nil, fmt.Errorf("processing command %q with %w", cmd.String(), err) } - logger.Logf("running command: %v", builtCmd.Args) + logger.LogWithArgs("running command", "command", strings.Join(builtCmd.Args, " ")) builtCmd.Dir = cwd if !cmd.SkipLogOutput { @@ -1246,7 +1250,7 @@ func RunCommands(ctx context.Context, logger Logger, namespace string, commands if err != nil { cmdListSize := len(commands) if i+1 < cmdListSize { - logger.Logf("command failure, skipping %d additional commands", cmdListSize-i-1) + logger.Error(fmt.Sprintf("command failure, skipping %d additional commands", cmdListSize-i-1)) } return bgs, err } @@ -1259,7 +1263,7 @@ func RunCommands(ctx context.Context, logger Logger, namespace string, commands } if len(bgs) > 0 { - logger.Log("background processes", bgs) + logger.LogWithArgs("background processes", bgs) } // handling of errs and bg processes external to this function return bgs, nil diff --git a/pkg/test/utils/logger.go b/pkg/test/utils/logger.go index 17b1603..9adeeab 100644 --- a/pkg/test/utils/logger.go +++ b/pkg/test/utils/logger.go @@ -2,16 +2,23 @@ package utils import ( "bytes" - "fmt" + "io" + "log/slog" + "os" "testing" "time" + + "github.com/charmbracelet/log" ) // Logger is an interface used by the KUTTL test operator to provide logging of tests. type Logger interface { - Log(args ...interface{}) - Logf(format string, args ...interface{}) - WithPrefix(string) Logger + Log(message string) + LogWithArgs(message string, args ...interface{}) + Error(message string) + ErrorWithArgs(message string, args ...interface{}) + WithNewBuffer() Logger + WithGroup(string) Logger Write(p []byte) (n int, err error) Flush() } @@ -20,40 +27,80 @@ type Logger interface { // output buffering (without this, the use of Parallel tests combined with subtests causes test // output to be mixed). type TestLogger struct { - prefix string - test *testing.T + test *testing.T + // NOTE (@NickLarsenNZ): This buffer is for the Write impl, output from commands run. It is not the general log buffer. buffer []byte + logger *slog.Logger + // NOTE (@NickLarsenNZ): This will default to io.Stdout + log_output io.Writer } // NewTestLogger creates a new test logger. -func NewTestLogger(test *testing.T, prefix string) *TestLogger { +// NOTE (@NickLarsenNZ): A new logger can be made for general code, but then we should be able to make a new one with a new buffer. +// Eg: +// +// kuttl_logger := NewTestLogger(...) +// specific_case_logger := kuttl_logger.WithNewBuffer().WithGroup("case-1").WithGroup("step-1") +// +// Then we need to think about how to _get_ the logs from the buffer on failure, at the end. +// +// specific_case_logger.Write(os.Stdout) +func NewTestLogger(test *testing.T, log_group string) *TestLogger { + // TODO (@NickLarsenNZ): toggle stdout vs buffer + // The complication is the layers of loggers (WithPrefix -> WithGroup) which would make the buffers disjoint. + // So when the relevant buffers are read, logs are not interleaved anymore. + + handler := log.NewWithOptions(os.Stdout, log.Options{ + TimeFormat: time.RFC3339, // Maybe want to use TimeOnly when run from an interactive terminal + ReportTimestamp: true, + }) + + // TODO (@NickLarsenNZ): Remove WithGroup here, it can be done as the logger is passed down from the haress down to the steps + logger := slog.New(handler).WithGroup(log_group) + return &TestLogger{ - prefix: prefix, - test: test, - buffer: []byte{}, + test: test, + buffer: []byte{}, + logger: logger, + log_output: os.Stdout, } } // Log logs the provided arguments with the logger's prefix. See testing.Log for more details. -func (t *TestLogger) Log(args ...interface{}) { - args = append([]interface{}{ - fmt.Sprintf("%s | %s |", time.Now().Format("15:04:05"), t.prefix), - }, args...) - t.test.Log(args...) +func (t *TestLogger) Log(message string) { + t.logger.Info(message) +} + +func (t *TestLogger) LogWithArgs(message string, args ...interface{}) { + t.logger.Info(message, args...) } -// Logf logs the provided arguments with the logger's prefix. See testing.Logf for more details. -func (t *TestLogger) Logf(format string, args ...interface{}) { - t.Log(fmt.Sprintf(format, args...)) +func (t *TestLogger) Error(message string) { + t.logger.Error(message) } -// WithPrefix returns a new TestLogger with the provided prefix appended to the current prefix. -func (t *TestLogger) WithPrefix(prefix string) Logger { - return NewTestLogger(t.test, fmt.Sprintf("%s/%s", t.prefix, prefix)) +func (t *TestLogger) ErrorWithArgs(message string, args ...interface{}) { + t.logger.Error(message, args...) +} + +// NOTE (@NickLarsenNZ): This will copy the logger, but create a new buffer +func (t *TestLogger) WithNewBuffer() Logger { + new_logger := t + new_logger.log_output = new(bytes.Buffer) + + return new_logger +} + +func (t *TestLogger) WithGroup(group string) Logger { + new_logger := t + new_logger.logger.WithGroup(group) + + return new_logger } // Write implements the io.Writer interface. // Logs each line written to it, buffers incomplete lines until the next Write() call. +// NOTE (@NickLarsenNZ): I believe this is needed so the logger can be passed to the command/script executer. func (t *TestLogger) Write(p []byte) (n int, err error) { t.buffer = append(t.buffer, p...)