@@ -12,6 +12,36 @@ const { DESTINATIONS } = require('../config/attribute-filter')
1212const { addSpanKind, isEntryPointSpan, reparentSpan, shouldCreateSpan, HTTP_LIBRARY , REGEXS , SPAN_KIND , CATEGORIES } = require ( './helpers' )
1313const EMPTY_USER_ATTRS = Object . freeze ( Object . create ( null ) )
1414const 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