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
|
package sentryhandler
import (
"context"
"fmt"
"strings"
"time"
raven "github.com/getsentry/raven-go"
grpc_ctxtags "github.com/grpc-ecosystem/go-grpc-middleware/tags"
"gitlab.com/gitlab-org/gitaly/internal/helper"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
)
var ignoredCodes = []codes.Code{
// OK means there was no error
codes.OK,
// Canceled and DeadlineExceeded indicate clients that disappeared or lost interest
codes.Canceled,
codes.DeadlineExceeded,
// We use FailedPrecondition to signal error conditions that are 'normal'
codes.FailedPrecondition,
}
// UnaryLogHandler handles access times and errors for unary RPC's
func UnaryLogHandler(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
start := time.Now()
resp, err := handler(ctx, req)
if err != nil {
logGrpcErrorToSentry(ctx, info.FullMethod, start, err)
}
return resp, err
}
// StreamLogHandler handles access times and errors for stream RPC's
func StreamLogHandler(srv interface{}, stream grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
start := time.Now()
err := handler(srv, stream)
if err != nil {
logGrpcErrorToSentry(stream.Context(), info.FullMethod, start, err)
}
return err
}
func stringMap(incoming map[string]interface{}) map[string]string {
result := make(map[string]string)
for i, v := range incoming {
result[i] = fmt.Sprintf("%v", v)
}
return result
}
func methodToCulprit(methodName string) string {
methodName = strings.TrimPrefix(methodName, "/gitaly.")
methodName = strings.Replace(methodName, "/", "::", 1)
return methodName
}
func logErrorToSentry(err error) (code codes.Code, bypass bool) {
code = helper.GrpcCode(err)
for _, ignoredCode := range ignoredCodes {
if code == ignoredCode {
return code, true
}
}
return code, false
}
func generateRavenPacket(ctx context.Context, method string, start time.Time, err error) (*raven.Packet, map[string]string) {
grpcErrorCode, bypass := logErrorToSentry(err)
if bypass {
return nil, nil
}
tags := grpc_ctxtags.Extract(ctx)
ravenDetails := stringMap(tags.Values())
ravenDetails["grpc.code"] = grpcErrorCode.String()
ravenDetails["grpc.method"] = method
ravenDetails["grpc.time_ms"] = fmt.Sprintf("%.0f", time.Since(start).Seconds()*1000)
ravenDetails["system"] = "grpc"
// Skip the stacktrace as it's not helpful in this context
packet := raven.NewPacket(err.Error(), raven.NewException(err, nil))
grpcMethod := methodToCulprit(method)
// Details on fingerprinting
// https://docs.sentry.io/learn/rollups/#customize-grouping-with-fingerprints
packet.Fingerprint = []string{"grpc", grpcMethod, grpcErrorCode.String()}
packet.Culprit = grpcMethod
return packet, ravenDetails
}
func logGrpcErrorToSentry(ctx context.Context, method string, start time.Time, err error) {
packet, tags := generateRavenPacket(ctx, method, start, err)
if packet == nil {
return
}
raven.Capture(packet, tags)
}
|