-
Notifications
You must be signed in to change notification settings - Fork 0
/
zap.go
145 lines (127 loc) · 3.79 KB
/
zap.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
package grpc_zap
import (
"context"
"path"
"time"
grpc_zap "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap"
"github.com/grpc-ecosystem/go-grpc-middleware/logging/zap/ctxzap"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"github.com/lixin9311/zapx"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/peer"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/proto"
)
type LoggingDecider func(ctx context.Context, fullMethodName string) bool
func UnaryServerInterceptor(logger *zap.Logger, logReq bool, decider LoggingDecider) grpc.UnaryServerInterceptor {
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
if !decider(ctx, info.FullMethod) {
return handler(ctx, req)
}
// populate basic fields
fullMethodString := info.FullMethod
service := path.Dir(fullMethodString)[1:]
method := path.Base(fullMethodString)
f1 := []zapcore.Field{
zap.String("grpc.service", service),
zap.String("grpc.method", method),
zapx.Context(ctx),
zapx.Metadata(ctx),
}
if d, ok := ctx.Deadline(); ok {
f1 = append(f1, zap.Time("grpc.request.deadline", d))
}
// append request
if logReq {
if pb, ok := req.(proto.Message); ok {
f1 = append(f1, zap.Reflect("grpc.request", &jsonpbObjectMarshaler{pb: pb}))
}
}
// wrap logger to context
callLog := logger.Named(service + "." + method).With(f1...)
startTime := time.Now()
newCtx := ctxzap.ToContext(ctx, callLog)
// call handler
resp, err = handler(newCtx, req)
// populate response
code := status.Code(err)
level := codeToLevel(code)
duration := time.Since(startTime)
status := runtime.HTTPStatusFromCode(code)
request := zapx.HTTPRequestEntry{
RequestMethod: "POST",
RequestURL: method,
Status: status,
Latency: duration,
}
if peer, ok := peer.FromContext(ctx); ok {
request.RemoteIP = peer.Addr.String()
}
f2 := []zap.Field{
zap.Error(err),
zapx.Request(request),
}
// append request
if logReq {
if pb, ok := resp.(proto.Message); ok {
f2 = append(f2, zap.Reflect("grpc.response", &jsonpbObjectMarshaler{pb: pb}))
}
}
ctxzap.Extract(newCtx).Check(level, code.String()).Write(f2...)
return resp, err
}
}
// codeToLevel is the default implementation of gRPC return codes and interceptor log level for server side.
func codeToLevel(code codes.Code) zapcore.Level {
switch code {
case codes.OK:
return zap.InfoLevel
case codes.Canceled:
return zap.InfoLevel
case codes.Unknown:
return zap.ErrorLevel
case codes.InvalidArgument:
return zap.InfoLevel
case codes.DeadlineExceeded:
return zap.WarnLevel
case codes.NotFound:
return zap.InfoLevel
case codes.AlreadyExists:
return zap.InfoLevel
case codes.PermissionDenied:
return zap.WarnLevel
case codes.Unauthenticated:
return zap.InfoLevel // unauthenticated requests can happen
case codes.ResourceExhausted:
return zap.WarnLevel
case codes.FailedPrecondition:
return zap.WarnLevel
case codes.Aborted:
return zap.WarnLevel
case codes.OutOfRange:
return zap.WarnLevel
case codes.Unimplemented:
return zap.ErrorLevel
case codes.Internal:
return zap.ErrorLevel
case codes.Unavailable:
return zap.WarnLevel
case codes.DataLoss:
return zap.ErrorLevel
default:
return zap.ErrorLevel
}
}
func PayloadUnaryServerInterceptor(logger *zap.Logger) grpc.UnaryServerInterceptor {
return grpc_zap.PayloadUnaryServerInterceptor(logger, func(context.Context, string, interface{}) bool { return true })
}
type jsonpbObjectMarshaler struct {
pb proto.Message
}
func (j *jsonpbObjectMarshaler) MarshalJSON() ([]byte, error) {
return protojson.Marshal(j.pb)
}