Skip to content

Commit c1684d0

Browse files
committed
[FAB-3012] Copy callstack from lower level error
This change adds the ability to copy the callstack and error message from a lower level error into a later error. For example, an error may occur in a common utility function that a different package may want to wrap with its own component/reason code, error message, and callstack while retaining the details from the wrapped error. Change-Id: Id143640bbaaba7bf125b343dbc614d046acbfd1a Signed-off-by: Will Lahti <[email protected]>
1 parent eba4a20 commit c1684d0

File tree

4 files changed

+298
-116
lines changed

4 files changed

+298
-116
lines changed

core/errors/errors.go

+87-67
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/*
22
Copyright Digital Asset Holdings, LLC 2016 All Rights Reserved.
3+
Copyright IBM Corp. 2017 All Rights Reserved.
34
45
Licensed under the Apache License, Version 2.0 (the "License");
56
you may not use this file except in compliance with the License.
@@ -19,8 +20,8 @@ package errors
1920
import (
2021
"bytes"
2122
"fmt"
23+
"regexp"
2224
"runtime"
23-
"strings"
2425

2526
"github.com/hyperledger/fabric/common/flogging"
2627
logging "github.com/op/go-logging"
@@ -29,135 +30,151 @@ import (
2930
// MaxCallStackLength is the maximum length of the stored call stack
3031
const MaxCallStackLength = 30
3132

32-
var errorLogger = logging.MustGetLogger("error")
33+
var (
34+
errorLogger = logging.MustGetLogger("error")
35+
componentPattern = "[A-Za-z]{3}"
36+
reasonPattern = "[0-9]{3}"
37+
)
3338

34-
// CallStackError is a general interface for
35-
// Fabric errors
39+
// CallStackError is a general interface for Fabric errors
3640
type CallStackError interface {
3741
error
3842
GetStack() string
3943
GetErrorCode() string
4044
GetComponentCode() string
4145
GetReasonCode() string
4246
Message() string
47+
GenerateStack(bool) CallStackError
48+
WrapError(error) CallStackError
4349
}
4450

4551
type callstack []uintptr
4652

47-
// the main idea is to have an error package
48-
// HLError is the 'super class' of all errors
49-
// It has a predefined, general error message
50-
// One has to create his own error in order to
51-
// create something more useful
52-
type hlError struct {
53+
// callError is the 'super class' of all errors
54+
type callError struct {
5355
stack callstack
5456
componentcode string
5557
reasoncode string
5658
message string
5759
args []interface{}
5860
stackGetter func(callstack) string
61+
prevErr error
5962
}
6063

61-
// newHLError creates a general HL error with a predefined message
62-
// and a stacktrace.
63-
func newHLError(debug bool) *hlError {
64-
e := &hlError{}
65-
setupHLError(e, debug)
66-
return e
67-
}
68-
69-
func setupHLError(e *hlError, debug bool) {
70-
e.componentcode = "UTILITY"
71-
e.reasoncode = "UNKNOWNERROR"
72-
e.message = "An unknown error occurred."
73-
if !debug {
64+
func setupCallError(e *callError, generateStack bool) {
65+
if !generateStack {
7466
e.stackGetter = noopGetStack
7567
return
7668
}
7769
e.stackGetter = getStack
7870
stack := make([]uintptr, MaxCallStackLength)
79-
skipCallersAndSetupHL := 2
80-
length := runtime.Callers(skipCallersAndSetupHL, stack[:])
71+
skipCallersAndSetup := 2
72+
length := runtime.Callers(skipCallersAndSetup, stack[:])
8173
e.stack = stack[:length]
8274
}
8375

8476
// Error comes from the error interface
85-
func (h *hlError) Error() string {
86-
return h.Message()
77+
func (e *callError) Error() string {
78+
return e.Message()
8779
}
8880

8981
// GetStack returns the call stack as a string
90-
func (h *hlError) GetStack() string {
91-
return h.stackGetter(h.stack)
82+
func (e *callError) GetStack() string {
83+
return e.stackGetter(e.stack)
9284
}
9385

9486
// GetComponentCode returns the component name
95-
func (h *hlError) GetComponentCode() string {
96-
return h.componentcode
87+
func (e *callError) GetComponentCode() string {
88+
return e.componentcode
9789
}
9890

9991
// GetReasonCode returns the reason code - i.e. why the error occurred
100-
func (h *hlError) GetReasonCode() string {
101-
return h.reasoncode
92+
func (e *callError) GetReasonCode() string {
93+
return e.reasoncode
10294
}
10395

10496
// GetErrorCode returns a formatted error code string
105-
func (h *hlError) GetErrorCode() string {
106-
return fmt.Sprintf("%s_%s", h.componentcode, h.reasoncode)
97+
func (e *callError) GetErrorCode() string {
98+
return fmt.Sprintf("%s:%s", e.componentcode, e.reasoncode)
10799
}
108100

109101
// Message returns the corresponding error message for this error in default
110102
// language.
111-
func (h *hlError) Message() string {
112-
message := h.GetErrorCode() + " - " + fmt.Sprintf(h.message, h.args...)
113-
103+
func (e *callError) Message() string {
104+
message := e.GetErrorCode() + " - " + fmt.Sprintf(e.message, e.args...)
114105
// check that the error has a callstack before proceeding
115-
if h.GetStack() != "" {
116-
// initialize logging level for errors from core.yaml. it can also be set
117-
// for code running on the peer dynamically via CLI using
118-
// "peer logging setlevel error <log-level>"
119-
errorLogLevelString := flogging.GetModuleLevel("error")
120-
121-
if errorLogLevelString == logging.DEBUG.String() {
122-
message = appendCallStack(message, h.GetStack())
106+
if e.GetStack() != "" {
107+
// stacktrace is enabled when `logging.error` in core.yaml is set to
108+
// DEBUG. it can also be toggled for code running on the peer dynamically
109+
// via CLI using `peer logging setlevel error <log-level>`
110+
errorLevel := flogging.GetModuleLevel("error")
111+
if errorLevel == logging.DEBUG.String() {
112+
message = appendCallStack(message, e.GetStack())
123113
}
124114
}
125-
115+
if e.prevErr != nil {
116+
message += "\nCaused by: " + e.prevErr.Error()
117+
}
126118
return message
127119
}
128120

129121
func appendCallStack(message string, callstack string) string {
130-
messageWithCallStack := message + "\n" + callstack
131-
132-
return messageWithCallStack
122+
return message + "\n" + callstack
133123
}
134124

135-
// Error creates a CallStackError using a specific Component Code and
136-
// Reason Code (no callstack is recorded)
125+
// Error creates a CallStackError using a specific component code and reason
126+
// code (no callstack is generated)
137127
func Error(componentcode string, reasoncode string, message string, args ...interface{}) CallStackError {
138-
return newCustomError(componentcode, reasoncode, message, false, args...)
128+
return newError(componentcode, reasoncode, message, args...).GenerateStack(false)
139129
}
140130

141-
// ErrorWithCallstack creates a CallStackError using a specific Component Code and
142-
// Reason Code and fills its callstack
131+
// ErrorWithCallstack creates a CallStackError using a specific component code
132+
// and reason code and generates its callstack
143133
func ErrorWithCallstack(componentcode string, reasoncode string, message string, args ...interface{}) CallStackError {
144-
return newCustomError(componentcode, reasoncode, message, true, args...)
134+
return newError(componentcode, reasoncode, message, args...).GenerateStack(true)
135+
}
136+
137+
func newError(componentcode string, reasoncode string, message string, args ...interface{}) CallStackError {
138+
e := &callError{}
139+
e.setErrorFields(componentcode, reasoncode, message, args...)
140+
return e
141+
}
142+
143+
// GenerateStack generates the callstack for a CallStackError
144+
func (e *callError) GenerateStack(flag bool) CallStackError {
145+
setupCallError(e, flag)
146+
return e
147+
}
148+
149+
// WrapError wraps a previous error into a CallStackError
150+
func (e *callError) WrapError(prevErr error) CallStackError {
151+
e.prevErr = prevErr
152+
return e
145153
}
146154

147-
func newCustomError(componentcode string, reasoncode string, message string, generateStack bool, args ...interface{}) CallStackError {
148-
e := &hlError{}
149-
setupHLError(e, generateStack)
150-
if componentcode != "" {
151-
e.componentcode = strings.ToUpper(componentcode)
155+
func (e *callError) setErrorFields(componentcode string, reasoncode string, message string, args ...interface{}) {
156+
if isValidComponentOrReasonCode(componentcode, componentPattern) {
157+
e.componentcode = componentcode
152158
}
153-
if reasoncode != "" {
154-
e.reasoncode = strings.ToUpper(reasoncode)
159+
if isValidComponentOrReasonCode(reasoncode, reasonPattern) {
160+
e.reasoncode = reasoncode
155161
}
156162
if message != "" {
157163
e.message = message
158164
}
159165
e.args = args
160-
return e
166+
}
167+
168+
func isValidComponentOrReasonCode(componentOrReasonCode string, regExp string) bool {
169+
if componentOrReasonCode == "" {
170+
return false
171+
}
172+
re, _ := regexp.Compile(regExp)
173+
matched := re.FindString(componentOrReasonCode)
174+
if len(matched) != len(componentOrReasonCode) {
175+
return false
176+
}
177+
return true
161178
}
162179

163180
func getStack(stack callstack) string {
@@ -169,12 +186,15 @@ func getStack(stack callstack) string {
169186
// are not useful for debugging
170187
const firstNonErrorModuleCall int = 2
171188
stack = stack[firstNonErrorModuleCall:]
172-
for _, pc := range stack {
189+
for i, pc := range stack {
173190
f := runtime.FuncForPC(pc)
174191
file, line := f.FileLine(pc)
175-
buf.WriteString(fmt.Sprintf("%s:%d %s\n", file, line, f.Name()))
192+
if i != len(stack)-1 {
193+
buf.WriteString(fmt.Sprintf("%s:%d %s\n", file, line, f.Name()))
194+
} else {
195+
buf.WriteString(fmt.Sprintf("%s:%d %s", file, line, f.Name()))
196+
}
176197
}
177-
178198
return fmt.Sprintf("%s", buf.Bytes())
179199
}
180200

0 commit comments

Comments
 (0)