/
flatten.go
115 lines (101 loc) · 3.59 KB
/
flatten.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
// Copyright 2019 The Cockroach Authors.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
package pgerror
import (
"bytes"
"fmt"
"strings"
"github.com/ruiaylin/pgparser/pgwire/pgcode"
"github.com/cockroachdb/errors"
)
// Flatten turns any error into a pgerror with fields populated. As
// the name implies, the details from the chain of causes is projected
// into a single struct. This is useful in at least two places:
//
// - to generate Error objects suitable for 19.1 nodes, which
// only recognize this type of payload.
// - to generate an error packet on pgwire.
//
// Additionally, this can be used in the remainder of the code
// base when an Error object is expected, until that code
// is updated to use the errors library directly.
//
// Flatten() returns a nil ptr if err was nil to start with.
func Flatten(err error) *Error {
if err == nil {
return nil
}
resErr := &Error{
Code: GetPGCode(err).String(),
Message: err.Error(),
Severity: GetSeverity(err),
}
// Populate the source field if available.
if file, line, fn, ok := errors.GetOneLineSource(err); ok {
resErr.Source = &Error_Source{File: file, Line: int32(line), Function: fn}
}
// Populate the details and hints.
resErr.Hint = errors.FlattenHints(err)
resErr.Detail = errors.FlattenDetails(err)
// Add a useful error prefix if not already there.
switch resErr.Code {
case pgcode.Internal.String():
// The string "internal error" clarifies the nature of the error
// to users, and is also introduced for compatibility with
// previous CockroachDB versions.
if !strings.HasPrefix(resErr.Message, InternalErrorPrefix) {
resErr.Message = InternalErrorPrefix + ": " + resErr.Message
}
// If the error flows towards a human user and does not get
// sent via telemetry, we want to empower the user to
// file a moderately useful error report. For this purpose,
// append the innermost stack trace.
resErr.Detail += getInnerMostStackTraceAsDetail(err)
case pgcode.SerializationFailure.String():
// The string "restart transaction" is asserted by test code. This
// can be changed if/when test code learns to use the 40001 code
// (or the errors library) instead.
//
// TODO(knz): investigate whether 3rd party frameworks parse this
// string instead of using the pg code to determine whether to
// retry.
if !strings.HasPrefix(resErr.Message, TxnRetryMsgPrefix) {
resErr.Message = TxnRetryMsgPrefix + ": " + resErr.Message
}
}
return resErr
}
func getInnerMostStackTraceAsDetail(err error) string {
if c := errors.UnwrapOnce(err); c != nil {
s := getInnerMostStackTraceAsDetail(c)
if s != "" {
return s
}
}
// Fall through: there is no stack trace so far.
if st := errors.GetReportableStackTrace(err); st != nil {
var t bytes.Buffer
t.WriteString("stack trace:\n")
for i := len(st.Frames) - 1; i >= 0; i-- {
f := st.Frames[i]
fmt.Fprintf(&t, "%s:%d: %s()\n", f.Filename, f.Lineno, f.Function)
}
return t.String()
}
return ""
}
// InternalErrorPrefix is prepended on internal errors.
const InternalErrorPrefix = "internal error"
// TxnRetryMsgPrefix is the prefix inserted in an error message when flattened
const TxnRetryMsgPrefix = "restart transaction"
// GetPGCode retrieves the error code for an error.
func GetPGCode(err error) pgcode.Code {
return GetPGCodeInternal(err, ComputeDefaultCode)
}