go.undefinedlabs.com/scopeagent@v0.4.2/errors/handler.go (about)

     1  package errors
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"path/filepath"
     7  	"strings"
     8  	"time"
     9  
    10  	"github.com/go-errors/errors"
    11  	"github.com/opentracing/opentracing-go"
    12  	"github.com/opentracing/opentracing-go/log"
    13  
    14  	"go.undefinedlabs.com/scopeagent/instrumentation"
    15  	"go.undefinedlabs.com/scopeagent/tags"
    16  	"go.undefinedlabs.com/scopeagent/tracer"
    17  )
    18  
    19  type StackFrames struct {
    20  	File       string
    21  	LineNumber int
    22  	Name       string
    23  	Package    string
    24  }
    25  
    26  var markSpanAsError = errors.New("")
    27  
    28  // Write exception event in span using the recover data from panic
    29  func WriteExceptionEvent(span opentracing.Span, recoverData interface{}, skipFrames int) {
    30  	span.SetTag("error", true)
    31  	if recoverData == markSpanAsError {
    32  		return
    33  	}
    34  	var exceptionFields = getExceptionLogFields("error", recoverData, skipFrames+1)
    35  	span.LogFields(exceptionFields...)
    36  }
    37  
    38  func WriteExceptionEventInRawSpan(rawSpan *tracer.RawSpan, err **errors.Error) {
    39  	if rawSpan.Tags == nil {
    40  		rawSpan.Tags = opentracing.Tags{}
    41  	}
    42  	rawSpan.Tags["error"] = true
    43  	if *err != markSpanAsError {
    44  		var exceptionFields = getExceptionLogFields("error", *err, 1)
    45  		if rawSpan.Logs == nil {
    46  			rawSpan.Logs = []opentracing.LogRecord{}
    47  		}
    48  		rawSpan.Logs = append(rawSpan.Logs, opentracing.LogRecord{
    49  			Timestamp: time.Now(),
    50  			Fields:    exceptionFields,
    51  		})
    52  		*err = markSpanAsError
    53  	}
    54  }
    55  
    56  // Gets the current stack frames array
    57  func getCurrentStackFrames(skip int) []StackFrames {
    58  	skip = skip + 1
    59  	err := errors.New(nil)
    60  	errStack := err.StackFrames()
    61  	nLength := len(errStack) - skip
    62  	if nLength < 0 {
    63  		return nil
    64  	}
    65  	stackFrames := make([]StackFrames, 0)
    66  	for idx, frame := range errStack {
    67  		if idx >= skip {
    68  			stackFrames = append(stackFrames, StackFrames{
    69  				File:       filepath.Clean(frame.File),
    70  				LineNumber: frame.LineNumber,
    71  				Name:       frame.Name,
    72  				Package:    frame.Package,
    73  			})
    74  		}
    75  	}
    76  	return stackFrames
    77  }
    78  
    79  // Write log event with stacktrace in span using the recover data from panic
    80  func LogPanic(ctx context.Context, recoverData interface{}, skipFrames int) {
    81  	span := opentracing.SpanFromContext(ctx)
    82  	if span == nil {
    83  		return
    84  	}
    85  	var exceptionFields = getExceptionLogFields(tags.LogEvent, recoverData, skipFrames+1)
    86  	exceptionFields = append(exceptionFields, log.String(tags.LogEventLevel, tags.LogLevel_ERROR))
    87  	span.LogFields(exceptionFields...)
    88  }
    89  
    90  // Gets the current stacktrace
    91  func GetCurrentStackTrace(skip int) map[string]interface{} {
    92  	var exFrames []map[string]interface{}
    93  	for _, frame := range getCurrentStackFrames(skip + 1) {
    94  		exFrames = append(exFrames, map[string]interface{}{
    95  			"name":   frame.Name,
    96  			"module": frame.Package,
    97  			"file":   frame.File,
    98  			"line":   frame.LineNumber,
    99  		})
   100  	}
   101  	return map[string]interface{}{
   102  		"frames": exFrames,
   103  	}
   104  }
   105  
   106  // Get the current error with the fixed stacktrace
   107  func GetCurrentError(recoverData interface{}) *errors.Error {
   108  	return errors.Wrap(recoverData, 1)
   109  }
   110  
   111  func getExceptionLogFields(eventType string, recoverData interface{}, skipFrames int) []log.Field {
   112  	if recoverData != nil {
   113  		err := errors.Wrap(recoverData, 2+skipFrames)
   114  		errMessage := err.Error()
   115  		errStack := err.StackFrames()
   116  		exceptionData := getExceptionFrameData(errMessage, errStack)
   117  		source := ""
   118  
   119  		if errStack != nil && len(errStack) > 0 {
   120  			sourceRoot := instrumentation.GetSourceRoot()
   121  			for _, currentFrame := range errStack {
   122  				dir := filepath.Dir(currentFrame.File)
   123  				if strings.Index(dir, sourceRoot) != -1 {
   124  					source = fmt.Sprintf("%s:%d", currentFrame.File, currentFrame.LineNumber)
   125  					break
   126  				}
   127  			}
   128  		}
   129  
   130  		fields := make([]log.Field, 5)
   131  		fields[0] = log.String(tags.EventType, eventType)
   132  		fields[1] = log.String(tags.EventSource, source)
   133  		fields[2] = log.String(tags.EventMessage, errMessage)
   134  		fields[3] = log.String(tags.EventStack, getStringStack(err, errStack))
   135  		fields[4] = log.Object(tags.EventException, exceptionData)
   136  		return fields
   137  	}
   138  	return nil
   139  }
   140  
   141  func getStringStack(err *errors.Error, errStack []errors.StackFrame) string {
   142  	var frames []string
   143  	for _, frame := range errStack {
   144  		frames = append(frames, frame.String())
   145  	}
   146  	return fmt.Sprintf("[%s]: %s\n\n%s", err.TypeName(), err.Error(), strings.Join(frames, ""))
   147  }
   148  
   149  func getExceptionFrameData(errMessage string, errStack []errors.StackFrame) map[string]interface{} {
   150  	var exFrames []map[string]interface{}
   151  	for _, frame := range errStack {
   152  		exFrames = append(exFrames, map[string]interface{}{
   153  			"name":   frame.Name,
   154  			"module": frame.Package,
   155  			"file":   filepath.Clean(frame.File),
   156  			"line":   frame.LineNumber,
   157  		})
   158  	}
   159  	exStack := map[string]interface{}{
   160  		"frames": exFrames,
   161  	}
   162  	return map[string]interface{}{
   163  		"message":    errMessage,
   164  		"stacktrace": exStack,
   165  	}
   166  }