@@ -13,14 +13,16 @@ import {
1313 createContextKey ,
1414 SpanStatusCode ,
1515 type Span ,
16- type Context ,
16+ type Context
1717} from '@opentelemetry/api'
1818
1919import { Config } from '@athenna/config'
2020import { NodeSDK } from '@opentelemetry/sdk-node'
2121import { Options , Macroable } from '@athenna/common'
2222import { getRPCMetadata , RPCType } from '@opentelemetry/core'
2323
24+ const otelCurrentContextBagKey = Symbol . for ( 'athenna.otel.currentContextBag' )
25+
2426export class OtelImpl extends Macroable {
2527 /**
2628 * Holds the OpenTelemetry SDK instance.
@@ -181,7 +183,10 @@ export class OtelImpl extends Macroable {
181183 * const tenantId = Otel.getContextValue(tenantIdKey)
182184 * ```
183185 */
184- public getContextValue < T = any > ( key : string | symbol , ctx ?: Context ) : T | undefined {
186+ public getContextValue < T = any > (
187+ key : string | symbol ,
188+ ctx ?: Context
189+ ) : T | undefined {
185190 return ( ctx || context . active ( ) ) . getValue ( key as any ) as T | undefined
186191 }
187192
@@ -193,7 +198,11 @@ export class OtelImpl extends Macroable {
193198 * const nextContext = Otel.setContextValue(tenantIdKey, 'tenant-1')
194199 * ```
195200 */
196- public setContextValue < T = any > ( key : string | symbol , value : T , ctx ?: Context ) {
201+ public setContextValue < T = any > (
202+ key : string | symbol ,
203+ value : T ,
204+ ctx ?: Context
205+ ) {
197206 return ( ctx || context . active ( ) ) . setValue ( key as any , value )
198207 }
199208
@@ -207,10 +216,84 @@ export class OtelImpl extends Macroable {
207216 * })
208217 * ```
209218 */
210- public withContextValue < T = any , Result = any > ( key : string | symbol , value : T , callback : ( ) => Result , ctx ?: Context ) {
219+ public withContextValue < T = any , Result = any > (
220+ key : string | symbol ,
221+ value : T ,
222+ callback : ( ) => Result ,
223+ ctx ?: Context
224+ ) {
211225 return context . with ( this . setContextValue ( key , value , ctx ) , callback )
212226 }
213227
228+ /**
229+ * Get a value from the mutable request-scoped context store.
230+ *
231+ * @example
232+ * ```ts
233+ * const exampleId = Otel.getCurrentContextValue('exampleId')
234+ * ```
235+ */
236+ public getCurrentContextValue < T = any > (
237+ key : string | symbol ,
238+ ctx ?: Context
239+ ) : T | undefined {
240+ return this . getCurrentContextStore ( ctx ) . get ( key ) as T | undefined
241+ }
242+
243+ /**
244+ * Set a value inside the mutable request-scoped context store.
245+ *
246+ * @example
247+ * ```ts
248+ * Otel.setCurrentContextValue('exampleId', 'example-id-from-controller')
249+ * ```
250+ */
251+ public setCurrentContextValue < T = any > (
252+ key : string | symbol ,
253+ value : T ,
254+ ctx ?: Context
255+ ) {
256+ this . getCurrentContextStore ( ctx ) . set ( key , value )
257+
258+ return this
259+ }
260+
261+ /**
262+ * Delete a value from the mutable request-scoped context store.
263+ *
264+ * @example
265+ * ```ts
266+ * Otel.deleteCurrentContextValue('exampleId')
267+ * ```
268+ */
269+ public deleteCurrentContextValue (
270+ key : string | symbol ,
271+ ctx ?: Context
272+ ) : boolean {
273+ return this . getCurrentContextStore ( ctx ) . delete ( key )
274+ }
275+
276+ /**
277+ * Set multiple values inside the mutable request-scoped context store.
278+ *
279+ * @example
280+ * ```ts
281+ * Otel.setCurrentContextValues({ tenantId: 'tenant-1', requestId: 'req-1' })
282+ * ```
283+ */
284+ public setCurrentContextValues (
285+ values : Record < string | symbol , unknown > ,
286+ ctx ?: Context
287+ ) {
288+ const store = this . getCurrentContextStore ( ctx )
289+
290+ for ( const key of Reflect . ownKeys ( values ) ) {
291+ store . set ( key as string | symbol , values [ key as keyof typeof values ] )
292+ }
293+
294+ return this
295+ }
296+
214297 /**
215298 * Get the HTTP RPC metadata for the current active context.
216299 *
@@ -258,6 +341,21 @@ export class OtelImpl extends Macroable {
258341 return new NodeSDK ( options )
259342 }
260343
344+ /**
345+ * Return the mutable request-scoped context store from the active context.
346+ */
347+ private getCurrentContextStore ( ctx ?: Context ) {
348+ const store = ( ctx || context . active ( ) ) . getValue (
349+ otelCurrentContextBagKey as any
350+ )
351+
352+ if ( ! store || ! ( store instanceof Map ) ) {
353+ throw new Error ( 'Current request context store is not initialized' )
354+ }
355+
356+ return store as Map < string | symbol , unknown >
357+ }
358+
261359 /**
262360 * Handle the error that occurs when recording a span.
263361 */
0 commit comments