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  }