-
Notifications
You must be signed in to change notification settings - Fork 4
/
error.go
98 lines (89 loc) · 2.48 KB
/
error.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
//go:build !tinygo.wasm
package federation
import (
"context"
"errors"
"fmt"
"log/slog"
"runtime/debug"
"strings"
"golang.org/x/sync/errgroup"
grpcstatus "google.golang.org/grpc/status"
)
// ErrorHandler Federation Service often needs to convert errors received from downstream services.
// If an error occurs during method execution in the Federation Service, this error handler is called and the returned error is treated as a final error.
type ErrorHandler func(ctx context.Context, methodName string, err error) error
// RecoveredError represents recovered error.
type RecoveredError struct {
Message string
Stack []string
}
func (e *RecoveredError) Error() string {
return fmt.Sprintf("recovered error: %s", e.Message)
}
func GoWithRecover(eg *errgroup.Group, fn func() (any, error)) {
eg.Go(func() (e error) {
defer func() {
if r := recover(); r != nil {
e = RecoverError(r, debug.Stack())
}
}()
_, err := fn()
return err
})
}
func OutputErrorLog(ctx context.Context, logger *slog.Logger, err error) {
if err == nil {
return
}
if status, ok := grpcstatus.FromError(err); ok {
logger.ErrorContext(ctx, status.Message(),
slog.Group("grpc_status",
slog.String("code", status.Code().String()),
slog.Any("details", status.Details()),
),
)
return
}
var recoveredErr *RecoveredError
if errors.As(err, &recoveredErr) {
trace := make([]interface{}, 0, len(recoveredErr.Stack))
for idx, stack := range recoveredErr.Stack {
trace = append(trace, slog.String(fmt.Sprint(idx+1), stack))
}
logger.ErrorContext(ctx, recoveredErr.Message, slog.Group("stack_trace", trace...))
return
}
logger.ErrorContext(ctx, err.Error())
}
func RecoverError(v interface{}, rawStack []byte) *RecoveredError {
msg := fmt.Sprint(v)
msgLines := strings.Split(msg, "\n")
if len(msgLines) <= 1 {
lines := strings.Split(string(rawStack), "\n")
stack := make([]string, 0, len(lines))
for _, line := range lines {
if line == "" {
continue
}
stack = append(stack, strings.TrimPrefix(line, "\t"))
}
return &RecoveredError{
Message: msg,
Stack: stack,
}
}
// If panic occurs under singleflight, singleflight's recover catches the error and gives a stack trace.
// Therefore, once the stack trace is removed.
stack := make([]string, 0, len(msgLines))
for _, line := range msgLines[1:] {
if line == "" {
continue
}
stack = append(stack, strings.TrimPrefix(line, "\t"))
}
return &RecoveredError{
Message: msgLines[0],
Stack: stack,
}
}