github.com/newrelic/go-agent@v3.26.0+incompatible/internal/span_events.go (about) 1 // Copyright 2020 New Relic Corporation. All rights reserved. 2 // SPDX-License-Identifier: Apache-2.0 3 4 package internal 5 6 import ( 7 "bytes" 8 "time" 9 ) 10 11 // https://source.datanerd.us/agents/agent-specs/blob/master/Span-Events.md 12 13 type spanCategory string 14 15 const ( 16 spanCategoryHTTP spanCategory = "http" 17 spanCategoryDatastore = "datastore" 18 spanCategoryGeneric = "generic" 19 ) 20 21 // SpanEvent represents a span event, necessary to support Distributed Tracing. 22 type SpanEvent struct { 23 TraceID string 24 GUID string 25 ParentID string 26 TransactionID string 27 Sampled bool 28 Priority Priority 29 Timestamp time.Time 30 Duration time.Duration 31 Name string 32 Category spanCategory 33 Component string 34 Kind string 35 IsEntrypoint bool 36 Attributes spanAttributeMap 37 } 38 39 // WriteJSON prepares JSON in the format expected by the collector. 40 func (e *SpanEvent) WriteJSON(buf *bytes.Buffer) { 41 w := jsonFieldsWriter{buf: buf} 42 buf.WriteByte('[') 43 buf.WriteByte('{') 44 w.stringField("type", "Span") 45 w.stringField("traceId", e.TraceID) 46 w.stringField("guid", e.GUID) 47 if "" != e.ParentID { 48 w.stringField("parentId", e.ParentID) 49 } 50 w.stringField("transactionId", e.TransactionID) 51 w.boolField("sampled", e.Sampled) 52 w.writerField("priority", e.Priority) 53 w.intField("timestamp", e.Timestamp.UnixNano()/(1000*1000)) // in milliseconds 54 w.floatField("duration", e.Duration.Seconds()) 55 w.stringField("name", e.Name) 56 w.stringField("category", string(e.Category)) 57 if e.IsEntrypoint { 58 w.boolField("nr.entryPoint", true) 59 } 60 if e.Component != "" { 61 w.stringField("component", e.Component) 62 } 63 if e.Kind != "" { 64 w.stringField("span.kind", e.Kind) 65 } 66 buf.WriteByte('}') 67 buf.WriteByte(',') 68 buf.WriteByte('{') 69 // user attributes section is unused 70 buf.WriteByte('}') 71 buf.WriteByte(',') 72 buf.WriteByte('{') 73 74 w = jsonFieldsWriter{buf: buf} 75 for key, val := range e.Attributes { 76 w.writerField(key.String(), val) 77 } 78 79 buf.WriteByte('}') 80 buf.WriteByte(']') 81 } 82 83 // MarshalJSON is used for testing. 84 func (e *SpanEvent) MarshalJSON() ([]byte, error) { 85 buf := bytes.NewBuffer(make([]byte, 0, 256)) 86 87 e.WriteJSON(buf) 88 89 return buf.Bytes(), nil 90 } 91 92 type spanEvents struct { 93 *analyticsEvents 94 } 95 96 func newSpanEvents(max int) *spanEvents { 97 return &spanEvents{ 98 analyticsEvents: newAnalyticsEvents(max), 99 } 100 } 101 102 func (events *spanEvents) addEvent(e *SpanEvent, cat *BetterCAT) { 103 e.TraceID = cat.TraceID() 104 e.TransactionID = cat.ID 105 e.Sampled = cat.Sampled 106 e.Priority = cat.Priority 107 events.addEventPopulated(e) 108 } 109 110 func (events *spanEvents) addEventPopulated(e *SpanEvent) { 111 events.analyticsEvents.addEvent(analyticsEvent{priority: e.Priority, jsonWriter: e}) 112 } 113 114 // MergeFromTransaction merges the span events from a transaction into the 115 // harvest's span events. This should only be called if the transaction was 116 // sampled and span events are enabled. 117 func (events *spanEvents) MergeFromTransaction(txndata *TxnData) { 118 root := &SpanEvent{ 119 GUID: txndata.getRootSpanID(), 120 Timestamp: txndata.Start, 121 Duration: txndata.Duration, 122 Name: txndata.FinalName, 123 Category: spanCategoryGeneric, 124 IsEntrypoint: true, 125 } 126 if nil != txndata.BetterCAT.Inbound { 127 root.ParentID = txndata.BetterCAT.Inbound.ID 128 } 129 events.addEvent(root, &txndata.BetterCAT) 130 131 for _, evt := range txndata.spanEvents { 132 events.addEvent(evt, &txndata.BetterCAT) 133 } 134 } 135 136 func (events *spanEvents) MergeIntoHarvest(h *Harvest) { 137 h.SpanEvents.mergeFailed(events.analyticsEvents) 138 } 139 140 func (events *spanEvents) Data(agentRunID string, harvestStart time.Time) ([]byte, error) { 141 return events.CollectorJSON(agentRunID) 142 } 143 144 func (events *spanEvents) EndpointMethod() string { 145 return cmdSpanEvents 146 }