Skip to content

Commit cfa8f41

Browse files
authored
feat: Added reduced type for partial granularity traces. (#3540)
1 parent fe65ff4 commit cfa8f41

File tree

4 files changed

+294
-50
lines changed

4 files changed

+294
-50
lines changed

lib/spans/span-event-aggregator.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ class SpanEventAggregator extends EventAggregator {
2222

2323
super(opts, agent)
2424
this.inProcessSpans = agent.config.distributed_tracing.in_process_spans.enabled
25+
this.partialGranularityMode = agent.config.distributed_tracing?.sampler?.partial_granularity?.enabled === false ? null : agent.config.distributed_tracing?.sampler?.partial_granularity?.type
2526
}
2627

2728
_toPayloadSync() {
@@ -75,7 +76,7 @@ class SpanEventAggregator extends EventAggregator {
7576

7677
return false
7778
}
78-
const span = SpanEvent.fromSegment({ segment, transaction, parentId, isRoot, inProcessSpans: this.inProcessSpans })
79+
const span = SpanEvent.fromSegment({ segment, transaction, parentId, isRoot, inProcessSpans: this.inProcessSpans, partialGranularityMode: this.partialGranularityMode })
7980

8081
if (span) {
8182
this.add(span, transaction.priority)

lib/spans/span-event.js

Lines changed: 138 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,36 @@ const { DESTINATIONS } = require('../config/attribute-filter')
1212
const { addSpanKind, isEntryPointSpan, reparentSpan, shouldCreateSpan, HTTP_LIBRARY, REGEXS, SPAN_KIND, CATEGORIES } = require('./helpers')
1313
const EMPTY_USER_ATTRS = Object.freeze(Object.create(null))
1414
const SERVER_ADDRESS = 'server.address'
15+
/**
16+
* This keeps a static list of attributes that are used by
17+
* one or more entity relationship rules to synthesize an entity relationship.
18+
* Note: These attributes also have corresponding TraceSegment attributes
19+
* as this list is checked before a span is made. The ones that are TraceSegment
20+
* attributes are noted in the comments. Any new span attributes being added must
21+
* be checked to ensure those are what is getting assigned to the TraceSegment as well.
22+
*/
23+
const SPAN_ENTITY_RELATIONSHIP_ATTRIBUTES = [
24+
'cloud.account.id',
25+
'cloud.platform',
26+
'cloud.region',
27+
'cloud.resource_id',
28+
'database_name', // gets mapped to `db.instance`
29+
'db.instance',
30+
'product', // gets mapped to `db.system`
31+
'db.system',
32+
'http.url',
33+
'url', // gets mapped to `http.url`
34+
'messaging.destination.name',
35+
'messaging.system',
36+
'peer.hostname',
37+
'host', // gets mapped to `server.address`
38+
'hostname', // gets mapped to `server.address`
39+
'server.address',
40+
'port', // gets mapped to `server.port`
41+
'port_path_or_id', // gets mapped to `server.port`
42+
'server.port',
43+
'span.kind',
44+
]
1545

1646
/**
1747
* All the intrinsic attributes for span events, regardless of kind.
@@ -31,6 +61,7 @@ class SpanIntrinsics {
3161
this.timestamp = null
3262
this.duration = null
3363
this['nr.entryPoint'] = null
64+
this['nr.pg'] = null
3465
this['span.kind'] = null
3566
this.trustedParentId = null
3667
this.tracingVendors = null
@@ -67,6 +98,39 @@ class SpanEvent {
6798
return this.intrinsics
6899
}
69100

101+
addIntrinsics({ segment, spanContext, transaction, parentId, isRoot, inProcessSpans, entryPoint }) {
102+
for (const [key, value] of Object.entries(spanContext.intrinsicAttributes)) {
103+
this.addIntrinsicAttribute(key, value)
104+
}
105+
106+
this.addIntrinsicAttribute('traceId', transaction.traceId)
107+
this.addIntrinsicAttribute('transactionId', transaction.id)
108+
this.addIntrinsicAttribute('sampled', transaction.sampled)
109+
this.addIntrinsicAttribute('priority', transaction.priority)
110+
this.addIntrinsicAttribute('name', segment.name)
111+
this.addIntrinsicAttribute('guid', segment.id)
112+
this.addIntrinsicAttribute('parentId', reparentSpan({ inProcessSpans, isRoot, segment, transaction, parentId }))
113+
114+
if (isRoot) {
115+
this.addIntrinsicAttribute('trustedParentId', transaction.traceContext.trustedParentId)
116+
if (transaction.traceContext.tracingVendors) {
117+
this.addIntrinsicAttribute('tracingVendors', transaction.traceContext.tracingVendors)
118+
}
119+
}
120+
121+
// Only set this if it will be `true`. Must be `null` otherwise.
122+
if (entryPoint) {
123+
this.addIntrinsicAttribute('nr.entryPoint', true)
124+
if (transaction.isPartialTrace) {
125+
this.addIntrinsicAttribute('nr.pg', true)
126+
}
127+
}
128+
129+
// Timestamp in milliseconds, duration in seconds. Yay consistency!
130+
this.addIntrinsicAttribute('timestamp', segment.timer.start)
131+
this.addIntrinsicAttribute('duration', segment.timer.getDurationInMillis() / 1000)
132+
}
133+
70134
addIntrinsicAttribute(key, value) {
71135
this.intrinsics[key] = value
72136
}
@@ -83,6 +147,66 @@ class SpanEvent {
83147
return HttpSpanEvent
84148
}
85149

150+
static isExitSpan(segment) {
151+
return REGEXS.CLIENT.EXTERNAL.test(segment.name) || REGEXS.CLIENT.DATASTORE.test(segment.name) || REGEXS.PRODUCER.test(segment.name)
152+
}
153+
154+
static isLlmSpan(segment) {
155+
return segment.name.startsWith('Llm/')
156+
}
157+
158+
/**
159+
* Filters attributes for partial trace span events based on a given mode.
160+
* The rules are as such:
161+
* - If not a partial trace, return all attributes.
162+
* - If an entry point span, return all attributes.
163+
* - If an LLM span, return all attributes.
164+
* - If not an exit span, return no attributes.
165+
* - If mode is 'reduced' and there are entity relationship attributes, return all attributes.
166+
* - Otherwise, return no attributes.
167+
*
168+
* @param {object} params to function
169+
* @param {TraceSegment} params.segment segment to filter attributes from
170+
* @param {SpanContext} params.spanContext span context to filter attributes from
171+
* @param {boolean} params.entryPoint whether the span is an entry point
172+
* @param {string} params.partialGranularityMode mode of partial trace ('reduced', 'essential', 'compact')
173+
* @param {boolean} params.isPartialTrace whether the trace is a partial trace
174+
* @returns {object} { attributes, customAttributes, dropSpan: boolean }
175+
*/
176+
static filterPartialTraceAttributes({ segment, spanContext, entryPoint, partialGranularityMode, isPartialTrace }) {
177+
const attributes = segment.attributes.get(DESTINATIONS.SPAN_EVENT)
178+
const customAttributes = spanContext.customAttributes.get(DESTINATIONS.SPAN_EVENT)
179+
if (!isPartialTrace || entryPoint || SpanEvent.isLlmSpan(segment)) {
180+
return { attributes, customAttributes, dropSpan: false }
181+
}
182+
183+
if (!SpanEvent.isExitSpan(segment)) {
184+
return { dropSpan: true }
185+
}
186+
187+
const attrKeys = Object.keys(attributes)
188+
const entityRelationshipAttrs = SPAN_ENTITY_RELATIONSHIP_ATTRIBUTES.filter((item) => attrKeys.includes(item))
189+
if (partialGranularityMode === 'reduced') {
190+
if (entityRelationshipAttrs.length > 0) {
191+
return { attributes, customAttributes, dropSpan: false }
192+
}
193+
}
194+
195+
return { dropSpan: true }
196+
}
197+
198+
static createSpan({ segment, attributes, customAttributes }) {
199+
let span = null
200+
if (HttpSpanEvent.testSegment(segment)) {
201+
span = new HttpSpanEvent(attributes, customAttributes)
202+
} else if (DatastoreSpanEvent.testSegment(segment)) {
203+
span = new DatastoreSpanEvent(attributes, customAttributes)
204+
} else {
205+
span = new SpanEvent(attributes, customAttributes)
206+
}
207+
return span
208+
}
209+
86210
/**
87211
* Constructs a `SpanEvent` from the given segment.
88212
*
@@ -95,9 +219,10 @@ class SpanEvent {
95219
* @param {?string} [params.parentId] ID of the segment's parent.
96220
* @param {boolean} [params.isRoot] if segment is root segment; defaults to `false`
97221
* @param {boolean} params.inProcessSpans if the segment is in-process, create span
222+
* @param {string} params.partialGranularityMode mode of partial trace ('reduced', 'essential', 'compact')
98223
* @returns {SpanEvent} The constructed event.
99224
*/
100-
static fromSegment({ segment, transaction, parentId = null, isRoot = false, inProcessSpans }) {
225+
static fromSegment({ segment, transaction, parentId = null, isRoot = false, inProcessSpans, partialGranularityMode }) {
101226
const entryPoint = isEntryPointSpan({ segment, transaction })
102227
if (!inProcessSpans && !shouldCreateSpan({ entryPoint, segment, transaction })) {
103228
return null
@@ -116,46 +241,14 @@ class SpanEvent {
116241
}
117242
}
118243

119-
const attributes = segment.attributes.get(DESTINATIONS.SPAN_EVENT)
120-
121-
const customAttributes = spanContext.customAttributes.get(DESTINATIONS.SPAN_EVENT)
122-
123-
let span = null
124-
if (HttpSpanEvent.testSegment(segment)) {
125-
span = new HttpSpanEvent(attributes, customAttributes)
126-
} else if (DatastoreSpanEvent.testSegment(segment)) {
127-
span = new DatastoreSpanEvent(attributes, customAttributes)
128-
} else {
129-
span = new SpanEvent(attributes, customAttributes)
130-
}
131-
132-
for (const [key, value] of Object.entries(spanContext.intrinsicAttributes)) {
133-
span.intrinsics[key] = value
134-
}
135-
136-
span.intrinsics.traceId = transaction.traceId
137-
span.intrinsics.guid = segment.id
138-
span.intrinsics.parentId = reparentSpan({ inProcessSpans, isRoot, segment, transaction, parentId })
139-
span.intrinsics.transactionId = transaction.id
140-
span.intrinsics.sampled = transaction.sampled
141-
span.intrinsics.priority = transaction.priority
142-
span.intrinsics.name = segment.name
143-
144-
if (isRoot) {
145-
span.intrinsics.trustedParentId = transaction.traceContext.trustedParentId
146-
if (transaction.traceContext.tracingVendors) {
147-
span.intrinsics.tracingVendors = transaction.traceContext.tracingVendors
148-
}
149-
}
150-
151-
// Only set this if it will be `true`. Must be `null` otherwise.
152-
if (entryPoint) {
153-
span.intrinsics['nr.entryPoint'] = true
244+
const { attributes, customAttributes, dropSpan } = SpanEvent.filterPartialTraceAttributes({ spanContext, entryPoint, segment, partialGranularityMode, isPartialTrace: transaction.isPartialTrace })
245+
// If attributes were stripped out due to partial trace filtering, do not create span.
246+
if (dropSpan) {
247+
return null
154248
}
155249

156-
// Timestamp in milliseconds, duration in seconds. Yay consistency!
157-
span.intrinsics.timestamp = segment.timer.start
158-
span.intrinsics.duration = segment.timer.getDurationInMillis() / 1000
250+
const span = SpanEvent.createSpan({ segment, attributes, customAttributes })
251+
span.addIntrinsics({ segment, spanContext, transaction, parentId, isRoot, inProcessSpans, entryPoint })
159252

160253
addSpanKind({ segment, span })
161254
return span
@@ -196,9 +289,9 @@ class HttpSpanEvent extends SpanEvent {
196289
constructor(attributes, customAttributes) {
197290
super(attributes, customAttributes)
198291

199-
this.intrinsics.category = CATEGORIES.HTTP
200-
this.intrinsics.component = attributes.library || HTTP_LIBRARY
201-
this.intrinsics['span.kind'] = SPAN_KIND.CLIENT
292+
this.addIntrinsicAttribute('category', CATEGORIES.HTTP)
293+
this.addIntrinsicAttribute('component', attributes.library || HTTP_LIBRARY)
294+
this.addIntrinsicAttribute('span.kind', SPAN_KIND.CLIENT)
202295

203296
if (attributes.library) {
204297
attributes.library = null
@@ -236,11 +329,11 @@ class DatastoreSpanEvent extends SpanEvent {
236329
constructor(attributes, customAttributes) {
237330
super(attributes, customAttributes)
238331

239-
this.intrinsics.category = CATEGORIES.DATASTORE
240-
this.intrinsics['span.kind'] = SPAN_KIND.CLIENT
332+
this.addIntrinsicAttribute('category', CATEGORIES.DATASTORE)
333+
this.addIntrinsicAttribute('span.kind', SPAN_KIND.CLIENT)
241334

242335
if (attributes.product) {
243-
this.intrinsics.component = attributes.product
336+
this.addIntrinsicAttribute('component', attributes.product)
244337
this.addAttribute('db.system', attributes.product)
245338
attributes.product = null
246339
}

0 commit comments

Comments
 (0)