diff --git a/v3/internal/jsonx/encode.go b/v3/internal/jsonx/encode.go index 6495829f7..b72558801 100644 --- a/v3/internal/jsonx/encode.go +++ b/v3/internal/jsonx/encode.go @@ -118,6 +118,21 @@ func AppendFloat(buf *bytes.Buffer, x float64) error { return nil } +func AppendFloat32(buf *bytes.Buffer, x float32) error { + var scratch [64]byte + x64 := float64(x) + + if math.IsInf(x64, 0) || math.IsNaN(x64) { + return &json.UnsupportedValueError{ + Value: reflect.ValueOf(x64), + Str: strconv.FormatFloat(x64, 'g', -1, 32), + } + } + + buf.Write(strconv.AppendFloat(scratch[:0], x64, 'g', -1, 32)) + return nil +} + // AppendFloatArray appends an array of numeric literals to buf. func AppendFloatArray(buf *bytes.Buffer, a ...float64) error { buf.WriteByte('[') diff --git a/v3/newrelic/distributed_tracing.go b/v3/newrelic/distributed_tracing.go index 42e6ddb13..9f04e09b8 100644 --- a/v3/newrelic/distributed_tracing.go +++ b/v3/newrelic/distributed_tracing.go @@ -4,6 +4,7 @@ package newrelic import ( + "bytes" "encoding/base64" "encoding/json" "errors" @@ -15,6 +16,7 @@ import ( "time" "github.com/newrelic/go-agent/v3/internal" + "github.com/newrelic/go-agent/v3/internal/jsonx" ) type distTraceVersion [2]int @@ -22,6 +24,13 @@ type distTraceVersion [2]int func (v distTraceVersion) major() int { return v[0] } func (v distTraceVersion) minor() int { return v[1] } +// WriteJSON implements the functionality to support writerField +// in our internal json builder. It appends the JSON representation +// of a distTraceVersion to the destination bytes.Buffer. +func (v distTraceVersion) WriteJSON(buf *bytes.Buffer) { + jsonx.AppendIntArray(buf, int64(v[0]), int64(v[1])) +} + const ( // callerTypeApp is the Type field's value for outbound payloads. callerTypeApp = "App" @@ -57,6 +66,13 @@ func (tm timestampMillis) MarshalJSON() ([]byte, error) { return json.Marshal(timeToUnixMilliseconds(tm.Time())) } +// WriteJSON implements the functionality to support writerField +// in our internal json builder. It appends the JSON representation +// of a timestampMillis value to the destination bytes.Buffer. +func (tm timestampMillis) WriteJSON(buf *bytes.Buffer) { + jsonx.AppendUint(buf, timeToUnixMilliseconds(tm.Time())) +} + func (tm timestampMillis) Time() time.Time { return time.Time(tm) } func (tm *timestampMillis) Set(t time.Time) { *tm = timestampMillis(t) } @@ -86,6 +102,37 @@ type payload struct { OriginalTraceState string `json:"-"` } +// WriteJSON implements the functionality to support writerField +// in our internal json builder. It appends the JSON representation +// of a payload struct to the destination bytes.Buffer. +func (p payload) WriteJSON(buf *bytes.Buffer) { + buf.WriteByte('{') + w := jsonFieldsWriter{buf: buf} + w.stringField("ty", p.Type) + w.stringField("ap", p.App) + w.stringField("ac", p.Account) + if p.TransactionID != "" { + w.stringField("tx", p.TransactionID) + } + if p.ID != "" { + w.stringField("id", p.ID) + } + w.stringField("tr", p.TracedID) + w.float32Field("pr", float32(p.Priority)) + + if p.Sampled == nil { + w.addKey("sa") + w.buf.WriteString("null") + } else { + w.boolField("sa", *p.Sampled) + } + w.writerField("ti", p.Timestamp) + if p.TrustedAccountKey != "" { + w.stringField("tk", p.TrustedAccountKey) + } + buf.WriteByte('}') +} + type payloadCaller struct { TransportType string Type string @@ -109,47 +156,52 @@ func (p payload) validateNewRelicData() error { // If a payload is missing both `guid` and `transactionId` is received, // a ParseException supportability metric should be generated. - if "" == p.TransactionID && "" == p.ID { + if p.TransactionID == "" && p.ID == "" { return errPayloadMissingGUIDTxnID } - if "" == p.Type { + if p.Type == "" { return errPayloadMissingType } - if "" == p.Account { + if p.Account == "" { return errPayloadMissingAccount } - if "" == p.App { + if p.App == "" { return errPayloadMissingApp } - if "" == p.TracedID { + if p.TracedID == "" { return errPayloadMissingTraceID } - if p.Timestamp.Time().IsZero() || 0 == p.Timestamp.Time().Unix() { + if p.Timestamp.Time().IsZero() || p.Timestamp.Time().Unix() == 0 { return errPayloadMissingTimestamp } return nil } +const payloadJSONStartingSizeEstimate = 256 + func (p payload) text(v distTraceVersion) []byte { // TrustedAccountKey should only be attached to the outbound payload if its value differs // from the Account field. if p.TrustedAccountKey == p.Account { p.TrustedAccountKey = "" } - js, _ := json.Marshal(struct { - Version distTraceVersion `json:"v"` - Data payload `json:"d"` - }{ - Version: v, - Data: p, - }) - return js + + js := bytes.NewBuffer(make([]byte, 0, payloadJSONStartingSizeEstimate)) + w := jsonFieldsWriter{ + buf: js, + } + js.WriteByte('{') + w.writerField("v", v) + w.writerField("d", p) + js.WriteByte('}') + + return js.Bytes() } // NRText implements newrelic.DistributedTracePayload. @@ -259,12 +311,12 @@ func processNRDTString(str string, support *distributedTracingSupport) (*payload return nil, nil } var decoded []byte - if '{' == str[0] { + if str[0] == '{' { decoded = []byte(str) } else { var err error decoded, err = base64.StdEncoding.DecodeString(str) - if nil != err { + if err != nil { support.AcceptPayloadParseException = true return nil, fmt.Errorf("unable to decode payload: %v", err) } @@ -273,12 +325,12 @@ func processNRDTString(str string, support *distributedTracingSupport) (*payload Version distTraceVersion `json:"v"` Data json.RawMessage `json:"d"` }{} - if err := json.Unmarshal(decoded, &envelope); nil != err { + if err := json.Unmarshal(decoded, &envelope); err != nil { support.AcceptPayloadParseException = true return nil, fmt.Errorf("unable to unmarshal payload: %v", err) } - if 0 == envelope.Version.major() && 0 == envelope.Version.minor() { + if envelope.Version.major() == 0 && envelope.Version.minor() == 0 { support.AcceptPayloadParseException = true return nil, errPayloadMissingVersion } @@ -289,7 +341,7 @@ func processNRDTString(str string, support *distributedTracingSupport) (*payload envelope.Version.major()) } payload := new(payload) - if err := json.Unmarshal(envelope.Data, payload); nil != err { + if err := json.Unmarshal(envelope.Data, payload); err != nil { support.AcceptPayloadParseException = true return nil, fmt.Errorf("unable to unmarshal payload data: %v", err) } @@ -305,12 +357,12 @@ func processNRDTString(str string, support *distributedTracingSupport) (*payload func processW3CHeaders(hdrs http.Header, trustedAccountKey string, support *distributedTracingSupport) (*payload, error) { p, err := processTraceParent(hdrs) - if nil != err { + if err != nil { support.TraceContextParentParseException = true return nil, err } err = processTraceState(hdrs, trustedAccountKey, p) - if nil != err { + if err != nil { if err == errInvalidNRTraceState { support.TraceContextStateInvalidNrEntry = true } else { @@ -394,7 +446,7 @@ func processTraceState(hdrs http.Header, trustedAccountKey string, p *payload) e app := matches[3] timestamp, err := strconv.ParseUint(matches[8], 10, 64) - if nil != err || "" == version || "" == parentType || "" == account || "" == app { + if err != nil || version == "" || parentType == "" || account == "" || app == "" { return errInvalidNRTraceState } diff --git a/v3/newrelic/json_object_writer.go b/v3/newrelic/json_object_writer.go index 3472c83d6..9bae24b88 100644 --- a/v3/newrelic/json_object_writer.go +++ b/v3/newrelic/json_object_writer.go @@ -44,6 +44,11 @@ func (w *jsonFieldsWriter) floatField(key string, val float64) { jsonx.AppendFloat(w.buf, val) } +func (w *jsonFieldsWriter) float32Field(key string, val float32) { + w.addKey(key) + jsonx.AppendFloat32(w.buf, val) +} + func (w *jsonFieldsWriter) boolField(key string, val bool) { w.addKey(key) if val {