Skip to content

Commit

Permalink
feat: Added otel consumer span processing
Browse files Browse the repository at this point in the history
  • Loading branch information
jsumners-nr committed Dec 13, 2024
1 parent 98b349e commit f3a0d1f
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 4 deletions.
19 changes: 15 additions & 4 deletions lib/otel/segment-synthesis.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
const { RulesEngine } = require('./rules')
const defaultLogger = require('../logger').child({ component: 'segment-synthesizer' })
const { DatabaseSegment, HttpExternalSegment, ServerSegment } = require('./segments')
const createConsumerSegment = require('./segments/consumer')

class SegmentSynthesizer {
constructor(agent, { logger = defaultLogger } = {}) {
Expand All @@ -27,14 +28,24 @@ class SegmentSynthesizer {
}

switch (rule.type) {
case 'external':
case 'external': {
return new HttpExternalSegment(this.agent, otelSpan)
case 'db':
}

case 'db': {
return new DatabaseSegment(this.agent, otelSpan)
case 'server':
}

case 'server': {
if (rule.isConsumer === true) {
return createConsumerSegment({ agent: this.agent, otelSpan })
}
return new ServerSegment(this.agent, otelSpan)
default:
}

default: {
this.logger.debug('Found type: %s, no synthesis rule currently built', rule.type)
}
}
}
}
Expand Down
47 changes: 47 additions & 0 deletions lib/otel/segments/consumer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Copyright 2024 New Relic Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/

'use strict'

module.exports = createConsumerSegment

// Notes:
// + https://github.com/open-telemetry/semantic-conventions/blob/v1.24.0/docs/messaging/messaging-spans.md
// + We probably want to inspect `messaging.system` so that we can generate
// attributes according to our own internal specs.

const Transaction = require('../../transaction/')
const { DESTINATIONS, TYPES } = Transaction

const {
// SEMATTRS_MESSAGING_SYSTEM,
SEMATTRS_MESSAGING_DESTINATION,
SEMATTRS_MESSAGING_OPERATION
} = require('@opentelemetry/semantic-conventions')

function createConsumerSegment({ agent, otelSpan }) {
const transaction = new Transaction(agent)
transaction.type = TYPES.BG

const operation = otelSpan.attributes[SEMATTRS_MESSAGING_OPERATION]
const destination = otelSpan.attributes[SEMATTRS_MESSAGING_DESTINATION] ?? 'unknown'
const segmentName = `OtherTransaction/consumer/${operation}/${destination}`

transaction.trace.attributes.addAttribute(
DESTINATIONS.TRANS_SCOPE,
'message.queueName',
destination
)
transaction.name = segmentName

const segment = agent.tracer.createSegment({
name: segmentName,
parent: transaction.trace.root,
transaction
})
transaction.baseSegment = segment

return { segment, transaction }
}
1 change: 1 addition & 0 deletions lib/transaction/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ function Transaction(agent) {
agent.emit('transactionStarted', this)
}

Transaction.DESTINATIONS = DESTS
Transaction.TYPES = TYPES
Transaction.TYPES_SET = TYPES_SET
Transaction.TRANSPORT_TYPES = TRANSPORT_TYPES
Expand Down
63 changes: 63 additions & 0 deletions test/unit/lib/otel/consumer.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* Copyright 2024 New Relic Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/

'use strict'

// Tests to verify that we can map OTEL "consumer" spans to NR segments.

const test = require('node:test')
const assert = require('node:assert')

const { BasicTracerProvider } = require('@opentelemetry/sdk-trace-base')
const { SpanKind } = require('@opentelemetry/api')

const { DESTINATIONS } = require('../../../../lib/transaction')
const helper = require('../../../lib/agent_helper')
const createSpan = require('./fixtures/span')
const SegmentSynthesizer = require('../../../../lib/otel/segment-synthesis')

test.beforeEach((ctx) => {
const logs = []
const logger = {
debug(...args) {
logs.push(args)
}
}
const agent = helper.loadMockedAgent()
const synth = new SegmentSynthesizer(agent, { logger })
const tracer = new BasicTracerProvider().getTracer('default')

ctx.nr = {
agent,
logger,
logs,
synth,
tracer
}
})

test.afterEach((ctx) => {
helper.unloadAgent(ctx.nr.agent)
})

test('should create consumer segment from otel span', (t) => {
const { synth, tracer } = t.nr
const span = createSpan({ tracer, kind: SpanKind.CONSUMER })
span.setAttribute('messaging.operation', 'receive')

const expectedName = 'OtherTransaction/consumer/receive/unknown'
const { segment, transaction } = synth.synthesize(span)
assert.equal(segment.name, expectedName)
assert.equal(segment.parentId, segment.root.id)
assert.equal(transaction.name, expectedName)
assert.equal(transaction.type, 'bg')
assert.equal(transaction.baseSegment, segment)
assert.equal(
transaction.trace.attributes.get(DESTINATIONS.TRANS_SCOPE)['message.queueName'],
'unknown'
)

transaction.end()
})

0 comments on commit f3a0d1f

Please sign in to comment.