From b6c51592508c7ecf129f854fd9da9d4a29865099 Mon Sep 17 00:00:00 2001 From: Halvard Skogsrud Date: Mon, 24 Jan 2022 17:14:06 +1100 Subject: [PATCH] Introduce go-logr to allow injecting loggers This change adds a dependency on `go-logr/logr` and replaces all uses of the `log` package from Go's standard library with a `logr`-based implementation. To ease the transition, and to keep the diff small, this change introduces a package `github.com/google/ko/pkg/log` that should be imported in place of `log` from the standard library. The new package provides some of the same functions as `log`, e.g., `Printf()` and `Fatalf()`. The replacement functions takes an extra `context.Context` argument. The `Context` is used to look up the `logr.Logger` to use for logging. Existing log statements have been updated to use the transition functions, with `Context` plumbed through where required. New code can use the new logging syntax, e.g.: ```go log.L(ctx).Info("something interesting happened", "key", "value") ``` or ```go log.L(ctx).Error(err, "uh oh", "what", "snafu") ``` This change alters the log output to `logr`'s structured output, with named fields for the log message and level. As an example, a log line that previously looked like this: ``` 2022/01/25 19:44:18 Building github.com/google/ko/test for linux/amd64 ``` Will look like as follows with this change: ``` 2022/01/25 19:46:02 ko: "level"=1 "msg"="Building github.com/google/ko/test for linux/amd64" ``` Fixes: #560 Related: #542 --- go.mod | 2 + go.sum | 5 +- main.go | 49 +- pkg/build/cache.go | 7 +- pkg/build/gobuild.go | 19 +- pkg/commands/apply.go | 11 +- pkg/commands/build.go | 2 +- pkg/commands/config.go | 4 +- pkg/commands/create.go | 11 +- pkg/commands/publisher_test.go | 2 +- pkg/commands/resolve.go | 2 +- pkg/commands/resolver.go | 16 +- pkg/commands/resolver_test.go | 5 +- pkg/commands/run.go | 11 +- pkg/log/log.go | 38 + pkg/log/transition.go | 81 ++ pkg/log/writer.go | 47 ++ pkg/publish/daemon.go | 13 +- pkg/publish/default.go | 16 +- pkg/publish/default_test.go | 26 +- pkg/publish/kind.go | 11 +- pkg/publish/layout.go | 9 +- pkg/publish/multi_test.go | 5 +- pkg/publish/options.go | 5 +- pkg/publish/tarball.go | 13 +- pkg/publish/tarball_test.go | 5 +- vendor/github.com/go-logr/logr/funcr/funcr.go | 759 ++++++++++++++++++ vendor/github.com/go-logr/logr/logr.go | 11 +- vendor/github.com/go-logr/stdr/LICENSE | 201 +++++ vendor/github.com/go-logr/stdr/README.md | 6 + vendor/github.com/go-logr/stdr/go.mod | 5 + vendor/github.com/go-logr/stdr/go.sum | 2 + vendor/github.com/go-logr/stdr/stdr.go | 170 ++++ vendor/modules.txt | 7 +- 34 files changed, 1481 insertions(+), 95 deletions(-) create mode 100644 pkg/log/log.go create mode 100644 pkg/log/transition.go create mode 100644 pkg/log/writer.go create mode 100644 vendor/github.com/go-logr/logr/funcr/funcr.go create mode 100644 vendor/github.com/go-logr/stdr/LICENSE create mode 100644 vendor/github.com/go-logr/stdr/README.md create mode 100644 vendor/github.com/go-logr/stdr/go.mod create mode 100644 vendor/github.com/go-logr/stdr/go.sum create mode 100644 vendor/github.com/go-logr/stdr/stdr.go diff --git a/go.mod b/go.mod index be5359e7a9..dc9f26c0d5 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,8 @@ require ( github.com/docker/docker v20.10.12+incompatible github.com/dprotaso/go-yit v0.0.0-20191028211022-135eb7262960 github.com/fsnotify/fsnotify v1.5.1 + github.com/go-logr/logr v1.2.2 + github.com/go-logr/stdr v1.2.2 github.com/go-training/helloworld v0.0.0-20200225145412-ba5f4379d78b github.com/google/go-cmp v0.5.7 github.com/google/go-containerregistry v0.8.0 diff --git a/go.sum b/go.sum index ed46f431af..25dd47e8de 100644 --- a/go.sum +++ b/go.sum @@ -602,8 +602,11 @@ github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7 github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v1.0.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.0 h1:QK40JKJyMdUDz+h+xvCsru/bJhvG0UxvePV0ufL/AcE= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.2 h1:ahHml/yUpnlb96Rp8HCvtYVPY8ZYpxq3g7UYchIYwbs= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= diff --git a/main.go b/main.go index 43d70b664a..ab8b49c937 100644 --- a/main.go +++ b/main.go @@ -18,21 +18,58 @@ package main import ( "context" - "log" + stdlog "log" "os" "os/signal" + "github.com/go-logr/logr" + "github.com/go-logr/stdr" "github.com/google/go-containerregistry/pkg/logs" + "github.com/google/ko/pkg/commands" + "github.com/google/ko/pkg/log" ) -func main() { - logs.Warn.SetOutput(os.Stderr) - logs.Progress.SetOutput(os.Stderr) +const ( + defaultVerbosity = 1 +) - ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt) +func main() { + ctx := context.Background() + ctx, stop := signal.NotifyContext(configureLogging(ctx), os.Interrupt) defer stop() if err := commands.Root.ExecuteContext(ctx); err != nil { - log.Fatal("error during command execution:", err) + log.Fatal(ctx, "error during command execution:", err) } } + +// configureLogging log logr logs logger logging +func configureLogging(ctx context.Context) context.Context { + logger := defaultLogger() + configureGGCRLogging(logger) + return logr.NewContext(ctx, logger) +} + +// defaultLogger returns a logr.Logger instance that wraps log.Logger from Go's standard library. +// This function is placed here so `pkg` does not depend on the `stdr` module. +func defaultLogger() logr.Logger { + stdLogger := stdlog.Default() + logger := stdr.New(stdLogger).V(defaultVerbosity).WithName("ko") + stdr.SetVerbosity(defaultVerbosity) + return logger +} + +// configureGGCRLogging maps ggcr log levels as follows: +// +// ggcr Warn -> logr V(0) +// ggcr Progress -> logr V(defaultVerbosity) +// ggcr Debug -> logr V(defaultVerbosity + 1) +func configureGGCRLogging(logger logr.Logger) { + ggcrLogger := logger.WithName("go-containerregistry") + logs.Warn.SetFlags(0) + logs.Warn.SetOutput(log.NewWriter(ggcrLogger.V(-1))) + logs.Progress.SetFlags(0) + logs.Progress.SetOutput(log.NewWriter(ggcrLogger)) + logs.Debug.SetFlags(0) + logs.Debug.SetOutput(log.NewWriter(ggcrLogger.V(1))) +} diff --git a/pkg/build/cache.go b/pkg/build/cache.go index af0e13d7f5..f27811b508 100644 --- a/pkg/build/cache.go +++ b/pkg/build/cache.go @@ -19,7 +19,6 @@ import ( "context" "encoding/json" "fmt" - "log" "os" "os/exec" "path/filepath" @@ -28,6 +27,8 @@ import ( v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/partial" + + "github.com/google/ko/pkg/log" ) type diffIDToDescriptor map[v1.Hash]v1.Descriptor @@ -61,7 +62,7 @@ func (c *layerCache) get(ctx context.Context, file string, miss layerFactory) (v return nil, err } if err := c.put(ctx, file, layer); err != nil { - log.Printf("failed to cache metadata %s: %v", file, err) + log.Printf(ctx, "failed to cache metadata %s: %v", file, err) } return layer, nil } @@ -203,7 +204,7 @@ func getBuildID(ctx context.Context, file string) (string, error) { cmd.Stdout = &output if err := cmd.Run(); err != nil { - log.Printf("Unexpected error running \"go tool buildid %s\": %v\n%v", err, file, output.String()) + log.Printf(ctx, "Unexpected error running \"go tool buildid %s\": %v\n%v", err, file, output.String()) return "", err } return strings.TrimSpace(output.String()), nil diff --git a/pkg/build/gobuild.go b/pkg/build/gobuild.go index febb3486c9..14a05c269a 100644 --- a/pkg/build/gobuild.go +++ b/pkg/build/gobuild.go @@ -25,7 +25,6 @@ import ( gb "go/build" "io" "io/ioutil" - "log" "os" "os/exec" "path" @@ -42,7 +41,6 @@ import ( "github.com/google/go-containerregistry/pkg/v1/mutate" "github.com/google/go-containerregistry/pkg/v1/tarball" "github.com/google/go-containerregistry/pkg/v1/types" - "github.com/google/ko/internal/sbom" specsv1 "github.com/opencontainers/image-spec/specs-go/v1" "github.com/sigstore/cosign/pkg/oci" ocimutate "github.com/sigstore/cosign/pkg/oci/mutate" @@ -52,6 +50,9 @@ import ( "golang.org/x/sync/errgroup" "golang.org/x/sync/semaphore" "golang.org/x/tools/go/packages" + + "github.com/google/ko/internal/sbom" + "github.com/google/ko/pkg/log" ) const ( @@ -288,12 +289,12 @@ func build(ctx context.Context, ip string, dir string, platform v1.Platform, con cmd.Stderr = &output cmd.Stdout = &output - log.Printf("Building %s for %s", ip, platformToString(platform)) + log.Printf(ctx, "Building %s for %s", ip, platformToString(platform)) if err := cmd.Run(); err != nil { if os.Getenv("KOCACHE") == "" { os.RemoveAll(tmpDir) } - log.Printf("Unexpected error running \"go build\": %v\n%v", err, output.String()) + log.Printf(ctx, "Unexpected error running \"go build\": %v\n%v", err, output.String()) return "", err } return file, nil @@ -469,7 +470,7 @@ const kodataRoot = "/var/run/ko" // walkRecursive performs a filepath.Walk of the given root directory adding it // to the provided tar.Writer with root -> chroot. All symlinks are dereferenced, // which is what leads to recursion when we encounter a directory symlink. -func walkRecursive(tw *tar.Writer, root, chroot string, creationTime v1.Time, platform *v1.Platform) error { +func walkRecursive(ctx context.Context, tw *tar.Writer, root, chroot string, creationTime v1.Time, platform *v1.Platform) error { return filepath.Walk(root, func(hostPath string, info os.FileInfo, err error) error { if hostPath == root { return nil @@ -486,7 +487,7 @@ func walkRecursive(tw *tar.Writer, root, chroot string, creationTime v1.Time, pl // Don't chase symlinks on Windows, where cross-compiled symlink support is not possible. if platform.OS == "windows" { if info.Mode()&os.ModeSymlink != 0 { - log.Println("skipping symlink in kodata for windows:", info.Name()) + log.Println(ctx, "skipping symlink in kodata for windows:", info.Name()) return nil } } @@ -503,7 +504,7 @@ func walkRecursive(tw *tar.Writer, root, chroot string, creationTime v1.Time, pl } // Skip other directories. if info.Mode().IsDir() { - return walkRecursive(tw, evalPath, newPath, creationTime, platform) + return walkRecursive(ctx, tw, evalPath, newPath, creationTime, platform) } // Open the file to copy it into the tarball. @@ -587,7 +588,7 @@ func (g *gobuild) tarKoData(ref reference, platform *v1.Platform) (*bytes.Buffer } } - return buf, walkRecursive(tw, root, chroot, creationTime, platform) + return buf, walkRecursive(g.ctx, tw, root, chroot, creationTime, platform) } func createTemplateData() map[string]interface{} { @@ -658,7 +659,7 @@ func (g *gobuild) configForImportPath(ip string) Config { } if config.ID != "" { - log.Printf("Using build config %s for %s", config.ID, ip) + log.Printf(g.ctx, "Using build config %s for %s", config.ID, ip) } return config diff --git a/pkg/commands/apply.go b/pkg/commands/apply.go index 8480a0c3bc..9a312121ba 100644 --- a/pkg/commands/apply.go +++ b/pkg/commands/apply.go @@ -17,15 +17,16 @@ package commands import ( "errors" "fmt" - "log" "os" "os/exec" "strings" - "github.com/google/ko/internal" - "github.com/google/ko/pkg/commands/options" "github.com/spf13/cobra" "golang.org/x/sync/errgroup" + + "github.com/google/ko/internal" + "github.com/google/ko/pkg/commands/options" + "github.com/google/ko/pkg/log" ) const kubectlFlagsWarningTemplate = `NOTICE! @@ -92,7 +93,7 @@ func addApply(topLevel *cobra.Command) { if err != nil { return fmt.Errorf("error creating builder: %w", err) } - publisher, err := makePublisher(po) + publisher, err := makePublisher(ctx, po) if err != nil { return fmt.Errorf("error creating publisher: %w", err) } @@ -104,7 +105,7 @@ func addApply(topLevel *cobra.Command) { argv := []string{"apply", "-f", "-"} if kflags := kf.Values(); len(kflags) != 0 { skflags := strings.Join(kflags, " ") - log.Printf(kubectlFlagsWarningTemplate, + log.Printf(ctx, kubectlFlagsWarningTemplate, "apply", skflags, "apply", skflags) argv = append(argv, kflags...) diff --git a/pkg/commands/build.go b/pkg/commands/build.go index 64d387632a..1c19944710 100644 --- a/pkg/commands/build.go +++ b/pkg/commands/build.go @@ -65,7 +65,7 @@ func addBuild(topLevel *cobra.Command) { if err != nil { return fmt.Errorf("error creating builder: %w", err) } - publisher, err := makePublisher(po) + publisher, err := makePublisher(ctx, po) if err != nil { return fmt.Errorf("error creating publisher: %w", err) } diff --git a/pkg/commands/config.go b/pkg/commands/config.go index 6f72c0c137..e19e568758 100644 --- a/pkg/commands/config.go +++ b/pkg/commands/config.go @@ -19,7 +19,6 @@ package commands import ( "context" "fmt" - "log" "os" "strconv" "strings" @@ -34,6 +33,7 @@ import ( "github.com/google/ko/pkg/build" "github.com/google/ko/pkg/commands/options" + "github.com/google/ko/pkg/log" "github.com/google/ko/pkg/publish" ) @@ -129,7 +129,7 @@ func getBaseImage(bo *options.BuildOptions) build.GetBase { return ref, cached, nil } - log.Printf("Using base %s for %s", ref, s) + log.Printf(ctx, "Using base %s for %s", ref, s) result, err := fetch(ctx, ref) if err != nil { return ref, result, err diff --git a/pkg/commands/create.go b/pkg/commands/create.go index f69cc14902..09fef29a2f 100644 --- a/pkg/commands/create.go +++ b/pkg/commands/create.go @@ -17,15 +17,16 @@ package commands import ( "errors" "fmt" - "log" "os" "os/exec" "strings" - "github.com/google/ko/internal" - "github.com/google/ko/pkg/commands/options" "github.com/spf13/cobra" "golang.org/x/sync/errgroup" + + "github.com/google/ko/internal" + "github.com/google/ko/pkg/commands/options" + "github.com/google/ko/pkg/log" ) // addCreate augments our CLI surface with apply. @@ -77,7 +78,7 @@ func addCreate(topLevel *cobra.Command) { if err != nil { return fmt.Errorf("error creating builder: %w", err) } - publisher, err := makePublisher(po) + publisher, err := makePublisher(ctx, po) if err != nil { return fmt.Errorf("error creating publisher: %w", err) } @@ -89,7 +90,7 @@ func addCreate(topLevel *cobra.Command) { argv := []string{"create", "-f", "-"} if kflags := kf.Values(); len(kflags) != 0 { skflags := strings.Join(stripPassword(kflags), " ") - log.Printf(kubectlFlagsWarningTemplate, + log.Printf(ctx, kubectlFlagsWarningTemplate, "create", skflags, "create", skflags) argv = append(argv, kflags...) diff --git a/pkg/commands/publisher_test.go b/pkg/commands/publisher_test.go index b0dd603373..3cb7a5b142 100644 --- a/pkg/commands/publisher_test.go +++ b/pkg/commands/publisher_test.go @@ -75,7 +75,7 @@ func TestPublishImages(t *testing.T) { DockerRepo: repo, PreserveImportPaths: true, } - publisher, err := NewPublisher(po) + publisher, err := NewPublisher(ctx, po) if err != nil { t.Fatalf("%s: MakePublisher(): %v", test.description, err) } diff --git a/pkg/commands/resolve.go b/pkg/commands/resolve.go index 9444a47689..f28150e382 100644 --- a/pkg/commands/resolve.go +++ b/pkg/commands/resolve.go @@ -62,7 +62,7 @@ func addResolve(topLevel *cobra.Command) { if err != nil { return fmt.Errorf("error creating builder: %w", err) } - publisher, err := makePublisher(po) + publisher, err := makePublisher(ctx, po) if err != nil { return fmt.Errorf("error creating publisher: %w", err) } diff --git a/pkg/commands/resolver.go b/pkg/commands/resolver.go index 2a47d86baa..72a61ac66d 100644 --- a/pkg/commands/resolver.go +++ b/pkg/commands/resolver.go @@ -23,7 +23,6 @@ import ( "fmt" "io" "io/ioutil" - "log" "os" "path" "strings" @@ -38,6 +37,7 @@ import ( "github.com/google/ko/pkg/build" "github.com/google/ko/pkg/commands/options" + "github.com/google/ko/pkg/log" "github.com/google/ko/pkg/publish" "github.com/google/ko/pkg/resolve" ) @@ -162,11 +162,11 @@ func makeBuilder(ctx context.Context, bo *options.BuildOptions) (*build.Caching, } // NewPublisher creates a ko publisher -func NewPublisher(po *options.PublishOptions) (publish.Interface, error) { - return makePublisher(po) +func NewPublisher(ctx context.Context, po *options.PublishOptions) (publish.Interface, error) { + return makePublisher(ctx, po) } -func makePublisher(po *options.PublishOptions) (publish.Interface, error) { +func makePublisher(ctx context.Context, po *options.PublishOptions) (publish.Interface, error) { // Create the publish.Interface that we will use to publish image references // to either a docker daemon or a container image registry. innerPublisher, err := func() (publish.Interface, error) { @@ -203,7 +203,7 @@ func makePublisher(po *options.PublishOptions) (publish.Interface, error) { publishers = append(publishers, lp) } if po.TarballFile != "" { - tp := publish.NewTarball(po.TarballFile, repoName, namer, po.Tags) + tp := publish.NewTarball(ctx, po.TarballFile, repoName, namer, po.Tags) publishers = append(publishers, tp) } userAgent := ua() @@ -211,7 +211,9 @@ func makePublisher(po *options.PublishOptions) (publish.Interface, error) { userAgent = po.UserAgent } if po.Push { - dp, err := publish.NewDefault(repoName, + dp, err := publish.NewDefault( + ctx, + repoName, publish.WithUserAgent(userAgent), publish.WithAuthFromKeychain(authn.DefaultKeychain), publish.WithNamer(namer), @@ -377,7 +379,7 @@ func resolveFilesToWriter( // isn't fatal. Just print it and keep the watch open. err := fmt.Errorf("error processing import paths in %q: %w", f, err) if fo.Watch { - log.Print(err) + log.Print(ctx, err) return nil } return err diff --git a/pkg/commands/resolver_test.go b/pkg/commands/resolver_test.go index 49906386e0..5eada5b1c7 100644 --- a/pkg/commands/resolver_test.go +++ b/pkg/commands/resolver_test.go @@ -281,12 +281,13 @@ func TestNewPublisherCanPublish(t *testing.T) { } for _, test := range tests { t.Run(test.description, func(t *testing.T) { - publisher, err := NewPublisher(test.po) + ctx := context.Background() + publisher, err := NewPublisher(ctx, test.po) if err != nil { t.Fatalf("NewPublisher(): %v", err) } defer publisher.Close() - ref, err := publisher.Publish(context.Background(), empty.Image, build.StrictScheme+importpath) + ref, err := publisher.Publish(ctx, empty.Image, build.StrictScheme+importpath) if test.shouldError { if err == nil || !strings.HasSuffix(err.Error(), test.wantError.Error()) { t.Errorf("%s: got error %v, wanted %v", test.description, err, test.wantError) diff --git a/pkg/commands/run.go b/pkg/commands/run.go index bb7efdaf08..24ec24a4a7 100644 --- a/pkg/commands/run.go +++ b/pkg/commands/run.go @@ -17,14 +17,15 @@ package commands import ( "errors" "fmt" - "log" "os" "os/exec" "path/filepath" "strings" - "github.com/google/ko/pkg/commands/options" "github.com/spf13/cobra" + + "github.com/google/ko/pkg/commands/options" + "github.com/google/ko/pkg/log" ) // addRun augments our CLI surface with run. @@ -72,7 +73,7 @@ func addRun(topLevel *cobra.Command) { if err != nil { return fmt.Errorf("error creating builder: %w", err) } - publisher, err := makePublisher(po) + publisher, err := makePublisher(ctx, po) if err != nil { return fmt.Errorf("error creating publisher: %w", err) } @@ -93,7 +94,7 @@ func addRun(topLevel *cobra.Command) { // Usually only one, but this is the simple way to access the // reference since the import path may have been qualified. for k, ref := range imgs { - log.Printf("Running %q", k) + log.Printf(ctx, "Running %q", k) pod := filepath.Base(ref.Context().String()) // These are better defaults: @@ -118,7 +119,7 @@ func addRun(topLevel *cobra.Command) { // "run --image " argv = append([]string{"run", pod}, argv...) - log.Printf("$ kubectl %s", strings.Join(argv, " ")) + log.Printf(ctx, "$ kubectl %s", strings.Join(argv, " ")) kubectlCmd := exec.CommandContext(ctx, "kubectl", argv...) // Pass through our environment diff --git a/pkg/log/log.go b/pkg/log/log.go new file mode 100644 index 0000000000..d7733f73f2 --- /dev/null +++ b/pkg/log/log.go @@ -0,0 +1,38 @@ +// Copyright 2022 Google LLC All Rights Reserved. +// +// 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 log + +import ( + "context" + "fmt" + "os" + + "github.com/go-logr/logr" +) + +// L returns a logr.Logger from the provided context. +// If the context is nil or doesn't contain a logr.Logger, it returns a logr.Logger that discards all messages. +func L(ctx context.Context) logr.Logger { + if ctx == nil { + fmt.Fprintln(os.Stderr, "nil context, discarding log messages") + return logr.Discard() + } + l := logr.FromContextOrDiscard(ctx) + if l == logr.Discard() { // the Discard() return value is comparable + fmt.Fprintln(os.Stderr, "no logger in context, discarding log messages") + return l + } + return l +} diff --git a/pkg/log/transition.go b/pkg/log/transition.go new file mode 100644 index 0000000000..651f5d88da --- /dev/null +++ b/pkg/log/transition.go @@ -0,0 +1,81 @@ +// Copyright 2022 Google LLC All Rights Reserved. +// +// 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 log + +import ( + "context" + "fmt" + "os" +) + +// The functions below are provided to help transition from the log package in +// Go's standard library to go-logr. +// For new code, please use the L() function to obtain a logr.Logger and use +// its methods to log. + +// Fatal is intended as a replacement for log.Fatal. +// It takes an extra Context argument that it uses to find a logr.Logger. +func Fatal(ctx context.Context, v ...interface{}) { + err := getError(v...) + L(ctx).Error(err, fmt.Sprint(v...)) + os.Exit(1) +} + +// Fatalf is intended as a replacement for log.Fatalf. +// It takes an extra Context argument that it uses to find a logr.Logger. +func Fatalf(ctx context.Context, format string, v ...interface{}) { + err := getError(v...) + L(ctx).Error(err, fmt.Sprintf(format, v...)) + os.Exit(1) +} + +// Fatalln is intended as a replacement for log.Fatalln. +// It takes an extra Context argument that it uses to find a logr.Logger. +func Fatalln(ctx context.Context, v ...interface{}) { + err := getError(v...) + L(ctx).Error(err, fmt.Sprintln(v...)) + os.Exit(1) +} + +// Print is intended as a replacement for log.Print. +// It takes an extra Context argument that it uses to find a logr.Logger. +func Print(ctx context.Context, v ...interface{}) { + L(ctx).Info(fmt.Sprint(v...)) +} + +// Printf is intended as a replacement for log.Printf. +// It takes an extra Context argument that it uses to find a logr.Logger. +func Printf(ctx context.Context, format string, v ...interface{}) { + L(ctx).Info(fmt.Sprintf(format, v...)) +} + +// Println is intended as a replacement for log.Println. +// It takes an extra Context argument that it uses to find a logr.Logger. +func Println(ctx context.Context, v ...interface{}) { + L(ctx).Info(fmt.Sprintln(v...)) +} + +// getError returns the first error from the arguments. +// If there are no errors, the function returns nil. +func getError(v ...interface{}) error { + var err error + for _, val := range v { + switch value := val.(type) { + case error: + err = value + } + } + return err +} diff --git a/pkg/log/writer.go b/pkg/log/writer.go new file mode 100644 index 0000000000..5e05d469f6 --- /dev/null +++ b/pkg/log/writer.go @@ -0,0 +1,47 @@ +// Copyright 2022 Google LLC All Rights Reserved. +// +// 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 log + +import ( + "io" + + "github.com/go-logr/logr" +) + +// Writer wraps a logr.Logger and implements io.Writer. +// This is useful for redirecting output of other loggers, +// e.g., Go's standard library logger. +type Writer struct { + io.Writer + + logger logr.Logger +} + +// NewWriter returns a log.Writer that wraps the provided logr.Logger +func NewWriter(logger logr.Logger) *Writer { + return &Writer{ + logger: logger, + } +} + +// Write implements io.Writer +func (w *Writer) Write(p []byte) (n int, err error) { + numBytes := len(p) + if numBytes > 0 && p[numBytes-1] == '\n' { + p = p[:numBytes-1] + } + w.logger.Info(string(p)) + return len(p), nil +} diff --git a/pkg/publish/daemon.go b/pkg/publish/daemon.go index 3938374ea4..dbced7dae4 100644 --- a/pkg/publish/daemon.go +++ b/pkg/publish/daemon.go @@ -19,14 +19,15 @@ package publish import ( "context" "fmt" - "log" "os" "strings" "github.com/google/go-containerregistry/pkg/name" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/daemon" + "github.com/google/ko/pkg/build" + "github.com/google/ko/pkg/log" ) const ( @@ -144,15 +145,15 @@ func (d *demon) Publish(ctx context.Context, br build.Result, s string) (name.Re return nil, err } - log.Printf("Loading %v", digestTag) + log.Printf(ctx, "Loading %v", digestTag) if resp, err := daemon.Write(digestTag, img, d.getOpts(ctx)...); err != nil { - log.Println("daemon.Write response: ", resp) + log.Println(ctx, "daemon.Write response: ", resp) return nil, err } - log.Printf("Loaded %v", digestTag) + log.Printf(ctx, "Loaded %v", digestTag) for _, tagName := range d.tags { - log.Printf("Adding tag %v", tagName) + log.Printf(ctx, "Adding tag %v", tagName) tag, err := name.NewTag(fmt.Sprintf("%s:%s", d.namer(d.base, s), tagName)) if err != nil { return nil, err @@ -161,7 +162,7 @@ func (d *demon) Publish(ctx context.Context, br build.Result, s string) (name.Re if err := daemon.Tag(digestTag, tag, d.getOpts(ctx)...); err != nil { return nil, err } - log.Printf("Added tag %v", tagName) + log.Printf(ctx, "Added tag %v", tagName) } return &digestTag, nil diff --git a/pkg/publish/default.go b/pkg/publish/default.go index b0f58cc994..d1719dc42f 100644 --- a/pkg/publish/default.go +++ b/pkg/publish/default.go @@ -18,7 +18,6 @@ import ( "context" "errors" "fmt" - "log" "net/http" "path" "strings" @@ -33,10 +32,12 @@ import ( "github.com/sigstore/cosign/pkg/oci/walk" "github.com/google/ko/pkg/build" + "github.com/google/ko/pkg/log" ) // defalt is intentionally misspelled to avoid keyword collision (and drive Jon nuts). type defalt struct { + ctx context.Context base string t http.RoundTripper userAgent string @@ -51,6 +52,7 @@ type defalt struct { type Option func(*defaultOpener) error type defaultOpener struct { + ctx context.Context base string t http.RoundTripper userAgent string @@ -86,6 +88,7 @@ func (do *defaultOpener) Open() (Interface, error) { } return &defalt{ + ctx: do.ctx, base: do.base, t: do.t, userAgent: do.userAgent, @@ -99,8 +102,9 @@ func (do *defaultOpener) Open() (Interface, error) { // NewDefault returns a new publish.Interface that publishes references under the provided base // repository using the default keychain to authenticate and the default naming scheme. -func NewDefault(base string, options ...Option) (Interface, error) { +func NewDefault(ctx context.Context, base string, options ...Option) (Interface, error) { do := &defaultOpener{ + ctx: ctx, base: base, t: http.DefaultTransport, userAgent: "ko", @@ -153,7 +157,7 @@ func pushResult(ctx context.Context, tag name.Tag, br build.Result, opt []remote } else if err := remote.Write(ref, f, opt...); err != nil { return fmt.Errorf("writing sbom: %w", err) } else { - log.Printf("Published SBOM %v", ref) + log.Printf(ctx, "Published SBOM %v", ref) } // TODO(mattmoor): Don't enable this until we start signing or it @@ -216,12 +220,12 @@ func (d *defalt) Publish(ctx context.Context, br build.Result, s string) (name.R } if i == 0 { - log.Printf("Publishing %v", tag) + log.Printf(ctx, "Publishing %v", tag) if err := pushResult(ctx, tag, br, ro); err != nil { return nil, err } } else { - log.Printf("Tagging %v", tag) + log.Printf(ctx, "Tagging %v", tag) if err := remote.Tag(tag, br, ro...); err != nil { return nil, err } @@ -251,7 +255,7 @@ func (d *defalt) Publish(ctx context.Context, br build.Result, s string) (name.R if err != nil { return nil, err } - log.Printf("Published %v", dig) + log.Printf(ctx, "Published %v", dig) return &dig, nil } diff --git a/pkg/publish/default_test.go b/pkg/publish/default_test.go index 768d9cd760..63b2f4c16f 100644 --- a/pkg/publish/default_test.go +++ b/pkg/publish/default_test.go @@ -73,11 +73,13 @@ func TestDefault(t *testing.T) { } repoName := fmt.Sprintf("%s/%s", u.Host, base) - def, err := publish.NewDefault(repoName) + ctx := context.Background() + + def, err := publish.NewDefault(ctx, repoName) if err != nil { t.Errorf("NewDefault() = %v", err) } - if d, err := def.Publish(context.Background(), br, build.StrictScheme+importpath); err != nil { + if d, err := def.Publish(ctx, br, build.StrictScheme+importpath); err != nil { t.Errorf("Publish() = %v", err) } else if !strings.HasPrefix(d.String(), tag.Repository.String()) { t.Errorf("Publish() = %v, wanted prefix %v", d, tag.Repository) @@ -110,12 +112,13 @@ func TestDefaultWithCustomNamer(t *testing.T) { } repoName := fmt.Sprintf("%s/%s", u.Host, base) + ctx := context.Background() - def, err := publish.NewDefault(repoName, publish.WithNamer(md5Hash)) + def, err := publish.NewDefault(ctx, repoName, publish.WithNamer(md5Hash)) if err != nil { t.Errorf("NewDefault() = %v", err) } - if d, err := def.Publish(context.Background(), br, build.StrictScheme+importpath); err != nil { + if d, err := def.Publish(ctx, br, build.StrictScheme+importpath); err != nil { t.Errorf("Publish() = %v", err) } else if !strings.HasPrefix(d.String(), repoName) { t.Errorf("Publish() = %v, wanted prefix %v", d, tag.Repository) @@ -143,12 +146,12 @@ func TestDefaultWithTags(t *testing.T) { } repoName := fmt.Sprintf("%s/%s", u.Host, base) - - def, err := publish.NewDefault(repoName, publish.WithTags([]string{"notLatest", "v1.2.3"})) + ctx := context.Background() + def, err := publish.NewDefault(ctx, repoName, publish.WithTags([]string{"notLatest", "v1.2.3"})) if err != nil { t.Errorf("NewDefault() = %v", err) } - if d, err := def.Publish(context.Background(), br, build.StrictScheme+importpath); err != nil { + if d, err := def.Publish(ctx, br, build.StrictScheme+importpath); err != nil { t.Errorf("Publish() = %v", err) } else if !strings.HasPrefix(d.String(), repoName) { t.Errorf("Publish() = %v, wanted prefix %v", d, tag.Repository) @@ -224,12 +227,13 @@ func TestDefaultWithReleaseTag(t *testing.T) { } repoName := fmt.Sprintf("%s/%s", u.Host, base) + ctx := context.Background() - def, err := publish.NewDefault(repoName, publish.WithTags([]string{releaseTag})) + def, err := publish.NewDefault(ctx, repoName, publish.WithTags([]string{releaseTag})) if err != nil { t.Errorf("NewDefault() = %v", err) } - if d, err := def.Publish(context.Background(), img, build.StrictScheme+importpath); err != nil { + if d, err := def.Publish(ctx, img, build.StrictScheme+importpath); err != nil { t.Errorf("Publish() = %v", err) } else if !strings.HasPrefix(d.String(), repoName) { t.Errorf("Publish() = %v, wanted prefix %v", d, tag.Repository) @@ -243,11 +247,11 @@ func TestDefaultWithReleaseTag(t *testing.T) { t.Errorf("Tag v1.2.3 was not created.") } - def, err = publish.NewDefault(repoName, publish.WithTags([]string{releaseTag}), publish.WithTagOnly(true)) + def, err = publish.NewDefault(ctx, repoName, publish.WithTags([]string{releaseTag}), publish.WithTagOnly(true)) if err != nil { t.Errorf("NewDefault() = %v", err) } - if d, err := def.Publish(context.Background(), img, build.StrictScheme+importpath); err != nil { + if d, err := def.Publish(ctx, img, build.StrictScheme+importpath); err != nil { t.Errorf("Publish() = %v", err) } else if !strings.HasPrefix(d.String(), repoName) { t.Errorf("Publish() = %v, wanted prefix %v", d, tag.Repository) diff --git a/pkg/publish/kind.go b/pkg/publish/kind.go index f20303cbf7..130cbaf6a1 100644 --- a/pkg/publish/kind.go +++ b/pkg/publish/kind.go @@ -17,13 +17,14 @@ package publish import ( "context" "fmt" - "log" "os" "strings" "github.com/google/go-containerregistry/pkg/name" v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/google/ko/pkg/build" + "github.com/google/ko/pkg/log" "github.com/google/ko/pkg/publish/kind" ) @@ -101,14 +102,14 @@ func (t *kindPublisher) Publish(ctx context.Context, br build.Result, s string) return nil, err } - log.Printf("Loading %v", digestTag) + log.Printf(ctx, "Loading %v", digestTag) if err := kind.Write(ctx, digestTag, img); err != nil { return nil, err } - log.Printf("Loaded %v", digestTag) + log.Printf(ctx, "Loaded %v", digestTag) for _, tagName := range t.tags { - log.Printf("Adding tag %v", tagName) + log.Printf(ctx, "Adding tag %v", tagName) tag, err := name.NewTag(fmt.Sprintf("%s:%s", t.namer(KindDomain, s), tagName)) if err != nil { return nil, err @@ -117,7 +118,7 @@ func (t *kindPublisher) Publish(ctx context.Context, br build.Result, s string) if err := kind.Tag(ctx, digestTag, tag); err != nil { return nil, err } - log.Printf("Added tag %v", tagName) + log.Printf(ctx, "Added tag %v", tagName) } return &digestTag, nil diff --git a/pkg/publish/layout.go b/pkg/publish/layout.go index d325a33bf2..ae31706039 100644 --- a/pkg/publish/layout.go +++ b/pkg/publish/layout.go @@ -17,14 +17,15 @@ package publish import ( "context" "fmt" - "log" "github.com/google/go-containerregistry/pkg/name" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/empty" "github.com/google/go-containerregistry/pkg/v1/layout" "github.com/google/go-containerregistry/pkg/v1/types" + "github.com/google/ko/pkg/build" + "github.com/google/ko/pkg/log" ) type LayoutPublisher struct { @@ -68,12 +69,12 @@ func (l *LayoutPublisher) writeResult(br build.Result) error { } // Publish implements publish.Interface. -func (l *LayoutPublisher) Publish(_ context.Context, br build.Result, s string) (name.Reference, error) { - log.Printf("Saving %v", s) +func (l *LayoutPublisher) Publish(ctx context.Context, br build.Result, s string) (name.Reference, error) { + log.Printf(ctx, "Saving %v", s) if err := l.writeResult(br); err != nil { return nil, err } - log.Printf("Saved %v", s) + log.Printf(ctx, "Saved %v", s) h, err := br.Digest() if err != nil { diff --git a/pkg/publish/multi_test.go b/pkg/publish/multi_test.go index fda208f4ad..8fbc9d4491 100644 --- a/pkg/publish/multi_test.go +++ b/pkg/publish/multi_test.go @@ -41,7 +41,8 @@ func TestMulti(t *testing.T) { defer fp.Close() defer os.Remove(fp.Name()) - tp := publish.NewTarball(fp.Name(), repoName, md5Hash, []string{}) + ctx := context.Background() + tp := publish.NewTarball(ctx, fp.Name(), repoName, md5Hash, []string{}) tmp, err := ioutil.TempDir("/tmp", "ko") if err != nil { @@ -55,7 +56,7 @@ func TestMulti(t *testing.T) { } p := publish.MultiPublisher(lp, tp) - if _, err := p.Publish(context.Background(), img, importpath); err != nil { + if _, err := p.Publish(ctx, img, importpath); err != nil { t.Errorf("Publish() = %v", err) } diff --git a/pkg/publish/options.go b/pkg/publish/options.go index ead9fd4050..2d3eeb792f 100644 --- a/pkg/publish/options.go +++ b/pkg/publish/options.go @@ -16,12 +16,13 @@ package publish import ( "crypto/tls" - "log" "net/http" "path" "github.com/google/go-containerregistry/pkg/authn" "github.com/google/go-containerregistry/pkg/name" + + "github.com/google/ko/pkg/log" ) // WithTransport is a functional option for overriding the default transport @@ -71,7 +72,7 @@ func WithAuthFromKeychain(keys authn.Keychain) Option { return err } if auth == authn.Anonymous { - log.Println("No matching credentials were found, falling back on anonymous") + log.Println(i.ctx, "No matching credentials were found, falling back on anonymous") } i.auth = auth return nil diff --git a/pkg/publish/tarball.go b/pkg/publish/tarball.go index 66f37ffe6f..f1e3039dd4 100644 --- a/pkg/publish/tarball.go +++ b/pkg/publish/tarball.go @@ -17,16 +17,18 @@ package publish import ( "context" "fmt" - "log" "strings" "github.com/google/go-containerregistry/pkg/name" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/tarball" + "github.com/google/ko/pkg/build" + "github.com/google/ko/pkg/log" ) type tar struct { + ctx context.Context file string base string namer Namer @@ -35,8 +37,9 @@ type tar struct { } // NewTarball returns a new publish.Interface that saves images to a tarball. -func NewTarball(file, base string, namer Namer, tags []string) Interface { +func NewTarball(ctx context.Context, file, base string, namer Namer, tags []string) Interface { return &tar{ + ctx: ctx, file: file, base: base, namer: namer, @@ -93,12 +96,12 @@ func (t *tar) Publish(_ context.Context, br build.Result, s string) (name.Refere } func (t *tar) Close() error { - log.Printf("Saving %v", t.file) + log.Printf(t.ctx, "Saving %v", t.file) if err := tarball.MultiRefWriteToFile(t.file, t.refs); err != nil { // Bad practice, but we log this here because right now we just defer the Close. - log.Printf("failed to save %q: %v", t.file, err) + log.Printf(t.ctx, "failed to save %q: %v", t.file, err) return err } - log.Printf("Saved %v", t.file) + log.Printf(t.ctx, "Saved %v", t.file) return nil } diff --git a/pkg/publish/tarball_test.go b/pkg/publish/tarball_test.go index c6e59037b8..00d2cd1440 100644 --- a/pkg/publish/tarball_test.go +++ b/pkg/publish/tarball_test.go @@ -61,8 +61,9 @@ func TestTarball(t *testing.T) { "debug", }} for _, tags := range tagss { - tp := publish.NewTarball(fp.Name(), repoName, md5Hash, tags) - if d, err := tp.Publish(context.Background(), img, importpath); err != nil { + ctx := context.Background() + tp := publish.NewTarball(ctx, fp.Name(), repoName, md5Hash, tags) + if d, err := tp.Publish(ctx, img, importpath); err != nil { t.Errorf("Publish() = %v", err) } else if !strings.HasPrefix(d.String(), tag.Repository.String()) { t.Errorf("Publish() = %v, wanted prefix %v", d, tag.Repository) diff --git a/vendor/github.com/go-logr/logr/funcr/funcr.go b/vendor/github.com/go-logr/logr/funcr/funcr.go new file mode 100644 index 0000000000..b23ab9679a --- /dev/null +++ b/vendor/github.com/go-logr/logr/funcr/funcr.go @@ -0,0 +1,759 @@ +/* +Copyright 2021 The logr 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 funcr implements formatting of structured log messages and +// optionally captures the call site and timestamp. +// +// The simplest way to use it is via its implementation of a +// github.com/go-logr/logr.LogSink with output through an arbitrary +// "write" function. See New and NewJSON for details. +// +// Custom LogSinks +// +// For users who need more control, a funcr.Formatter can be embedded inside +// your own custom LogSink implementation. This is useful when the LogSink +// needs to implement additional methods, for example. +// +// Formatting +// +// This will respect logr.Marshaler, fmt.Stringer, and error interfaces for +// values which are being logged. When rendering a struct, funcr will use Go's +// standard JSON tags (all except "string"). +package funcr + +import ( + "bytes" + "encoding" + "fmt" + "path/filepath" + "reflect" + "runtime" + "strconv" + "strings" + "time" + + "github.com/go-logr/logr" +) + +// New returns a logr.Logger which is implemented by an arbitrary function. +func New(fn func(prefix, args string), opts Options) logr.Logger { + return logr.New(newSink(fn, NewFormatter(opts))) +} + +// NewJSON returns a logr.Logger which is implemented by an arbitrary function +// and produces JSON output. +func NewJSON(fn func(obj string), opts Options) logr.Logger { + fnWrapper := func(_, obj string) { + fn(obj) + } + return logr.New(newSink(fnWrapper, NewFormatterJSON(opts))) +} + +// Underlier exposes access to the underlying logging function. Since +// callers only have a logr.Logger, they have to know which +// implementation is in use, so this interface is less of an +// abstraction and more of a way to test type conversion. +type Underlier interface { + GetUnderlying() func(prefix, args string) +} + +func newSink(fn func(prefix, args string), formatter Formatter) logr.LogSink { + l := &fnlogger{ + Formatter: formatter, + write: fn, + } + // For skipping fnlogger.Info and fnlogger.Error. + l.Formatter.AddCallDepth(1) + return l +} + +// Options carries parameters which influence the way logs are generated. +type Options struct { + // LogCaller tells funcr to add a "caller" key to some or all log lines. + // This has some overhead, so some users might not want it. + LogCaller MessageClass + + // LogCallerFunc tells funcr to also log the calling function name. This + // has no effect if caller logging is not enabled (see Options.LogCaller). + LogCallerFunc bool + + // LogTimestamp tells funcr to add a "ts" key to log lines. This has some + // overhead, so some users might not want it. + LogTimestamp bool + + // TimestampFormat tells funcr how to render timestamps when LogTimestamp + // is enabled. If not specified, a default format will be used. For more + // details, see docs for Go's time.Layout. + TimestampFormat string + + // Verbosity tells funcr which V logs to produce. Higher values enable + // more logs. Info logs at or below this level will be written, while logs + // above this level will be discarded. + Verbosity int + + // RenderBuiltinsHook allows users to mutate the list of key-value pairs + // while a log line is being rendered. The kvList argument follows logr + // conventions - each pair of slice elements is comprised of a string key + // and an arbitrary value (verified and sanitized before calling this + // hook). The value returned must follow the same conventions. This hook + // can be used to audit or modify logged data. For example, you might want + // to prefix all of funcr's built-in keys with some string. This hook is + // only called for built-in (provided by funcr itself) key-value pairs. + // Equivalent hooks are offered for key-value pairs saved via + // logr.Logger.WithValues or Formatter.AddValues (see RenderValuesHook) and + // for user-provided pairs (see RenderArgsHook). + RenderBuiltinsHook func(kvList []interface{}) []interface{} + + // RenderValuesHook is the same as RenderBuiltinsHook, except that it is + // only called for key-value pairs saved via logr.Logger.WithValues. See + // RenderBuiltinsHook for more details. + RenderValuesHook func(kvList []interface{}) []interface{} + + // RenderArgsHook is the same as RenderBuiltinsHook, except that it is only + // called for key-value pairs passed directly to Info and Error. See + // RenderBuiltinsHook for more details. + RenderArgsHook func(kvList []interface{}) []interface{} + + // MaxLogDepth tells funcr how many levels of nested fields (e.g. a struct + // that contains a struct, etc.) it may log. Every time it finds a struct, + // slice, array, or map the depth is increased by one. When the maximum is + // reached, the value will be converted to a string indicating that the max + // depth has been exceeded. If this field is not specified, a default + // value will be used. + MaxLogDepth int +} + +// MessageClass indicates which category or categories of messages to consider. +type MessageClass int + +const ( + // None ignores all message classes. + None MessageClass = iota + // All considers all message classes. + All + // Info only considers info messages. + Info + // Error only considers error messages. + Error +) + +// fnlogger inherits some of its LogSink implementation from Formatter +// and just needs to add some glue code. +type fnlogger struct { + Formatter + write func(prefix, args string) +} + +func (l fnlogger) WithName(name string) logr.LogSink { + l.Formatter.AddName(name) + return &l +} + +func (l fnlogger) WithValues(kvList ...interface{}) logr.LogSink { + l.Formatter.AddValues(kvList) + return &l +} + +func (l fnlogger) WithCallDepth(depth int) logr.LogSink { + l.Formatter.AddCallDepth(depth) + return &l +} + +func (l fnlogger) Info(level int, msg string, kvList ...interface{}) { + prefix, args := l.FormatInfo(level, msg, kvList) + l.write(prefix, args) +} + +func (l fnlogger) Error(err error, msg string, kvList ...interface{}) { + prefix, args := l.FormatError(err, msg, kvList) + l.write(prefix, args) +} + +func (l fnlogger) GetUnderlying() func(prefix, args string) { + return l.write +} + +// Assert conformance to the interfaces. +var _ logr.LogSink = &fnlogger{} +var _ logr.CallDepthLogSink = &fnlogger{} +var _ Underlier = &fnlogger{} + +// NewFormatter constructs a Formatter which emits a JSON-like key=value format. +func NewFormatter(opts Options) Formatter { + return newFormatter(opts, outputKeyValue) +} + +// NewFormatterJSON constructs a Formatter which emits strict JSON. +func NewFormatterJSON(opts Options) Formatter { + return newFormatter(opts, outputJSON) +} + +// Defaults for Options. +const defaultTimestampFormat = "2006-01-02 15:04:05.000000" +const defaultMaxLogDepth = 16 + +func newFormatter(opts Options, outfmt outputFormat) Formatter { + if opts.TimestampFormat == "" { + opts.TimestampFormat = defaultTimestampFormat + } + if opts.MaxLogDepth == 0 { + opts.MaxLogDepth = defaultMaxLogDepth + } + f := Formatter{ + outputFormat: outfmt, + prefix: "", + values: nil, + depth: 0, + opts: opts, + } + return f +} + +// Formatter is an opaque struct which can be embedded in a LogSink +// implementation. It should be constructed with NewFormatter. Some of +// its methods directly implement logr.LogSink. +type Formatter struct { + outputFormat outputFormat + prefix string + values []interface{} + valuesStr string + depth int + opts Options +} + +// outputFormat indicates which outputFormat to use. +type outputFormat int + +const ( + // outputKeyValue emits a JSON-like key=value format, but not strict JSON. + outputKeyValue outputFormat = iota + // outputJSON emits strict JSON. + outputJSON +) + +// PseudoStruct is a list of key-value pairs that gets logged as a struct. +type PseudoStruct []interface{} + +// render produces a log line, ready to use. +func (f Formatter) render(builtins, args []interface{}) string { + // Empirically bytes.Buffer is faster than strings.Builder for this. + buf := bytes.NewBuffer(make([]byte, 0, 1024)) + if f.outputFormat == outputJSON { + buf.WriteByte('{') + } + vals := builtins + if hook := f.opts.RenderBuiltinsHook; hook != nil { + vals = hook(f.sanitize(vals)) + } + f.flatten(buf, vals, false, false) // keys are ours, no need to escape + continuing := len(builtins) > 0 + if len(f.valuesStr) > 0 { + if continuing { + if f.outputFormat == outputJSON { + buf.WriteByte(',') + } else { + buf.WriteByte(' ') + } + } + continuing = true + buf.WriteString(f.valuesStr) + } + vals = args + if hook := f.opts.RenderArgsHook; hook != nil { + vals = hook(f.sanitize(vals)) + } + f.flatten(buf, vals, continuing, true) // escape user-provided keys + if f.outputFormat == outputJSON { + buf.WriteByte('}') + } + return buf.String() +} + +// flatten renders a list of key-value pairs into a buffer. If continuing is +// true, it assumes that the buffer has previous values and will emit a +// separator (which depends on the output format) before the first pair it +// writes. If escapeKeys is true, the keys are assumed to have +// non-JSON-compatible characters in them and must be evaluated for escapes. +// +// This function returns a potentially modified version of kvList, which +// ensures that there is a value for every key (adding a value if needed) and +// that each key is a string (substituting a key if needed). +func (f Formatter) flatten(buf *bytes.Buffer, kvList []interface{}, continuing bool, escapeKeys bool) []interface{} { + // This logic overlaps with sanitize() but saves one type-cast per key, + // which can be measurable. + if len(kvList)%2 != 0 { + kvList = append(kvList, noValue) + } + for i := 0; i < len(kvList); i += 2 { + k, ok := kvList[i].(string) + if !ok { + k = f.nonStringKey(kvList[i]) + kvList[i] = k + } + v := kvList[i+1] + + if i > 0 || continuing { + if f.outputFormat == outputJSON { + buf.WriteByte(',') + } else { + // In theory the format could be something we don't understand. In + // practice, we control it, so it won't be. + buf.WriteByte(' ') + } + } + + if escapeKeys { + buf.WriteString(prettyString(k)) + } else { + // this is faster + buf.WriteByte('"') + buf.WriteString(k) + buf.WriteByte('"') + } + if f.outputFormat == outputJSON { + buf.WriteByte(':') + } else { + buf.WriteByte('=') + } + buf.WriteString(f.pretty(v)) + } + return kvList +} + +func (f Formatter) pretty(value interface{}) string { + return f.prettyWithFlags(value, 0, 0) +} + +const ( + flagRawStruct = 0x1 // do not print braces on structs +) + +// TODO: This is not fast. Most of the overhead goes here. +func (f Formatter) prettyWithFlags(value interface{}, flags uint32, depth int) string { + if depth > f.opts.MaxLogDepth { + return `""` + } + + // Handle types that take full control of logging. + if v, ok := value.(logr.Marshaler); ok { + // Replace the value with what the type wants to get logged. + // That then gets handled below via reflection. + value = v.MarshalLog() + } + + // Handle types that want to format themselves. + switch v := value.(type) { + case fmt.Stringer: + value = v.String() + case error: + value = v.Error() + } + + // Handling the most common types without reflect is a small perf win. + switch v := value.(type) { + case bool: + return strconv.FormatBool(v) + case string: + return prettyString(v) + case int: + return strconv.FormatInt(int64(v), 10) + case int8: + return strconv.FormatInt(int64(v), 10) + case int16: + return strconv.FormatInt(int64(v), 10) + case int32: + return strconv.FormatInt(int64(v), 10) + case int64: + return strconv.FormatInt(int64(v), 10) + case uint: + return strconv.FormatUint(uint64(v), 10) + case uint8: + return strconv.FormatUint(uint64(v), 10) + case uint16: + return strconv.FormatUint(uint64(v), 10) + case uint32: + return strconv.FormatUint(uint64(v), 10) + case uint64: + return strconv.FormatUint(v, 10) + case uintptr: + return strconv.FormatUint(uint64(v), 10) + case float32: + return strconv.FormatFloat(float64(v), 'f', -1, 32) + case float64: + return strconv.FormatFloat(v, 'f', -1, 64) + case complex64: + return `"` + strconv.FormatComplex(complex128(v), 'f', -1, 64) + `"` + case complex128: + return `"` + strconv.FormatComplex(v, 'f', -1, 128) + `"` + case PseudoStruct: + buf := bytes.NewBuffer(make([]byte, 0, 1024)) + v = f.sanitize(v) + if flags&flagRawStruct == 0 { + buf.WriteByte('{') + } + for i := 0; i < len(v); i += 2 { + if i > 0 { + buf.WriteByte(',') + } + // arbitrary keys might need escaping + buf.WriteString(prettyString(v[i].(string))) + buf.WriteByte(':') + buf.WriteString(f.prettyWithFlags(v[i+1], 0, depth+1)) + } + if flags&flagRawStruct == 0 { + buf.WriteByte('}') + } + return buf.String() + } + + buf := bytes.NewBuffer(make([]byte, 0, 256)) + t := reflect.TypeOf(value) + if t == nil { + return "null" + } + v := reflect.ValueOf(value) + switch t.Kind() { + case reflect.Bool: + return strconv.FormatBool(v.Bool()) + case reflect.String: + return prettyString(v.String()) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return strconv.FormatInt(int64(v.Int()), 10) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return strconv.FormatUint(uint64(v.Uint()), 10) + case reflect.Float32: + return strconv.FormatFloat(float64(v.Float()), 'f', -1, 32) + case reflect.Float64: + return strconv.FormatFloat(v.Float(), 'f', -1, 64) + case reflect.Complex64: + return `"` + strconv.FormatComplex(complex128(v.Complex()), 'f', -1, 64) + `"` + case reflect.Complex128: + return `"` + strconv.FormatComplex(v.Complex(), 'f', -1, 128) + `"` + case reflect.Struct: + if flags&flagRawStruct == 0 { + buf.WriteByte('{') + } + for i := 0; i < t.NumField(); i++ { + fld := t.Field(i) + if fld.PkgPath != "" { + // reflect says this field is only defined for non-exported fields. + continue + } + if !v.Field(i).CanInterface() { + // reflect isn't clear exactly what this means, but we can't use it. + continue + } + name := "" + omitempty := false + if tag, found := fld.Tag.Lookup("json"); found { + if tag == "-" { + continue + } + if comma := strings.Index(tag, ","); comma != -1 { + if n := tag[:comma]; n != "" { + name = n + } + rest := tag[comma:] + if strings.Contains(rest, ",omitempty,") || strings.HasSuffix(rest, ",omitempty") { + omitempty = true + } + } else { + name = tag + } + } + if omitempty && isEmpty(v.Field(i)) { + continue + } + if i > 0 { + buf.WriteByte(',') + } + if fld.Anonymous && fld.Type.Kind() == reflect.Struct && name == "" { + buf.WriteString(f.prettyWithFlags(v.Field(i).Interface(), flags|flagRawStruct, depth+1)) + continue + } + if name == "" { + name = fld.Name + } + // field names can't contain characters which need escaping + buf.WriteByte('"') + buf.WriteString(name) + buf.WriteByte('"') + buf.WriteByte(':') + buf.WriteString(f.prettyWithFlags(v.Field(i).Interface(), 0, depth+1)) + } + if flags&flagRawStruct == 0 { + buf.WriteByte('}') + } + return buf.String() + case reflect.Slice, reflect.Array: + buf.WriteByte('[') + for i := 0; i < v.Len(); i++ { + if i > 0 { + buf.WriteByte(',') + } + e := v.Index(i) + buf.WriteString(f.prettyWithFlags(e.Interface(), 0, depth+1)) + } + buf.WriteByte(']') + return buf.String() + case reflect.Map: + buf.WriteByte('{') + // This does not sort the map keys, for best perf. + it := v.MapRange() + i := 0 + for it.Next() { + if i > 0 { + buf.WriteByte(',') + } + // If a map key supports TextMarshaler, use it. + keystr := "" + if m, ok := it.Key().Interface().(encoding.TextMarshaler); ok { + txt, err := m.MarshalText() + if err != nil { + keystr = fmt.Sprintf("", err.Error()) + } else { + keystr = string(txt) + } + keystr = prettyString(keystr) + } else { + // prettyWithFlags will produce already-escaped values + keystr = f.prettyWithFlags(it.Key().Interface(), 0, depth+1) + if t.Key().Kind() != reflect.String { + // JSON only does string keys. Unlike Go's standard JSON, we'll + // convert just about anything to a string. + keystr = prettyString(keystr) + } + } + buf.WriteString(keystr) + buf.WriteByte(':') + buf.WriteString(f.prettyWithFlags(it.Value().Interface(), 0, depth+1)) + i++ + } + buf.WriteByte('}') + return buf.String() + case reflect.Ptr, reflect.Interface: + if v.IsNil() { + return "null" + } + return f.prettyWithFlags(v.Elem().Interface(), 0, depth) + } + return fmt.Sprintf(`""`, t.Kind().String()) +} + +func prettyString(s string) string { + // Avoid escaping (which does allocations) if we can. + if needsEscape(s) { + return strconv.Quote(s) + } + b := bytes.NewBuffer(make([]byte, 0, 1024)) + b.WriteByte('"') + b.WriteString(s) + b.WriteByte('"') + return b.String() +} + +// needsEscape determines whether the input string needs to be escaped or not, +// without doing any allocations. +func needsEscape(s string) bool { + for _, r := range s { + if !strconv.IsPrint(r) || r == '\\' || r == '"' { + return true + } + } + return false +} + +func isEmpty(v reflect.Value) bool { + switch v.Kind() { + case reflect.Array, reflect.Map, reflect.Slice, reflect.String: + return v.Len() == 0 + case reflect.Bool: + return !v.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Complex64, reflect.Complex128: + return v.Complex() == 0 + case reflect.Interface, reflect.Ptr: + return v.IsNil() + } + return false +} + +// Caller represents the original call site for a log line, after considering +// logr.Logger.WithCallDepth and logr.Logger.WithCallStackHelper. The File and +// Line fields will always be provided, while the Func field is optional. +// Users can set the render hook fields in Options to examine logged key-value +// pairs, one of which will be {"caller", Caller} if the Options.LogCaller +// field is enabled for the given MessageClass. +type Caller struct { + // File is the basename of the file for this call site. + File string `json:"file"` + // Line is the line number in the file for this call site. + Line int `json:"line"` + // Func is the function name for this call site, or empty if + // Options.LogCallerFunc is not enabled. + Func string `json:"function,omitempty"` +} + +func (f Formatter) caller() Caller { + // +1 for this frame, +1 for Info/Error. + pc, file, line, ok := runtime.Caller(f.depth + 2) + if !ok { + return Caller{"", 0, ""} + } + fn := "" + if f.opts.LogCallerFunc { + if fp := runtime.FuncForPC(pc); fp != nil { + fn = fp.Name() + } + } + + return Caller{filepath.Base(file), line, fn} +} + +const noValue = "" + +func (f Formatter) nonStringKey(v interface{}) string { + return fmt.Sprintf("", f.snippet(v)) +} + +// snippet produces a short snippet string of an arbitrary value. +func (f Formatter) snippet(v interface{}) string { + const snipLen = 16 + + snip := f.pretty(v) + if len(snip) > snipLen { + snip = snip[:snipLen] + } + return snip +} + +// sanitize ensures that a list of key-value pairs has a value for every key +// (adding a value if needed) and that each key is a string (substituting a key +// if needed). +func (f Formatter) sanitize(kvList []interface{}) []interface{} { + if len(kvList)%2 != 0 { + kvList = append(kvList, noValue) + } + for i := 0; i < len(kvList); i += 2 { + _, ok := kvList[i].(string) + if !ok { + kvList[i] = f.nonStringKey(kvList[i]) + } + } + return kvList +} + +// Init configures this Formatter from runtime info, such as the call depth +// imposed by logr itself. +// Note that this receiver is a pointer, so depth can be saved. +func (f *Formatter) Init(info logr.RuntimeInfo) { + f.depth += info.CallDepth +} + +// Enabled checks whether an info message at the given level should be logged. +func (f Formatter) Enabled(level int) bool { + return level <= f.opts.Verbosity +} + +// GetDepth returns the current depth of this Formatter. This is useful for +// implementations which do their own caller attribution. +func (f Formatter) GetDepth() int { + return f.depth +} + +// FormatInfo renders an Info log message into strings. The prefix will be +// empty when no names were set (via AddNames), or when the output is +// configured for JSON. +func (f Formatter) FormatInfo(level int, msg string, kvList []interface{}) (prefix, argsStr string) { + args := make([]interface{}, 0, 64) // using a constant here impacts perf + prefix = f.prefix + if f.outputFormat == outputJSON { + args = append(args, "logger", prefix) + prefix = "" + } + if f.opts.LogTimestamp { + args = append(args, "ts", time.Now().Format(f.opts.TimestampFormat)) + } + if policy := f.opts.LogCaller; policy == All || policy == Info { + args = append(args, "caller", f.caller()) + } + args = append(args, "level", level, "msg", msg) + return prefix, f.render(args, kvList) +} + +// FormatError renders an Error log message into strings. The prefix will be +// empty when no names were set (via AddNames), or when the output is +// configured for JSON. +func (f Formatter) FormatError(err error, msg string, kvList []interface{}) (prefix, argsStr string) { + args := make([]interface{}, 0, 64) // using a constant here impacts perf + prefix = f.prefix + if f.outputFormat == outputJSON { + args = append(args, "logger", prefix) + prefix = "" + } + if f.opts.LogTimestamp { + args = append(args, "ts", time.Now().Format(f.opts.TimestampFormat)) + } + if policy := f.opts.LogCaller; policy == All || policy == Error { + args = append(args, "caller", f.caller()) + } + args = append(args, "msg", msg) + var loggableErr interface{} + if err != nil { + loggableErr = err.Error() + } + args = append(args, "error", loggableErr) + return f.prefix, f.render(args, kvList) +} + +// AddName appends the specified name. funcr uses '/' characters to separate +// name elements. Callers should not pass '/' in the provided name string, but +// this library does not actually enforce that. +func (f *Formatter) AddName(name string) { + if len(f.prefix) > 0 { + f.prefix += "/" + } + f.prefix += name +} + +// AddValues adds key-value pairs to the set of saved values to be logged with +// each log line. +func (f *Formatter) AddValues(kvList []interface{}) { + // Three slice args forces a copy. + n := len(f.values) + f.values = append(f.values[:n:n], kvList...) + + vals := f.values + if hook := f.opts.RenderValuesHook; hook != nil { + vals = hook(f.sanitize(vals)) + } + + // Pre-render values, so we don't have to do it on each Info/Error call. + buf := bytes.NewBuffer(make([]byte, 0, 1024)) + f.flatten(buf, vals, false, true) // escape user-provided keys + f.valuesStr = buf.String() +} + +// AddCallDepth increases the number of stack-frames to skip when attributing +// the log line to a file and line. +func (f *Formatter) AddCallDepth(depth int) { + f.depth += depth +} diff --git a/vendor/github.com/go-logr/logr/logr.go b/vendor/github.com/go-logr/logr/logr.go index 44cd398c9f..c05482a203 100644 --- a/vendor/github.com/go-logr/logr/logr.go +++ b/vendor/github.com/go-logr/logr/logr.go @@ -43,7 +43,9 @@ limitations under the License. // // Info() and Error() are very similar, but they are separate methods so that // LogSink implementations can choose to do things like attach additional -// information (such as stack traces) on calls to Error(). +// information (such as stack traces) on calls to Error(). Error() messages are +// always logged, regardless of the current verbosity. If there is no error +// instance available, passing nil is valid. // // Verbosity // @@ -53,6 +55,7 @@ limitations under the License. // Log-lines with V-levels that are not enabled (as per the LogSink) will not // be written. Level V(0) is the default, and logger.V(0).Info() has the same // meaning as logger.Info(). Negative V-levels have the same meaning as V(0). +// Error messages do not have a verbosity level and are always logged. // // Where we might have written: // if flVerbose >= 2 { @@ -253,11 +256,13 @@ func (l Logger) Info(msg string, keysAndValues ...interface{}) { // Error logs an error, with the given message and key/value pairs as context. // It functions similarly to Info, but may have unique behavior, and should be // preferred for logging errors (see the package documentations for more -// information). +// information). The log message will always be emitted, regardless of +// verbosity level. // // The msg argument should be used to add context to any underlying error, // while the err argument should be used to attach the actual error that -// triggered this log line, if present. +// triggered this log line, if present. The err parameter is optional +// and nil may be passed instead of an error instance. func (l Logger) Error(err error, msg string, keysAndValues ...interface{}) { if withHelper, ok := l.sink.(CallStackHelperLogSink); ok { withHelper.GetCallStackHelper()() diff --git a/vendor/github.com/go-logr/stdr/LICENSE b/vendor/github.com/go-logr/stdr/LICENSE new file mode 100644 index 0000000000..261eeb9e9f --- /dev/null +++ b/vendor/github.com/go-logr/stdr/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/vendor/github.com/go-logr/stdr/README.md b/vendor/github.com/go-logr/stdr/README.md new file mode 100644 index 0000000000..5158667890 --- /dev/null +++ b/vendor/github.com/go-logr/stdr/README.md @@ -0,0 +1,6 @@ +# Minimal Go logging using logr and Go's standard library + +[![Go Reference](https://pkg.go.dev/badge/github.com/go-logr/stdr.svg)](https://pkg.go.dev/github.com/go-logr/stdr) + +This package implements the [logr interface](https://github.com/go-logr/logr) +in terms of Go's standard log package(https://pkg.go.dev/log). diff --git a/vendor/github.com/go-logr/stdr/go.mod b/vendor/github.com/go-logr/stdr/go.mod new file mode 100644 index 0000000000..a59b113ef1 --- /dev/null +++ b/vendor/github.com/go-logr/stdr/go.mod @@ -0,0 +1,5 @@ +module github.com/go-logr/stdr + +go 1.16 + +require github.com/go-logr/logr v1.2.2 diff --git a/vendor/github.com/go-logr/stdr/go.sum b/vendor/github.com/go-logr/stdr/go.sum new file mode 100644 index 0000000000..f985ced204 --- /dev/null +++ b/vendor/github.com/go-logr/stdr/go.sum @@ -0,0 +1,2 @@ +github.com/go-logr/logr v1.2.2 h1:ahHml/yUpnlb96Rp8HCvtYVPY8ZYpxq3g7UYchIYwbs= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= diff --git a/vendor/github.com/go-logr/stdr/stdr.go b/vendor/github.com/go-logr/stdr/stdr.go new file mode 100644 index 0000000000..93a8aab51b --- /dev/null +++ b/vendor/github.com/go-logr/stdr/stdr.go @@ -0,0 +1,170 @@ +/* +Copyright 2019 The logr 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 stdr implements github.com/go-logr/logr.Logger in terms of +// Go's standard log package. +package stdr + +import ( + "log" + "os" + + "github.com/go-logr/logr" + "github.com/go-logr/logr/funcr" +) + +// The global verbosity level. See SetVerbosity(). +var globalVerbosity int + +// SetVerbosity sets the global level against which all info logs will be +// compared. If this is greater than or equal to the "V" of the logger, the +// message will be logged. A higher value here means more logs will be written. +// The previous verbosity value is returned. This is not concurrent-safe - +// callers must be sure to call it from only one goroutine. +func SetVerbosity(v int) int { + old := globalVerbosity + globalVerbosity = v + return old +} + +// New returns a logr.Logger which is implemented by Go's standard log package, +// or something like it. If std is nil, this will use a default logger +// instead. +// +// Example: stdr.New(log.New(os.Stderr, "", log.LstdFlags|log.Lshortfile))) +func New(std StdLogger) logr.Logger { + return NewWithOptions(std, Options{}) +} + +// NewWithOptions returns a logr.Logger which is implemented by Go's standard +// log package, or something like it. See New for details. +func NewWithOptions(std StdLogger, opts Options) logr.Logger { + if std == nil { + // Go's log.Default() is only available in 1.16 and higher. + std = log.New(os.Stderr, "", log.LstdFlags) + } + + if opts.Depth < 0 { + opts.Depth = 0 + } + + fopts := funcr.Options{ + LogCaller: funcr.MessageClass(opts.LogCaller), + } + + sl := &logger{ + Formatter: funcr.NewFormatter(fopts), + std: std, + } + + // For skipping our own logger.Info/Error. + sl.Formatter.AddCallDepth(1 + opts.Depth) + + return logr.New(sl) +} + +// Options carries parameters which influence the way logs are generated. +type Options struct { + // Depth biases the assumed number of call frames to the "true" caller. + // This is useful when the calling code calls a function which then calls + // stdr (e.g. a logging shim to another API). Values less than zero will + // be treated as zero. + Depth int + + // LogCaller tells stdr to add a "caller" key to some or all log lines. + // Go's log package has options to log this natively, too. + LogCaller MessageClass + + // TODO: add an option to log the date/time +} + +// MessageClass indicates which category or categories of messages to consider. +type MessageClass int + +const ( + // None ignores all message classes. + None MessageClass = iota + // All considers all message classes. + All + // Info only considers info messages. + Info + // Error only considers error messages. + Error +) + +// StdLogger is the subset of the Go stdlib log.Logger API that is needed for +// this adapter. +type StdLogger interface { + // Output is the same as log.Output and log.Logger.Output. + Output(calldepth int, logline string) error +} + +type logger struct { + funcr.Formatter + std StdLogger +} + +var _ logr.LogSink = &logger{} +var _ logr.CallDepthLogSink = &logger{} + +func (l logger) Enabled(level int) bool { + return globalVerbosity >= level +} + +func (l logger) Info(level int, msg string, kvList ...interface{}) { + prefix, args := l.FormatInfo(level, msg, kvList) + if prefix != "" { + args = prefix + ": " + args + } + _ = l.std.Output(l.Formatter.GetDepth()+1, args) +} + +func (l logger) Error(err error, msg string, kvList ...interface{}) { + prefix, args := l.FormatError(err, msg, kvList) + if prefix != "" { + args = prefix + ": " + args + } + _ = l.std.Output(l.Formatter.GetDepth()+1, args) +} + +func (l logger) WithName(name string) logr.LogSink { + l.Formatter.AddName(name) + return &l +} + +func (l logger) WithValues(kvList ...interface{}) logr.LogSink { + l.Formatter.AddValues(kvList) + return &l +} + +func (l logger) WithCallDepth(depth int) logr.LogSink { + l.Formatter.AddCallDepth(depth) + return &l +} + +// Underlier exposes access to the underlying logging implementation. Since +// callers only have a logr.Logger, they have to know which implementation is +// in use, so this interface is less of an abstraction and more of way to test +// type conversion. +type Underlier interface { + GetUnderlying() StdLogger +} + +// GetUnderlying returns the StdLogger underneath this logger. Since StdLogger +// is itself an interface, the result may or may not be a Go log.Logger. +func (l logger) GetUnderlying() StdLogger { + return l.std +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 191085cbaa..19118adc9c 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -63,8 +63,13 @@ github.com/evanphx/json-patch/v5 # github.com/fsnotify/fsnotify v1.5.1 ## explicit github.com/fsnotify/fsnotify -# github.com/go-logr/logr v1.2.0 +# github.com/go-logr/logr v1.2.2 +## explicit github.com/go-logr/logr +github.com/go-logr/logr/funcr +# github.com/go-logr/stdr v1.2.2 +## explicit +github.com/go-logr/stdr # github.com/go-training/helloworld v0.0.0-20200225145412-ba5f4379d78b ## explicit github.com/go-training/helloworld