github.com/tenywen/fabric@v1.0.0-beta.0.20170620030522-a5b1ed380643/common/errors/errors.go (about)

     1  /*
     2   Copyright Digital Asset Holdings, LLC 2016 All Rights Reserved.
     3   Copyright IBM Corp. 2017 All Rights Reserved.
     4  
     5   Licensed under the Apache License, Version 2.0 (the "License");
     6   you may not use this file except in compliance with the License.
     7   You may obtain a copy of the License at
     8  
     9        http://www.apache.org/licenses/LICENSE-2.0
    10  
    11   Unless required by applicable law or agreed to in writing, software
    12   distributed under the License is distributed on an "AS IS" BASIS,
    13   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   See the License for the specific language governing permissions and
    15   limitations under the License.
    16  */
    17  
    18  package errors
    19  
    20  import (
    21  	"bytes"
    22  	"fmt"
    23  	"regexp"
    24  	"runtime"
    25  )
    26  
    27  // MaxCallStackLength is the maximum length of the stored call stack
    28  const MaxCallStackLength = 30
    29  
    30  var (
    31  	componentPattern = "[A-Za-z]{3}"
    32  	reasonPattern    = "[0-9]{3}"
    33  )
    34  
    35  // CallStackError is a general interface for Fabric errors
    36  type CallStackError interface {
    37  	error
    38  	GetStack() string
    39  	GetErrorCode() string
    40  	GetComponentCode() string
    41  	GetReasonCode() string
    42  	Message() string
    43  	GenerateStack(bool) CallStackError
    44  	WrapError(error) CallStackError
    45  }
    46  
    47  type callstack []uintptr
    48  
    49  // callError is the 'super class' of all errors
    50  type callError struct {
    51  	stack         callstack
    52  	componentcode string
    53  	reasoncode    string
    54  	message       string
    55  	args          []interface{}
    56  	stackGetter   func(callstack) string
    57  	prevErr       error
    58  }
    59  
    60  func setupCallError(e *callError, generateStack bool) {
    61  	if !generateStack {
    62  		e.stackGetter = noopGetStack
    63  		return
    64  	}
    65  	e.stackGetter = getStack
    66  	stack := make([]uintptr, MaxCallStackLength)
    67  	skipCallersAndSetup := 2
    68  	length := runtime.Callers(skipCallersAndSetup, stack[:])
    69  	e.stack = stack[:length]
    70  }
    71  
    72  // Error comes from the error interface - it returns the error message and
    73  // appends the callstack, if available
    74  func (e *callError) Error() string {
    75  	message := e.GetErrorCode() + " - " + fmt.Sprintf(e.message, e.args...)
    76  	// check that the error has a callstack before proceeding
    77  	if e.GetStack() != "" {
    78  		message = appendCallStack(message, e.GetStack())
    79  	}
    80  	if e.prevErr != nil {
    81  		message += "\nCaused by: " + e.prevErr.Error()
    82  	}
    83  	return message
    84  }
    85  
    86  // GetStack returns the call stack as a string
    87  func (e *callError) GetStack() string {
    88  	return e.stackGetter(e.stack)
    89  }
    90  
    91  // GetComponentCode returns the component name
    92  func (e *callError) GetComponentCode() string {
    93  	return e.componentcode
    94  }
    95  
    96  // GetReasonCode returns the reason code - i.e. why the error occurred
    97  func (e *callError) GetReasonCode() string {
    98  	return e.reasoncode
    99  }
   100  
   101  // GetErrorCode returns a formatted error code string
   102  func (e *callError) GetErrorCode() string {
   103  	return fmt.Sprintf("%s:%s", e.componentcode, e.reasoncode)
   104  }
   105  
   106  // Message returns the corresponding error message for this error in default
   107  // language.
   108  func (e *callError) Message() string {
   109  	message := e.GetErrorCode() + " - " + fmt.Sprintf(e.message, e.args...)
   110  
   111  	if e.prevErr != nil {
   112  		switch previousError := e.prevErr.(type) {
   113  		case CallStackError:
   114  			message += "\nCaused by: " + previousError.Message()
   115  		default:
   116  			message += "\nCaused by: " + e.prevErr.Error()
   117  		}
   118  	}
   119  	return message
   120  }
   121  
   122  func appendCallStack(message string, callstack string) string {
   123  	return message + "\n" + callstack
   124  }
   125  
   126  // Error creates a CallStackError using a specific component code and reason
   127  // code (no callstack is generated)
   128  func Error(componentcode string, reasoncode string, message string, args ...interface{}) CallStackError {
   129  	return newError(componentcode, reasoncode, message, args...).GenerateStack(false)
   130  }
   131  
   132  // ErrorWithCallstack creates a CallStackError using a specific component code
   133  // and reason code and generates its callstack
   134  func ErrorWithCallstack(componentcode string, reasoncode string, message string, args ...interface{}) CallStackError {
   135  	return newError(componentcode, reasoncode, message, args...).GenerateStack(true)
   136  }
   137  
   138  func newError(componentcode string, reasoncode string, message string, args ...interface{}) CallStackError {
   139  	e := &callError{}
   140  	e.setErrorFields(componentcode, reasoncode, message, args...)
   141  	return e
   142  }
   143  
   144  // GenerateStack generates the callstack for a CallStackError
   145  func (e *callError) GenerateStack(flag bool) CallStackError {
   146  	setupCallError(e, flag)
   147  	return e
   148  }
   149  
   150  // WrapError wraps a previous error into a CallStackError
   151  func (e *callError) WrapError(prevErr error) CallStackError {
   152  	e.prevErr = prevErr
   153  	return e
   154  }
   155  
   156  func (e *callError) setErrorFields(componentcode string, reasoncode string, message string, args ...interface{}) {
   157  	if isValidComponentOrReasonCode(componentcode, componentPattern) {
   158  		e.componentcode = componentcode
   159  	}
   160  	if isValidComponentOrReasonCode(reasoncode, reasonPattern) {
   161  		e.reasoncode = reasoncode
   162  	}
   163  	if message != "" {
   164  		e.message = message
   165  	}
   166  	e.args = args
   167  }
   168  
   169  func isValidComponentOrReasonCode(componentOrReasonCode string, regExp string) bool {
   170  	if componentOrReasonCode == "" {
   171  		return false
   172  	}
   173  	re, _ := regexp.Compile(regExp)
   174  	matched := re.FindString(componentOrReasonCode)
   175  	if len(matched) != len(componentOrReasonCode) {
   176  		return false
   177  	}
   178  	return true
   179  }
   180  
   181  func getStack(stack callstack) string {
   182  	buf := bytes.Buffer{}
   183  	if stack == nil {
   184  		return fmt.Sprintf("No call stack available")
   185  	}
   186  	// this removes the core/errors module calls from the callstack because they
   187  	// are not useful for debugging
   188  	const firstNonErrorModuleCall int = 2
   189  	stack = stack[firstNonErrorModuleCall:]
   190  	for i, pc := range stack {
   191  		f := runtime.FuncForPC(pc)
   192  		file, line := f.FileLine(pc)
   193  		if i != len(stack)-1 {
   194  			buf.WriteString(fmt.Sprintf("%s:%d %s\n", file, line, f.Name()))
   195  		} else {
   196  			buf.WriteString(fmt.Sprintf("%s:%d %s", file, line, f.Name()))
   197  		}
   198  	}
   199  	return fmt.Sprintf("%s", buf.Bytes())
   200  }
   201  
   202  func noopGetStack(stack callstack) string {
   203  	return ""
   204  }