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) -}