diff --git a/packages/instrumentation-oracledb/src/OracleTelemetryTraceHandler.ts b/packages/instrumentation-oracledb/src/OracleTelemetryTraceHandler.ts index e3a5151437..24c1206488 100644 --- a/packages/instrumentation-oracledb/src/OracleTelemetryTraceHandler.ts +++ b/packages/instrumentation-oracledb/src/OracleTelemetryTraceHandler.ts @@ -24,6 +24,8 @@ import { SpanKind, trace, diag, + TraceFlags, + SpanContext, } from '@opentelemetry/api'; import { ATTR_DB_NAMESPACE, @@ -63,6 +65,14 @@ function getTraceHandlerBaseClass( } } +export function buildTraceparent( + spanContext: SpanContext +): string | undefined { + return `00-${spanContext.traceId}-${spanContext.spanId}-0${Number( + spanContext.traceFlags || TraceFlags.NONE + ).toString(16)}`; +} + export function getOracleTelemetryTraceHandlerClass( obj: typeof oracleDBTypes ): any { @@ -354,6 +364,27 @@ export function getOracleTelemetryTraceHandlerClass( }; if (traceContext.fn) { + if ( + this._instrumentConfig.traceContextPropagation && + (traceContext.operation === SpanNames.EXECUTE || + traceContext.operation === SpanNames.EXECUTE_MANY) + ) { + const connection = traceContext.additionalConfig?.self; + const traceparent = buildTraceparent( + traceContext.userContext.span.spanContext() + ); + if (connection && 'action' in connection && traceparent) { + try { + connection.action = traceparent; + } catch (err) { + diag.debug( + 'Failed to set connection.action for trace propagation', + err + ); + } + } + } + // wrap the active span context to the exported function. traceContext.fn = context.bind( trace.setSpan(context.active(), traceContext.userContext.span), diff --git a/packages/instrumentation-oracledb/src/types.ts b/packages/instrumentation-oracledb/src/types.ts index 821554609e..c8b9451b44 100644 --- a/packages/instrumentation-oracledb/src/types.ts +++ b/packages/instrumentation-oracledb/src/types.ts @@ -91,4 +91,11 @@ export interface OracleInstrumentationConfig extends InstrumentationConfig { * @default false */ requireParentSpan?: boolean; + + /** + * Automatic propagation of trace context using V$SESSION.ACTION. + * + * @default false + */ + traceContextPropagation?: boolean; } diff --git a/packages/instrumentation-oracledb/test/oracle.test.ts b/packages/instrumentation-oracledb/test/oracle.test.ts index c833eb0ac0..e97869b365 100644 --- a/packages/instrumentation-oracledb/test/oracle.test.ts +++ b/packages/instrumentation-oracledb/test/oracle.test.ts @@ -36,6 +36,7 @@ import { import * as assert from 'assert'; import { OracleInstrumentation } from '../src'; import { SpanNames } from '../src/constants'; +import { buildTraceparent} from '../src/OracleTelemetryTraceHandler'; import { ATTR_DB_NAMESPACE, @@ -524,6 +525,7 @@ describe('oracledb', () => { instrumentation.setConfig({ enhancedDatabaseReporting: false, dbStatementDump: false, + traceContextPropagation: false, }); }); @@ -975,6 +977,28 @@ describe('oracledb', () => { }); }); + it('should propagate trace context via connection.action when enabled', async () => { + instrumentation.setConfig({ traceContextPropagation: true }); + const result = await connection.execute( + "select sys_context('USERENV', 'ACTION') as action from dual", + [], + { outFormat: oracledb.OUT_FORMAT_OBJECT } + ); + const row = result.rows?.[0] as Record | undefined; + const actionValue = row?.ACTION; + const spans = memoryExporter.getFinishedSpans(); + const executeSpan = spans[spans.length - 1]; + assert.ok(executeSpan, 'expected span to verify trace propagation'); + assert.ok( + executeSpan.name.startsWith(SpanNames.EXECUTE), + `expected execute span, got ${executeSpan.name}` + ); + const expectedTraceparent = buildTraceparent( + executeSpan.spanContext() + ); + assert.strictEqual(actionValue, expectedTraceparent); + }); + it('should intercept connection.execute(sql, values) bind-by-name', async () => { const span = tracer.startSpan('test span'); await context.with(trace.setSpan(context.active(), span), async () => {