From 359a26610fffa139cc5287745e1c063c0f168fc5 Mon Sep 17 00:00:00 2001 From: Bob Evans Date: Tue, 17 Dec 2024 10:47:56 -0500 Subject: [PATCH] feat: Added segment synthesis for internal spans (#2840) --- lib/otel/rules.js | 20 +++++++++++++++++++ lib/otel/rules.json | 1 + lib/otel/segment-synthesis.js | 5 ++++- lib/otel/segments/index.js | 2 ++ lib/otel/segments/internal.js | 17 ++++++++++++++++ test/unit/lib/otel/rules.test.js | 11 ++++++++++ .../unit/lib/otel/segment-synthesizer.test.js | 19 ++++++++++++++++++ 7 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 lib/otel/segments/internal.js diff --git a/lib/otel/rules.js b/lib/otel/rules.js index ec4ec16859..560b0bacaa 100644 --- a/lib/otel/rules.js +++ b/lib/otel/rules.js @@ -46,6 +46,7 @@ class Rule { static OTEL_SPAN_KIND_SERVER = 'server' static OTEL_SPAN_KIND_CLIENT = 'client' static OTEL_SPAN_KIND_PRODUCER = 'producer' + static OTEL_SPAN_KIND_INTERNAL = 'internal' #name #spanKinds @@ -90,6 +91,10 @@ class Rule { return this.#spanKinds.includes('consumer') } + get isInternalRule() { + return this.#spanKinds.includes('internal') + } + get isProducerRule() { return this.#spanKinds.includes(Rule.OTEL_SPAN_KIND_PRODUCER) } @@ -127,6 +132,7 @@ class RulesEngine { #fallbackServerRules = new Map() #clientRules = new Map() #fallbackClientRules = new Map() + #fallbackInternalRules = new Map() #fallbackProducerRules = new Map() constructor() { @@ -140,6 +146,8 @@ class RulesEngine { this.#fallbackClientRules.set(rule.name, rule) } else if (rule.isProducerRule === true) { this.#fallbackProducerRules.set(rule.name, rule) + } else if (rule.isInternalRule === true) { + this.#fallbackInternalRules.set(rule.name, rule) } continue } @@ -209,6 +217,18 @@ class RulesEngine { } break } + + // there currently are no internal rules, just fallback + // if we add new rules they will have to be wired up + case SpanKind.INTERNAL: { + for (const rule of this.#fallbackInternalRules.values()) { + if (rule.matches(otelSpan) === true) { + result = rule + break + } + } + break + } } return result diff --git a/lib/otel/rules.json b/lib/otel/rules.json index 98e61b8f55..0864b28b47 100644 --- a/lib/otel/rules.json +++ b/lib/otel/rules.json @@ -476,6 +476,7 @@ }, { "name": "Fallback", + "type": "internal", "matcher": { "required_span_kinds": [ "internal" diff --git a/lib/otel/segment-synthesis.js b/lib/otel/segment-synthesis.js index 78af79d7be..3866111051 100644 --- a/lib/otel/segment-synthesis.js +++ b/lib/otel/segment-synthesis.js @@ -10,7 +10,8 @@ const { createDbSegment, createHttpExternalSegment, createServerSegment, - createProducerSegment + createProducerSegment, + createInternalSegment } = require('./segments') class SegmentSynthesizer { @@ -36,6 +37,8 @@ class SegmentSynthesizer { return createDbSegment(this.agent, otelSpan) case 'external': return createHttpExternalSegment(this.agent, otelSpan) + case 'internal': + return createInternalSegment(this.agent, otelSpan) case 'producer': return createProducerSegment(this.agent, otelSpan) case 'server': diff --git a/lib/otel/segments/index.js b/lib/otel/segments/index.js index 27e72a15cd..87efae43da 100644 --- a/lib/otel/segments/index.js +++ b/lib/otel/segments/index.js @@ -8,10 +8,12 @@ const createHttpExternalSegment = require('./http-external') const createDbSegment = require('./database') const createServerSegment = require('./server') const createProducerSegment = require('./producer') +const createInternalSegment = require('./internal') module.exports = { createDbSegment, createHttpExternalSegment, + createInternalSegment, createProducerSegment, createServerSegment } diff --git a/lib/otel/segments/internal.js b/lib/otel/segments/internal.js new file mode 100644 index 0000000000..827ac8288d --- /dev/null +++ b/lib/otel/segments/internal.js @@ -0,0 +1,17 @@ +/* + * Copyright 2024 New Relic Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +'use strict' + +module.exports = function createInternalSegment(agent, otelSpan) { + const context = agent.tracer.getContext() + const name = `Custom/${otelSpan.name}` + const segment = agent.tracer.createSegment({ + name, + parent: context.segment, + transaction: context.transaction + }) + return { segment, transaction: context.transaction } +} diff --git a/test/unit/lib/otel/rules.test.js b/test/unit/lib/otel/rules.test.js index c38f0b55c6..57ade4938f 100644 --- a/test/unit/lib/otel/rules.test.js +++ b/test/unit/lib/otel/rules.test.js @@ -76,3 +76,14 @@ test('fallback producer rule is met', () => { assert.notEqual(rule, undefined) assert.equal(rule.name, 'FallbackProducer') }) + +test('fallback internal rule is met', () => { + const engine = new RulesEngine() + const span = new Span(tracer, ROOT_CONTEXT, 'test-span', spanContext, SpanKind.INTERNAL, parentId) + span.setAttribute('foo.bar', 'baz') + span.end() + + const rule = engine.test(span) + assert.notEqual(rule, undefined) + assert.equal(rule.name, 'Fallback') +}) diff --git a/test/unit/lib/otel/segment-synthesizer.test.js b/test/unit/lib/otel/segment-synthesizer.test.js index 8351ea89ec..39c0903bb0 100644 --- a/test/unit/lib/otel/segment-synthesizer.test.js +++ b/test/unit/lib/otel/segment-synthesizer.test.js @@ -214,6 +214,25 @@ test('should create queue producer segment', (t, end) => { }) }) +test('should create internal custom segment', (t, end) => { + const { agent, synthesizer, parentId, tracer } = t.nr + helper.runInTransaction(agent, (tx) => { + const span = createSpan({ + name: 'doer-of-stuff', + kind: SpanKind.INTERNAL, + parentId, + tx, + tracer + }) + const { segment, transaction } = synthesizer.synthesize(span) + assert.equal(tx.id, transaction.id) + assert.equal(segment.name, 'Custom/doer-of-stuff') + assert.equal(segment.parentId, tx.trace.root.id) + tx.end() + end() + }) +}) + test('should log warning span does not match a rule', (t, end) => { const { agent, synthesizer, loggerMock, parentId, tracer } = t.nr