Skip to content

Commit c2103eb

Browse files
committed
feat: Added otel consumer span processing
1 parent fe95d98 commit c2103eb

File tree

8 files changed

+149
-43
lines changed

8 files changed

+149
-43
lines changed

lib/otel/rules.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@
165165
},
166166
{
167167
"name": "OtelMessagingConsumer1_24",
168+
"type": "consumer",
168169
"matcher": {
169170
"required_span_kinds": [
170171
"consumer"
@@ -200,6 +201,7 @@
200201
},
201202
{
202203
"name": "FallbackConsumer",
204+
"type": "consumer",
203205
"matcher": {
204206
"required_span_kinds": [
205207
"consumer"

lib/otel/segment-synthesis.js

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@
77
const { RulesEngine } = require('./rules')
88
const defaultLogger = require('../logger').child({ component: 'segment-synthesizer' })
99
const {
10+
createConsumerSegment,
1011
createDbSegment,
1112
createHttpExternalSegment,
12-
createServerSegment,
1313
createProducerSegment,
14-
createInternalSegment
14+
createServerSegment
1515
} = require('./segments')
1616

1717
class SegmentSynthesizer {
@@ -33,18 +33,29 @@ class SegmentSynthesizer {
3333
}
3434

3535
switch (rule.type) {
36-
case 'db':
36+
case 'consumer': {
37+
return createConsumerSegment(this.agent, otelSpan)
38+
}
39+
40+
case 'db': {
3741
return createDbSegment(this.agent, otelSpan)
38-
case 'external':
42+
}
43+
44+
case 'external': {
3945
return createHttpExternalSegment(this.agent, otelSpan)
40-
case 'internal':
41-
return createInternalSegment(this.agent, otelSpan)
42-
case 'producer':
46+
}
47+
48+
case 'producer': {
4349
return createProducerSegment(this.agent, otelSpan)
44-
case 'server':
50+
}
51+
52+
case 'server': {
4553
return createServerSegment(this.agent, otelSpan)
46-
default:
54+
}
55+
56+
default: {
4757
this.logger.debug('Found type: %s, no synthesis rule currently built', rule.type)
58+
}
4859
}
4960
}
5061
}

lib/otel/segments/consumer.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Copyright 2024 New Relic Corporation. All rights reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
'use strict'
7+
8+
module.exports = createConsumerSegment
9+
10+
// Notes:
11+
// + https://github.com/open-telemetry/semantic-conventions/blob/v1.24.0/docs/messaging/messaging-spans.md
12+
// + We probably want to inspect `messaging.system` so that we can generate
13+
// attributes according to our own internal specs.
14+
15+
const Transaction = require('../../transaction/')
16+
const { DESTINATIONS, TYPES } = Transaction
17+
18+
const {
19+
SEMATTRS_MESSAGING_SYSTEM,
20+
SEMATTRS_MESSAGING_DESTINATION,
21+
SEMATTRS_MESSAGING_DESTINATION_KIND
22+
} = require('@opentelemetry/semantic-conventions')
23+
24+
function createConsumerSegment(agent, otelSpan) {
25+
const transaction = new Transaction(agent)
26+
transaction.type = TYPES.BG
27+
28+
const system = otelSpan.attributes[SEMATTRS_MESSAGING_SYSTEM] ?? 'unknown'
29+
const destination = otelSpan.attributes[SEMATTRS_MESSAGING_DESTINATION] ?? 'unknown'
30+
const destKind = otelSpan.attributes[SEMATTRS_MESSAGING_DESTINATION_KIND] ?? 'unknown'
31+
const segmentName = `OtherTransaction/Message/${system}/${destKind}/Named/${destination}`
32+
33+
const txAttrs = transaction.trace.attributes
34+
txAttrs.addAttribute(DESTINATIONS.TRANS_SCOPE, 'message.queueName', destination)
35+
// txAttrs.addAttribute(
36+
// DESTINATIONS.TRANS_SCOPE,
37+
// 'host',
38+
//
39+
// )
40+
transaction.name = segmentName
41+
42+
const segment = agent.tracer.createSegment({
43+
name: segmentName,
44+
parent: transaction.trace.root,
45+
transaction
46+
})
47+
transaction.baseSegment = segment
48+
49+
return { segment, transaction }
50+
}

lib/otel/segments/index.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,17 @@
44
*/
55

66
'use strict'
7-
const createHttpExternalSegment = require('./http-external')
7+
8+
const createConsumerSegment = require('./consumer')
89
const createDbSegment = require('./database')
9-
const createServerSegment = require('./server')
10+
const createHttpExternalSegment = require('./http-external')
1011
const createProducerSegment = require('./producer')
11-
const createInternalSegment = require('./internal')
12+
const createServerSegment = require('./server')
1213

1314
module.exports = {
15+
createConsumerSegment,
1416
createDbSegment,
1517
createHttpExternalSegment,
16-
createInternalSegment,
1718
createProducerSegment,
1819
createServerSegment
1920
}

lib/transaction/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ function Transaction(agent) {
157157
agent.emit('transactionStarted', this)
158158
}
159159

160+
Transaction.DESTINATIONS = DESTS
160161
Transaction.TYPES = TYPES
161162
Transaction.TYPES_SET = TYPES_SET
162163
Transaction.TRANSPORT_TYPES = TRANSPORT_TYPES

test/unit/lib/otel/consumer.test.js

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
* Copyright 2024 New Relic Corporation. All rights reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
'use strict'
7+
8+
// Tests to verify that we can map OTEL "consumer" spans to NR segments.
9+
10+
const test = require('node:test')
11+
const assert = require('node:assert')
12+
13+
const { BasicTracerProvider } = require('@opentelemetry/sdk-trace-base')
14+
const { SpanKind } = require('@opentelemetry/api')
15+
const {
16+
SEMATTRS_MESSAGING_SYSTEM,
17+
SEMATTRS_MESSAGING_DESTINATION,
18+
SEMATTRS_MESSAGING_DESTINATION_KIND
19+
} = require('@opentelemetry/semantic-conventions')
20+
21+
const { DESTINATIONS } = require('../../../../lib/transaction')
22+
const helper = require('../../../lib/agent_helper')
23+
const createSpan = require('./fixtures/span')
24+
const SegmentSynthesizer = require('../../../../lib/otel/segment-synthesis')
25+
26+
test.beforeEach((ctx) => {
27+
const logs = []
28+
const logger = {
29+
debug(...args) {
30+
logs.push(args)
31+
}
32+
}
33+
const agent = helper.loadMockedAgent()
34+
const synth = new SegmentSynthesizer(agent, { logger })
35+
const tracer = new BasicTracerProvider().getTracer('default')
36+
37+
ctx.nr = {
38+
agent,
39+
logger,
40+
logs,
41+
synth,
42+
tracer
43+
}
44+
})
45+
46+
test.afterEach((ctx) => {
47+
helper.unloadAgent(ctx.nr.agent)
48+
})
49+
50+
test('should create consumer segment from otel span', (t) => {
51+
const { synth, tracer } = t.nr
52+
const span = createSpan({ tracer, kind: SpanKind.CONSUMER })
53+
span.setAttribute('messaging.operation', 'receive')
54+
span.setAttribute(SEMATTRS_MESSAGING_SYSTEM, 'msgqueuer')
55+
span.setAttribute(SEMATTRS_MESSAGING_DESTINATION, 'dest1')
56+
span.setAttribute(SEMATTRS_MESSAGING_DESTINATION_KIND, 'topic1')
57+
58+
const expectedName = 'OtherTransaction/Message/msgqueuer/topic1/Named/dest1'
59+
const { segment, transaction } = synth.synthesize(span)
60+
assert.equal(segment.name, expectedName)
61+
assert.equal(segment.parentId, segment.root.id)
62+
assert.equal(transaction.name, expectedName)
63+
assert.equal(transaction.type, 'bg')
64+
assert.equal(transaction.baseSegment, segment)
65+
assert.equal(
66+
transaction.trace.attributes.get(DESTINATIONS.TRANS_SCOPE)['message.queueName'],
67+
'dest1'
68+
)
69+
70+
transaction.end()
71+
})

test/unit/lib/otel/rules.test.js

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -76,14 +76,3 @@ test('fallback producer rule is met', () => {
7676
assert.notEqual(rule, undefined)
7777
assert.equal(rule.name, 'FallbackProducer')
7878
})
79-
80-
test('fallback internal rule is met', () => {
81-
const engine = new RulesEngine()
82-
const span = new Span(tracer, ROOT_CONTEXT, 'test-span', spanContext, SpanKind.INTERNAL, parentId)
83-
span.setAttribute('foo.bar', 'baz')
84-
span.end()
85-
86-
const rule = engine.test(span)
87-
assert.notEqual(rule, undefined)
88-
assert.equal(rule.name, 'Fallback')
89-
})

test/unit/lib/otel/segment-synthesizer.test.js

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -214,25 +214,6 @@ test('should create queue producer segment', (t, end) => {
214214
})
215215
})
216216

217-
test('should create internal custom segment', (t, end) => {
218-
const { agent, synthesizer, parentId, tracer } = t.nr
219-
helper.runInTransaction(agent, (tx) => {
220-
const span = createSpan({
221-
name: 'doer-of-stuff',
222-
kind: SpanKind.INTERNAL,
223-
parentId,
224-
tx,
225-
tracer
226-
})
227-
const { segment, transaction } = synthesizer.synthesize(span)
228-
assert.equal(tx.id, transaction.id)
229-
assert.equal(segment.name, 'Custom/doer-of-stuff')
230-
assert.equal(segment.parentId, tx.trace.root.id)
231-
tx.end()
232-
end()
233-
})
234-
})
235-
236217
test('should log warning span does not match a rule', (t, end) => {
237218
const { agent, synthesizer, loggerMock, parentId, tracer } = t.nr
238219

0 commit comments

Comments
 (0)