From 45feee91e94b6cb6fdab1f4ba2ce01610b52ce86 Mon Sep 17 00:00:00 2001 From: searKing <471030698@qq.com> Date: Thu, 29 Aug 2024 11:08:21 +0800 Subject: [PATCH] refactor(webserver): split logging and recover into seperated package. --- pkg/webserver/gin.go | 3 +- pkg/webserver/go.mod | 16 +++--- pkg/webserver/go.sum | 34 +++++++------ pkg/webserver/{ => pkg/logging}/logging.go | 4 +- pkg/webserver/pkg/recovery/recovery.go | 58 ++++++++++++++++++++++ pkg/webserver/webserver.factory.go | 35 ++----------- 6 files changed, 93 insertions(+), 57 deletions(-) rename pkg/webserver/{ => pkg/logging}/logging.go (87%) create mode 100644 pkg/webserver/pkg/recovery/recovery.go diff --git a/pkg/webserver/gin.go b/pkg/webserver/gin.go index f05036ce..d8acaaa3 100644 --- a/pkg/webserver/gin.go +++ b/pkg/webserver/gin.go @@ -13,6 +13,7 @@ import ( "github.com/gin-gonic/gin" slices_ "github.com/searKing/golang/go/exp/slices" "github.com/searKing/golang/pkg/webserver/healthz" + "github.com/searKing/golang/pkg/webserver/pkg/logging" gin_ "github.com/searKing/golang/third_party/github.com/gin-gonic/gin" ) @@ -31,7 +32,7 @@ func (mux *ginMuxer) Handle(pattern string, handler http.Handler) { func GinLogFormatter(layout string) func(param gin.LogFormatterParams) string { return gin_.LogFormatterWithExtra(layout, func(param gin.LogFormatterParams) string { if param.Request != nil { - attrs := extractLoggingAttrs(param.Request.Context()) + attrs := logging.Attrs(param.Request.Context()) if len(attrs) > 0 { extra := strings.Join(slices_.MapFunc(attrs, func(e slog.Attr) string { return e.String() }), ", ") return fmt.Sprintf(" | %v", extra) diff --git a/pkg/webserver/go.mod b/pkg/webserver/go.mod index 27ba06e7..1d72cb68 100644 --- a/pkg/webserver/go.mod +++ b/pkg/webserver/go.mod @@ -10,9 +10,9 @@ require ( github.com/searKing/golang/third_party/github.com/gin-gonic/gin v1.2.118 github.com/searKing/golang/third_party/github.com/grpc-ecosystem/grpc-gateway-v2 v1.2.118 github.com/searKing/golang/third_party/google.golang.org/grpc v1.2.118 - golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 - golang.org/x/sync v0.7.0 - google.golang.org/grpc v1.64.1 + golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 + golang.org/x/sync v0.8.0 + google.golang.org/grpc v1.66.0 ) require ( @@ -28,7 +28,7 @@ require ( github.com/goccy/go-json v0.10.2 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.2.7 // indirect github.com/leodido/go-urn v1.4.0 // indirect @@ -43,10 +43,10 @@ require ( golang.org/x/net v0.26.0 // indirect golang.org/x/sys v0.21.0 // indirect golang.org/x/term v0.21.0 // indirect - golang.org/x/text v0.16.0 // indirect - golang.org/x/time v0.5.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240513163218-0867130af1f8 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240617180043-68d350f18fd4 // indirect + golang.org/x/text v0.17.0 // indirect + golang.org/x/time v0.6.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240827150818-7e3bb234dfed // indirect google.golang.org/protobuf v1.34.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/pkg/webserver/go.sum b/pkg/webserver/go.sum index fa87b515..131e9097 100644 --- a/pkg/webserver/go.sum +++ b/pkg/webserver/go.sum @@ -34,8 +34,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 h1:pRhl55Yx1eC7BZ1N+BBWwnKaMyD8uC+34TLdndZMAKk= github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0/go.mod h1:XKMd7iuf/RGPSMJ/U4HP0zS2Z9Fh8Ps9a+6X26m/tmI= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= @@ -84,28 +84,30 @@ golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= -golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= -golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= +golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa h1:ELnwvuAXPNtPk1TJRuGkI9fDTwym6AYBu0qzT8AcHdI= +golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= +golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 h1:kx6Ds3MlpiUHKj7syVnbp57++8WpuKPcR5yjLBjvLEA= +golang.org/x/exp v0.0.0-20240823005443-9b4947da3948/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA= golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= -golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= -golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= -google.golang.org/genproto/googleapis/api v0.0.0-20240513163218-0867130af1f8 h1:W5Xj/70xIA4x60O/IFyXivR5MGqblAb8R3w26pnD6No= -google.golang.org/genproto/googleapis/api v0.0.0-20240513163218-0867130af1f8/go.mod h1:vPrPUTsDCYxXWjP7clS81mZ6/803D8K4iM9Ma27VKas= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240617180043-68d350f18fd4 h1:Di6ANFilr+S60a4S61ZM00vLdw0IrQOSMS2/6mrnOU0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240617180043-68d350f18fd4/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= -google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= -google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= +golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 h1:wKguEg1hsxI2/L3hUYrpo1RVi48K+uTyzKqprwLXsb8= +google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142/go.mod h1:d6be+8HhtEtucleCbxpPW9PA9XwISACu8nvpPqF0BVo= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240827150818-7e3bb234dfed h1:J6izYgfBXAI3xTKLgxzTmUltdYaLsuBxFCgDHWJ/eXg= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240827150818-7e3bb234dfed/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= +google.golang.org/grpc v1.66.0 h1:DibZuoBznOxbDQxRINckZcUvnCEvrW9pcWIE2yF9r1c= +google.golang.org/grpc v1.66.0/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/pkg/webserver/logging.go b/pkg/webserver/pkg/logging/logging.go similarity index 87% rename from pkg/webserver/logging.go rename to pkg/webserver/pkg/logging/logging.go index 57cdba95..bad44f44 100644 --- a/pkg/webserver/logging.go +++ b/pkg/webserver/pkg/logging/logging.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package webserver +package logging import ( "context" @@ -11,7 +11,7 @@ import ( "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/logging" ) -func extractLoggingAttrs(ctx context.Context) []slog.Attr { +func Attrs(ctx context.Context) []slog.Attr { return fieldsToAttrSlice(logging.ExtractFields(ctx)) } diff --git a/pkg/webserver/pkg/recovery/recovery.go b/pkg/webserver/pkg/recovery/recovery.go new file mode 100644 index 00000000..0892b810 --- /dev/null +++ b/pkg/webserver/pkg/recovery/recovery.go @@ -0,0 +1,58 @@ +// Copyright 2024 The searKing Author. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package recovery + +import ( + "context" + "fmt" + "log/slog" + "os" + "runtime/debug" + + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + grpcrecovery "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/recovery" + slices_ "github.com/searKing/golang/go/exp/slices" + slog_ "github.com/searKing/golang/go/log/slog" + "github.com/searKing/golang/pkg/webserver/pkg/logging" + grpc_ "github.com/searKing/golang/third_party/github.com/grpc-ecosystem/grpc-gateway-v2/grpc" +) + +// UnaryServerInterceptor returns a new unary server interceptor that performs recovering from a panic. +func UnaryServerInterceptor() grpc.UnaryServerInterceptor { + return grpcrecovery.UnaryServerInterceptor(grpcrecovery.WithRecoveryHandlerContext(recoveryLogHandler)) +} + +// StreamServerInterceptor returns a new stream server interceptor that performs recovering from a panic. +func StreamServerInterceptor() grpc.StreamServerInterceptor { + return grpcrecovery.StreamServerInterceptor(grpcrecovery.WithRecoveryHandlerContext(recoveryLogHandler)) +} + +// WrapRecovery returns a new unary server interceptor that performs recovering from a panic. +func WrapRecovery[REQ any, RESP any](handler grpc_.UnaryHandler[REQ, RESP]) grpc_.UnaryHandler[REQ, RESP] { + return func(ctx context.Context, req REQ) (_ RESP, err error) { + defer func() { + if r := recover(); r != nil { + err = recoveryLogHandler(ctx, r) + } + }() + + resp, err := handler(ctx, req) + return resp, err + } +} + +func recoveryLogHandler(ctx context.Context, p any) (err error) { + { + _, _ = os.Stderr.Write([]byte(fmt.Sprintf("panic: %s", p))) + debug.PrintStack() + _, _ = os.Stderr.Write([]byte(" [recovered]\n")) + } + logger := slog.With(slices_.MapFunc(logging.Attrs(ctx), func(e slog.Attr) any { return e })...) + logger.With(slog_.Error(status.Errorf(codes.Internal, "%s at %s", p, debug.Stack()))).Error("recovered in grpc") + return status.Errorf(codes.Internal, "%s", p) +} diff --git a/pkg/webserver/webserver.factory.go b/pkg/webserver/webserver.factory.go index 5fcc4b5e..8a8ac2ce 100644 --- a/pkg/webserver/webserver.factory.go +++ b/pkg/webserver/webserver.factory.go @@ -16,24 +16,21 @@ import ( "net/url" "os" "path/filepath" - "runtime/debug" "strings" "time" "github.com/gin-gonic/gin" - grpcrecovery "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/recovery" "github.com/rs/cors" - slices_ "github.com/searKing/golang/go/exp/slices" + "google.golang.org/grpc" + slog_ "github.com/searKing/golang/go/log/slog" net_ "github.com/searKing/golang/go/net" "github.com/searKing/golang/pkg/webserver/healthz" + "github.com/searKing/golang/pkg/webserver/pkg/recovery" gin_ "github.com/searKing/golang/third_party/github.com/gin-gonic/gin" grpc_ "github.com/searKing/golang/third_party/github.com/grpc-ecosystem/grpc-gateway-v2/grpc" "github.com/searKing/golang/third_party/google.golang.org/grpc/interceptors/burstlimit" "github.com/searKing/golang/third_party/google.golang.org/grpc/interceptors/timeoutlimit" - "google.golang.org/grpc" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" ) // FactoryConfigFunc is an alias for a function that will take in a pointer to an FactoryConfig and modify it @@ -176,30 +173,8 @@ func (f *Factory) New() (*WebServer, error) { } { // recover - opts = append(opts, grpc_.WithGrpcUnaryServerChain(grpcrecovery.UnaryServerInterceptor( - grpcrecovery.WithRecoveryHandlerContext(func(ctx context.Context, p any) (err error) { - slog.With(slices_.MapFunc(extractLoggingAttrs(ctx), func(e slog.Attr) any { return e })...). - Error("recovered in grpc", slog_.Error(status.Errorf(codes.Internal, "%s at %s", p, debug.Stack()))) - { - _, _ = os.Stderr.Write([]byte(fmt.Sprintf("panic: %s", p))) - debug.PrintStack() - _, _ = os.Stderr.Write([]byte(" [recovered]")) - _, _ = os.Stderr.Write([]byte("\n")) - } - return status.Errorf(codes.Internal, "%s", p) - })))) - opts = append(opts, grpc_.WithGrpcStreamServerChain(grpcrecovery.StreamServerInterceptor( - grpcrecovery.WithRecoveryHandlerContext(func(ctx context.Context, p any) (err error) { - slog.With(slices_.MapFunc(extractLoggingAttrs(ctx), func(e slog.Attr) any { return e })...). - Error("recovered in grpc", slog_.Error(status.Errorf(codes.Internal, "%s at %s", p, debug.Stack()))) - { - _, _ = os.Stderr.Write([]byte(fmt.Sprintf("panic: %s", p))) - debug.PrintStack() - _, _ = os.Stderr.Write([]byte(" [recovered]")) - _, _ = os.Stderr.Write([]byte("\n")) - } - return status.Errorf(codes.Internal, "%s", p) - })))) + opts = append(opts, grpc_.WithGrpcUnaryServerChain(recovery.UnaryServerInterceptor())) + opts = append(opts, grpc_.WithGrpcStreamServerChain(recovery.StreamServerInterceptor())) } { // handle request timeout