github.com/waldiirawan/apm-agent-go/v2@v2.2.2/error.go (about)

     1  // Licensed to Elasticsearch B.V. under one or more contributor
     2  // license agreements. See the NOTICE file distributed with
     3  // this work for additional information regarding copyright
     4  // ownership. Elasticsearch B.V. licenses this file to you under
     5  // the Apache License, Version 2.0 (the "License"); you may
     6  // 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,
    12  // software distributed under the License is distributed on an
    13  // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    14  // KIND, either express or implied.  See the License for the
    15  // specific language governing permissions and limitations
    16  // under the License.
    17  
    18  package apm // import "github.com/waldiirawan/apm-agent-go/v2"
    19  
    20  import (
    21  	"crypto/rand"
    22  	"fmt"
    23  	"net"
    24  	"os"
    25  	"reflect"
    26  	"syscall"
    27  	"time"
    28  
    29  	"github.com/waldiirawan/apm-agent-go/v2/model"
    30  	"github.com/waldiirawan/apm-agent-go/v2/stacktrace"
    31  )
    32  
    33  const (
    34  	// maxErrorGraphSize is the maximum number of errors
    35  	// to report in an error tree. Once this number of
    36  	// nodes is reached, we will stop recursing through
    37  	// error causes.
    38  	maxErrorTreeNodes = 50
    39  )
    40  
    41  // Recovered creates an Error with t.NewError(err), where
    42  // err is either v (if v implements error), or otherwise
    43  // fmt.Errorf("%v", v). The value v is expected to have
    44  // come from a panic.
    45  func (t *Tracer) Recovered(v interface{}) *Error {
    46  	var e *Error
    47  	switch v := v.(type) {
    48  	case error:
    49  		e = t.NewError(v)
    50  	default:
    51  		e = t.NewError(fmt.Errorf("%v", v))
    52  	}
    53  	return e
    54  }
    55  
    56  // NewError returns a new Error with details taken from err.
    57  // NewError will panic if called with a nil error.
    58  //
    59  // The exception message will be set to err.Error().
    60  // The exception module and type will be set to the package
    61  // and type name of the cause of the error, respectively,
    62  // where the cause has the same definition as given by
    63  // github.com/pkg/errors.
    64  //
    65  // If err implements
    66  //
    67  //	type interface {
    68  //	    StackTrace() github.com/pkg/errors.StackTrace
    69  //	}
    70  //
    71  // or
    72  //
    73  //	type interface {
    74  //	    StackTrace() []stacktrace.Frame
    75  //	}
    76  //
    77  // then one of those will be used to set the error
    78  // stacktrace. Otherwise, NewError will take a stacktrace.
    79  //
    80  // If err implements
    81  //
    82  //	type interface {Type() string}
    83  //
    84  // then that will be used to set the error type.
    85  //
    86  // If err implements
    87  //
    88  //	type interface {Code() string}
    89  //
    90  // or
    91  //
    92  //	type interface {Code() float64}
    93  //
    94  // then one of those will be used to set the error code.
    95  func (t *Tracer) NewError(err error) *Error {
    96  	if err == nil {
    97  		panic("NewError must be called with a non-nil error")
    98  	}
    99  	e := t.newError()
   100  	e.cause = err
   101  	e.err = err.Error()
   102  	if e.recording {
   103  		rand.Read(e.ID[:]) // ignore error, can't do anything about it
   104  		initException(&e.exception, err, e.stackTraceLimit)
   105  		if len(e.exception.stacktrace) == 0 {
   106  			e.SetStacktrace(2)
   107  		}
   108  	}
   109  	return e
   110  }
   111  
   112  // NewErrorLog returns a new Error for the given ErrorLogRecord.
   113  //
   114  // The resulting Error's stacktrace will not be set. Call the
   115  // SetStacktrace method to set it, if desired.
   116  //
   117  // If r.Message is empty, "[EMPTY]" will be used.
   118  func (t *Tracer) NewErrorLog(r ErrorLogRecord) *Error {
   119  	e := t.newError()
   120  	e.cause = r.Error
   121  	e.err = e.log.Message
   122  	if e.recording {
   123  		e.log = ErrorLogRecord{
   124  			Message:       truncateString(r.Message),
   125  			MessageFormat: truncateString(r.MessageFormat),
   126  			Level:         truncateString(r.Level),
   127  			LoggerName:    truncateString(r.LoggerName),
   128  		}
   129  		if e.log.Message == "" {
   130  			e.log.Message = "[EMPTY]"
   131  		}
   132  		rand.Read(e.ID[:]) // ignore error, can't do anything about it
   133  		if r.Error != nil {
   134  			initException(&e.exception, r.Error, e.stackTraceLimit)
   135  		}
   136  	}
   137  	return e
   138  }
   139  
   140  // newError returns a new Error associated with the Tracer.
   141  func (t *Tracer) newError() *Error {
   142  	e, _ := t.errorDataPool.Get().(*ErrorData)
   143  	if e == nil {
   144  		e = &ErrorData{
   145  			tracer: t,
   146  			Context: Context{
   147  				captureBodyMask: CaptureBodyErrors,
   148  			},
   149  		}
   150  	}
   151  
   152  	instrumentationConfig := t.instrumentationConfig()
   153  	e.recording = instrumentationConfig.recording
   154  	if e.recording {
   155  		e.Timestamp = time.Now()
   156  		e.Context.captureHeaders = instrumentationConfig.captureHeaders
   157  		e.Context.sanitizedFieldNames = instrumentationConfig.sanitizedFieldNames
   158  		e.stackTraceLimit = instrumentationConfig.stackTraceLimit
   159  	}
   160  
   161  	return &Error{ErrorData: e}
   162  }
   163  
   164  // Error describes an error occurring in the monitored service.
   165  type Error struct {
   166  	// ErrorData holds the error data. This field is set to nil when
   167  	// the error's Send method is called.
   168  	*ErrorData
   169  
   170  	// cause holds the original error.
   171  	//
   172  	// It is accessible via the Cause method:
   173  	// https://godoc.org/github.com/pkg/errors#Cause
   174  	cause error
   175  
   176  	// string holds original error string
   177  	err string
   178  }
   179  
   180  // ErrorData holds the details for an error, and is embedded inside Error.
   181  // When the error is sent, its ErrorData field will be set to nil.
   182  type ErrorData struct {
   183  	tracer             *Tracer
   184  	recording          bool
   185  	stackTraceLimit    int
   186  	exception          exceptionData
   187  	log                ErrorLogRecord
   188  	logStacktrace      []stacktrace.Frame
   189  	transactionSampled bool
   190  	transactionName    string
   191  	transactionType    string
   192  
   193  	// ID is the unique identifier of the error. This is set by
   194  	// the various error constructors, and is exposed only so
   195  	// the error ID can be logged or displayed to the user.
   196  	ID ErrorID
   197  
   198  	// TraceID is the unique identifier of the trace in which
   199  	// this error occurred. If the error is not associated with
   200  	// a trace, this will be the zero value.
   201  	TraceID TraceID
   202  
   203  	// TransactionID is the unique identifier of the transaction
   204  	// in which this error occurred. If the error is not associated
   205  	// with a transaction, this will be the zero value.
   206  	TransactionID SpanID
   207  
   208  	// ParentID is the unique identifier of the transaction or span
   209  	// in which this error occurred. If the error is not associated
   210  	// with a transaction or span, this will be the zero value.
   211  	ParentID SpanID
   212  
   213  	// Culprit is the name of the function that caused the error.
   214  	//
   215  	// This is initially unset; if it remains unset by the time
   216  	// Send is invoked, and the error has a stacktrace, the first
   217  	// non-library frame in the stacktrace will be considered the
   218  	// culprit.
   219  	Culprit string
   220  
   221  	// Timestamp records the time at which the error occurred.
   222  	// This is set when the Error object is created, but may
   223  	// be overridden any time before the Send method is called.
   224  	Timestamp time.Time
   225  
   226  	// Handled records whether or not the error was handled. This
   227  	// is ignored by "log" errors with no associated error value.
   228  	Handled bool
   229  
   230  	// Context holds the context for this error.
   231  	Context Context
   232  }
   233  
   234  // Cause returns original error assigned to Error, nil if Error or Error.cause is nil.
   235  // https://godoc.org/github.com/pkg/errors#Cause
   236  func (e *Error) Cause() error {
   237  	if e != nil {
   238  		return e.cause
   239  	}
   240  	return nil
   241  }
   242  
   243  // Error returns string message for error.
   244  // if Error or Error.cause is nil, "[EMPTY]" will be used.
   245  func (e *Error) Error() string {
   246  	if e != nil {
   247  		return e.err
   248  	}
   249  	return "[EMPTY]"
   250  }
   251  
   252  // SetTransaction sets TraceID, TransactionID, and ParentID to the transaction's
   253  // IDs, and records the transaction's Type and whether or not it was sampled.
   254  //
   255  // If any custom context has been recorded in tx, it will also be carried across
   256  // to e, but will not override any custom context already recorded on e.
   257  //
   258  // SetTransaction also has the effect of setting tx's default outcome to "failure",
   259  // unless it is explicitly specified by the instrumentation.
   260  func (e *Error) SetTransaction(tx *Transaction) {
   261  	tx.mu.RLock()
   262  	traceContext := tx.traceContext
   263  	var txType, txName string
   264  	var custom model.IfaceMap
   265  	if !tx.ended() {
   266  		txType = tx.Type
   267  		txName = tx.Name
   268  		custom = tx.Context.model.Custom
   269  		tx.TransactionData.mu.Lock()
   270  		tx.TransactionData.errorCaptured = true
   271  		tx.TransactionData.mu.Unlock()
   272  	}
   273  	tx.mu.RUnlock()
   274  	e.setSpanData(traceContext, traceContext.Span, txType, txName, custom)
   275  }
   276  
   277  // SetSpan sets TraceID, TransactionID, and ParentID to the span's IDs.
   278  //
   279  // There is no need to call both SetTransaction and SetSpan. If you do call
   280  // both, then SetSpan must be called second in order to set the error's
   281  // ParentID correctly.
   282  //
   283  // If any custom context has been recorded in s's transaction, it will
   284  // also be carried across to e, but will not override any custom context
   285  // already recorded on e.
   286  func (e *Error) SetSpan(s *Span) {
   287  	var txType, txName string
   288  	var custom model.IfaceMap
   289  	if s.tx != nil {
   290  		s.tx.mu.RLock()
   291  		if !s.tx.ended() {
   292  			txType = s.tx.Type
   293  			txName = s.tx.Name
   294  			custom = s.tx.Context.model.Custom
   295  			s.tx.TransactionData.mu.Lock()
   296  			s.tx.TransactionData.errorCaptured = true
   297  			s.tx.TransactionData.mu.Unlock()
   298  		}
   299  		s.tx.mu.RUnlock()
   300  
   301  		s.mu.RLock()
   302  		if !s.ended() {
   303  			s.SpanData.mu.Lock()
   304  			s.SpanData.errorCaptured = true
   305  			s.SpanData.mu.Unlock()
   306  		}
   307  		s.mu.RUnlock()
   308  	}
   309  	e.setSpanData(s.traceContext, s.transactionID, txType, txName, custom)
   310  }
   311  
   312  func (e *Error) setSpanData(
   313  	traceContext TraceContext,
   314  	transactionID SpanID,
   315  	transactionType, transactionName string,
   316  	customContext model.IfaceMap,
   317  ) {
   318  	e.TraceID = traceContext.Trace
   319  	e.ParentID = traceContext.Span
   320  	e.TransactionID = transactionID
   321  	e.transactionSampled = traceContext.Options.Recorded()
   322  	if e.transactionSampled {
   323  		e.transactionName = transactionName
   324  		e.transactionType = transactionType
   325  	}
   326  	if n := len(customContext); n != 0 {
   327  		m := len(e.Context.model.Custom)
   328  		e.Context.model.Custom = append(e.Context.model.Custom, customContext...)
   329  		// If there was already custom context in e, shift the custom context from
   330  		// tx to the beginning of the slice so that e's context takes precedence.
   331  		if m != 0 {
   332  			copy(e.Context.model.Custom[n:], e.Context.model.Custom[:m])
   333  			copy(e.Context.model.Custom[:n], customContext)
   334  		}
   335  	}
   336  }
   337  
   338  // Send enqueues the error for sending to the Elastic APM server.
   339  //
   340  // Send will set e.ErrorData to nil, so the error must not be
   341  // modified after Send returns.
   342  func (e *Error) Send() {
   343  	if e == nil || e.sent() {
   344  		return
   345  	}
   346  	if e.recording {
   347  		e.ErrorData.enqueue()
   348  	} else {
   349  		e.reset()
   350  	}
   351  	e.ErrorData = nil
   352  }
   353  
   354  func (e *Error) sent() bool {
   355  	return e.ErrorData == nil
   356  }
   357  
   358  func (e *ErrorData) enqueue() {
   359  	select {
   360  	case e.tracer.events <- tracerEvent{eventType: errorEvent, err: e}:
   361  	default:
   362  		// Enqueuing an error should never block.
   363  		e.tracer.stats.accumulate(TracerStats{ErrorsDropped: 1})
   364  		e.reset()
   365  	}
   366  }
   367  
   368  func (e *ErrorData) reset() {
   369  	*e = ErrorData{
   370  		tracer:        e.tracer,
   371  		logStacktrace: e.logStacktrace[:0],
   372  		Context:       e.Context,
   373  		exception:     e.exception,
   374  	}
   375  	e.Context.reset()
   376  	e.exception.reset()
   377  	e.tracer.errorDataPool.Put(e)
   378  }
   379  
   380  type exceptionData struct {
   381  	message    string
   382  	stacktrace []stacktrace.Frame
   383  	cause      []exceptionData
   384  	ErrorDetails
   385  }
   386  
   387  func (e *exceptionData) reset() {
   388  	*e = exceptionData{
   389  		cause:      e.cause[:0],
   390  		stacktrace: e.stacktrace[:0],
   391  		ErrorDetails: ErrorDetails{
   392  			attrs: e.ErrorDetails.attrs,
   393  			Cause: e.ErrorDetails.Cause[:0],
   394  		},
   395  	}
   396  	for k := range e.attrs {
   397  		delete(e.attrs, k)
   398  	}
   399  }
   400  
   401  func initException(e *exceptionData, err error, stackTraceLimit int) {
   402  	b := exceptionDataBuilder{stackTraceLimit: stackTraceLimit}
   403  	b.init(e, err)
   404  }
   405  
   406  type exceptionDataBuilder struct {
   407  	stackTraceLimit int
   408  	errorCount      int
   409  	pointerErrors   map[uintptr]struct{}
   410  }
   411  
   412  func (b *exceptionDataBuilder) init(e *exceptionData, err error) bool {
   413  	b.errorCount++
   414  	reflectValue := reflect.ValueOf(err)
   415  	reflectType := reflectValue.Type()
   416  	switch reflectType.Kind() {
   417  	case reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr, reflect.Slice, reflect.UnsafePointer:
   418  		// Prevent infinite recursion due to cyclic error causes.
   419  		ptrVal := reflectValue.Pointer()
   420  		if b.pointerErrors == nil {
   421  			b.pointerErrors = map[uintptr]struct{}{ptrVal: struct{}{}}
   422  		} else {
   423  			if _, ok := b.pointerErrors[ptrVal]; ok {
   424  				return false
   425  			}
   426  			b.pointerErrors[ptrVal] = struct{}{}
   427  		}
   428  	}
   429  
   430  	e.message = truncateString(err.Error())
   431  	if e.message == "" {
   432  		e.message = "[EMPTY]"
   433  	}
   434  
   435  	namedType := reflectType
   436  	if reflectType.Name() == "" && reflectType.Kind() == reflect.Ptr {
   437  		namedType = reflectType.Elem()
   438  	}
   439  	e.Type.Name = namedType.Name()
   440  	e.Type.PackagePath = namedType.PkgPath()
   441  
   442  	// If the error implements Type, use that to
   443  	// override the type name determined through
   444  	// reflection.
   445  	if err, ok := err.(interface {
   446  		Type() string
   447  	}); ok {
   448  		e.Type.Name = err.Type()
   449  	}
   450  
   451  	// If the error implements a Code method, use
   452  	// that to set the exception code.
   453  	switch err := err.(type) {
   454  	case interface {
   455  		Code() string
   456  	}:
   457  		e.Code.String = err.Code()
   458  	case interface {
   459  		Code() float64
   460  	}:
   461  		e.Code.Number = err.Code()
   462  	}
   463  
   464  	// If the error implements an Unwrap or Cause method, use that to set the cause error.
   465  	// Unwrap is defined by errors wrapped using fmt.Errorf, while Cause is defined by
   466  	// errors wrapped using pkg/errors.Wrap.
   467  	switch err := err.(type) {
   468  	case interface{ Unwrap() error }:
   469  		if cause := err.Unwrap(); cause != nil {
   470  			e.ErrorDetails.Cause = append(e.ErrorDetails.Cause, cause)
   471  		}
   472  	case interface{ Cause() error }:
   473  		if cause := err.Cause(); cause != nil {
   474  			e.ErrorDetails.Cause = append(e.ErrorDetails.Cause, cause)
   475  		}
   476  	}
   477  
   478  	// Run registered ErrorDetailers over the error.
   479  	for _, ed := range typeErrorDetailers[reflectType] {
   480  		ed.ErrorDetails(err, &e.ErrorDetails)
   481  	}
   482  	for _, ed := range errorDetailers {
   483  		ed.ErrorDetails(err, &e.ErrorDetails)
   484  	}
   485  
   486  	e.Code.String = truncateString(e.Code.String)
   487  	e.Type.Name = truncateString(e.Type.Name)
   488  	e.Type.PackagePath = truncateString(e.Type.PackagePath)
   489  	e.stacktrace = stacktrace.AppendErrorStacktrace(e.stacktrace, err, b.stackTraceLimit)
   490  
   491  	for _, err := range e.ErrorDetails.Cause {
   492  		if b.errorCount >= maxErrorTreeNodes {
   493  			break
   494  		}
   495  		var data exceptionData
   496  		if b.init(&data, err) {
   497  			e.cause = append(e.cause, data)
   498  		}
   499  	}
   500  	return true
   501  }
   502  
   503  // SetStacktrace sets the stacktrace for the error,
   504  // skipping the first skip number of frames, excluding
   505  // the SetStacktrace function.
   506  func (e *Error) SetStacktrace(skip int) {
   507  	out := &e.exception.stacktrace
   508  	if e.log.Message != "" {
   509  		out = &e.logStacktrace
   510  	}
   511  	*out = stacktrace.AppendStacktrace((*out)[:0], skip+1, e.stackTraceLimit)
   512  }
   513  
   514  // ErrorLogRecord holds details of an error log record.
   515  type ErrorLogRecord struct {
   516  	// Message holds the message for the log record,
   517  	// e.g. "failed to connect to %s".
   518  	//
   519  	// If this is empty, "[EMPTY]" will be used.
   520  	Message string
   521  
   522  	// MessageFormat holds the non-interpolated format
   523  	// of the log record, e.g. "failed to connect to %s".
   524  	//
   525  	// This is optional.
   526  	MessageFormat string
   527  
   528  	// Level holds the severity level of the log record.
   529  	//
   530  	// This is optional.
   531  	Level string
   532  
   533  	// LoggerName holds the name of the logger used.
   534  	//
   535  	// This is optional.
   536  	LoggerName string
   537  
   538  	// Error is an error associated with the log record.
   539  	//
   540  	// This is optional.
   541  	Error error
   542  }
   543  
   544  // ErrorID uniquely identifies an error.
   545  type ErrorID TraceID
   546  
   547  // String returns id in its hex-encoded format.
   548  func (id ErrorID) String() string {
   549  	return TraceID(id).String()
   550  }
   551  
   552  func init() {
   553  	RegisterErrorDetailer(ErrorDetailerFunc(func(err error, details *ErrorDetails) {
   554  		if errTemporary(err) {
   555  			details.SetAttr("temporary", true)
   556  		}
   557  		if errTimeout(err) {
   558  			details.SetAttr("timeout", true)
   559  		}
   560  	}))
   561  	RegisterTypeErrorDetailer(reflect.TypeOf(&net.OpError{}), ErrorDetailerFunc(func(err error, details *ErrorDetails) {
   562  		opErr := err.(*net.OpError)
   563  		details.SetAttr("op", opErr.Op)
   564  		details.SetAttr("net", opErr.Net)
   565  		if opErr.Source != nil {
   566  			if addr := opErr.Source; addr != nil {
   567  				details.SetAttr("source", fmt.Sprintf("%s:%s", addr.Network(), addr.String()))
   568  			}
   569  		}
   570  		if opErr.Addr != nil {
   571  			if addr := opErr.Addr; addr != nil {
   572  				details.SetAttr("addr", fmt.Sprintf("%s:%s", addr.Network(), addr.String()))
   573  			}
   574  		}
   575  		details.Cause = append(details.Cause, opErr.Err)
   576  	}))
   577  	RegisterTypeErrorDetailer(reflect.TypeOf(&os.LinkError{}), ErrorDetailerFunc(func(err error, details *ErrorDetails) {
   578  		linkErr := err.(*os.LinkError)
   579  		details.SetAttr("op", linkErr.Op)
   580  		details.SetAttr("old", linkErr.Old)
   581  		details.SetAttr("new", linkErr.New)
   582  		details.Cause = append(details.Cause, linkErr.Err)
   583  	}))
   584  	RegisterTypeErrorDetailer(reflect.TypeOf(&os.PathError{}), ErrorDetailerFunc(func(err error, details *ErrorDetails) {
   585  		pathErr := err.(*os.PathError)
   586  		details.SetAttr("op", pathErr.Op)
   587  		details.SetAttr("path", pathErr.Path)
   588  		details.Cause = append(details.Cause, pathErr.Err)
   589  	}))
   590  	RegisterTypeErrorDetailer(reflect.TypeOf(&os.SyscallError{}), ErrorDetailerFunc(func(err error, details *ErrorDetails) {
   591  		syscallErr := err.(*os.SyscallError)
   592  		details.SetAttr("syscall", syscallErr.Syscall)
   593  		details.Cause = append(details.Cause, syscallErr.Err)
   594  	}))
   595  	RegisterTypeErrorDetailer(reflect.TypeOf(syscall.Errno(0)), ErrorDetailerFunc(func(err error, details *ErrorDetails) {
   596  		errno := err.(syscall.Errno)
   597  		details.Code.String = errnoName(errno)
   598  		if details.Code.String == "" {
   599  			details.Code.Number = float64(errno)
   600  		}
   601  	}))
   602  }
   603  
   604  func errTemporary(err error) bool {
   605  	type temporaryError interface {
   606  		Temporary() bool
   607  	}
   608  	terr, ok := err.(temporaryError)
   609  	return ok && terr.Temporary()
   610  }
   611  
   612  func errTimeout(err error) bool {
   613  	type timeoutError interface {
   614  		Timeout() bool
   615  	}
   616  	terr, ok := err.(timeoutError)
   617  	return ok && terr.Timeout()
   618  }
   619  
   620  // RegisterTypeErrorDetailer registers e to be called for any error with
   621  // the concrete type t.
   622  //
   623  // Each ErrorDetailer registered in this way will be called, in the order
   624  // registered, for each error of type t created via Tracer.NewError or
   625  // Tracer.NewErrorLog.
   626  //
   627  // RegisterTypeErrorDetailer must not be called during tracer operation;
   628  // it is intended to be called at package init time.
   629  func RegisterTypeErrorDetailer(t reflect.Type, e ErrorDetailer) {
   630  	typeErrorDetailers[t] = append(typeErrorDetailers[t], e)
   631  }
   632  
   633  // RegisterErrorDetailer registers e in the global list of ErrorDetailers.
   634  //
   635  // Each ErrorDetailer registered in this way will be called, in the order
   636  // registered, for each error created via Tracer.NewError or Tracer.NewErrorLog.
   637  //
   638  // RegisterErrorDetailer must not be called during tracer operation; it is
   639  // intended to be called at package init time.
   640  func RegisterErrorDetailer(e ErrorDetailer) {
   641  	errorDetailers = append(errorDetailers, e)
   642  }
   643  
   644  var (
   645  	typeErrorDetailers = make(map[reflect.Type][]ErrorDetailer)
   646  	errorDetailers     []ErrorDetailer
   647  )
   648  
   649  // ErrorDetails holds details of an error, which can be altered or
   650  // extended by registering an ErrorDetailer with RegisterErrorDetailer
   651  // or RegisterTypeErrorDetailer.
   652  type ErrorDetails struct {
   653  	attrs map[string]interface{}
   654  
   655  	// Type holds information about the error type, initialized
   656  	// with the type name and type package path using reflection.
   657  	Type struct {
   658  		// Name holds the error type name.
   659  		Name string
   660  
   661  		// PackagePath holds the error type package path.
   662  		PackagePath string
   663  	}
   664  
   665  	// Code holds an error code.
   666  	Code struct {
   667  		// String holds a string-based error code. If this is set, then Number is ignored.
   668  		//
   669  		// This field will be initialized to the result of calling an error's Code method,
   670  		// if the error implements the following interface:
   671  		//
   672  		//     type interface StringCoder {
   673  		//         Code() string
   674  		//     }
   675  		String string
   676  
   677  		// Number holds a numerical error code. This is ignored if String is set.
   678  		//
   679  		// This field will be initialized to the result of calling an error's Code
   680  		// method, if the error implements the following interface:
   681  		//
   682  		//     type interface NumberCoder {
   683  		//         Code() float64
   684  		//     }
   685  		Number float64
   686  	}
   687  
   688  	// Cause holds the errors that were the cause of this error.
   689  	Cause []error
   690  }
   691  
   692  // SetAttr sets the attribute with key k to value v.
   693  func (d *ErrorDetails) SetAttr(k string, v interface{}) {
   694  	if d.attrs == nil {
   695  		d.attrs = make(map[string]interface{})
   696  	}
   697  	d.attrs[k] = v
   698  }
   699  
   700  // ErrorDetailer defines an interface for altering or extending the ErrorDetails for an error.
   701  //
   702  // ErrorDetailers can be registered using the package-level functions RegisterErrorDetailer and
   703  // RegisterTypeErrorDetailer.
   704  type ErrorDetailer interface {
   705  	// ErrorDetails is called to update or alter details for err.
   706  	ErrorDetails(err error, details *ErrorDetails)
   707  }
   708  
   709  // ErrorDetailerFunc is a function type implementing ErrorDetailer.
   710  type ErrorDetailerFunc func(error, *ErrorDetails)
   711  
   712  // ErrorDetails calls f(err, details).
   713  func (f ErrorDetailerFunc) ErrorDetails(err error, details *ErrorDetails) {
   714  	f(err, details)
   715  }