diff --git a/src/core/ProtobufCommunicator.ts b/src/core/ProtobufCommunicator.ts index aa3225e6..c6613a1e 100644 --- a/src/core/ProtobufCommunicator.ts +++ b/src/core/ProtobufCommunicator.ts @@ -170,7 +170,7 @@ export class ProtobufCommunicator { requestId, tags: {}, outboundSpan: cleanSpan, - stackTrace: this.getStackTrace(), + stackTrace: cleanSpan?.stackTrace, }); const sdkMessage: SDKMessage = SDKMessage.create({ @@ -231,7 +231,7 @@ export class ProtobufCommunicator { requestId, tags: {}, outboundSpan: cleanSpan, - stackTrace: this.getStackTrace(), + stackTrace: cleanSpan?.stackTrace, }); const sdkMessage = SDKMessage.create({ diff --git a/src/core/types.ts b/src/core/types.ts index d7231148..431a77b1 100644 --- a/src/core/types.ts +++ b/src/core/types.ts @@ -99,6 +99,7 @@ export type CleanSpanData = { }; // sdk-specific isUsed?: boolean; + stackTrace?: string; }; export type MockRequestData = { @@ -111,6 +112,7 @@ export type MockRequestData = { submoduleName: string; inputValue: unknown; kind: SpanKind; + stackTrace?: string; }; export type MetadataObject = { diff --git a/src/instrumentation/core/utils/index.ts b/src/instrumentation/core/utils/index.ts index 02f6350e..0c25769f 100644 --- a/src/instrumentation/core/utils/index.ts +++ b/src/instrumentation/core/utils/index.ts @@ -2,3 +2,4 @@ export * from "./modeUtils"; export * from "./selfExclusion"; export * from "./modeUtils"; export * from "./shimmerUtils"; +export * from "./stackTraceUtils"; diff --git a/src/instrumentation/core/utils/mockResponseUtils.ts b/src/instrumentation/core/utils/mockResponseUtils.ts index b4fc8eb0..3f80d2da 100644 --- a/src/instrumentation/core/utils/mockResponseUtils.ts +++ b/src/instrumentation/core/utils/mockResponseUtils.ts @@ -58,6 +58,7 @@ function convertMockRequestDataToCleanSpanData( code: StatusCode.OK, message: "OK", }, + stackTrace: mockRequestData.stackTrace, }; } diff --git a/src/instrumentation/core/utils/stackTraceUtils.ts b/src/instrumentation/core/utils/stackTraceUtils.ts new file mode 100644 index 00000000..b9775fd6 --- /dev/null +++ b/src/instrumentation/core/utils/stackTraceUtils.ts @@ -0,0 +1,46 @@ +/** + * Helper functions for capturing stack traces in replay mode + * + * TODO: Consider using a structured format for stack frames: + * + * { + * "frames": [ + * { + * "fileName": "file.js", + * "lineNumber": 10, + * "columnNumber": 20, + * "functionName": "functionName" + * } + * ] + * } + * + * This would allow for more efficient matching and filtering of stack frames. + * It would also allow for more accurate stack trace reconstruction in replay mode. + */ + +/** + * + * @param excludeClassNames - Class names to exclude from the stack trace + * @returns The stack trace as a string + */ +export function captureStackTrace(excludeClassNames: string[] = []): string { + const originalStackTraceLimit = Error.stackTraceLimit; // Default is 10 + + Error.stackTraceLimit = 100; + const s = new Error().stack || ""; + Error.stackTraceLimit = originalStackTraceLimit; + + const defaultExcludes = [ + "drift-node-sdk/src/instrumentation", + "drift-node-sdk/src/core", + "node_modules/@use-tusk", + ]; + + const allExcludes = [...defaultExcludes, ...excludeClassNames]; + + return s + .split("\n") + .slice(2) // Skip "Error" and capture method lines + .filter((l) => !allExcludes.some(exclude => l.includes(exclude))) + .join("\n"); +} diff --git a/src/instrumentation/libraries/fetch/Instrumentation.ts b/src/instrumentation/libraries/fetch/Instrumentation.ts index eb2c588e..1232619d 100644 --- a/src/instrumentation/libraries/fetch/Instrumentation.ts +++ b/src/instrumentation/libraries/fetch/Instrumentation.ts @@ -4,7 +4,7 @@ import { SpanInfo, SpanUtils } from "../../../core/tracing/SpanUtils"; import { SpanKind, SpanStatusCode } from "@opentelemetry/api"; import { TuskDriftCore, TuskDriftMode } from "../../../core/TuskDrift"; import { getDecodedType, httpBodyEncoder } from "../http/utils"; -import { isTuskDriftIngestionUrl, TUSK_SKIP_HEADER } from "../../core/utils"; +import { captureStackTrace, isTuskDriftIngestionUrl, TUSK_SKIP_HEADER } from "../../core/utils"; import { findMockResponseAsync } from "../../core/utils/mockResponseUtils"; import { handleReplayMode, handleRecordMode } from "../../core/utils/modeUtils"; import { FetchInputValue, FetchOutputValue, FetchInstrumentationConfig } from "./types"; @@ -60,7 +60,8 @@ export class FetchInstrumentation extends TdInstrumentationBase { input: string | URL | Request, init?: RequestInit, ): Promise { - return self._handleFetchRequest(input, init); + const stackTrace = captureStackTrace(["FetchInstrumentation"]); + return self._handleFetchRequest(input, init, stackTrace); } as typeof globalThis.fetch; logger.debug("Global fetch patching complete"); @@ -69,6 +70,7 @@ export class FetchInstrumentation extends TdInstrumentationBase { private async _handleFetchRequest( input: string | URL | Request, init?: RequestInit, + stackTrace?: string, ): Promise { // Parse request details const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url; @@ -118,7 +120,7 @@ export class FetchInstrumentation extends TdInstrumentationBase { isPreAppStart: false, }, (spanInfo) => { - return this._handleReplayFetch(inputValue, spanInfo); + return this._handleReplayFetch(inputValue, spanInfo, stackTrace); }, ); }, @@ -242,6 +244,7 @@ export class FetchInstrumentation extends TdInstrumentationBase { private async _handleReplayFetch( inputValue: FetchInputValue, spanInfo: SpanInfo, + stackTrace?: string, ): Promise { const mockData = await findMockResponseAsync({ mockRequestData: { @@ -254,6 +257,7 @@ export class FetchInstrumentation extends TdInstrumentationBase { submoduleName: inputValue.method, inputValue, kind: SpanKind.CLIENT, + stackTrace, }, tuskDrift: this.tuskDrift, inputValueSchemaMerges: { diff --git a/src/instrumentation/libraries/grpc/Instrumentation.ts b/src/instrumentation/libraries/grpc/Instrumentation.ts index 476db83f..e945e009 100644 --- a/src/instrumentation/libraries/grpc/Instrumentation.ts +++ b/src/instrumentation/libraries/grpc/Instrumentation.ts @@ -4,7 +4,7 @@ import { TdInstrumentationNodeModuleFile } from "../../core/baseClasses/TdInstru import { SpanUtils, SpanInfo } from "../../../core/tracing/SpanUtils"; import { SpanKind, SpanStatusCode } from "@opentelemetry/api"; import { TuskDriftCore, TuskDriftMode } from "../../../core/TuskDrift"; -import { wrap } from "../../core/utils"; +import { captureStackTrace, wrap } from "../../core/utils"; import { findMockResponseAsync } from "../../core/utils/mockResponseUtils"; import { handleRecordMode, handleReplayMode } from "../../core/utils/modeUtils"; import { PackageType } from "@use-tusk/drift-schemas/core/span"; @@ -303,6 +303,8 @@ export class GrpcInstrumentation extends TdInstrumentationBase { // Handle replay mode if (self.mode === TuskDriftMode.REPLAY) { + const stackTrace = captureStackTrace(["GrpcInstrumentation"]); + return handleReplayMode({ replayModeHandler: () => { return SpanUtils.createAndExecuteSpan( @@ -324,6 +326,7 @@ export class GrpcInstrumentation extends TdInstrumentationBase { inputValue, callback, MetadataConstructor, + stackTrace, ); }, ); @@ -525,6 +528,7 @@ export class GrpcInstrumentation extends TdInstrumentationBase { inputValue: GrpcClientInputValue, callback: Function, MetadataConstructor: any, + stackTrace?: string, ): Promise { logger.debug(`[GrpcInstrumentation] Replaying gRPC unary request`); @@ -539,6 +543,7 @@ export class GrpcInstrumentation extends TdInstrumentationBase { instrumentationName: this.INSTRUMENTATION_NAME, submoduleName: "client", kind: SpanKind.CLIENT, + stackTrace, }, tuskDrift: this.tuskDrift, }); diff --git a/src/instrumentation/libraries/http/HttpReplayHooks.ts b/src/instrumentation/libraries/http/HttpReplayHooks.ts index a636144f..09a51342 100644 --- a/src/instrumentation/libraries/http/HttpReplayHooks.ts +++ b/src/instrumentation/libraries/http/HttpReplayHooks.ts @@ -31,12 +31,14 @@ export class HttpReplayHooks { protocol, args, spanInfo, + stackTrace, }: { method: string; requestOptions: RequestOptions; protocol: "http" | "https"; args: any[]; spanInfo: SpanInfo; + stackTrace?: string; }): TdMockClientRequest | undefined { logger.debug( `[HttpReplayHooks] Handling outbound ${protocol.toUpperCase()} ${method} request in replay mode`, @@ -66,7 +68,7 @@ export class HttpReplayHooks { }; // Create and return the mock client request - const mockRequest = new TdMockClientRequest(mockOptions, spanInfo, callback); + const mockRequest = new TdMockClientRequest(mockOptions, spanInfo, callback, stackTrace); // For GET/HEAD requests, automatically call .end() to match native http.get() behavior // Native http.get() and https.get() are convenience methods that automatically call .end() diff --git a/src/instrumentation/libraries/http/Instrumentation.ts b/src/instrumentation/libraries/http/Instrumentation.ts index 06b2c780..55e90133 100644 --- a/src/instrumentation/libraries/http/Instrumentation.ts +++ b/src/instrumentation/libraries/http/Instrumentation.ts @@ -29,6 +29,7 @@ import { handleRecordMode, handleReplayMode, isTuskDriftIngestionUrl, + captureStackTrace, } from "../../core/utils"; import { PackageType, StatusCode } from "@use-tusk/drift-schemas/core/span"; import { @@ -1047,6 +1048,8 @@ export class HttpInstrumentation extends TdInstrumentationBase { ); if (self.mode === TuskDriftMode.REPLAY) { + const stackTrace = captureStackTrace(["HttpInstrumentation"]); + return handleReplayMode({ replayModeHandler: () => { // Build input value object for replay mode @@ -1082,6 +1085,7 @@ export class HttpInstrumentation extends TdInstrumentationBase { protocol: requestProtocol, args, spanInfo, + stackTrace, }); }, ); diff --git a/src/instrumentation/libraries/http/mocks/TdMockClientRequest.ts b/src/instrumentation/libraries/http/mocks/TdMockClientRequest.ts index 8136ef97..2cdf3d9c 100644 --- a/src/instrumentation/libraries/http/mocks/TdMockClientRequest.ts +++ b/src/instrumentation/libraries/http/mocks/TdMockClientRequest.ts @@ -42,6 +42,7 @@ export class TdMockClientRequest extends EventEmitter { public finished: boolean = false; private tuskDrift: TuskDriftCore; private spanInfo: SpanInfo; + private stackTrace?: string; private requestBodyBuffers: Buffer[] = []; private playbackStarted: boolean = false; @@ -52,12 +53,14 @@ export class TdMockClientRequest extends EventEmitter { options: TdMockClientRequestOptions, spanInfo: SpanInfo, callback?: (res: IncomingMessage) => void, + stackTrace?: string, ) { super(); TdMockClientRequest._setupPrototype(); this.tuskDrift = TuskDriftCore.getInstance(); this.spanInfo = spanInfo; + this.stackTrace = stackTrace; if (!options || Object.keys(options).length === 0) { throw new Error( @@ -302,6 +305,7 @@ export class TdMockClientRequest extends EventEmitter { submoduleName: rawInputValue.method, inputValue, kind: SpanKind.CLIENT, + stackTrace: this.stackTrace, }, tuskDrift: this.tuskDrift, inputValueSchemaMerges: { diff --git a/src/instrumentation/libraries/ioredis/Instrumentation.ts b/src/instrumentation/libraries/ioredis/Instrumentation.ts index 96cd37fd..c86d657e 100644 --- a/src/instrumentation/libraries/ioredis/Instrumentation.ts +++ b/src/instrumentation/libraries/ioredis/Instrumentation.ts @@ -20,6 +20,7 @@ import { import { convertValueToJsonable } from "./utils"; import { PackageType } from "@use-tusk/drift-schemas/core/span"; import { logger } from "../../../core/utils/logger"; +import { captureStackTrace } from "src/instrumentation/core/utils"; const SUPPORTED_VERSIONS = [">=4.11.0 <5", "5.*"]; @@ -175,6 +176,8 @@ export class IORedisInstrumentation extends TdInstrumentationBase { // Handle replay mode if (self.mode === TuskDriftMode.REPLAY) { + const stackTrace = captureStackTrace(["IORedisInstrumentation"]); + return handleReplayMode({ replayModeHandler: () => { return SpanUtils.createAndExecuteSpan( @@ -191,7 +194,7 @@ export class IORedisInstrumentation extends TdInstrumentationBase { isPreAppStart: false, }, (spanInfo) => { - return self._handleReplaySendCommand(spanInfo, cmd, inputValue, commandName); + return self._handleReplaySendCommand(spanInfo, cmd, inputValue, commandName, stackTrace); }, ); }, @@ -449,6 +452,7 @@ export class IORedisInstrumentation extends TdInstrumentationBase { cmd: IORedisCommand, inputValue: IORedisInputValue, commandName: string, + stackTrace?: string, ): Promise { logger.debug(`[IORedisInstrumentation] Replaying IORedis command ${cmd.name}`); @@ -462,6 +466,7 @@ export class IORedisInstrumentation extends TdInstrumentationBase { instrumentationName: this.INSTRUMENTATION_NAME, submoduleName: cmd.name, kind: SpanKind.CLIENT, + stackTrace, }, tuskDrift: this.tuskDrift, }); diff --git a/src/instrumentation/libraries/jsonwebtoken/Instrumentation.ts b/src/instrumentation/libraries/jsonwebtoken/Instrumentation.ts index 1825bbcf..793f6e9f 100644 --- a/src/instrumentation/libraries/jsonwebtoken/Instrumentation.ts +++ b/src/instrumentation/libraries/jsonwebtoken/Instrumentation.ts @@ -3,7 +3,7 @@ import { TdInstrumentationNodeModule } from "../../core/baseClasses/TdInstrument import { SpanUtils, SpanInfo } from "../../../core/tracing/SpanUtils"; import { SpanKind, SpanStatusCode } from "@opentelemetry/api"; import { TuskDriftCore, TuskDriftMode } from "../../../core/TuskDrift"; -import { wrap } from "../../core/utils"; +import { captureStackTrace, wrap } from "../../core/utils"; import { findMockResponseAsync } from "../../core/utils/mockResponseUtils"; import { handleRecordMode, handleReplayMode } from "../../core/utils/modeUtils"; import { @@ -116,6 +116,8 @@ export class JsonwebtokenInstrumentation extends TdInstrumentationBase { // Handle replay mode (only if app is ready) if (self.mode === TuskDriftMode.REPLAY) { + const stackTrace = captureStackTrace(["JsonwebtokenInstrumentation"]); + return handleReplayMode({ replayModeHandler: () => { // Create span in replay mode @@ -132,7 +134,7 @@ export class JsonwebtokenInstrumentation extends TdInstrumentationBase { isPreAppStart: false, }, (spanInfo) => { - return self.handleReplayVerify(verifyConfig, inputValue, spanInfo); + return self.handleReplayVerify(verifyConfig, inputValue, spanInfo, stackTrace); }, ); }, @@ -204,6 +206,8 @@ export class JsonwebtokenInstrumentation extends TdInstrumentationBase { // Handle replay mode (only if app is ready) if (self.mode === TuskDriftMode.REPLAY) { + const stackTrace = captureStackTrace(["JsonwebtokenInstrumentation"]); + return handleReplayMode({ replayModeHandler: () => { // Create span in replay mode @@ -220,7 +224,7 @@ export class JsonwebtokenInstrumentation extends TdInstrumentationBase { isPreAppStart: false, }, (spanInfo) => { - return self.handleReplaySign(signConfig, inputValue, spanInfo); + return self.handleReplaySign(signConfig, inputValue, spanInfo, stackTrace); }, ); }, @@ -500,6 +504,7 @@ export class JsonwebtokenInstrumentation extends TdInstrumentationBase { verifyConfig: VerifyQueryConfig, inputValue: JwtVerifyInputValue, spanInfo: SpanInfo, + stackTrace?: string, ): Promise { logger.debug(`[JsonwebtokenInstrumentation] Replaying JWT verify`); @@ -514,6 +519,7 @@ export class JsonwebtokenInstrumentation extends TdInstrumentationBase { submoduleName: "verify", inputValue: inputValue, kind: SpanKind.CLIENT, + stackTrace, }, tuskDrift: this.tuskDrift, }); @@ -578,6 +584,7 @@ export class JsonwebtokenInstrumentation extends TdInstrumentationBase { signConfig: SignQueryConfig, inputValue: JwtSignInputValue, spanInfo: SpanInfo, + stackTrace?: string, ): Promise { logger.debug(`[JsonwebtokenInstrumentation] Replaying JWT sign`); @@ -592,6 +599,7 @@ export class JsonwebtokenInstrumentation extends TdInstrumentationBase { submoduleName: "sign", inputValue: inputValue, kind: SpanKind.CLIENT, + stackTrace, }, tuskDrift: this.tuskDrift, }); diff --git a/src/instrumentation/libraries/mysql2/Instrumentation.ts b/src/instrumentation/libraries/mysql2/Instrumentation.ts index 1c96f6cc..90b0e0b8 100644 --- a/src/instrumentation/libraries/mysql2/Instrumentation.ts +++ b/src/instrumentation/libraries/mysql2/Instrumentation.ts @@ -21,6 +21,7 @@ import { PackageType } from "@use-tusk/drift-schemas/core/span"; import { logger } from "../../../core/utils/logger"; import { TdMysql2ConnectionMock } from "./mocks/TdMysql2ConnectionMock"; import { TdMysql2QueryMock } from "./mocks/TdMysql2QueryMock"; +import { captureStackTrace } from "src/instrumentation/core/utils"; import { TdMysql2ConnectionEventMock } from "./mocks/TdMysql2ConnectionEventMock"; // Version ranges for mysql2 @@ -338,6 +339,8 @@ export class Mysql2Instrumentation extends TdInstrumentationBase { // Handle replay mode (only if app is ready) if (self.mode === TuskDriftMode.REPLAY) { + const stackTrace = captureStackTrace(["Mysql2Instrumentation"]); + return handleReplayMode({ replayModeHandler: () => { const spanName = `mysql2.${clientType}.query`; @@ -355,7 +358,7 @@ export class Mysql2Instrumentation extends TdInstrumentationBase { isPreAppStart: false, }, (spanInfo) => { - return self.handleReplayQuery(queryConfig, inputValue, spanInfo, "query"); + return self.handleReplayQuery(queryConfig, inputValue, spanInfo, "query", stackTrace); }, ); }, @@ -427,6 +430,8 @@ export class Mysql2Instrumentation extends TdInstrumentationBase { // Handle replay mode (only if app is ready) if (self.mode === TuskDriftMode.REPLAY) { + const stackTrace = captureStackTrace(["Mysql2Instrumentation"]); + return handleReplayMode({ replayModeHandler: () => { const spanName = `mysql2.${clientType}.execute`; @@ -444,7 +449,7 @@ export class Mysql2Instrumentation extends TdInstrumentationBase { isPreAppStart: false, }, (spanInfo) => { - return self.handleReplayQuery(queryConfig, inputValue, spanInfo, "execute"); + return self.handleReplayQuery(queryConfig, inputValue, spanInfo, "execute", stackTrace); }, ); }, @@ -991,8 +996,9 @@ export class Mysql2Instrumentation extends TdInstrumentationBase { inputValue: Mysql2InputValue, spanInfo: SpanInfo, submoduleName: string = "query", + stackTrace?: string, ): any { - return this.queryMock.handleReplayQuery(queryConfig, inputValue, spanInfo, submoduleName); + return this.queryMock.handleReplayQuery(queryConfig, inputValue, spanInfo, submoduleName, stackTrace); } private _handleRecordPoolGetConnectionInSpan( diff --git a/src/instrumentation/libraries/mysql2/mocks/TdMysql2ConnectionMock.ts b/src/instrumentation/libraries/mysql2/mocks/TdMysql2ConnectionMock.ts index 20ac8f60..cec332a1 100644 --- a/src/instrumentation/libraries/mysql2/mocks/TdMysql2ConnectionMock.ts +++ b/src/instrumentation/libraries/mysql2/mocks/TdMysql2ConnectionMock.ts @@ -4,6 +4,7 @@ import { createMockInputValue } from "../../../../core/utils"; import { SpanInfo } from "../../../../core/tracing/SpanUtils"; import { logger } from "../../../../core/utils/logger"; import { QueryCallback } from "../types"; +import { captureStackTrace } from "src/instrumentation/core/utils"; /** * Mock MySQL2 connection/pool connection for replay mode @@ -38,6 +39,8 @@ export class TdMysql2ConnectionMock extends EventEmitter { query(...args: any[]) { logger.debug(`[TdMysql2ConnectionMock] Mock connection query intercepted in REPLAY mode`); + const stackTrace = captureStackTrace(["TdMysql2ConnectionMock"]); + // Parse query arguments similar to the main query patch const queryConfig = this.mysql2Instrumentation.parseQueryArgs(args); @@ -61,12 +64,14 @@ export class TdMysql2ConnectionMock extends EventEmitter { const inputValue = createMockInputValue(rawInputValue); - return this.mysql2Instrumentation.handleReplayQuery(queryConfig, inputValue, this.spanInfo); + return this.mysql2Instrumentation.handleReplayQuery(queryConfig, inputValue, this.spanInfo, stackTrace); } execute(...args: any[]) { logger.debug(`[TdMysql2ConnectionMock] Mock connection execute intercepted in REPLAY mode`); + const stackTrace = captureStackTrace(["TdMysql2ConnectionMock"]); + // Parse execute arguments similar to the main execute patch const queryConfig = this.mysql2Instrumentation.parseQueryArgs(args); @@ -90,7 +95,7 @@ export class TdMysql2ConnectionMock extends EventEmitter { const inputValue = createMockInputValue(rawInputValue); - return this.mysql2Instrumentation.handleReplayQuery(queryConfig, inputValue, this.spanInfo); + return this.mysql2Instrumentation.handleReplayQuery(queryConfig, inputValue, this.spanInfo, stackTrace); } release() { diff --git a/src/instrumentation/libraries/mysql2/mocks/TdMysql2QueryMock.ts b/src/instrumentation/libraries/mysql2/mocks/TdMysql2QueryMock.ts index 799fcb53..1a5a56b8 100644 --- a/src/instrumentation/libraries/mysql2/mocks/TdMysql2QueryMock.ts +++ b/src/instrumentation/libraries/mysql2/mocks/TdMysql2QueryMock.ts @@ -30,6 +30,7 @@ export class TdMysql2QueryMock { inputValue: Mysql2InputValue, spanInfo: SpanInfo, submoduleName: string = "query", + stackTrace?: string, ): any { logger.debug(`[Mysql2Instrumentation] Replaying MySQL2 query`); @@ -46,6 +47,7 @@ export class TdMysql2QueryMock { spanInfo, spanName, submoduleName, + stackTrace, ); } @@ -60,6 +62,7 @@ export class TdMysql2QueryMock { spanInfo: SpanInfo, spanName: string, submoduleName: string, + stackTrace?: string, ): EventEmitter { const emitter = new EventEmitter(); @@ -83,7 +86,7 @@ export class TdMysql2QueryMock { // Fetch mock data asynchronously and emit events (async () => { try { - const mockData = await this._fetchMockData(inputValue, spanInfo, spanName, submoduleName); + const mockData = await this._fetchMockData(inputValue, spanInfo, spanName, submoduleName, stackTrace); if (!mockData) { const sql = queryConfig.sql || inputValue.sql || "UNKNOWN_QUERY"; @@ -154,6 +157,7 @@ export class TdMysql2QueryMock { spanInfo: SpanInfo, spanName: string, submoduleName: string, + stackTrace?: string, ) { return await findMockResponseAsync({ mockRequestData: { @@ -165,6 +169,7 @@ export class TdMysql2QueryMock { instrumentationName: this.INSTRUMENTATION_NAME, submoduleName: submoduleName, kind: SpanKind.CLIENT, + stackTrace, }, tuskDrift: this.tuskDrift, }); diff --git a/src/instrumentation/libraries/pg/Instrumentation.ts b/src/instrumentation/libraries/pg/Instrumentation.ts index 3e29e846..8dc9ed27 100644 --- a/src/instrumentation/libraries/pg/Instrumentation.ts +++ b/src/instrumentation/libraries/pg/Instrumentation.ts @@ -3,7 +3,7 @@ import { TdInstrumentationNodeModule } from "../../core/baseClasses/TdInstrument import { SpanUtils, SpanInfo } from "../../../core/tracing/SpanUtils"; import { SpanKind, SpanStatusCode } from "@opentelemetry/api"; import { TuskDriftCore, TuskDriftMode } from "../../../core/TuskDrift"; -import { wrap } from "../../core/utils"; +import { captureStackTrace, wrap } from "../../core/utils"; import { TdPgClientMock } from "./mocks/TdPgClientMock"; import { findMockResponseAsync } from "../../core/utils/mockResponseUtils"; import { handleRecordMode, handleReplayMode } from "../../core/utils/modeUtils"; @@ -101,6 +101,8 @@ export class PgInstrumentation extends TdInstrumentationBase { // Handle replay mode (only if app is ready) if (self.mode === TuskDriftMode.REPLAY) { + const stackTrace = captureStackTrace(["PgInstrumentation"]); + return handleReplayMode({ replayModeHandler: () => { // Create span in replay mode @@ -120,7 +122,7 @@ export class PgInstrumentation extends TdInstrumentationBase { isPreAppStart: false, }, (spanInfo) => { - return self.handleReplayQuery(queryConfig, inputValue, spanInfo); + return self.handleReplayQuery(queryConfig, inputValue, spanInfo, stackTrace); }, ); }, @@ -358,6 +360,7 @@ export class PgInstrumentation extends TdInstrumentationBase { queryConfig: QueryConfig, inputValue: PgClientInputValue, spanInfo: SpanInfo, + stackTrace?: string, ): Promise { logger.debug(`[PgInstrumentation] Replaying PG query`); @@ -375,6 +378,7 @@ export class PgInstrumentation extends TdInstrumentationBase { instrumentationName: this.INSTRUMENTATION_NAME, submoduleName: "query", kind: SpanKind.CLIENT, + stackTrace, }, tuskDrift: this.tuskDrift, }); diff --git a/src/instrumentation/libraries/pg/mocks/TdPgClientMock.ts b/src/instrumentation/libraries/pg/mocks/TdPgClientMock.ts index 2f112574..3fa67fde 100644 --- a/src/instrumentation/libraries/pg/mocks/TdPgClientMock.ts +++ b/src/instrumentation/libraries/pg/mocks/TdPgClientMock.ts @@ -3,6 +3,7 @@ import { PgInstrumentation } from "../Instrumentation"; import { createMockInputValue } from "../../../../core/utils"; import { SpanInfo } from "../../../../core/tracing/SpanUtils"; import { logger } from "../../../../core/utils/logger"; +import { captureStackTrace } from "src/instrumentation/core/utils"; /** * Mock PostgreSQL client for replay mode @@ -21,6 +22,8 @@ export class TdPgClientMock extends EventEmitter { query(...args: any[]) { logger.debug(`[TdPgClientMock] Mock pool client query intercepted in REPLAY mode`); + const stackTrace = captureStackTrace(["TdPgClientMock"]); + // Parse query arguments similar to the main query patch const queryConfig = this.pgInstrumentation.parseQueryArgs(args); @@ -37,7 +40,7 @@ export class TdPgClientMock extends EventEmitter { const inputValue = createMockInputValue(rawInputValue); - return this.pgInstrumentation.handleReplayQuery(queryConfig, inputValue, this.spanInfo); + return this.pgInstrumentation.handleReplayQuery(queryConfig, inputValue, this.spanInfo, stackTrace); } release() { diff --git a/src/instrumentation/libraries/postgres/Instrumentation.ts b/src/instrumentation/libraries/postgres/Instrumentation.ts index 251f0365..abed444e 100644 --- a/src/instrumentation/libraries/postgres/Instrumentation.ts +++ b/src/instrumentation/libraries/postgres/Instrumentation.ts @@ -3,7 +3,7 @@ import { TdInstrumentationNodeModule } from "../../core/baseClasses/TdInstrument import { SpanUtils, SpanInfo } from "../../../core/tracing/SpanUtils"; import { SpanKind, SpanStatusCode } from "@opentelemetry/api"; import { TuskDriftCore, TuskDriftMode } from "../../../core/TuskDrift"; -import { wrap } from "../../core/utils"; +import { captureStackTrace, wrap } from "../../core/utils"; import { findMockResponseAsync } from "../../core/utils/mockResponseUtils"; import { handleRecordMode, handleReplayMode } from "../../core/utils/modeUtils"; import { @@ -369,6 +369,8 @@ export class PostgresInstrumentation extends TdInstrumentationBase { // Handle replay mode (only if app is ready) if (this.mode === TuskDriftMode.REPLAY) { + const stackTrace = captureStackTrace(["PostgresInstrumentation"]); + return handleReplayMode({ replayModeHandler: () => { return SpanUtils.createAndExecuteSpan( @@ -390,6 +392,7 @@ export class PostgresInstrumentation extends TdInstrumentationBase { spanInfo, submodule: "query", name: "postgres.query", + stackTrace, }); }, ); @@ -451,6 +454,8 @@ export class PostgresInstrumentation extends TdInstrumentationBase { }; if (this.mode === TuskDriftMode.REPLAY) { + const stackTrace = captureStackTrace(["PostgresInstrumentation"]); + return handleReplayMode({ replayModeHandler: () => { return this._createPendingQueryWrapper(() => { @@ -473,6 +478,7 @@ export class PostgresInstrumentation extends TdInstrumentationBase { spanInfo, submodule: "unsafe", name: "postgres.unsafe", + stackTrace, }); }, ); @@ -532,6 +538,8 @@ export class PostgresInstrumentation extends TdInstrumentationBase { }; if (this.mode === TuskDriftMode.REPLAY) { + const stackTrace = captureStackTrace(["PostgresInstrumentation"]); + return handleReplayMode({ replayModeHandler: () => { return SpanUtils.createAndExecuteSpan( @@ -548,7 +556,7 @@ export class PostgresInstrumentation extends TdInstrumentationBase { isPreAppStart: false, }, (spanInfo) => { - return this._handleReplayBeginTransaction(spanInfo, options); + return this._handleReplayBeginTransaction(spanInfo, options, stackTrace); }, ); }, @@ -681,7 +689,7 @@ export class PostgresInstrumentation extends TdInstrumentationBase { return promise; // Return the original promise } - private async _handleReplayBeginTransaction(spanInfo: SpanInfo, options?: string): Promise { + private async _handleReplayBeginTransaction(spanInfo: SpanInfo, options?: string, stackTrace?: string): Promise { logger.debug(`[PostgresInstrumentation] Replaying Postgres transaction`); // Find mock data for the transaction @@ -698,6 +706,7 @@ export class PostgresInstrumentation extends TdInstrumentationBase { instrumentationName: this.INSTRUMENTATION_NAME, submoduleName: "transaction", kind: SpanKind.CLIENT, + stackTrace, }, tuskDrift: this.tuskDrift, }); @@ -792,11 +801,13 @@ export class PostgresInstrumentation extends TdInstrumentationBase { spanInfo, submodule, name, + stackTrace, }: { inputValue: PostgresClientInputValue; spanInfo: SpanInfo; submodule: string; name: string; + stackTrace?: string; }): Promise { logger.debug(`[PostgresInstrumentation] Replaying Postgres sql query`); @@ -810,6 +821,7 @@ export class PostgresInstrumentation extends TdInstrumentationBase { instrumentationName: this.INSTRUMENTATION_NAME, submoduleName: submodule, kind: SpanKind.CLIENT, + stackTrace, }, tuskDrift: this.tuskDrift, }); @@ -853,11 +865,13 @@ export class PostgresInstrumentation extends TdInstrumentationBase { spanInfo, submodule, name, + stackTrace, }: { inputValue: PostgresClientInputValue; spanInfo: SpanInfo; submodule: string; name: string; + stackTrace?: string; }): Promise { logger.debug(`[PostgresInstrumentation] Replaying Postgres unsafe query`); @@ -871,6 +885,7 @@ export class PostgresInstrumentation extends TdInstrumentationBase { instrumentationName: this.INSTRUMENTATION_NAME, submoduleName: submodule, kind: SpanKind.CLIENT, + stackTrace, }, tuskDrift: this.tuskDrift, });