package internal import ( "bytes" "fmt" "net/http" "strconv" "time" "github.com/newrelic/go-agent/internal/jsonx" ) const ( // PanicErrorKlass is the error klass used for errors generated by // recovering panics in txn.End. PanicErrorKlass = "panic" ) func panicValueMsg(v interface{}) string { switch val := v.(type) { case error: return val.Error() default: return fmt.Sprintf("%v", v) } } // TxnErrorFromPanic creates a new TxnError from a panic. func TxnErrorFromPanic(now time.Time, v interface{}) ErrorData { return ErrorData{ When: now, Msg: panicValueMsg(v), Klass: PanicErrorKlass, } } // TxnErrorFromResponseCode creates a new TxnError from an http response code. func TxnErrorFromResponseCode(now time.Time, code int) ErrorData { return ErrorData{ When: now, Msg: http.StatusText(code), Klass: strconv.Itoa(code), } } // ErrorData contains the information about a recorded error. type ErrorData struct { When time.Time Stack StackTrace ExtraAttributes map[string]interface{} Msg string Klass string } // TxnError combines error data with information about a transaction. TxnError is used for // both error events and traced errors. type TxnError struct { ErrorData TxnEvent } // ErrorEvent and tracedError are separate types so that error events and traced errors can have // different WriteJSON methods. type ErrorEvent TxnError type tracedError TxnError // TxnErrors is a set of errors captured in a Transaction. type TxnErrors []*ErrorData // NewTxnErrors returns a new empty TxnErrors. func NewTxnErrors(max int) TxnErrors { return make([]*ErrorData, 0, max) } // Add adds a TxnError. func (errors *TxnErrors) Add(e ErrorData) { if len(*errors) < cap(*errors) { *errors = append(*errors, &e) } } func (h *tracedError) WriteJSON(buf *bytes.Buffer) { buf.WriteByte('[') jsonx.AppendFloat(buf, timeToFloatMilliseconds(h.When)) buf.WriteByte(',') jsonx.AppendString(buf, h.FinalName) buf.WriteByte(',') jsonx.AppendString(buf, h.Msg) buf.WriteByte(',') jsonx.AppendString(buf, h.Klass) buf.WriteByte(',') buf.WriteByte('{') buf.WriteString(`"agentAttributes"`) buf.WriteByte(':') agentAttributesJSON(h.Attrs, buf, destError) buf.WriteByte(',') buf.WriteString(`"userAttributes"`) buf.WriteByte(':') userAttributesJSON(h.Attrs, buf, destError, h.ErrorData.ExtraAttributes) buf.WriteByte(',') buf.WriteString(`"intrinsics"`) buf.WriteByte(':') intrinsicsJSON(&h.TxnEvent, buf) if nil != h.Stack { buf.WriteByte(',') buf.WriteString(`"stack_trace"`) buf.WriteByte(':') h.Stack.WriteJSON(buf) } if h.CleanURL != "" { buf.WriteByte(',') buf.WriteString(`"request_uri"`) buf.WriteByte(':') jsonx.AppendString(buf, h.CleanURL) } buf.WriteByte('}') buf.WriteByte(']') } // MarshalJSON is used for testing. func (h *tracedError) MarshalJSON() ([]byte, error) { buf := &bytes.Buffer{} h.WriteJSON(buf) return buf.Bytes(), nil } type harvestErrors []*tracedError func newHarvestErrors(max int) harvestErrors { return make([]*tracedError, 0, max) } // MergeTxnErrors merges a transaction's errors into the harvest's errors. func MergeTxnErrors(errors *harvestErrors, errs TxnErrors, txnEvent TxnEvent) { for _, e := range errs { if len(*errors) == cap(*errors) { return } *errors = append(*errors, &tracedError{ TxnEvent: txnEvent, ErrorData: *e, }) } } func (errors harvestErrors) Data(agentRunID string, harvestStart time.Time) ([]byte, error) { if 0 == len(errors) { return nil, nil } estimate := 1024 * len(errors) buf := bytes.NewBuffer(make([]byte, 0, estimate)) buf.WriteByte('[') jsonx.AppendString(buf, agentRunID) buf.WriteByte(',') buf.WriteByte('[') for i, e := range errors { if i > 0 { buf.WriteByte(',') } e.WriteJSON(buf) } buf.WriteByte(']') buf.WriteByte(']') return buf.Bytes(), nil } func (errors harvestErrors) MergeIntoHarvest(h *Harvest) {} func (errors harvestErrors) EndpointMethod() string { return cmdErrorData }