package internal import ( "bytes" "fmt" "net/http" "reflect" "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{}) TxnError { return TxnError{ When: now, Msg: panicValueMsg(v), Klass: PanicErrorKlass, } } // TxnErrorFromError creates a new TxnError from an error. func TxnErrorFromError(now time.Time, err error) TxnError { return TxnError{ When: now, Msg: err.Error(), Klass: reflect.TypeOf(err).String(), } } // TxnErrorFromResponseCode creates a new TxnError from an http response code. func TxnErrorFromResponseCode(now time.Time, code int) TxnError { return TxnError{ When: now, Msg: http.StatusText(code), Klass: strconv.Itoa(code), } } // TxnError is an error captured in a Transaction. type TxnError struct { When time.Time Stack *StackTrace Msg string Klass string } // TxnErrors is a set of errors captured in a Transaction. type TxnErrors []*TxnError // NewTxnErrors returns a new empty TxnErrors. func NewTxnErrors(max int) TxnErrors { return make([]*TxnError, 0, max) } // Add adds a TxnError. func (errors *TxnErrors) Add(e TxnError) { if len(*errors) < cap(*errors) { *errors = append(*errors, &e) } } func (h *harvestError) WriteJSON(buf *bytes.Buffer) { buf.WriteByte('[') jsonx.AppendFloat(buf, timeToFloatMilliseconds(h.When)) buf.WriteByte(',') jsonx.AppendString(buf, h.txnName) buf.WriteByte(',') jsonx.AppendString(buf, h.Msg) buf.WriteByte(',') jsonx.AppendString(buf, h.Klass) buf.WriteByte(',') buf.WriteByte('{') w := jsonFieldsWriter{buf: buf} if nil != h.Stack { w.writerField("stack_trace", h.Stack) } w.writerField("agentAttributes", agentAttributesJSONWriter{ attributes: h.attrs, dest: destError, }) w.writerField("userAttributes", userAttributesJSONWriter{ attributes: h.attrs, dest: destError, }) w.rawField("intrinsics", JSONString("{}")) if h.requestURI != "" { w.stringField("request_uri", h.requestURI) } buf.WriteByte('}') buf.WriteByte(']') } // MarshalJSON is used for testing. func (h *harvestError) MarshalJSON() ([]byte, error) { buf := &bytes.Buffer{} h.WriteJSON(buf) return buf.Bytes(), nil } type harvestError struct { TxnError txnName string requestURI string attrs *Attributes } type harvestErrors struct { errors []*harvestError } func newHarvestErrors(max int) *harvestErrors { return &harvestErrors{ errors: make([]*harvestError, 0, max), } } func harvestErrorFromTxnError(e *TxnError, txnName string, requestURI string, attrs *Attributes) *harvestError { return &harvestError{ TxnError: *e, txnName: txnName, requestURI: requestURI, attrs: attrs, } } func addTxnError(errors *harvestErrors, e *TxnError, txnName string, requestURI string, attrs *Attributes) { he := harvestErrorFromTxnError(e, txnName, requestURI, attrs) errors.errors = append(errors.errors, he) } // MergeTxnErrors merges a transaction's errors into the harvest's errors. func MergeTxnErrors(errors *harvestErrors, errs TxnErrors, txnName string, requestURI string, attrs *Attributes) { for _, e := range errs { if len(errors.errors) == cap(errors.errors) { return } addTxnError(errors, e, txnName, requestURI, attrs) } } func (errors *harvestErrors) Data(agentRunID string, harvestStart time.Time) ([]byte, error) { if 0 == len(errors.errors) { return nil, nil } estimate := 1024 * len(errors.errors) buf := bytes.NewBuffer(make([]byte, 0, estimate)) buf.WriteByte('[') jsonx.AppendString(buf, agentRunID) buf.WriteByte(',') buf.WriteByte('[') for i, e := range errors.errors { if i > 0 { buf.WriteByte(',') } e.WriteJSON(buf) } buf.WriteByte(']') buf.WriteByte(']') return buf.Bytes(), nil } func (errors *harvestErrors) MergeIntoHarvest(h *Harvest) {}