File: span_events.go

package info (click to toggle)
golang-github-newrelic-go-agent 3.15.2-9
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, forky, sid, trixie
  • size: 8,356 kB
  • sloc: sh: 65; makefile: 6
file content (146 lines) | stat: -rw-r--r-- 3,754 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
// Copyright 2020 New Relic Corporation. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

package internal

import (
	"bytes"
	"time"
)

// https://source.datanerd.us/agents/agent-specs/blob/master/Span-Events.md

type spanCategory string

const (
	spanCategoryHTTP      spanCategory = "http"
	spanCategoryDatastore              = "datastore"
	spanCategoryGeneric                = "generic"
)

// SpanEvent represents a span event, necessary to support Distributed Tracing.
type SpanEvent struct {
	TraceID       string
	GUID          string
	ParentID      string
	TransactionID string
	Sampled       bool
	Priority      Priority
	Timestamp     time.Time
	Duration      time.Duration
	Name          string
	Category      spanCategory
	Component     string
	Kind          string
	IsEntrypoint  bool
	Attributes    spanAttributeMap
}

// WriteJSON prepares JSON in the format expected by the collector.
func (e *SpanEvent) WriteJSON(buf *bytes.Buffer) {
	w := jsonFieldsWriter{buf: buf}
	buf.WriteByte('[')
	buf.WriteByte('{')
	w.stringField("type", "Span")
	w.stringField("traceId", e.TraceID)
	w.stringField("guid", e.GUID)
	if "" != e.ParentID {
		w.stringField("parentId", e.ParentID)
	}
	w.stringField("transactionId", e.TransactionID)
	w.boolField("sampled", e.Sampled)
	w.writerField("priority", e.Priority)
	w.intField("timestamp", e.Timestamp.UnixNano()/(1000*1000)) // in milliseconds
	w.floatField("duration", e.Duration.Seconds())
	w.stringField("name", e.Name)
	w.stringField("category", string(e.Category))
	if e.IsEntrypoint {
		w.boolField("nr.entryPoint", true)
	}
	if e.Component != "" {
		w.stringField("component", e.Component)
	}
	if e.Kind != "" {
		w.stringField("span.kind", e.Kind)
	}
	buf.WriteByte('}')
	buf.WriteByte(',')
	buf.WriteByte('{')
	// user attributes section is unused
	buf.WriteByte('}')
	buf.WriteByte(',')
	buf.WriteByte('{')

	w = jsonFieldsWriter{buf: buf}
	for key, val := range e.Attributes {
		w.writerField(key.String(), val)
	}

	buf.WriteByte('}')
	buf.WriteByte(']')
}

// MarshalJSON is used for testing.
func (e *SpanEvent) MarshalJSON() ([]byte, error) {
	buf := bytes.NewBuffer(make([]byte, 0, 256))

	e.WriteJSON(buf)

	return buf.Bytes(), nil
}

type spanEvents struct {
	*analyticsEvents
}

func newSpanEvents(max int) *spanEvents {
	return &spanEvents{
		analyticsEvents: newAnalyticsEvents(max),
	}
}

func (events *spanEvents) addEvent(e *SpanEvent, cat *BetterCAT) {
	e.TraceID = cat.TraceID()
	e.TransactionID = cat.ID
	e.Sampled = cat.Sampled
	e.Priority = cat.Priority
	events.addEventPopulated(e)
}

func (events *spanEvents) addEventPopulated(e *SpanEvent) {
	events.analyticsEvents.addEvent(analyticsEvent{priority: e.Priority, jsonWriter: e})
}

// MergeFromTransaction merges the span events from a transaction into the
// harvest's span events.  This should only be called if the transaction was
// sampled and span events are enabled.
func (events *spanEvents) MergeFromTransaction(txndata *TxnData) {
	root := &SpanEvent{
		GUID:         txndata.getRootSpanID(),
		Timestamp:    txndata.Start,
		Duration:     txndata.Duration,
		Name:         txndata.FinalName,
		Category:     spanCategoryGeneric,
		IsEntrypoint: true,
	}
	if nil != txndata.BetterCAT.Inbound {
		root.ParentID = txndata.BetterCAT.Inbound.ID
	}
	events.addEvent(root, &txndata.BetterCAT)

	for _, evt := range txndata.spanEvents {
		events.addEvent(evt, &txndata.BetterCAT)
	}
}

func (events *spanEvents) MergeIntoHarvest(h *Harvest) {
	h.SpanEvents.mergeFailed(events.analyticsEvents)
}

func (events *spanEvents) Data(agentRunID string, harvestStart time.Time) ([]byte, error) {
	return events.CollectorJSON(agentRunID)
}

func (events *spanEvents) EndpointMethod() string {
	return cmdSpanEvents
}