github.com/mongodb/grip@v0.0.0-20240213223901-f906268d82b9/errors_timestamp.go (about)

     1  package grip
     2  
     3  import (
     4  	"fmt"
     5  	"time"
     6  
     7  	"github.com/mongodb/grip/level"
     8  	"github.com/pkg/errors"
     9  )
    10  
    11  type errorCauser interface {
    12  	Cause() error
    13  }
    14  
    15  // ErrorTimeFinder unwraps a timestamp annotated error if possible and
    16  // is capable of finding a timestamp in an error that has been
    17  // annotated using pkg/errors.
    18  func ErrorTimeFinder(err error) (time.Time, bool) {
    19  	if err == nil {
    20  		return time.Time{}, false
    21  	}
    22  
    23  	if tserr, ok := err.(*timestampError); ok {
    24  		if tserr == nil {
    25  			return time.Time{}, false
    26  		}
    27  		return tserr.time, true
    28  	}
    29  
    30  	for {
    31  		if e, ok := err.(errorCauser); ok {
    32  			if tserr, ok := e.(*timestampError); ok {
    33  				if tserr == nil {
    34  					break
    35  				}
    36  				return tserr.time, true
    37  			}
    38  			err = e.Cause()
    39  			continue
    40  		}
    41  		break
    42  	}
    43  
    44  	return time.Time{}, false
    45  }
    46  
    47  type timestampError struct {
    48  	err      error
    49  	time     time.Time
    50  	extended bool
    51  
    52  	// message.Composer data
    53  	ErrorValue string `bson:"error" json:"error" yaml:"error"`
    54  	Extended   string `bson:"extended,omitempty" json:"extended,omitempty" yaml:"extended,omitempty"`
    55  	Metadata   struct {
    56  		Level   level.Priority         `bson:"level,omitempty" json:"level,omitempty" yaml:"level,omitempty"`
    57  		Context map[string]interface{} `bson:"context,omitempty" json:"context,omitempty" yaml:"context,omitempty"`
    58  	} `bson:"metadata,omitempty" json:"metadata,omitempty" yaml:"metadata,omitempty"`
    59  }
    60  
    61  func newTimeStampError(err error) *timestampError {
    62  	if err == nil {
    63  		return nil
    64  	}
    65  
    66  	switch v := err.(type) {
    67  	case *timestampError:
    68  		return v
    69  	default:
    70  		return &timestampError{
    71  			err:  err,
    72  			time: time.Now(),
    73  		}
    74  	}
    75  }
    76  
    77  func (e *timestampError) setExtended(v bool) *timestampError { e.extended = v; return e }
    78  
    79  // WrapErrorTime annotates an error with the timestamp. The underlying
    80  // concrete object implements message.Composer as well as error.
    81  func WrapErrorTime(err error) error {
    82  	return newTimeStampError(err)
    83  }
    84  
    85  // WrapErrorTimeMessage annotates an error with the timestamp and a
    86  // string form. The underlying concrete object implements
    87  // message.Composer as well as error.
    88  func WrapErrorTimeMessage(err error, m string) error {
    89  	return newTimeStampError(errors.WithMessage(err, m))
    90  }
    91  
    92  // WrapErrorTimeMessagef annotates an error with a timestamp and a
    93  // string formated message, like fmt.Sprintf or fmt.Errorf. The
    94  // underlying concrete object implements  message.Composer as well as
    95  // error.
    96  func WrapErrorTimeMessagef(err error, m string, args ...interface{}) error {
    97  	return newTimeStampError(errors.WithMessage(err, fmt.Sprintf(m, args...)))
    98  }
    99  
   100  func (e *timestampError) String() string {
   101  	if e.err == nil {
   102  		return ""
   103  	}
   104  
   105  	if e.extended {
   106  		return fmt.Sprintf("%+v", e.err)
   107  	}
   108  
   109  	return e.err.Error()
   110  }
   111  
   112  func (e *timestampError) Cause() error { return e.err }
   113  
   114  func (e *timestampError) Error() string {
   115  	return fmt.Sprintf("[%s], %s", e.time.Format(time.RFC3339), e.String())
   116  }
   117  
   118  func (e *timestampError) Format(s fmt.State, verb rune) {
   119  	switch verb {
   120  	case 'v':
   121  		if s.Flag('+') {
   122  			_, _ = fmt.Fprintf(s, "[%s] %+v", e.time.Format(time.RFC3339), e.Cause())
   123  		}
   124  		fallthrough
   125  	case 's':
   126  		_, _ = fmt.Fprintf(s, "[%s] %s", e.time.Format(time.RFC3339), e.String())
   127  	case 'q':
   128  		_, _ = fmt.Fprintf(s, "[%s] %q", e.time.Format(time.RFC3339), e.String())
   129  	}
   130  }
   131  
   132  func (e *timestampError) Loggable() bool { return e.err != nil }
   133  func (e *timestampError) Raw() interface{} {
   134  	if e.ErrorValue == "" {
   135  		e.ErrorValue = e.String()
   136  
   137  		extended := fmt.Sprintf("%+v", e.err)
   138  		if extended != e.ErrorValue {
   139  			e.Extended = extended
   140  		}
   141  	}
   142  
   143  	return e
   144  }
   145  
   146  func (e *timestampError) Annotate(key string, v interface{}) error {
   147  	if e.Metadata.Context == nil {
   148  		e.Metadata.Context = map[string]interface{}{
   149  			key: v,
   150  		}
   151  		return nil
   152  	}
   153  
   154  	if _, ok := e.Metadata.Context[key]; ok {
   155  		return fmt.Errorf("key '%s' already exists", key)
   156  	}
   157  
   158  	e.Metadata.Context[key] = v
   159  
   160  	return nil
   161  }
   162  
   163  func (e *timestampError) Priority() level.Priority {
   164  	return e.Metadata.Level
   165  }
   166  func (e *timestampError) SetPriority(l level.Priority) error {
   167  	if !l.IsValid() {
   168  		return fmt.Errorf("%s (%d) is not a valid priority", l, l)
   169  	}
   170  
   171  	e.Metadata.Level = l
   172  	return nil
   173  }