diff --git a/go.mod b/go.mod index 384351ab5..8fad57c51 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ toolchain go1.21.0 require ( code.cloudfoundry.org/credhub-cli v0.0.0-20231204140658-79b9df0d93d4 - code.cloudfoundry.org/lager/v3 v3.0.2 + code.cloudfoundry.org/lager/v3 v3.0.3 github.com/blang/semver/v4 v4.0.0 github.com/cloudfoundry-community/go-uaa v0.3.2 github.com/cloudfoundry/bosh-cli/v7 v7.5.0 @@ -47,7 +47,7 @@ require ( github.com/jpillora/backoff v1.0.0 // indirect github.com/leodido/go-urn v1.2.4 // indirect github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect - github.com/openzipkin/zipkin-go v0.4.1 // indirect + github.com/openzipkin/zipkin-go v0.4.2 // indirect github.com/pivotal-cf/paraphernalia v0.0.0-20180203224945-a64ae2051c20 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect diff --git a/go.sum b/go.sum index ca90d2423..ac046a4ee 100644 --- a/go.sum +++ b/go.sum @@ -3,8 +3,8 @@ code.cloudfoundry.org/clock v1.1.0 h1:XLzC6W3Ah/Y7ht1rmZ6+QfPdt1iGWEAAtIZXgiaj57 code.cloudfoundry.org/clock v1.1.0/go.mod h1:yA3fxddT9RINQL2XHS7PS+OXxKCGhfrZmlNUCIM6AKo= code.cloudfoundry.org/credhub-cli v0.0.0-20231204140658-79b9df0d93d4 h1:WeXdrMb3kfcdvtq0p7T5jZEHZrbfmgW1tCMOCTtJmfI= code.cloudfoundry.org/credhub-cli v0.0.0-20231204140658-79b9df0d93d4/go.mod h1:ZJVWekRF6nXKSND+7FS8l40+bKX1wriNpmfX85x/LYo= -code.cloudfoundry.org/lager/v3 v3.0.2 h1:H0dcQY+814G1Ea0e5K/AMaMpcr+Pe5Iv+AALJEwrP9U= -code.cloudfoundry.org/lager/v3 v3.0.2/go.mod h1:zA6tOIWhr5uZUez+PGpdfBHDWQOfhOrr0cgKDagZPwk= +code.cloudfoundry.org/lager/v3 v3.0.3 h1:/UTmadZfIaKuT/whEinSxK1mzRfNu1uPfvjFfGqiwzM= +code.cloudfoundry.org/lager/v3 v3.0.3/go.mod h1:Zn5q1SrIuuHjEUE7xerMKt3ztunrJQCZETAo7rV0CH8= code.cloudfoundry.org/tlsconfig v0.0.0-20231017135636-f0e44068c22f h1:5OUq3fp3kg9ztdzVX7V7SvSZ06rKWhG3CybVmXsj8O8= code.cloudfoundry.org/tlsconfig v0.0.0-20231017135636-f0e44068c22f/go.mod h1:C8SxvGRSutmgzV2FxH8Zwqz2Q8HsaAITQRQFKhlDzPw= filippo.io/edwards25519 v1.0.0-rc.1 h1:m0VOOB23frXZvAOK44usCgLWvtsxIoMCTBGJZlpmGfU= @@ -115,8 +115,8 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8= github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= -github.com/openzipkin/zipkin-go v0.4.1 h1:kNd/ST2yLLWhaWrkgchya40TJabe8Hioj9udfPcEO5A= -github.com/openzipkin/zipkin-go v0.4.1/go.mod h1:qY0VqDSN1pOBN94dBc6w2GJlWLiovAyg7Qt6/I9HecM= +github.com/openzipkin/zipkin-go v0.4.2 h1:zjqfqHjUpPmB3c1GlCvvgsM1G4LkvqQbBDueDOCg/jA= +github.com/openzipkin/zipkin-go v0.4.2/go.mod h1:ZeVkFjuuBiSy13y8vpSDCjMi9GoI3hPpCJSBx/EYFhY= github.com/pborman/uuid v1.2.1 h1:+ZZIw58t/ozdjRaXh/3awHfmWRbzYxJoAdNJxe/3pvw= github.com/pborman/uuid v1.2.1/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pivotal-cf/brokerapi/v10 v10.1.1 h1:ZVNgqnS7iA5FP6Z2azRPrcnj7k1Pwy/0C+n9zPGWtZk= diff --git a/vendor/code.cloudfoundry.org/lager/v3/README.md b/vendor/code.cloudfoundry.org/lager/v3/README.md index 194b33860..9a4248ade 100644 --- a/vendor/code.cloudfoundry.org/lager/v3/README.md +++ b/vendor/code.cloudfoundry.org/lager/v3/README.md @@ -17,6 +17,30 @@ import ( logger := lager.NewLogger("my-app") ``` +### Lager and [`log/slog`](https://pkg.go.dev/log/slog) +Lager was written long before Go 1.21 introduced structured logging in the standard library. +There are some wrapper functions for interoperability between Lager and `slog`, +which are only available when using Go 1.21 and higher. + +Lager can be used as an [`slog.Handler`](https://pkg.go.dev/log/slog#Handler) using the `NewHandler()` function: + +```go +func codeThatAcceptsSlog(l *slog.Logger) { ... } + +lagerLogger := lager.NewLogger("my-lager-logger") + +codeThatAcceptsSlog(slog.New(lager.NewHandler(lagerLogger))) +``` + +An `slog.Logger` can be used as a Lager `Sink` using the `NewSlogSink()` function: +```go +var *slog.Logger l = codeThatReturnsSlog() + +lagerLogger := lager.NewLogger("my-lager-logger") + +lagerLogger.RegisterSink(lager.NewSlogSink(l)) +``` + ### Sinks Lager can write logs to a variety of destinations. You can specify the destinations diff --git a/vendor/code.cloudfoundry.org/lager/v3/handler.go b/vendor/code.cloudfoundry.org/lager/v3/handler.go new file mode 100644 index 000000000..2cdaf7c32 --- /dev/null +++ b/vendor/code.cloudfoundry.org/lager/v3/handler.go @@ -0,0 +1,162 @@ +//go:build go1.21 + +package lager + +import ( + "context" + "fmt" + "log/slog" +) + +// NewHandler wraps the logger as a slog.Handler +// The supplied Logger must be a lager.logger +// type created by lager.NewLogger(), otherwise +// it panics. +// +// Note the following log level conversions: +// +// slog.LevelDebug -> lager.DEBUG +// slog.LevelError -> lager.ERROR +// slog.LevelError -> lager.FATAL +// default -> lager.INFO +func NewHandler(l Logger) slog.Handler { + switch ll := l.(type) { + case *logger: + return &handler{logger: ll} + default: + panic("lager.Logger must be an instance of lager.logger") + } +} + +// Type decorator is used to decorate the attributes with groups and more attributes +type decorator func(map[string]any) map[string]any + +// Type handler is a slog.Handler that wraps a lager logger. +// It uses the logger concrete type rather than the Logger interface +// because it uses methods not available on the interface. +type handler struct { + logger *logger + decorators []decorator +} + +// Enabled always returns true +func (h *handler) Enabled(_ context.Context, _ slog.Level) bool { + return true +} + +// Handle converts a slog.Record into a lager.LogFormat and passes it to every Sink +func (h *handler) Handle(_ context.Context, r slog.Record) error { + log := LogFormat{ + time: r.Time, + Timestamp: formatTimestamp(r.Time), + Source: h.logger.component, + Message: fmt.Sprintf("%s.%s", h.logger.task, r.Message), + LogLevel: toLogLevel(r.Level), + Data: h.logger.baseData(h.decorate(attrFromRecord(r))), + } + + for _, sink := range h.logger.sinks { + sink.Log(log) + } + + return nil +} + +// WithAttrs returns a new slog.Handler which always adds the specified attributes +func (h *handler) WithAttrs(attrs []slog.Attr) slog.Handler { + return &handler{ + logger: h.logger, + decorators: append(h.decorators, attrDecorator(attrs)), + } +} + +// WithGroup returns a new slog.Handler which always logs attributes in the specified group +func (h *handler) WithGroup(name string) slog.Handler { + return &handler{ + logger: h.logger, + decorators: append(h.decorators, groupDecorator(name)), + } +} + +// decorate will decorate a body using the decorators that have been defined +func (h *handler) decorate(body map[string]any) map[string]any { + for i := len(h.decorators) - 1; i >= 0; i-- { // reverse iteration + body = h.decorators[i](body) + } + return body +} + +// attrDecorator returns a decorator for the specified attributes +func attrDecorator(attrs []slog.Attr) decorator { + return func(body map[string]any) map[string]any { + if body == nil { + body = make(map[string]any) + } + processAttrs(attrs, body) + return body + } +} + +// groupDecorator returns a decorator for the specified group name +func groupDecorator(group string) decorator { + return func(body map[string]any) map[string]any { + switch len(body) { + case 0: + return nil + default: + return map[string]any{group: body} + } + } +} + +// attrFromRecord extracts and processes the attributes from a record +func attrFromRecord(r slog.Record) map[string]any { + if r.NumAttrs() == 0 { + return nil + } + + body := make(map[string]any, r.NumAttrs()) + r.Attrs(func(attr slog.Attr) bool { + processAttr(attr, body) + return true + }) + + return body +} + +// processAttrs calls processAttr() for each attribute +func processAttrs(attrs []slog.Attr, target map[string]any) { + for _, attr := range attrs { + processAttr(attr, target) + } +} + +// processAttr adds the attribute to the target with appropriate transformations +func processAttr(attr slog.Attr, target map[string]any) { + rv := attr.Value.Resolve() + + switch { + case rv.Kind() == slog.KindGroup && attr.Key != "": + nt := make(map[string]any) + processAttrs(attr.Value.Group(), nt) + target[attr.Key] = nt + case rv.Kind() == slog.KindGroup && attr.Key == "": + processAttrs(attr.Value.Group(), target) + case attr.Key == "": + // skip + default: + target[attr.Key] = rv.Any() + } +} + +// toLogLevel converts from slog levels to lager levels +func toLogLevel(l slog.Level) LogLevel { + switch l { + case slog.LevelDebug: + return DEBUG + case slog.LevelError, slog.LevelWarn: + return ERROR + default: + return INFO + } +} diff --git a/vendor/code.cloudfoundry.org/lager/v3/slog_sink.go b/vendor/code.cloudfoundry.org/lager/v3/slog_sink.go new file mode 100644 index 000000000..095e16a60 --- /dev/null +++ b/vendor/code.cloudfoundry.org/lager/v3/slog_sink.go @@ -0,0 +1,63 @@ +//go:build go1.21 + +package lager + +import ( + "context" + "log/slog" +) + +// Type slogSink wraps an slog.Logger as a Sink +type slogSink struct { + logger *slog.Logger +} + +// NewSlogSink wraps a slog.Logger as a lager Sink +// This allows code using slog to integrate with code that uses lager +// Note the following log level conversions: +// +// lager.DEBUG -> slog.LevelDebug +// lager.ERROR -> slog.LevelError +// lager.FATAL -> slog.LevelError +// default -> slog.LevelInfo +func NewSlogSink(l *slog.Logger) Sink { + return &slogSink{logger: l} +} + +// Log exists to implement the lager.Sink interface. +func (l *slogSink) Log(f LogFormat) { + // For lager.Error() and lager.Fatal() the error (and stacktrace) are already in f.Data + r := slog.NewRecord(f.time, toSlogLevel(f.LogLevel), f.Message, 0) + r.AddAttrs(toAttr(f.Data)...) + + // By calling the handler directly we can pass through the original timestamp, + // whereas calling a method on the logger would generate a new timestamp + l.logger.Handler().Handle(context.Background(), r) +} + +// toAttr converts a lager.Data into []slog.Attr +func toAttr(d Data) []slog.Attr { + l := len(d) + if l == 0 { + return nil + } + + attr := make([]slog.Attr, 0, l) + for k, v := range d { + attr = append(attr, slog.Any(k, v)) + } + + return attr +} + +// toSlogLevel converts lager log levels to slog levels +func toSlogLevel(l LogLevel) slog.Level { + switch l { + case DEBUG: + return slog.LevelDebug + case ERROR, FATAL: + return slog.LevelError + default: + return slog.LevelInfo + } +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 17cab3e5c..6045f9f3f 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -9,7 +9,7 @@ code.cloudfoundry.org/credhub-cli/credhub/credentials/values code.cloudfoundry.org/credhub-cli/credhub/permissions code.cloudfoundry.org/credhub-cli/credhub/server code.cloudfoundry.org/credhub-cli/errors -# code.cloudfoundry.org/lager/v3 v3.0.2 +# code.cloudfoundry.org/lager/v3 v3.0.3 ## explicit; go 1.19 code.cloudfoundry.org/lager/v3 code.cloudfoundry.org/lager/v3/internal/truncate @@ -157,7 +157,7 @@ github.com/onsi/gomega/matchers/support/goraph/edge github.com/onsi/gomega/matchers/support/goraph/node github.com/onsi/gomega/matchers/support/goraph/util github.com/onsi/gomega/types -# github.com/openzipkin/zipkin-go v0.4.1 +# github.com/openzipkin/zipkin-go v0.4.2 ## explicit; go 1.18 github.com/openzipkin/zipkin-go/idgenerator github.com/openzipkin/zipkin-go/model