180 lines
4.1 KiB
Go
180 lines
4.1 KiB
Go
|
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) {}
|