From 0aeada65cb3721447da341d679bce38d2dd3d139 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oscar=20S=C3=B6derlund?= Date: Tue, 8 Oct 2024 08:46:57 +0200 Subject: [PATCH] feat: migrate cloudrunner internals to slog This is the turning-point for the migration from zap to slog - all internal logging in cloudrunner now uses the slog APIs and the slog logging backends (TextHandler in development and JSONHandler in production). The zap APIs continue to work, and will be marked as deprecated when the slog APIs have proven stable in production for a few weeks. --- cloudconfig/config.go | 44 +++++++++++++++++++ cloudconfig/zap.go | 69 ----------------------------- cloudmux/mux.go | 32 +++++--------- cloudmux/mux_test.go | 3 -- cloudotel/errorhandler.go | 22 +++------- cloudpubsub/httphandler.go | 9 ++-- cloudserver/http.go | 7 +-- cloudserver/middleware.go | 7 +-- cloudslog/handler.go | 3 ++ cloudtrace/idhook.go | 6 +-- cloudtrace/metadata.go | 2 + cloudtrace/middleware.go | 24 ---------- cloudzap/level.go | 22 +++++++++- examples/cmd/grpc-server/main.go | 3 +- examples/cmd/http-server/main.go | 2 +- go.mod | 3 +- go.sum | 6 +-- grpcserver.go | 6 +-- httpserver.go | 3 +- options.go | 7 ++- run.go | 75 ++++++++++---------------------- 21 files changed, 137 insertions(+), 218 deletions(-) delete mode 100644 cloudconfig/zap.go diff --git a/cloudconfig/config.go b/cloudconfig/config.go index e0ca5af2..b0befdc6 100644 --- a/cloudconfig/config.go +++ b/cloudconfig/config.go @@ -3,8 +3,12 @@ package cloudconfig import ( "fmt" "io" + "log/slog" "os" "text/tabwriter" + "time" + + "go.opentelemetry.io/otel/codes" ) // envPrefix can be set during build-time to append a prefix to all environment variables loaded into the RunConfig. @@ -108,3 +112,43 @@ func (c *Config) PrintUsage(w io.Writer) { } _ = tabs.Flush() } + +// LogValue implements [slog.LogValuer]. +func (c *Config) LogValue() slog.Value { + attrs := make([]slog.Attr, 0, len(c.configSpecs)) + for _, configSpec := range c.configSpecs { + attrs = append(attrs, slog.Any(configSpec.name, fieldSpecsValue(configSpec.fieldSpecs))) + } + return slog.GroupValue(attrs...) +} + +type fieldSpecsValue []fieldSpec + +func (fsv fieldSpecsValue) LogValue() slog.Value { + attrs := make([]slog.Attr, 0, len(fsv)) + for _, fs := range fsv { + if fs.Secret { + attrs = append(attrs, slog.String(fs.Key, "")) + continue + } + switch value := fs.Value.Interface().(type) { + case time.Duration: + attrs = append(attrs, slog.Duration(fs.Key, value)) + case []codes.Code: + logValue := make([]string, 0, len(value)) + for _, code := range value { + logValue = append(logValue, code.String()) + } + attrs = append(attrs, slog.Any(fs.Key, logValue)) + case map[codes.Code]slog.Level: + logValue := make(map[string]string, len(value)) + for code, level := range value { + logValue[code.String()] = level.String() + } + attrs = append(attrs, slog.Any(fs.Key, logValue)) + default: + attrs = append(attrs, slog.Any(fs.Key, fs.Value.Interface())) + } + } + return slog.GroupValue(attrs...) +} diff --git a/cloudconfig/zap.go b/cloudconfig/zap.go deleted file mode 100644 index fedc5775..00000000 --- a/cloudconfig/zap.go +++ /dev/null @@ -1,69 +0,0 @@ -package cloudconfig - -import ( - "time" - - "go.uber.org/zap/zapcore" - "google.golang.org/grpc/codes" -) - -// MarshalLogObject implements zapcore.ObjectMarshaler. -func (c *Config) MarshalLogObject(encoder zapcore.ObjectEncoder) error { - for _, configSpec := range c.configSpecs { - if err := encoder.AddObject(configSpec.name, fieldSpecsMarshaler(configSpec.fieldSpecs)); err != nil { - return err - } - } - return nil -} - -type fieldSpecsMarshaler []fieldSpec - -// MarshalLogObject implements zapcore.ObjectMarshaler. -func (fm fieldSpecsMarshaler) MarshalLogObject(encoder zapcore.ObjectEncoder) error { - for _, fs := range fm { - if fs.Secret { - encoder.AddString(fs.Key, "") - continue - } - switch value := fs.Value.Interface().(type) { - case time.Duration: - encoder.AddDuration(fs.Key, value) - case []codes.Code: - if err := encoder.AddArray(fs.Key, codesMarshaler(value)); err != nil { - return err - } - case map[codes.Code]zapcore.Level: - if len(value) > 0 { - if err := encoder.AddObject(fs.Key, codeToLevelMarshaler(value)); err != nil { - return err - } - } - default: - if err := encoder.AddReflected(fs.Key, value); err != nil { - return err - } - } - } - return nil -} - -type codeToLevelMarshaler map[codes.Code]zapcore.Level - -// MarshalLogObject implements zapcore.ObjectMarshaler. -func (c codeToLevelMarshaler) MarshalLogObject(encoder zapcore.ObjectEncoder) error { - for code, level := range c { - encoder.AddString(code.String(), level.String()) - } - return nil -} - -type codesMarshaler []codes.Code - -// MarshalLogArray implements zapcore.ArrayMarshaler. -func (c codesMarshaler) MarshalLogArray(encoder zapcore.ArrayEncoder) error { - for _, code := range c { - encoder.AppendString(code.String()) - } - return nil -} diff --git a/cloudmux/mux.go b/cloudmux/mux.go index e3f4d8b2..617d969e 100644 --- a/cloudmux/mux.go +++ b/cloudmux/mux.go @@ -4,14 +4,13 @@ import ( "context" "errors" "fmt" + "log/slog" "net" "net/http" "strings" "time" "github.com/soheilhy/cmux" - "go.einride.tech/cloudrunner/cloudzap" - "go.uber.org/zap" "golang.org/x/sync/errgroup" "google.golang.org/grpc" ) @@ -31,54 +30,45 @@ func ServeGRPCHTTP( cmux.HTTP2MatchHeaderFieldSendSettings("content-type", "application/grpc+proto"), ) httpL := m.Match(cmux.Any()) - logger, ok := cloudzap.GetLogger(ctx) - if !ok { - logger = zap.NewNop() - } - var g errgroup.Group - // wait for context to be canceled and gracefully stop all servers. g.Go(func() error { <-ctx.Done() - - logger.Debug("stopping cmux server") + slog.DebugContext(ctx, "stopping cmux server") m.Close() - - logger.Debug("stopping HTTP server") + slog.DebugContext(ctx, "stopping HTTP server") // use a new context because the parent ctx is already canceled. ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) defer cancel() if err := httpServer.Shutdown(ctx); err != nil && !isClosedErr(err) { - logger.Warn("stopping http server", zap.Error(err)) + slog.WarnContext(ctx, "stopping http server", slog.Any("error", err)) } - - logger.Debug("stopping gRPC server") + slog.DebugContext(ctx, "stopping gRPC server") grpcServer.GracefulStop() - logger.Debug("stopped both http and grpc server") + slog.DebugContext(ctx, "stopped both http and grpc server") return nil }) g.Go(func() error { - logger.Debug("serving gRPC") + slog.DebugContext(ctx, "serving gRPC") if err := grpcServer.Serve(grpcL); err != nil && !isClosedErr(err) { return fmt.Errorf("serve gRPC: %w", err) } - logger.Debug("stopped serving gRPC") + slog.DebugContext(ctx, "stopped serving gRPC") return nil }) g.Go(func() error { - logger.Debug("serving HTTP") + slog.DebugContext(ctx, "serving HTTP") if err := httpServer.Serve(httpL); err != nil && !isClosedErr(err) { return fmt.Errorf("serve HTTP: %w", err) } - logger.Debug("stopped serving HTTP") + slog.DebugContext(ctx, "stopped serving HTTP") return nil }) if err := m.Serve(); err != nil && !isClosedErr(err) { - logger.Error("oops", zap.Error(err)) + slog.ErrorContext(ctx, "oops", slog.Any("error", err)) return fmt.Errorf("serve cmux: %w", err) } return g.Wait() diff --git a/cloudmux/mux_test.go b/cloudmux/mux_test.go index 61509b16..fc7d32d2 100644 --- a/cloudmux/mux_test.go +++ b/cloudmux/mux_test.go @@ -9,8 +9,6 @@ import ( "testing" "time" - "go.einride.tech/cloudrunner/cloudzap" - "go.uber.org/zap/zaptest" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/examples/helloworld/helloworld" @@ -118,7 +116,6 @@ func TestServe_GracefulHTTP(t *testing.T) { func newTestFixture(t *testing.T) *testFixture { t.Helper() ctx, cancel := context.WithCancel(context.Background()) - ctx = cloudzap.WithLogger(ctx, zaptest.NewLogger(t)) var lc net.ListenConfig lis, err := lc.Listen(ctx, "tcp", ":0") assert.NilError(t, err) diff --git a/cloudotel/errorhandler.go b/cloudotel/errorhandler.go index db758074..3576ce5a 100644 --- a/cloudotel/errorhandler.go +++ b/cloudotel/errorhandler.go @@ -2,27 +2,17 @@ package cloudotel import ( "context" + "log/slog" - "go.einride.tech/cloudrunner/cloudzap" "go.opentelemetry.io/otel" "go.uber.org/zap" "go.uber.org/zap/zapcore" ) // NewErrorLogger returns a new otel.ErrorHandler that logs errors using the provided logger, level and message. -func NewErrorLogger(logger *zap.Logger, level zapcore.Level, message string) otel.ErrorHandler { - return errorHandler{logger: logger, level: level, message: message} -} - -type errorHandler struct { - logger *zap.Logger - level zapcore.Level - message string -} - -// Handle implements otel.ErrorHandler. -func (e errorHandler) Handle(err error) { - e.logger.Check(e.level, e.message).Write(zap.Error(err)) +// Deprecated: This is a no-op as part of the migration from zap to slog. +func NewErrorLogger(*zap.Logger, zapcore.Level, string) otel.ErrorHandler { + return otel.ErrorHandlerFunc(func(error) {}) } // RegisterErrorHandler registers a global OpenTelemetry error handler. @@ -43,7 +33,5 @@ func handleError(ctx context.Context, err error) { // https://pkg.go.dev/go.opentelemetry.io/otel/bridge/opencensus return } - if logger, ok := cloudzap.GetLogger(ctx); ok { - logger.Warn("otel error", zap.Error(err)) - } + slog.WarnContext(ctx, "otel error", slog.Any("error", err)) } diff --git a/cloudpubsub/httphandler.go b/cloudpubsub/httphandler.go index 9fc07c31..a8a24157 100644 --- a/cloudpubsub/httphandler.go +++ b/cloudpubsub/httphandler.go @@ -3,14 +3,13 @@ package cloudpubsub import ( "context" "encoding/json" + "log/slog" "net/http" "time" "cloud.google.com/go/pubsub/apiv1/pubsubpb" "go.einride.tech/cloudrunner/cloudrequestlog" "go.einride.tech/cloudrunner/cloudstatus" - "go.einride.tech/cloudrunner/cloudzap" - "go.uber.org/zap" "google.golang.org/grpc/status" "google.golang.org/protobuf/types/known/timestamppb" ) @@ -40,7 +39,7 @@ func (fn httpHandlerFn) ServeHTTP(w http.ResponseWriter, r *http.Request) { } if err := json.NewDecoder(r.Body).Decode(&payload); err != nil { if fields, ok := cloudrequestlog.GetAdditionalFields(r.Context()); ok { - fields.Add(zap.Error(err)) + fields.Add(slog.Any("error", err)) } http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) return @@ -53,12 +52,12 @@ func (fn httpHandlerFn) ServeHTTP(w http.ResponseWriter, r *http.Request) { OrderingKey: payload.Message.OrderingKey, } if fields, ok := cloudrequestlog.GetAdditionalFields(r.Context()); ok { - fields.Add(cloudzap.ProtoMessage("pubsubMessage", &pubsubMessage)) + fields.Add(slog.Any("pubsubMessage", &pubsubMessage)) } ctx := withSubscription(r.Context(), payload.Subscription) if err := fn(ctx, &pubsubMessage); err != nil { if fields, ok := cloudrequestlog.GetAdditionalFields(r.Context()); ok { - fields.Add(zap.Error(err)) + fields.Add(slog.Any("error", err)) } code := status.Code(err) httpStatus := cloudstatus.ToHTTP(code) diff --git a/cloudserver/http.go b/cloudserver/http.go index 0b40985d..82a946ca 100644 --- a/cloudserver/http.go +++ b/cloudserver/http.go @@ -3,10 +3,11 @@ package cloudserver import ( "context" "fmt" + "log/slog" "net/http" + "runtime/debug" "go.einride.tech/cloudrunner/cloudrequestlog" - "go.uber.org/zap" ) // HTTPServer provides HTTP server middleware. @@ -17,8 +18,8 @@ func (i *Middleware) HTTPServer(next http.Handler) http.Handler { writer.WriteHeader(http.StatusInternalServerError) if fields, ok := cloudrequestlog.GetAdditionalFields(request.Context()); ok { fields.Add( - zap.Stack("stack"), - zap.Error(fmt.Errorf("recovered panic: %v", r)), + slog.String("stack", string(debug.Stack())), + slog.Any("error", fmt.Errorf("recovered panic: %v", r)), ) } } diff --git a/cloudserver/middleware.go b/cloudserver/middleware.go index b6eccd89..5ec0c480 100644 --- a/cloudserver/middleware.go +++ b/cloudserver/middleware.go @@ -4,12 +4,13 @@ import ( "context" "errors" "fmt" + "log/slog" "runtime" + "runtime/debug" "go.einride.tech/cloudrunner/clouderror" "go.einride.tech/cloudrunner/cloudrequestlog" "go.einride.tech/cloudrunner/cloudstream" - "go.uber.org/zap" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" @@ -35,7 +36,7 @@ func (i *Middleware) GRPCUnaryServerInterceptor( status.New(codes.Internal, "internal error"), ) if additionalFields, ok := cloudrequestlog.GetAdditionalFields(ctx); ok { - additionalFields.Add(zap.Stack("stack")) + additionalFields.Add(slog.String("stack", string(debug.Stack()))) } } }() @@ -70,7 +71,7 @@ func (i *Middleware) GRPCStreamServerInterceptor( status.New(codes.Internal, "internal error"), ) if additionalFields, ok := cloudrequestlog.GetAdditionalFields(ss.Context()); ok { - additionalFields.Add(zap.Stack("stack")) + additionalFields.Add(slog.String("stack", string(debug.Stack()))) } } }() diff --git a/cloudslog/handler.go b/cloudslog/handler.go index 1c13a533..fb5c9c52 100644 --- a/cloudslog/handler.go +++ b/cloudslog/handler.go @@ -10,6 +10,7 @@ import ( "go.opentelemetry.io/otel/sdk/resource" "go.opentelemetry.io/otel/trace" ltype "google.golang.org/genproto/googleapis/logging/type" + "google.golang.org/grpc/status" "google.golang.org/protobuf/proto" ) @@ -94,6 +95,8 @@ func (r *attrReplacer) replaceAttr(_ []string, attr slog.Attr) slog.Attr { attr.Value = slog.AnyValue(newBuildInfoValue(value)) case *ltype.HttpRequest: attr.Value = slog.AnyValue(newProtoValue(fixHTTPRequest(value), r.config.ProtoMessageSizeLimit)) + case *status.Status: + attr.Value = slog.AnyValue(newProtoValue(value.Proto(), r.config.ProtoMessageSizeLimit)) case proto.Message: if needsRedact(value) { value = proto.Clone(value) diff --git a/cloudtrace/idhook.go b/cloudtrace/idhook.go index e8aa2f0c..47354e85 100644 --- a/cloudtrace/idhook.go +++ b/cloudtrace/idhook.go @@ -2,9 +2,9 @@ package cloudtrace import ( "context" + "log/slog" - "go.einride.tech/cloudrunner/cloudzap" - "go.uber.org/zap" + "go.einride.tech/cloudrunner/cloudslog" ) // IDKey is the log entry key for trace IDs. @@ -15,5 +15,5 @@ const IDKey = "traceId" // The trace ID can be used to filter on logs for the same trace across multiple projects. // Experimental: May be removed in a future update. func IDHook(ctx context.Context, traceContext Context) context.Context { - return cloudzap.WithLoggerFields(ctx, zap.String(IDKey, traceContext.TraceID)) + return cloudslog.With(ctx, slog.String(IDKey, traceContext.TraceID)) } diff --git a/cloudtrace/metadata.go b/cloudtrace/metadata.go index a938c132..b4a8a799 100644 --- a/cloudtrace/metadata.go +++ b/cloudtrace/metadata.go @@ -31,11 +31,13 @@ func FromIncomingContext(ctx context.Context) (Context, bool) { type contextKey struct{} // SetContext sets the cloud trace context to the provided context. +// Deprecated: Use OpenTelemetry middleware for trace extraction. func SetContext(ctx context.Context, ctxx Context) context.Context { return context.WithValue(ctx, contextKey{}, ctxx) } // GetContext gets the cloud trace context from the provided context if it exists. +// Deprecated: Use OpenTelemetry trace.SpanContextFromContext. func GetContext(ctx context.Context) (Context, bool) { result, ok := ctx.Value(contextKey{}).(Context) if !ok { diff --git a/cloudtrace/middleware.go b/cloudtrace/middleware.go index ebe3d96e..ce12b381 100644 --- a/cloudtrace/middleware.go +++ b/cloudtrace/middleware.go @@ -5,8 +5,6 @@ import ( "net/http" "go.einride.tech/cloudrunner/cloudstream" - "go.einride.tech/cloudrunner/cloudzap" - "go.uber.org/zap" "google.golang.org/grpc" "google.golang.org/grpc/metadata" ) @@ -35,7 +33,6 @@ func (i *Middleware) GRPCServerUnaryInterceptor( return handler(ctx, req) } ctx = i.withOutgoingRequestTracing(ctx, values[0]) - ctx = i.withLogTracing(ctx, values[0]) ctx = i.withInternalContext(ctx, values[0]) return handler(ctx, req) } @@ -57,7 +54,6 @@ func (i *Middleware) GRPCStreamServerInterceptor( } ctx := ss.Context() ctx = i.withOutgoingRequestTracing(ctx, values[0]) - ctx = i.withLogTracing(ctx, values[0]) ctx = i.withInternalContext(ctx, values[0]) return handler(srv, cloudstream.NewContextualServerStream(ctx, ss)) } @@ -72,7 +68,6 @@ func (i *Middleware) HTTPServer(next http.Handler) http.Handler { } w.Header().Set(ContextHeader, header) ctx := i.withOutgoingRequestTracing(r.Context(), header) - ctx = i.withLogTracing(ctx, header) ctx = i.withInternalContext(ctx, header) next.ServeHTTP(w, r.WithContext(ctx)) }) @@ -89,22 +84,3 @@ func (i *Middleware) withInternalContext(ctx context.Context, header string) con } return SetContext(ctx, result) } - -func (i *Middleware) withLogTracing(ctx context.Context, header string) context.Context { - var traceContext Context - if err := traceContext.UnmarshalString(header); err != nil { - return ctx - } - if i.TraceHook != nil { - ctx = i.TraceHook(ctx, traceContext) - } - fields := make([]zap.Field, 0, 3) - fields = append(fields, cloudzap.Trace(i.ProjectID, traceContext.TraceID)) - if traceContext.SpanID != "" { - fields = append(fields, cloudzap.SpanID(traceContext.SpanID)) - } - if traceContext.Sampled { - fields = append(fields, cloudzap.TraceSampled(traceContext.Sampled)) - } - return cloudzap.WithLoggerFields(ctx, fields...) -} diff --git a/cloudzap/level.go b/cloudzap/level.go index ed394cad..8fccce62 100644 --- a/cloudzap/level.go +++ b/cloudzap/level.go @@ -1,6 +1,10 @@ package cloudzap -import "go.uber.org/zap/zapcore" +import ( + "log/slog" + + "go.uber.org/zap/zapcore" +) // LevelToSeverity converts a zapcore.Level to its corresponding Cloud Logging severity level. // See: https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry#logseverity. @@ -24,3 +28,19 @@ func LevelToSeverity(l zapcore.Level) string { return "DEFAULT" } } + +// LevelToSlog converts a [zapcore.Level] to a [slog.Level]. +func LevelToSlog(l zapcore.Level) slog.Level { + switch l { + case zapcore.DebugLevel: + return slog.LevelDebug + case zapcore.InfoLevel: + return slog.LevelInfo + case zapcore.WarnLevel: + return slog.LevelWarn + case zapcore.ErrorLevel, zapcore.DPanicLevel, zapcore.PanicLevel, zapcore.FatalLevel: + return slog.LevelError + default: + return slog.LevelDebug + } +} diff --git a/examples/cmd/grpc-server/main.go b/examples/cmd/grpc-server/main.go index 77d2ef82..a83659a0 100644 --- a/examples/cmd/grpc-server/main.go +++ b/examples/cmd/grpc-server/main.go @@ -3,6 +3,7 @@ package main import ( "context" "log" + "log/slog" "go.einride.tech/cloudrunner" "google.golang.org/grpc/health" @@ -11,7 +12,7 @@ import ( func main() { if err := cloudrunner.Run(func(ctx context.Context) error { - cloudrunner.Logger(ctx).Info("hello world") + slog.InfoContext(ctx, "hello world") grpcServer := cloudrunner.NewGRPCServer(ctx) healthServer := health.NewServer() grpc_health_v1.RegisterHealthServer(grpcServer, healthServer) diff --git a/examples/cmd/http-server/main.go b/examples/cmd/http-server/main.go index dd4542e1..b5b1db6d 100644 --- a/examples/cmd/http-server/main.go +++ b/examples/cmd/http-server/main.go @@ -12,7 +12,7 @@ import ( func main() { if err := cloudrunner.Run(func(ctx context.Context) error { - cloudrunner.Logger(ctx).Info("hello world") + slog.InfoContext(ctx, "hello world") httpServer := cloudrunner.NewHTTPServer(ctx, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { slog.InfoContext(ctx, "hello from handler") cloudrunner.AddRequestLogFields(r.Context(), "foo", "bar") diff --git a/go.mod b/go.mod index 12ee6334..eb366aa5 100644 --- a/go.mod +++ b/go.mod @@ -27,7 +27,6 @@ require ( go.opentelemetry.io/otel/sdk/metric v1.30.0 go.opentelemetry.io/otel/trace v1.30.0 go.uber.org/zap v1.27.0 - go.uber.org/zap/exp v0.2.0 golang.org/x/net v0.29.0 golang.org/x/oauth2 v0.23.0 golang.org/x/sync v0.8.0 @@ -71,7 +70,7 @@ require ( github.com/yusufpapurcu/wmi v1.2.4 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/otel/metric v1.30.0 // indirect - go.uber.org/multierr v1.10.0 // indirect + go.uber.org/multierr v1.11.0 // indirect golang.org/x/crypto v0.27.0 // indirect golang.org/x/sys v0.25.0 // indirect golang.org/x/text v0.18.0 // indirect diff --git a/go.sum b/go.sum index 0cd505d2..a8750268 100644 --- a/go.sum +++ b/go.sum @@ -183,13 +183,11 @@ go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= -go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= -go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -go.uber.org/zap/exp v0.2.0 h1:FtGenNNeCATRB3CmB/yEUnjEFeJWpB/pMcy7e2bKPYs= -go.uber.org/zap/exp v0.2.0/go.mod h1:t0gqAIdh1MfKv9EwN/dLwfZnJxe9ITAZN78HEWPFWDQ= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= diff --git a/grpcserver.go b/grpcserver.go index c19167bc..b68c51f5 100644 --- a/grpcserver.go +++ b/grpcserver.go @@ -3,11 +3,11 @@ package cloudrunner import ( "context" "fmt" + "log/slog" "net" "time" "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" - "go.uber.org/zap" "google.golang.org/grpc" "google.golang.org/grpc/keepalive" ) @@ -63,9 +63,9 @@ func ListenGRPC(ctx context.Context, grpcServer *grpc.Server) error { } go func() { <-ctx.Done() - Logger(ctx).Info("gRPCServer shutting down") + slog.InfoContext(ctx, "gRPCServer shutting down") grpcServer.GracefulStop() }() - Logger(ctx).Info("gRPCServer listening", zap.String("address", address)) + slog.InfoContext(ctx, "gRPCServer listening", slog.String("address", address)) return grpcServer.Serve(listener) } diff --git a/httpserver.go b/httpserver.go index f6737a80..a0b76734 100644 --- a/httpserver.go +++ b/httpserver.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "log/slog" "net/http" "go.einride.tech/cloudrunner/cloudserver" @@ -55,7 +56,7 @@ func ListenHTTP(ctx context.Context, httpServer *http.Server) error { Logger(ctx).Error("HTTPServer shutdown error", zap.Error(err)) } }() - Logger(ctx).Info("HTTPServer listening", zap.String("address", httpServer.Addr)) + slog.InfoContext(ctx, "HTTPServer listening", slog.String("address", httpServer.Addr)) if err := httpServer.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { return err } diff --git a/options.go b/options.go index 0e46c8cc..e43615a9 100644 --- a/options.go +++ b/options.go @@ -13,10 +13,9 @@ import ( type Option func(*runContext) // WithRequestLoggerMessageTransformer configures the request logger with a message transformer. -func WithRequestLoggerMessageTransformer(transformer func(proto.Message) proto.Message) Option { - return func(run *runContext) { - run.requestLoggerMiddleware.MessageTransformer = transformer - } +// Deprecated: This was historically used for redaction. All proto messages are now automatically redacted. +func WithRequestLoggerMessageTransformer(func(proto.Message) proto.Message) Option { + return func(*runContext) {} } // WithConfig configures an additional config struct to be loaded. diff --git a/run.go b/run.go index 8b40dde3..eed1de84 100644 --- a/run.go +++ b/run.go @@ -19,12 +19,9 @@ import ( "go.einride.tech/cloudrunner/cloudrequestlog" "go.einride.tech/cloudrunner/cloudruntime" "go.einride.tech/cloudrunner/cloudserver" + "go.einride.tech/cloudrunner/cloudslog" "go.einride.tech/cloudrunner/cloudtrace" "go.einride.tech/cloudrunner/cloudzap" - "go.einride.tech/protobuf-sensitive/protosensitive" - "go.uber.org/zap" - "go.uber.org/zap/exp/zapslog" - "go.uber.org/zap/zapcore" "google.golang.org/grpc" ) @@ -96,19 +93,20 @@ func Run(fn func(context.Context) error, options ...Option) (err error) { } run.serverMiddleware.Config = run.config.Server run.requestLoggerMiddleware.Config = run.config.RequestLogger - if run.requestLoggerMiddleware.MessageTransformer == nil { - run.requestLoggerMiddleware.MessageTransformer = protosensitive.Redact - } ctx = withRunContext(ctx, &run) ctx = cloudruntime.WithConfig(ctx, run.config.Runtime) logger, err := cloudzap.NewLogger(run.config.Logger) if err != nil { return fmt.Errorf("cloudrunner.Run: %w", err) } - // Set the global default log/slog logger to write to our zap logger - slog.SetDefault(newSlogger(logger)) run.loggerMiddleware.Logger = logger ctx = cloudzap.WithLogger(ctx, logger) + // Set the global default log/slog logger. + slog.SetDefault(slog.New(cloudslog.NewHandler(cloudslog.LoggerConfig{ + Development: run.config.Logger.Development, + Level: cloudzap.LevelToSlog(run.config.Logger.Level), + ProtoMessageSizeLimit: run.config.RequestLogger.MessageSizeLimit, + }))) if err := cloudprofiler.Start(run.config.Profiler); err != nil { return fmt.Errorf("cloudrunner.Run: %w", err) } @@ -133,36 +131,39 @@ func Run(fn func(context.Context) error, options ...Option) (err error) { // See https://cloud.google.com/run/docs/container-contract#instance-shutdown for more details. shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() - logger.Info("shutting down") + slog.InfoContext(ctx, "shutting down") err := errors.Join( stopTraceExporter(shutdownCtx), stopMetricExporter(shutdownCtx), ) if err != nil { - logger.Warn("unable to call shutdown routines:\n", zap.Error(err)) + slog.WarnContext(ctx, "unable to call shutdown routines", slog.Any("error", err)) } }() - logger.Info( + slog.InfoContext( + ctx, "up and running", - zap.Object("config", config), - cloudzap.Resource("resource", resource), - zap.Object("buildInfo", buildInfoMarshaler{buildInfo: buildInfo}), + slog.Any("config", config), + slog.Any("resource", resource), + slog.Any("buildInfo", buildInfo), ) - defer logger.Info("goodbye") + defer slog.InfoContext(ctx, "goodbye") defer func() { if r := recover(); r != nil { - var msg zap.Field + var msg slog.Attr if err2, ok := r.(error); ok { - msg = zap.Error(err2) + msg = slog.Any("error", err2) err = err2 } else { - msg = zap.Any("msg", r) + msg = slog.Any("msg", r) err = fmt.Errorf("recovered panic") } - logger.Error( + slog.LogAttrs( + ctx, + slog.LevelError, "recovered panic", msg, - zap.Stack("stack"), + slog.String("stack", string(debug.Stack())), ) } }() @@ -191,35 +192,3 @@ func getRunContext(ctx context.Context) (*runContext, bool) { result, ok := ctx.Value(runContextKey{}).(*runContext) return result, ok } - -type buildInfoMarshaler struct { - buildInfo *debug.BuildInfo -} - -func (b buildInfoMarshaler) MarshalLogObject(encoder zapcore.ObjectEncoder) error { - if b.buildInfo == nil { - return nil - } - encoder.AddString("mainPath", b.buildInfo.Main.Path) - encoder.AddString("goVersion", b.buildInfo.GoVersion) - return encoder.AddObject("buildSettings", buildSettingsMarshaler(b.buildInfo.Settings)) -} - -type buildSettingsMarshaler []debug.BuildSetting - -func (b buildSettingsMarshaler) MarshalLogObject(encoder zapcore.ObjectEncoder) error { - for _, setting := range b { - encoder.AddString(setting.Key, setting.Value) - } - return nil -} - -// newSlogger returns a slog logger in which the underlying handler writes to the given zap logger. -// this func is kept here instead of in the cloudslog package to avoid having a api surface -// that encompasses zap in that package. -func newSlogger(zl *zap.Logger) *slog.Logger { - slogHandler := zapslog.NewHandler(zl.Core(), &zapslog.HandlerOptions{ - AddSource: true, // same as zap's AddCaller - }) - return slog.New(slogHandler) -}