Skip to content

Commit d11c071

Browse files
authored
refactor: Separated context classes for agent in standard and opentelemetry bridge mode (#2967)
1 parent 770bf6f commit d11c071

File tree

6 files changed

+126
-73
lines changed

6 files changed

+126
-73
lines changed

lib/context-manager/async-local-context-manager.js

+4-2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
const { AsyncLocalStorage } = require('async_hooks')
99
const Context = require('./context')
10+
const OtelContext = require('../otel/context')
1011

1112
/**
1213
* Class for managing state in the agent.
@@ -18,7 +19,8 @@ const Context = require('./context')
1819
* @class
1920
*/
2021
class AsyncLocalContextManager {
21-
constructor() {
22+
constructor(isOtelBridgeMode) {
23+
this.isOtelBridgeMode = isOtelBridgeMode
2224
this._asyncLocalStorage = new AsyncLocalStorage()
2325
}
2426

@@ -28,7 +30,7 @@ class AsyncLocalContextManager {
2830
* @returns {object} The current active context.
2931
*/
3032
getContext() {
31-
return this._asyncLocalStorage.getStore() || new Context()
33+
return this._asyncLocalStorage.getStore() || (this.isOtelBridgeMode ? new OtelContext() : new Context())
3234
}
3335

3436
/**

lib/context-manager/context.js

+3-66
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,11 @@
44
*/
55

66
'use strict'
7-
const { otelSynthesis } = require('../symbols')
8-
const FakeSpan = require('../otel/fake-span')
97

108
module.exports = class Context {
11-
constructor(transaction, segment, parentContext) {
9+
constructor(transaction, segment) {
1210
this._transaction = transaction
1311
this._segment = segment
14-
this._otelCtx = parentContext ? new Map(parentContext) : new Map()
1512
}
1613

1714
get segment() {
@@ -26,83 +23,23 @@ module.exports = class Context {
2623
* Constructs a new context from segment about to be bound to context manager
2724
* along with the current transaction.
2825
*
29-
* If agent is in otel bridge mode it will also bind a FakeSpan to the otel ctx.
30-
*
3126
* @param {object} params to function
3227
* @param {TraceSegment} params.segment segment to bind to context
3328
* @param {Transaction} params.transaction active transaction
3429
* @returns {Context} a newly constructed context
3530
*/
3631
enterSegment({ segment, transaction = this._transaction }) {
37-
if (transaction?.agent?.otelSpanKey) {
38-
this._otelCtx.set(transaction.agent.otelSpanKey, new FakeSpan(segment, transaction))
39-
}
4032
return new this.constructor(transaction, segment, this._otelCtx)
4133
}
4234

4335
/**
4436
* Constructs a new context from transaction about to be bound to context manager.
4537
* It uses the trace root segment as the segment in context.
4638
*
47-
* If agent is in otel bridge mode it will also bind a FakeSpan to the otel ctx.
48-
*
49-
* @param {object} params to function
50-
* @param {TraceSegment} params.segment transaction trace root segment
51-
* @param {Transaction} params.transaction transaction to bind to context
52-
* @param transaction
39+
* @param {Transaction} transaction transaction to bind to context
5340
* @returns {Context} a newly constructed context
5441
*/
5542
enterTransaction(transaction) {
56-
if (transaction?.agent?.otelSpanKey) {
57-
this._otelCtx.set(transaction.agent.otelSpanKey, new FakeSpan(transaction.trace.root, transaction))
58-
}
59-
return new this.constructor(transaction, transaction.trace.root, this._otelCtx)
60-
}
61-
62-
/**
63-
* Required for bridging OTEL data into the agent.
64-
*
65-
* @param {string} key Stored entity name to retrieve.
66-
*
67-
* @returns {*} The stored value.
68-
*/
69-
getValue(key) {
70-
return this._otelCtx.get(key)
71-
}
72-
73-
/**
74-
* Required for bridging OTEL data into the agent.
75-
*
76-
* @param {string} key Name for stored value.
77-
* @param {*} value Value to store.
78-
*
79-
* @returns {object} The context manager object.
80-
*/
81-
setValue(key, value) {
82-
let ctx
83-
84-
if (value[otelSynthesis] && value[otelSynthesis].segment && value[otelSynthesis].transaction) {
85-
const { segment, transaction } = value[otelSynthesis]
86-
segment.start()
87-
ctx = new this.constructor(transaction, segment, this._otelCtx)
88-
} else {
89-
ctx = new this.constructor(this._transaction, this._segment, this._otelCtx)
90-
}
91-
92-
ctx._otelCtx.set(key, value)
93-
return ctx
94-
}
95-
96-
/**
97-
* Required for bridging OTEL data into the agent.
98-
*
99-
* @param {string} key Named value to remove from the store.
100-
*
101-
* @returns {object} The context manager object.
102-
*/
103-
deleteValue(key) {
104-
const ctx = new this.constructor(this._transaction, this._segment, this._otelCtx)
105-
ctx._otelCtx.delete(key)
106-
return ctx
43+
return new this.constructor(transaction, transaction.trace.root)
10744
}
10845
}

lib/otel/context.js

+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/*
2+
* Copyright 2025 New Relic Corporation. All rights reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
'use strict'
7+
const { otelSynthesis } = require('../symbols')
8+
const FakeSpan = require('./fake-span')
9+
const Context = require('../context-manager/context')
10+
11+
module.exports = class OtelContext extends Context {
12+
constructor(transaction, segment, parentContext) {
13+
super(transaction, segment)
14+
this._otelCtx = parentContext ? new Map(parentContext) : new Map()
15+
}
16+
17+
/**
18+
* Constructs a new context from segment about to be bound to context manager
19+
* along with the current transaction. It will also bind a FakeSpan to the `_otelCtx`
20+
*
21+
* @param {object} params to function
22+
* @param {TraceSegment} params.segment segment to bind to context
23+
* @param {Transaction} params.transaction active transaction
24+
* @returns {OtelContext} a newly constructed context
25+
*/
26+
enterSegment({ segment, transaction = this._transaction }) {
27+
this._otelCtx.set(transaction.agent.otelSpanKey, new FakeSpan(segment, transaction))
28+
return new this.constructor(transaction, segment, this._otelCtx)
29+
}
30+
31+
/**
32+
* Constructs a new context from transaction about to be bound to context manager.
33+
* It uses the trace root segment as the segment in context. It will also bind a FakeSpan to the `_otelCtx`.
34+
*
35+
* @param {Transaction} transaction transaction to bind to context
36+
* @returns {OtelContext} a newly constructed context
37+
*/
38+
enterTransaction(transaction) {
39+
this._otelCtx.set(transaction.agent.otelSpanKey, new FakeSpan(transaction.trace.root, transaction))
40+
return new this.constructor(transaction, transaction.trace.root, this._otelCtx)
41+
}
42+
43+
/**
44+
* Used to retrieve data from `_otelCtx`
45+
*
46+
* @param {string} key Stored entity name to retrieve.
47+
*
48+
* @returns {*} The stored value.
49+
*/
50+
getValue(key) {
51+
return this._otelCtx.get(key)
52+
}
53+
54+
/**
55+
* Used to set data on `_otelCtx`
56+
*
57+
* @param {string} key Name for stored value.
58+
* @param {*} value Value to store.
59+
*
60+
* @returns {OtelContext} The context object.
61+
*/
62+
setValue(key, value) {
63+
let ctx
64+
65+
if (value[otelSynthesis] && value[otelSynthesis].segment && value[otelSynthesis].transaction) {
66+
const { segment, transaction } = value[otelSynthesis]
67+
segment.start()
68+
ctx = new this.constructor(transaction, segment, this._otelCtx)
69+
} else {
70+
ctx = new this.constructor(this._transaction, this._segment, this._otelCtx)
71+
}
72+
73+
ctx._otelCtx.set(key, value)
74+
return ctx
75+
}
76+
77+
/**
78+
* Used to remove data from `_otelCtx`
79+
*
80+
* @param {string} key Named value to remove from the store.
81+
*
82+
* @returns {OtelContext} The context object.
83+
*/
84+
deleteValue(key) {
85+
const ctx = new this.constructor(this._transaction, this._segment, this._otelCtx)
86+
ctx._otelCtx.delete(key)
87+
return ctx
88+
}
89+
}

lib/transaction/tracer/index.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ function Tracer(agent) {
2323
}
2424

2525
this.agent = agent
26-
this._contextManager = new AsyncLocalContextManager()
26+
this._contextManager = new AsyncLocalContextManager(agent.config.feature_flag.opentelemetry_bridge)
2727
}
2828

2929
Tracer.prototype.getContext = getContext

test/unit/context-manager/async-local-context-manager.test.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ test('runInContext()', async (t) => {
8888
})
8989

9090
await t.test('should restore previous context on exception', () => {
91-
const contextManager = new AsyncLocalContextManager({})
91+
const contextManager = new AsyncLocalContextManager()
9292

9393
const context = contextManager.getContext()
9494
const previousContext = context.enterSegment({ segment: 'previous', transaction: 'tx' })
@@ -109,7 +109,7 @@ test('runInContext()', async (t) => {
109109
})
110110

111111
await t.test('should apply `cbThis` arg to execution', (t, end) => {
112-
const contextManager = new AsyncLocalContextManager({})
112+
const contextManager = new AsyncLocalContextManager()
113113

114114
const context = contextManager.getContext()
115115
const expectedThis = () => {}
@@ -123,7 +123,7 @@ test('runInContext()', async (t) => {
123123
})
124124

125125
await t.test('should apply args array to execution', (t, end) => {
126-
const contextManager = new AsyncLocalContextManager({})
126+
const contextManager = new AsyncLocalContextManager()
127127

128128
const context = contextManager.getContext()
129129
const expectedArg1 = 'first arg'
@@ -140,7 +140,7 @@ test('runInContext()', async (t) => {
140140
})
141141

142142
await t.test('should apply arguments construct to execution', (t, end) => {
143-
const contextManager = new AsyncLocalContextManager({})
143+
const contextManager = new AsyncLocalContextManager()
144144
const context = contextManager.getContext()
145145
const expectedArg1 = 'first arg'
146146
const expectedArg2 = 'second arg'

test/unit/lib/otel/context.test.js

+25
Original file line numberDiff line numberDiff line change
@@ -72,3 +72,28 @@ test('should remove otelSynthesis symbol when it exists on value', () => {
7272
const otelData = newCtx.getValue(key)
7373
assert.deepEqual(otelData, { test: 'value' })
7474
})
75+
76+
test('should add transaction and trace root to otel ctx', (t) => {
77+
const { agent } = t.nr
78+
const ctx = otel.context.active()
79+
const transaction = { agent, traceId: 'traceId', trace: { root: { id: 'segmentId' } } }
80+
const newContext = ctx.enterTransaction(transaction)
81+
const fakeSpan = newContext.getValue(agent.otelSpanKey)
82+
assert.deepEqual(fakeSpan, {
83+
segment: transaction.trace.root,
84+
transaction
85+
})
86+
})
87+
88+
test('should add segment to otel ctx', (t) => {
89+
const { agent } = t.nr
90+
const ctx = otel.context.active()
91+
ctx._transaction = { agent, traceId: 'traceId' }
92+
const segment = { id: 'segmentId' }
93+
const newContext = ctx.enterSegment({ segment })
94+
const fakeSpan = newContext.getValue(agent.otelSpanKey)
95+
assert.deepEqual(fakeSpan, {
96+
segment,
97+
transaction: newContext.transaction
98+
})
99+
})

0 commit comments

Comments
 (0)