@use-tusk/drift-node-sdk 0.1.5 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import { createRequire } from "node:module";
2
2
  import * as fs$1 from "fs";
3
3
  import fs from "fs";
4
- import os from "os";
5
4
  import * as path$1 from "path";
6
5
  import path, { normalize } from "path";
7
- import { satisfies } from "semver";
6
+ import { parse, satisfies } from "semver";
7
+ import os from "os";
8
8
  import { Hook } from "require-in-the-middle";
9
9
  import { Hook as Hook$1 } from "import-in-the-middle";
10
10
  import { Struct, Value } from "@use-tusk/drift-schemas/google/protobuf/struct";
@@ -53,6 +53,182 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
53
53
  }) : target, mod));
54
54
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
55
55
 
56
+ //#endregion
57
+ //#region src/nextjs/utils.ts
58
+ /**
59
+ * Get the installed Next.js version by reading package.json from node_modules.
60
+ *
61
+ * @returns The Next.js version string, or undefined if not found
62
+ */
63
+ function getNextjsVersion() {
64
+ try {
65
+ const nextPackageJsonPath = path$1.join(process.cwd(), "node_modules", "next", "package.json");
66
+ if (fs$1.existsSync(nextPackageJsonPath)) return JSON.parse(fs$1.readFileSync(nextPackageJsonPath, "utf-8")).version;
67
+ } catch (error) {}
68
+ }
69
+ /**
70
+ * Parse a semantic version string into its components.
71
+ *
72
+ * @param version - The version string to parse (e.g., "15.0.0-canary.124")
73
+ * @returns Parsed version object with major, minor, patch, and prerelease
74
+ */
75
+ function parseVersion(version$1) {
76
+ try {
77
+ const parsed = parse(version$1);
78
+ if (!parsed) return {
79
+ major: void 0,
80
+ minor: void 0,
81
+ patch: void 0,
82
+ prerelease: void 0
83
+ };
84
+ return {
85
+ major: parsed.major,
86
+ minor: parsed.minor,
87
+ patch: parsed.patch,
88
+ prerelease: parsed.prerelease.length > 0 ? parsed.prerelease.join(".") : void 0
89
+ };
90
+ } catch {
91
+ return {
92
+ major: void 0,
93
+ minor: void 0,
94
+ patch: void 0,
95
+ prerelease: void 0
96
+ };
97
+ }
98
+ }
99
+ /**
100
+ * Check if the Next.js version requires the instrumentationHook to be set.
101
+ * From Next.js 15.0.0-rc.1 onwards, the instrumentationHook is no longer needed
102
+ * and Next.js will warn if it's set.
103
+ *
104
+ * @param version - The Next.js version string
105
+ * @returns true if instrumentationHook should be set, false otherwise
106
+ */
107
+ function shouldSetInstrumentationHook(version$1) {
108
+ if (!version$1) return true;
109
+ const { major, minor, patch, prerelease } = parseVersion(version$1);
110
+ if (major === void 0 || minor === void 0 || patch === void 0) return true;
111
+ if (major >= 16) return false;
112
+ if (major < 15) return true;
113
+ if (major === 15) {
114
+ if (minor > 0 || patch > 0) return false;
115
+ if (minor === 0 && patch === 0 && prerelease === void 0) return false;
116
+ if (prerelease?.startsWith("rc.")) {
117
+ if (parseInt(prerelease.split(".")[1] || "0", 10) >= 1) return false;
118
+ }
119
+ if (prerelease?.startsWith("canary.")) {
120
+ if (parseInt(prerelease.split(".")[1] || "0", 10) >= 124) return false;
121
+ }
122
+ return true;
123
+ }
124
+ return true;
125
+ }
126
+ /**
127
+ * Log a message if debug mode is enabled.
128
+ *
129
+ * @param debug - Whether debug mode is enabled
130
+ * @param message - The message to log
131
+ */
132
+ function debugLog(debug, message) {
133
+ if (debug) console.log(`[Tusk Drift] ${message}`);
134
+ }
135
+ /**
136
+ * Log a warning message if warnings are not suppressed.
137
+ *
138
+ * @param suppress - Whether to suppress the warning
139
+ * @param message - The warning message to log
140
+ */
141
+ function warn(suppress, message) {
142
+ if (!suppress) console.warn(`[Tusk Drift] ${message}`);
143
+ }
144
+
145
+ //#endregion
146
+ //#region src/nextjs/withTuskDrift.ts
147
+ /**
148
+ * Wraps your Next.js configuration with Tusk Drift instrumentation setup.
149
+ *
150
+ * This function automatically configures Next.js to work with Tusk Drift by:
151
+ * - Enabling the Next.js instrumentation hook (for Next.js < 15.0.0-rc.1)
152
+ * - Configuring webpack externals to prevent bundling of core instrumentation packages
153
+ * - Preserving your existing Next.js configuration and webpack customizations
154
+ *
155
+ * @param nextConfig - Your existing Next.js configuration object (optional)
156
+ * @param options - Additional options to configure Tusk Drift's behavior (optional)
157
+ * @returns The wrapped Next.js configuration with Tusk Drift instrumentation enabled
158
+ *
159
+ * @example
160
+ * Basic usage:
161
+ * ```javascript
162
+ * // next.config.js
163
+ * const { withTuskDrift } = require('@use-tusk/drift-node-sdk');
164
+ *
165
+ * module.exports = withTuskDrift({
166
+ * // Your Next.js config
167
+ * });
168
+ * ```
169
+ *
170
+ * @example
171
+ * With debug logging:
172
+ * ```javascript
173
+ * // next.config.js
174
+ * const { withTuskDrift } = require('@use-tusk/drift-node-sdk');
175
+ *
176
+ * module.exports = withTuskDrift(
177
+ * {
178
+ * // Your Next.js config
179
+ * },
180
+ * {
181
+ * debug: true,
182
+ * }
183
+ * );
184
+ * ```
185
+ *
186
+ * @remarks
187
+ * The following webpack externals are added for server-side builds:
188
+ * - `require-in-the-middle` - Required for CommonJS module interception
189
+ * - `jsonpath` - Required for schema manipulation
190
+ */
191
+ function withTuskDrift(nextConfig = {}, options = {}) {
192
+ const config = nextConfig;
193
+ const debug = options.debug || false;
194
+ const suppressAllWarnings = options.suppressWarnings || false;
195
+ const nextjsVersion = getNextjsVersion();
196
+ if (nextjsVersion) debugLog(debug, `Detected Next.js version: ${nextjsVersion}`);
197
+ else warn(suppressAllWarnings || false, "Could not detect Next.js version. Some features may not work correctly. If you encounter issues, please ensure Next.js is properly installed.");
198
+ const needsInstrumentationHook = !options.disableInstrumentationHook && shouldSetInstrumentationHook(nextjsVersion);
199
+ const wrappedConfig = {
200
+ ...config,
201
+ ...needsInstrumentationHook ? { experimental: {
202
+ ...config.experimental,
203
+ instrumentationHook: true
204
+ } } : { experimental: config.experimental },
205
+ webpack: (webpackConfig, webpackOptions) => {
206
+ if (webpackOptions.isServer) {
207
+ const originalExternals = webpackConfig.externals;
208
+ const coreExternals = ["require-in-the-middle", "jsonpath"];
209
+ if (!originalExternals) {
210
+ webpackConfig.externals = coreExternals;
211
+ debugLog(debug, "Created new externals array with core packages");
212
+ } else if (Array.isArray(originalExternals)) {
213
+ for (const pkg of coreExternals) if (!originalExternals.includes(pkg)) {
214
+ originalExternals.push(pkg);
215
+ debugLog(debug, `Added ${pkg} to webpack externals`);
216
+ }
217
+ } else {
218
+ webpackConfig.externals = [originalExternals, ...coreExternals];
219
+ debugLog(debug, "Wrapped existing externals with core packages");
220
+ }
221
+ }
222
+ if (typeof config.webpack === "function") return config.webpack(webpackConfig, webpackOptions);
223
+ return webpackConfig;
224
+ }
225
+ };
226
+ if (needsInstrumentationHook) debugLog(debug, "Set experimental.instrumentationHook to true");
227
+ else debugLog(debug, "Skipped setting experimental.instrumentationHook (not needed for Next.js 15.0.0-rc.1+)");
228
+ if (options.disableInstrumentationHook && nextjsVersion && shouldSetInstrumentationHook(nextjsVersion)) warn(suppressAllWarnings || false, "You disabled instrumentationHook, but your Next.js version requires it. Tusk Drift may not initialize properly. Please remove the disableInstrumentationHook option.");
229
+ return wrappedConfig;
230
+ }
231
+
56
232
  //#endregion
57
233
  //#region src/instrumentation/core/baseClasses/TdInstrumentationAbstract.ts
58
234
  var TdInstrumentationAbstract = class {
@@ -76,7 +252,7 @@ var TdInstrumentationAbstract = class {
76
252
 
77
253
  //#endregion
78
254
  //#region package.json
79
- var version = "0.1.5";
255
+ var version = "0.1.7";
80
256
 
81
257
  //#endregion
82
258
  //#region src/version.ts
@@ -1172,7 +1348,7 @@ var JsonSchemaHelper = class JsonSchemaHelper {
1172
1348
  }
1173
1349
  /**
1174
1350
  * Generate schema from data object using standardized types
1175
- *
1351
+ *
1176
1352
  * Note: We properties always exists on JsonSchema because proto3 maps cannot be marked optional.
1177
1353
  * The JSON data is a bit inefficient because of this, but the easiest way to handle this is to keep it for now.
1178
1354
  */
@@ -1274,7 +1450,7 @@ var JsonSchemaHelper = class JsonSchemaHelper {
1274
1450
  if (schema.decodedType === DecodedType$1.JSON && typeof decodedValue === "string") decodedValue = JSON.parse(decodedValue);
1275
1451
  decodedData[key] = decodedValue;
1276
1452
  } catch (error) {
1277
- logger.warn(`[JsonSchemaHelper] Failed to decode ${key}:`, error);
1453
+ logger.debug(`[JsonSchemaHelper] Failed to decode ${key}:`, error);
1278
1454
  decodedData[key] = data[key];
1279
1455
  }
1280
1456
  return decodedData;
@@ -1362,7 +1538,8 @@ function convertMockRequestDataToCleanSpanData(mockRequestData, tuskDrift, input
1362
1538
  status: {
1363
1539
  code: StatusCode.OK,
1364
1540
  message: "OK"
1365
- }
1541
+ },
1542
+ stackTrace: mockRequestData.stackTrace
1366
1543
  };
1367
1544
  }
1368
1545
  /**
@@ -1407,7 +1584,7 @@ let ClientRequest;
1407
1584
  * Mock ClientRequest implementation for Tusk Drift HTTP replay
1408
1585
  */
1409
1586
  var TdMockClientRequest = class TdMockClientRequest extends EventEmitter {
1410
- constructor(options, spanInfo, callback) {
1587
+ constructor(options, spanInfo, callback, stackTrace) {
1411
1588
  super();
1412
1589
  this.INSTRUMENTATION_NAME = "HttpInstrumentation";
1413
1590
  this.finished = false;
@@ -1418,6 +1595,7 @@ var TdMockClientRequest = class TdMockClientRequest extends EventEmitter {
1418
1595
  TdMockClientRequest._setupPrototype();
1419
1596
  this.tuskDrift = TuskDriftCore.getInstance();
1420
1597
  this.spanInfo = spanInfo;
1598
+ this.stackTrace = stackTrace;
1421
1599
  if (!options || Object.keys(options).length === 0) throw new Error("Making a request with empty `options` is not supported in TdMockClientRequest");
1422
1600
  this.options = {
1423
1601
  ...options,
@@ -1553,7 +1731,8 @@ var TdMockClientRequest = class TdMockClientRequest extends EventEmitter {
1553
1731
  instrumentationName: this.INSTRUMENTATION_NAME,
1554
1732
  submoduleName: rawInputValue.method,
1555
1733
  inputValue,
1556
- kind: SpanKind.CLIENT
1734
+ kind: SpanKind.CLIENT,
1735
+ stackTrace: this.stackTrace
1557
1736
  },
1558
1737
  tuskDrift: this.tuskDrift,
1559
1738
  inputValueSchemaMerges: {
@@ -1697,7 +1876,7 @@ var HttpReplayHooks = class {
1697
1876
  * Handle outbound HTTP requests in replay mode
1698
1877
  * Uses TdMockClientRequest for simplified mocking approach
1699
1878
  */
1700
- handleOutboundReplayRequest({ method, requestOptions, protocol, args, spanInfo }) {
1879
+ handleOutboundReplayRequest({ method, requestOptions, protocol, args, spanInfo, stackTrace }) {
1701
1880
  logger.debug(`[HttpReplayHooks] Handling outbound ${protocol.toUpperCase()} ${method} request in replay mode`);
1702
1881
  let callback;
1703
1882
  if (args.length > 1 && typeof args[1] === "function") callback = args[1];
@@ -1714,7 +1893,7 @@ var HttpReplayHooks = class {
1714
1893
  port: requestOptions.port ? Number(requestOptions.port) : void 0,
1715
1894
  method
1716
1895
  };
1717
- const mockRequest = new TdMockClientRequest(mockOptions, spanInfo, callback);
1896
+ const mockRequest = new TdMockClientRequest(mockOptions, spanInfo, callback, stackTrace);
1718
1897
  if (method === "GET" || method === "HEAD") process.nextTick(() => {
1719
1898
  mockRequest.end();
1720
1899
  });
@@ -1783,17 +1962,17 @@ function isWrapped$1(func) {
1783
1962
  */
1784
1963
  function wrap(target, propertyName, wrapper) {
1785
1964
  if (typeof target[propertyName] !== "function") {
1786
- logger.warn(`Cannot wrap non-function property: ${propertyName}`);
1965
+ logger.debug(`Cannot wrap non-function property: ${propertyName}`);
1787
1966
  return;
1788
1967
  }
1789
1968
  if (isWrapped$1(target[propertyName])) {
1790
- logger.warn(`Property ${propertyName} is already wrapped`);
1969
+ logger.debug(`Property ${propertyName} is already wrapped`);
1791
1970
  return;
1792
1971
  }
1793
1972
  const original = target[propertyName];
1794
1973
  const wrapped = wrapper(original);
1795
1974
  if (typeof wrapped !== "function") {
1796
- logger.warn(`Wrapper must return a function for property: ${propertyName}`);
1975
+ logger.debug(`Wrapper must return a function for property: ${propertyName}`);
1797
1976
  return;
1798
1977
  }
1799
1978
  wrapped._isWrapped = true;
@@ -1803,6 +1982,45 @@ function wrap(target, propertyName, wrapper) {
1803
1982
  return wrapped;
1804
1983
  }
1805
1984
 
1985
+ //#endregion
1986
+ //#region src/instrumentation/core/utils/stackTraceUtils.ts
1987
+ /**
1988
+ * Helper functions for capturing stack traces in replay mode
1989
+ *
1990
+ * TODO: Consider using a structured format for stack frames:
1991
+ *
1992
+ * {
1993
+ * "frames": [
1994
+ * {
1995
+ * "fileName": "file.js",
1996
+ * "lineNumber": 10,
1997
+ * "columnNumber": 20,
1998
+ * "functionName": "functionName"
1999
+ * }
2000
+ * ]
2001
+ * }
2002
+ *
2003
+ * This would allow for more efficient matching and filtering of stack frames.
2004
+ * It would also allow for more accurate stack trace reconstruction in replay mode.
2005
+ */
2006
+ /**
2007
+ *
2008
+ * @param excludeClassNames - Class names to exclude from the stack trace
2009
+ * @returns The stack trace as a string
2010
+ */
2011
+ function captureStackTrace(excludeClassNames = []) {
2012
+ const originalStackTraceLimit = Error.stackTraceLimit;
2013
+ Error.stackTraceLimit = 100;
2014
+ const s = (/* @__PURE__ */ new Error()).stack || "";
2015
+ Error.stackTraceLimit = originalStackTraceLimit;
2016
+ const allExcludes = [...[
2017
+ "drift-node-sdk/src/instrumentation",
2018
+ "drift-node-sdk/src/core",
2019
+ "node_modules/@use-tusk"
2020
+ ], ...excludeClassNames];
2021
+ return s.split("\n").slice(2).filter((l) => !allExcludes.some((exclude) => l.includes(exclude))).join("\n");
2022
+ }
2023
+
1806
2024
  //#endregion
1807
2025
  //#region src/instrumentation/libraries/http/HttpTransformEngine.ts
1808
2026
  /**
@@ -2241,6 +2459,7 @@ var HttpInstrumentation = class extends TdInstrumentationBase {
2241
2459
  };
2242
2460
  const replayTraceId = this.replayHooks.extractTraceIdFromHeaders(req);
2243
2461
  if (!replayTraceId) return originalHandler.call(this);
2462
+ logger.debug(`[HttpInstrumentation] Setting replay trace id`, replayTraceId);
2244
2463
  const envVars = this.replayHooks.extractEnvVarsFromHeaders(req);
2245
2464
  if (envVars) EnvVarTracker.setEnvVars(replayTraceId, envVars);
2246
2465
  const ctxWithReplayTraceId = SpanUtils.setCurrentReplayTraceId(replayTraceId);
@@ -2750,37 +2969,40 @@ var HttpInstrumentation = class extends TdInstrumentationBase {
2750
2969
  } else requestOptions = args[0] || {};
2751
2970
  const method = requestOptions.method || "GET";
2752
2971
  const requestProtocol = self._normalizeProtocol(requestOptions.protocol || void 0, protocol);
2753
- if (self.mode === TuskDriftMode.REPLAY) return handleReplayMode({ replayModeHandler: () => {
2754
- const headers = normalizeHeaders(requestOptions.headers || {});
2755
- const inputValue = {
2756
- method,
2757
- path: requestOptions.path || void 0,
2758
- headers,
2759
- protocol: requestProtocol,
2760
- hostname: requestOptions.hostname || requestOptions.host || void 0,
2761
- port: requestOptions.port ? Number(requestOptions.port) : void 0,
2762
- timeout: requestOptions.timeout || void 0
2763
- };
2764
- return SpanUtils.createAndExecuteSpan(self.mode, () => originalRequest.apply(this, args), {
2765
- name: requestOptions.path || `${requestProtocol.toUpperCase()} ${method}`,
2766
- kind: SpanKind.CLIENT,
2767
- packageName: requestProtocol,
2768
- packageType: PackageType.HTTP,
2769
- instrumentationName: self.INSTRUMENTATION_NAME,
2770
- submodule: method,
2771
- inputValue,
2772
- isPreAppStart: false
2773
- }, (spanInfo) => {
2774
- return self.replayHooks.handleOutboundReplayRequest({
2972
+ if (self.mode === TuskDriftMode.REPLAY) {
2973
+ const stackTrace = captureStackTrace(["HttpInstrumentation"]);
2974
+ return handleReplayMode({ replayModeHandler: () => {
2975
+ const headers = normalizeHeaders(requestOptions.headers || {});
2976
+ const inputValue = {
2775
2977
  method,
2776
- requestOptions,
2978
+ path: requestOptions.path || void 0,
2979
+ headers,
2777
2980
  protocol: requestProtocol,
2778
- args,
2779
- spanInfo
2981
+ hostname: requestOptions.hostname || requestOptions.host || void 0,
2982
+ port: requestOptions.port ? Number(requestOptions.port) : void 0,
2983
+ timeout: requestOptions.timeout || void 0
2984
+ };
2985
+ return SpanUtils.createAndExecuteSpan(self.mode, () => originalRequest.apply(this, args), {
2986
+ name: requestOptions.path || `${requestProtocol.toUpperCase()} ${method}`,
2987
+ kind: SpanKind.CLIENT,
2988
+ packageName: requestProtocol,
2989
+ packageType: PackageType.HTTP,
2990
+ instrumentationName: self.INSTRUMENTATION_NAME,
2991
+ submodule: method,
2992
+ inputValue,
2993
+ isPreAppStart: false
2994
+ }, (spanInfo) => {
2995
+ return self.replayHooks.handleOutboundReplayRequest({
2996
+ method,
2997
+ requestOptions,
2998
+ protocol: requestProtocol,
2999
+ args,
3000
+ spanInfo,
3001
+ stackTrace
3002
+ });
2780
3003
  });
2781
- });
2782
- } });
2783
- else if (self.mode === TuskDriftMode.RECORD) return handleRecordMode({
3004
+ } });
3005
+ } else if (self.mode === TuskDriftMode.RECORD) return handleRecordMode({
2784
3006
  originalFunctionCall: () => originalRequest.apply(this, args),
2785
3007
  recordModeHandler: ({ isPreAppStart }) => {
2786
3008
  const headers = normalizeHeaders(requestOptions.headers || {});
@@ -3235,6 +3457,7 @@ var TdPgClientMock = class extends EventEmitter {
3235
3457
  }
3236
3458
  query(...args) {
3237
3459
  logger.debug(`[TdPgClientMock] Mock pool client query intercepted in REPLAY mode`);
3460
+ const stackTrace = captureStackTrace(["TdPgClientMock"]);
3238
3461
  const queryConfig = this.pgInstrumentation.parseQueryArgs(args);
3239
3462
  if (!queryConfig || !queryConfig.text) {
3240
3463
  logger.debug(`[TdPgClientMock] Could not parse mock client query, returning empty result`);
@@ -3249,7 +3472,7 @@ var TdPgClientMock = class extends EventEmitter {
3249
3472
  clientType: "client"
3250
3473
  };
3251
3474
  const inputValue = createMockInputValue(rawInputValue);
3252
- return this.pgInstrumentation.handleReplayQuery(queryConfig, inputValue, this.spanInfo);
3475
+ return this.pgInstrumentation.handleReplayQuery(queryConfig, inputValue, this.spanInfo, stackTrace);
3253
3476
  }
3254
3477
  release() {
3255
3478
  this.emit("end");
@@ -3352,23 +3575,25 @@ var PgInstrumentation = class extends TdInstrumentationBase {
3352
3575
  values: queryConfig.values || [],
3353
3576
  clientType
3354
3577
  };
3355
- if (self.mode === TuskDriftMode.REPLAY) return handleReplayMode({ replayModeHandler: () => {
3356
- const packageName = inputValue.clientType === "pool" ? "pg-pool" : "pg";
3357
- const spanName = inputValue.clientType === "pool" ? "pg-pool.query" : "pg.query";
3358
- return SpanUtils.createAndExecuteSpan(self.mode, () => originalQuery.apply(this, args), {
3359
- name: spanName,
3360
- kind: SpanKind.CLIENT,
3361
- submodule: "query",
3362
- packageType: PackageType.PG,
3363
- packageName,
3364
- instrumentationName: self.INSTRUMENTATION_NAME,
3365
- inputValue,
3366
- isPreAppStart: false
3367
- }, (spanInfo) => {
3368
- return self.handleReplayQuery(queryConfig, inputValue, spanInfo);
3369
- });
3370
- } });
3371
- else if (self.mode === TuskDriftMode.RECORD) return handleRecordMode({
3578
+ if (self.mode === TuskDriftMode.REPLAY) {
3579
+ const stackTrace = captureStackTrace(["PgInstrumentation"]);
3580
+ return handleReplayMode({ replayModeHandler: () => {
3581
+ const packageName = inputValue.clientType === "pool" ? "pg-pool" : "pg";
3582
+ const spanName = inputValue.clientType === "pool" ? "pg-pool.query" : "pg.query";
3583
+ return SpanUtils.createAndExecuteSpan(self.mode, () => originalQuery.apply(this, args), {
3584
+ name: spanName,
3585
+ kind: SpanKind.CLIENT,
3586
+ submodule: "query",
3587
+ packageType: PackageType.PG,
3588
+ packageName,
3589
+ instrumentationName: self.INSTRUMENTATION_NAME,
3590
+ inputValue,
3591
+ isPreAppStart: false
3592
+ }, (spanInfo) => {
3593
+ return self.handleReplayQuery(queryConfig, inputValue, spanInfo, stackTrace);
3594
+ });
3595
+ } });
3596
+ } else if (self.mode === TuskDriftMode.RECORD) return handleRecordMode({
3372
3597
  originalFunctionCall: () => originalQuery.apply(this, args),
3373
3598
  recordModeHandler: ({ isPreAppStart }) => {
3374
3599
  const packageName = inputValue.clientType === "pool" ? "pg-pool" : "pg";
@@ -3514,7 +3739,7 @@ var PgInstrumentation = class extends TdInstrumentationBase {
3514
3739
  throw error;
3515
3740
  });
3516
3741
  }
3517
- async handleReplayQuery(queryConfig, inputValue, spanInfo) {
3742
+ async handleReplayQuery(queryConfig, inputValue, spanInfo, stackTrace) {
3518
3743
  logger.debug(`[PgInstrumentation] Replaying PG query`);
3519
3744
  const packageName = inputValue.clientType === "pool" ? "pg-pool" : "pg";
3520
3745
  const spanName = inputValue.clientType === "pool" ? "pg-pool.query" : "pg.query";
@@ -3527,7 +3752,8 @@ var PgInstrumentation = class extends TdInstrumentationBase {
3527
3752
  packageName,
3528
3753
  instrumentationName: this.INSTRUMENTATION_NAME,
3529
3754
  submoduleName: "query",
3530
- kind: SpanKind.CLIENT
3755
+ kind: SpanKind.CLIENT,
3756
+ stackTrace
3531
3757
  },
3532
3758
  tuskDrift: this.tuskDrift
3533
3759
  });
@@ -3995,26 +4221,29 @@ var PostgresInstrumentation = class extends TdInstrumentationBase {
3995
4221
  query: query.trim(),
3996
4222
  parameters: values
3997
4223
  };
3998
- if (this.mode === TuskDriftMode.REPLAY) return handleReplayMode({ replayModeHandler: () => {
3999
- return SpanUtils.createAndExecuteSpan(this.mode, () => originalSql.call(this, strings, ...values), {
4000
- name: "postgres.query",
4001
- kind: SpanKind.CLIENT,
4002
- submodule: "query",
4003
- packageType: PackageType.PG,
4004
- packageName: "postgres",
4005
- instrumentationName: this.INSTRUMENTATION_NAME,
4006
- inputValue,
4007
- isPreAppStart: false
4008
- }, (spanInfo) => {
4009
- return this.handleReplaySqlQuery({
4010
- inputValue,
4011
- spanInfo,
4224
+ if (this.mode === TuskDriftMode.REPLAY) {
4225
+ const stackTrace = captureStackTrace(["PostgresInstrumentation"]);
4226
+ return handleReplayMode({ replayModeHandler: () => {
4227
+ return SpanUtils.createAndExecuteSpan(this.mode, () => originalSql.call(this, strings, ...values), {
4228
+ name: "postgres.query",
4229
+ kind: SpanKind.CLIENT,
4012
4230
  submodule: "query",
4013
- name: "postgres.query"
4231
+ packageType: PackageType.PG,
4232
+ packageName: "postgres",
4233
+ instrumentationName: this.INSTRUMENTATION_NAME,
4234
+ inputValue,
4235
+ isPreAppStart: false
4236
+ }, (spanInfo) => {
4237
+ return this.handleReplaySqlQuery({
4238
+ inputValue,
4239
+ spanInfo,
4240
+ submodule: "query",
4241
+ name: "postgres.query",
4242
+ stackTrace
4243
+ });
4014
4244
  });
4015
- });
4016
- } });
4017
- else if (this.mode === TuskDriftMode.RECORD) return handleRecordMode({
4245
+ } });
4246
+ } else if (this.mode === TuskDriftMode.RECORD) return handleRecordMode({
4018
4247
  originalFunctionCall: () => originalSql.call(this, strings, ...values),
4019
4248
  recordModeHandler: ({ isPreAppStart }) => {
4020
4249
  return SpanUtils.createAndExecuteSpan(this.mode, () => originalSql.call(this, strings, ...values), {
@@ -4045,28 +4274,31 @@ var PostgresInstrumentation = class extends TdInstrumentationBase {
4045
4274
  parameters: parameters || [],
4046
4275
  options: queryOptions
4047
4276
  };
4048
- if (this.mode === TuskDriftMode.REPLAY) return handleReplayMode({ replayModeHandler: () => {
4049
- return this._createPendingQueryWrapper(() => {
4050
- return SpanUtils.createAndExecuteSpan(this.mode, () => executeUnsafe(), {
4051
- name: "postgres.unsafe",
4052
- kind: SpanKind.CLIENT,
4053
- submodule: "unsafe",
4054
- packageType: PackageType.PG,
4055
- packageName: "postgres",
4056
- instrumentationName: this.INSTRUMENTATION_NAME,
4057
- inputValue,
4058
- isPreAppStart: false
4059
- }, (spanInfo) => {
4060
- return this.handleReplayUnsafeQuery({
4061
- inputValue,
4062
- spanInfo,
4277
+ if (this.mode === TuskDriftMode.REPLAY) {
4278
+ const stackTrace = captureStackTrace(["PostgresInstrumentation"]);
4279
+ return handleReplayMode({ replayModeHandler: () => {
4280
+ return this._createPendingQueryWrapper(() => {
4281
+ return SpanUtils.createAndExecuteSpan(this.mode, () => executeUnsafe(), {
4282
+ name: "postgres.unsafe",
4283
+ kind: SpanKind.CLIENT,
4063
4284
  submodule: "unsafe",
4064
- name: "postgres.unsafe"
4285
+ packageType: PackageType.PG,
4286
+ packageName: "postgres",
4287
+ instrumentationName: this.INSTRUMENTATION_NAME,
4288
+ inputValue,
4289
+ isPreAppStart: false
4290
+ }, (spanInfo) => {
4291
+ return this.handleReplayUnsafeQuery({
4292
+ inputValue,
4293
+ spanInfo,
4294
+ submodule: "unsafe",
4295
+ name: "postgres.unsafe",
4296
+ stackTrace
4297
+ });
4065
4298
  });
4066
4299
  });
4067
- });
4068
- } });
4069
- else if (this.mode === TuskDriftMode.RECORD) return handleRecordMode({
4300
+ } });
4301
+ } else if (this.mode === TuskDriftMode.RECORD) return handleRecordMode({
4070
4302
  originalFunctionCall: executeUnsafe,
4071
4303
  recordModeHandler: ({ isPreAppStart }) => {
4072
4304
  return SpanUtils.createAndExecuteSpan(this.mode, executeUnsafe, {
@@ -4096,21 +4328,23 @@ var PostgresInstrumentation = class extends TdInstrumentationBase {
4096
4328
  else if (transactionCallback) return originalBegin.call(sqlInstance, transactionCallback);
4097
4329
  else return originalBegin.call(sqlInstance, options || void 0);
4098
4330
  };
4099
- if (this.mode === TuskDriftMode.REPLAY) return handleReplayMode({ replayModeHandler: () => {
4100
- return SpanUtils.createAndExecuteSpan(this.mode, () => executeBegin(), {
4101
- name: "postgres.begin",
4102
- kind: SpanKind.CLIENT,
4103
- submodule: "transaction",
4104
- packageType: PackageType.PG,
4105
- packageName: "postgres",
4106
- instrumentationName: this.INSTRUMENTATION_NAME,
4107
- inputValue,
4108
- isPreAppStart: false
4109
- }, (spanInfo) => {
4110
- return this._handleReplayBeginTransaction(spanInfo, options);
4111
- });
4112
- } });
4113
- else if (this.mode === TuskDriftMode.RECORD) return handleRecordMode({
4331
+ if (this.mode === TuskDriftMode.REPLAY) {
4332
+ const stackTrace = captureStackTrace(["PostgresInstrumentation"]);
4333
+ return handleReplayMode({ replayModeHandler: () => {
4334
+ return SpanUtils.createAndExecuteSpan(this.mode, () => executeBegin(), {
4335
+ name: "postgres.begin",
4336
+ kind: SpanKind.CLIENT,
4337
+ submodule: "transaction",
4338
+ packageType: PackageType.PG,
4339
+ packageName: "postgres",
4340
+ instrumentationName: this.INSTRUMENTATION_NAME,
4341
+ inputValue,
4342
+ isPreAppStart: false
4343
+ }, (spanInfo) => {
4344
+ return this._handleReplayBeginTransaction(spanInfo, options, stackTrace);
4345
+ });
4346
+ } });
4347
+ } else if (this.mode === TuskDriftMode.RECORD) return handleRecordMode({
4114
4348
  originalFunctionCall: executeBegin,
4115
4349
  recordModeHandler: ({ isPreAppStart }) => {
4116
4350
  return SpanUtils.createAndExecuteSpan(this.mode, executeBegin, {
@@ -4204,7 +4438,7 @@ var PostgresInstrumentation = class extends TdInstrumentationBase {
4204
4438
  });
4205
4439
  return promise;
4206
4440
  }
4207
- async _handleReplayBeginTransaction(spanInfo, options) {
4441
+ async _handleReplayBeginTransaction(spanInfo, options, stackTrace) {
4208
4442
  logger.debug(`[PostgresInstrumentation] Replaying Postgres transaction`);
4209
4443
  const mockData = await findMockResponseAsync({
4210
4444
  mockRequestData: {
@@ -4218,7 +4452,8 @@ var PostgresInstrumentation = class extends TdInstrumentationBase {
4218
4452
  packageName: "postgres",
4219
4453
  instrumentationName: this.INSTRUMENTATION_NAME,
4220
4454
  submoduleName: "transaction",
4221
- kind: SpanKind.CLIENT
4455
+ kind: SpanKind.CLIENT,
4456
+ stackTrace
4222
4457
  },
4223
4458
  tuskDrift: this.tuskDrift
4224
4459
  });
@@ -4273,7 +4508,7 @@ var PostgresInstrumentation = class extends TdInstrumentationBase {
4273
4508
  throw error;
4274
4509
  }
4275
4510
  }
4276
- async handleReplaySqlQuery({ inputValue, spanInfo, submodule, name }) {
4511
+ async handleReplaySqlQuery({ inputValue, spanInfo, submodule, name, stackTrace }) {
4277
4512
  logger.debug(`[PostgresInstrumentation] Replaying Postgres sql query`);
4278
4513
  const mockData = await findMockResponseAsync({
4279
4514
  mockRequestData: {
@@ -4284,7 +4519,8 @@ var PostgresInstrumentation = class extends TdInstrumentationBase {
4284
4519
  packageName: "postgres",
4285
4520
  instrumentationName: this.INSTRUMENTATION_NAME,
4286
4521
  submoduleName: submodule,
4287
- kind: SpanKind.CLIENT
4522
+ kind: SpanKind.CLIENT,
4523
+ stackTrace
4288
4524
  },
4289
4525
  tuskDrift: this.tuskDrift
4290
4526
  });
@@ -4303,7 +4539,7 @@ var PostgresInstrumentation = class extends TdInstrumentationBase {
4303
4539
  count: isResultObject ? processedResult.count : void 0
4304
4540
  });
4305
4541
  }
4306
- async handleReplayUnsafeQuery({ inputValue, spanInfo, submodule, name }) {
4542
+ async handleReplayUnsafeQuery({ inputValue, spanInfo, submodule, name, stackTrace }) {
4307
4543
  logger.debug(`[PostgresInstrumentation] Replaying Postgres unsafe query`);
4308
4544
  const mockData = await findMockResponseAsync({
4309
4545
  mockRequestData: {
@@ -4314,7 +4550,8 @@ var PostgresInstrumentation = class extends TdInstrumentationBase {
4314
4550
  packageName: "postgres",
4315
4551
  instrumentationName: this.INSTRUMENTATION_NAME,
4316
4552
  submoduleName: submodule,
4317
- kind: SpanKind.CLIENT
4553
+ kind: SpanKind.CLIENT,
4554
+ stackTrace
4318
4555
  },
4319
4556
  tuskDrift: this.tuskDrift
4320
4557
  });
@@ -4426,6 +4663,7 @@ var TdMysql2ConnectionMock = class extends EventEmitter {
4426
4663
  }
4427
4664
  query(...args) {
4428
4665
  logger.debug(`[TdMysql2ConnectionMock] Mock connection query intercepted in REPLAY mode`);
4666
+ const stackTrace = captureStackTrace(["TdMysql2ConnectionMock"]);
4429
4667
  const queryConfig = this.mysql2Instrumentation.parseQueryArgs(args);
4430
4668
  if (!queryConfig || !queryConfig.sql) {
4431
4669
  logger.debug(`[TdMysql2ConnectionMock] Could not parse mock connection query, returning empty result`);
@@ -4445,10 +4683,11 @@ var TdMysql2ConnectionMock = class extends EventEmitter {
4445
4683
  clientType: this.clientType
4446
4684
  };
4447
4685
  const inputValue = createMockInputValue(rawInputValue);
4448
- return this.mysql2Instrumentation.handleReplayQuery(queryConfig, inputValue, this.spanInfo);
4686
+ return this.mysql2Instrumentation.handleReplayQuery(queryConfig, inputValue, this.spanInfo, stackTrace);
4449
4687
  }
4450
4688
  execute(...args) {
4451
4689
  logger.debug(`[TdMysql2ConnectionMock] Mock connection execute intercepted in REPLAY mode`);
4690
+ const stackTrace = captureStackTrace(["TdMysql2ConnectionMock"]);
4452
4691
  const queryConfig = this.mysql2Instrumentation.parseQueryArgs(args);
4453
4692
  if (!queryConfig || !queryConfig.sql) {
4454
4693
  logger.debug(`[TdMysql2ConnectionMock] Could not parse mock connection execute, returning empty result`);
@@ -4468,7 +4707,7 @@ var TdMysql2ConnectionMock = class extends EventEmitter {
4468
4707
  clientType: this.clientType
4469
4708
  };
4470
4709
  const inputValue = createMockInputValue(rawInputValue);
4471
- return this.mysql2Instrumentation.handleReplayQuery(queryConfig, inputValue, this.spanInfo);
4710
+ return this.mysql2Instrumentation.handleReplayQuery(queryConfig, inputValue, this.spanInfo, stackTrace);
4472
4711
  }
4473
4712
  release() {
4474
4713
  this.emit("end");
@@ -4562,20 +4801,33 @@ var TdMysql2QueryMock = class {
4562
4801
  /**
4563
4802
  * Handle replay of a MySQL2 query (query or execute)
4564
4803
  */
4565
- handleReplayQuery(queryConfig, inputValue, spanInfo, submoduleName = "query") {
4804
+ handleReplayQuery(queryConfig, inputValue, spanInfo, submoduleName = "query", stackTrace) {
4566
4805
  logger.debug(`[Mysql2Instrumentation] Replaying MySQL2 query`);
4567
4806
  const spanName = `mysql2.${inputValue.clientType}.${submoduleName}`;
4568
- return this._handleQuery(queryConfig, inputValue, spanInfo, spanName, submoduleName);
4807
+ return this._handleQuery(queryConfig, inputValue, spanInfo, spanName, submoduleName, stackTrace);
4569
4808
  }
4570
4809
  /**
4571
4810
  * Handle query - always returns an EventEmitter (like mysql2 does)
4572
4811
  * This handles both callback and streaming modes
4812
+ * The EventEmitter is also thenable (has a .then() method) to support await/Promise usage
4573
4813
  */
4574
- _handleQuery(queryConfig, inputValue, spanInfo, spanName, submoduleName) {
4814
+ _handleQuery(queryConfig, inputValue, spanInfo, spanName, submoduleName, stackTrace) {
4575
4815
  const emitter = new EventEmitter();
4816
+ let storedRows = null;
4817
+ let storedFields = null;
4818
+ emitter.then = function(onResolve, onReject) {
4819
+ return new Promise((resolve, reject) => {
4820
+ emitter.once("end", () => {
4821
+ resolve([storedRows, storedFields]);
4822
+ });
4823
+ emitter.once("error", (error) => {
4824
+ reject(error);
4825
+ });
4826
+ }).then(onResolve, onReject);
4827
+ };
4576
4828
  (async () => {
4577
4829
  try {
4578
- const mockData = await this._fetchMockData(inputValue, spanInfo, spanName, submoduleName);
4830
+ const mockData = await this._fetchMockData(inputValue, spanInfo, spanName, submoduleName, stackTrace);
4579
4831
  if (!mockData) {
4580
4832
  const sql = queryConfig.sql || inputValue.sql || "UNKNOWN_QUERY";
4581
4833
  logger.warn(`[Mysql2Instrumentation] No mock data found for MySQL2 query: ${sql}`);
@@ -4587,6 +4839,8 @@ var TdMysql2QueryMock = class {
4587
4839
  return;
4588
4840
  }
4589
4841
  const processedResult = this._convertMysql2Types(mockData.result);
4842
+ storedRows = processedResult.rows;
4843
+ storedFields = processedResult.fields;
4590
4844
  process.nextTick(() => {
4591
4845
  if (processedResult.fields) emitter.emit("fields", processedResult.fields);
4592
4846
  if (queryConfig.callback) queryConfig.callback(null, processedResult.rows, processedResult.fields);
@@ -4606,7 +4860,7 @@ var TdMysql2QueryMock = class {
4606
4860
  /**
4607
4861
  * Fetch mock data from CLI
4608
4862
  */
4609
- async _fetchMockData(inputValue, spanInfo, spanName, submoduleName) {
4863
+ async _fetchMockData(inputValue, spanInfo, spanName, submoduleName, stackTrace) {
4610
4864
  return await findMockResponseAsync({
4611
4865
  mockRequestData: {
4612
4866
  traceId: spanInfo.traceId,
@@ -4616,7 +4870,8 @@ var TdMysql2QueryMock = class {
4616
4870
  packageName: "mysql2",
4617
4871
  instrumentationName: this.INSTRUMENTATION_NAME,
4618
4872
  submoduleName,
4619
- kind: SpanKind.CLIENT
4873
+ kind: SpanKind.CLIENT,
4874
+ stackTrace
4620
4875
  },
4621
4876
  tuskDrift: this.tuskDrift
4622
4877
  });
@@ -4640,9 +4895,57 @@ var TdMysql2QueryMock = class {
4640
4895
  }
4641
4896
  };
4642
4897
 
4898
+ //#endregion
4899
+ //#region src/instrumentation/libraries/mysql2/mocks/TdMysql2ConnectionEventMock.ts
4900
+ /**
4901
+ * Mock for MySQL2 connection events (connect/error)
4902
+ * Handles replay of recorded connection establishment events in REPLAY mode
4903
+ * Recording happens through normal SpanUtils flow in RECORD mode
4904
+ */
4905
+ var TdMysql2ConnectionEventMock = class {
4906
+ constructor(spanInfo) {
4907
+ this.INSTRUMENTATION_NAME = "Mysql2Instrumentation";
4908
+ this.spanInfo = spanInfo;
4909
+ this.tuskDrift = TuskDriftCore.getInstance();
4910
+ }
4911
+ /**
4912
+ * Get recorded connection event for replay
4913
+ * Returns { output } for success, or throws error if connection failed
4914
+ * The connection events are recorded automatically via SpanUtils in record mode
4915
+ */
4916
+ async getReplayedConnectionEvent(inputValue) {
4917
+ logger.debug(`[TdMysql2ConnectionEventMock] Retrieving recorded connection event`);
4918
+ try {
4919
+ const mockData = await findMockResponseAsync({
4920
+ mockRequestData: {
4921
+ traceId: this.spanInfo.traceId,
4922
+ spanId: this.spanInfo.spanId,
4923
+ name: "mysql2.connection.create",
4924
+ inputValue,
4925
+ packageName: "mysql2",
4926
+ instrumentationName: this.INSTRUMENTATION_NAME,
4927
+ submoduleName: "connectEvent",
4928
+ kind: SpanKind.CLIENT
4929
+ },
4930
+ tuskDrift: this.tuskDrift
4931
+ });
4932
+ if (!mockData) {
4933
+ logger.warn(`[TdMysql2ConnectionEventMock] No mock data found, using default success`);
4934
+ return { output: {} };
4935
+ }
4936
+ return { output: mockData.result || {} };
4937
+ } catch (error) {
4938
+ logger.error(`[TdMysql2ConnectionEventMock] Error getting replay value:`, error);
4939
+ return { output: {} };
4940
+ }
4941
+ }
4942
+ };
4943
+
4643
4944
  //#endregion
4644
4945
  //#region src/instrumentation/libraries/mysql2/Instrumentation.ts
4645
- const SUPPORTED_VERSIONS$1 = [">=3.0.0 <4.0.0"];
4946
+ const COMPLETE_SUPPORTED_VERSIONS = ">=2.3.3 <4.0.0";
4947
+ const V2_3_3_TO_3_11_4 = ">=2.3.3 <3.11.5";
4948
+ const V3_11_5_TO_4_0 = ">=3.11.5 <4.0.0";
4646
4949
  var Mysql2Instrumentation = class extends TdInstrumentationBase {
4647
4950
  constructor(config = {}) {
4648
4951
  super("mysql2", config);
@@ -4653,42 +4956,103 @@ var Mysql2Instrumentation = class extends TdInstrumentationBase {
4653
4956
  init() {
4654
4957
  return [new TdInstrumentationNodeModule({
4655
4958
  name: "mysql2",
4656
- supportedVersions: SUPPORTED_VERSIONS$1,
4959
+ supportedVersions: [COMPLETE_SUPPORTED_VERSIONS],
4657
4960
  files: [
4658
4961
  new TdInstrumentationNodeModuleFile({
4659
4962
  name: "mysql2/lib/connection.js",
4660
- supportedVersions: SUPPORTED_VERSIONS$1,
4661
- patch: (moduleExports) => this._patchConnection(moduleExports)
4963
+ supportedVersions: [V2_3_3_TO_3_11_4],
4964
+ patch: (moduleExports) => this._patchConnectionV2(moduleExports)
4965
+ }),
4966
+ new TdInstrumentationNodeModuleFile({
4967
+ name: "mysql2/lib/base/connection.js",
4968
+ supportedVersions: [V3_11_5_TO_4_0],
4969
+ patch: (moduleExports) => this._patchBaseConnection(moduleExports)
4970
+ }),
4971
+ new TdInstrumentationNodeModuleFile({
4972
+ name: "mysql2/lib/connection.js",
4973
+ supportedVersions: [V3_11_5_TO_4_0],
4974
+ patch: (moduleExports) => this._patchConnectionV3(moduleExports)
4662
4975
  }),
4663
4976
  new TdInstrumentationNodeModuleFile({
4664
4977
  name: "mysql2/lib/pool.js",
4665
- supportedVersions: SUPPORTED_VERSIONS$1,
4666
- patch: (moduleExports) => this._patchPool(moduleExports)
4978
+ supportedVersions: [V2_3_3_TO_3_11_4],
4979
+ patch: (moduleExports) => this._patchPoolV2(moduleExports)
4667
4980
  }),
4668
4981
  new TdInstrumentationNodeModuleFile({
4669
- name: "mysql2/lib/pool_connection.js",
4670
- supportedVersions: SUPPORTED_VERSIONS$1,
4671
- patch: (moduleExports) => this._patchPoolConnection(moduleExports)
4982
+ name: "mysql2/lib/pool.js",
4983
+ supportedVersions: [V3_11_5_TO_4_0],
4984
+ patch: (moduleExports) => this._patchPoolV3(moduleExports)
4672
4985
  }),
4673
4986
  new TdInstrumentationNodeModuleFile({
4674
- name: "mysql2/lib/create_connection.js",
4675
- supportedVersions: SUPPORTED_VERSIONS$1,
4676
- patch: (moduleExports) => this._patchCreateConnectionFile(moduleExports)
4987
+ name: "mysql2/lib/pool_connection.js",
4988
+ supportedVersions: [V2_3_3_TO_3_11_4],
4989
+ patch: (moduleExports) => this._patchPoolConnectionV2(moduleExports)
4677
4990
  }),
4678
4991
  new TdInstrumentationNodeModuleFile({
4679
- name: "mysql2/lib/create_pool.js",
4680
- supportedVersions: SUPPORTED_VERSIONS$1,
4681
- patch: (moduleExports) => this._patchCreatePoolFile(moduleExports)
4992
+ name: "mysql2/lib/pool_connection.js",
4993
+ supportedVersions: [V3_11_5_TO_4_0],
4994
+ patch: (moduleExports) => this._patchPoolConnectionV3(moduleExports)
4682
4995
  })
4683
4996
  ]
4684
4997
  })];
4685
4998
  }
4686
- _patchConnection(ConnectionClass) {
4687
- logger.debug(`[Mysql2Instrumentation] Patching Connection class`);
4999
+ _patchBaseConnection(BaseConnectionClass) {
5000
+ logger.debug(`[Mysql2Instrumentation] Patching BaseConnection class`);
5001
+ if (this.isModulePatched(BaseConnectionClass)) {
5002
+ logger.debug(`[Mysql2Instrumentation] BaseConnection class already patched, skipping`);
5003
+ return BaseConnectionClass;
5004
+ }
5005
+ if (BaseConnectionClass.prototype && BaseConnectionClass.prototype.query) {
5006
+ if (!isWrapped$1(BaseConnectionClass.prototype.query)) {
5007
+ this._wrap(BaseConnectionClass.prototype, "query", this._getQueryPatchFn("connection"));
5008
+ logger.debug(`[Mysql2Instrumentation] Wrapped BaseConnection.prototype.query`);
5009
+ }
5010
+ }
5011
+ if (BaseConnectionClass.prototype && BaseConnectionClass.prototype.execute) {
5012
+ if (!isWrapped$1(BaseConnectionClass.prototype.execute)) {
5013
+ this._wrap(BaseConnectionClass.prototype, "execute", this._getExecutePatchFn("connection"));
5014
+ logger.debug(`[Mysql2Instrumentation] Wrapped BaseConnection.prototype.execute`);
5015
+ }
5016
+ }
5017
+ if (BaseConnectionClass.prototype && BaseConnectionClass.prototype.connect) {
5018
+ if (!isWrapped$1(BaseConnectionClass.prototype.connect)) {
5019
+ this._wrap(BaseConnectionClass.prototype, "connect", this._getConnectPatchFn("connection"));
5020
+ logger.debug(`[Mysql2Instrumentation] Wrapped BaseConnection.prototype.connect`);
5021
+ }
5022
+ }
5023
+ if (BaseConnectionClass.prototype && BaseConnectionClass.prototype.ping) {
5024
+ if (!isWrapped$1(BaseConnectionClass.prototype.ping)) {
5025
+ this._wrap(BaseConnectionClass.prototype, "ping", this._getPingPatchFn("connection"));
5026
+ logger.debug(`[Mysql2Instrumentation] Wrapped BaseConnection.prototype.ping`);
5027
+ }
5028
+ }
5029
+ if (BaseConnectionClass.prototype && BaseConnectionClass.prototype.end) {
5030
+ if (!isWrapped$1(BaseConnectionClass.prototype.end)) {
5031
+ this._wrap(BaseConnectionClass.prototype, "end", this._getEndPatchFn("connection"));
5032
+ logger.debug(`[Mysql2Instrumentation] Wrapped BaseConnection.prototype.end`);
5033
+ }
5034
+ }
5035
+ this.markModuleAsPatched(BaseConnectionClass);
5036
+ logger.debug(`[Mysql2Instrumentation] BaseConnection class patching complete`);
5037
+ return BaseConnectionClass;
5038
+ }
5039
+ _patchConnectionV2(ConnectionClass) {
5040
+ logger.debug(`[Mysql2Instrumentation] Patching Connection class (v2)`);
4688
5041
  if (this.isModulePatched(ConnectionClass)) {
4689
5042
  logger.debug(`[Mysql2Instrumentation] Connection class already patched, skipping`);
4690
5043
  return ConnectionClass;
4691
5044
  }
5045
+ this._patchConnectionPrototypes(ConnectionClass);
5046
+ const patchedConnectionClass = this._getPatchedConnectionClass(ConnectionClass);
5047
+ this.markModuleAsPatched(ConnectionClass);
5048
+ logger.debug(`[Mysql2Instrumentation] Connection class (v2) patching complete`);
5049
+ return patchedConnectionClass;
5050
+ }
5051
+ _patchConnectionV3(ConnectionClass) {
5052
+ logger.debug(`[Mysql2Instrumentation] Connection class (v3) - wrapping constructor only`);
5053
+ return this._getPatchedConnectionClass(ConnectionClass);
5054
+ }
5055
+ _patchConnectionPrototypes(ConnectionClass) {
4692
5056
  if (ConnectionClass.prototype && ConnectionClass.prototype.query) {
4693
5057
  if (!isWrapped$1(ConnectionClass.prototype.query)) {
4694
5058
  this._wrap(ConnectionClass.prototype, "query", this._getQueryPatchFn("connection"));
@@ -4719,16 +5083,30 @@ var Mysql2Instrumentation = class extends TdInstrumentationBase {
4719
5083
  logger.debug(`[Mysql2Instrumentation] Wrapped Connection.prototype.end`);
4720
5084
  }
4721
5085
  }
4722
- this.markModuleAsPatched(ConnectionClass);
4723
- logger.debug(`[Mysql2Instrumentation] Connection class patching complete`);
4724
- return ConnectionClass;
4725
5086
  }
4726
- _patchPool(PoolClass) {
4727
- logger.debug(`[Mysql2Instrumentation] Patching Pool class`);
5087
+ _patchPoolV2(PoolClass) {
5088
+ logger.debug(`[Mysql2Instrumentation] Patching Pool class (v2)`);
5089
+ if (this.isModulePatched(PoolClass)) {
5090
+ logger.debug(`[Mysql2Instrumentation] Pool class already patched, skipping`);
5091
+ return PoolClass;
5092
+ }
5093
+ this._patchPoolPrototypes(PoolClass);
5094
+ this.markModuleAsPatched(PoolClass);
5095
+ logger.debug(`[Mysql2Instrumentation] Pool class (v2) patching complete`);
5096
+ return PoolClass;
5097
+ }
5098
+ _patchPoolV3(PoolClass) {
5099
+ logger.debug(`[Mysql2Instrumentation] Patching Pool class (v3)`);
4728
5100
  if (this.isModulePatched(PoolClass)) {
4729
5101
  logger.debug(`[Mysql2Instrumentation] Pool class already patched, skipping`);
4730
5102
  return PoolClass;
4731
5103
  }
5104
+ this._patchPoolPrototypes(PoolClass);
5105
+ this.markModuleAsPatched(PoolClass);
5106
+ logger.debug(`[Mysql2Instrumentation] Pool class (v3) patching complete`);
5107
+ return PoolClass;
5108
+ }
5109
+ _patchPoolPrototypes(PoolClass) {
4732
5110
  if (PoolClass.prototype && PoolClass.prototype.query) {
4733
5111
  if (!isWrapped$1(PoolClass.prototype.query)) {
4734
5112
  this._wrap(PoolClass.prototype, "query", this._getQueryPatchFn("pool"));
@@ -4747,30 +5125,19 @@ var Mysql2Instrumentation = class extends TdInstrumentationBase {
4747
5125
  logger.debug(`[Mysql2Instrumentation] Wrapped Pool.prototype.getConnection`);
4748
5126
  }
4749
5127
  }
4750
- this.markModuleAsPatched(PoolClass);
4751
- logger.debug(`[Mysql2Instrumentation] Pool class patching complete`);
4752
- return PoolClass;
4753
- }
4754
- _patchPoolConnection(PoolConnectionClass) {
4755
- logger.debug(`[Mysql2Instrumentation] Patching PoolConnection class`);
4756
- if (this.isModulePatched(PoolConnectionClass)) {
4757
- logger.debug(`[Mysql2Instrumentation] PoolConnection class already patched, skipping`);
4758
- return PoolConnectionClass;
4759
- }
4760
- if (PoolConnectionClass.prototype && PoolConnectionClass.prototype.query) {
4761
- if (!isWrapped$1(PoolConnectionClass.prototype.query)) {
4762
- this._wrap(PoolConnectionClass.prototype, "query", this._getQueryPatchFn("poolConnection"));
4763
- logger.debug(`[Mysql2Instrumentation] Wrapped PoolConnection.prototype.query`);
5128
+ if (PoolClass.prototype && PoolClass.prototype.end) {
5129
+ if (!isWrapped$1(PoolClass.prototype.end)) {
5130
+ this._wrap(PoolClass.prototype, "end", this._getEndPatchFn("pool"));
5131
+ logger.debug(`[Mysql2Instrumentation] Wrapped Pool.prototype.end`);
4764
5132
  }
4765
5133
  }
4766
- if (PoolConnectionClass.prototype && PoolConnectionClass.prototype.execute) {
4767
- if (!isWrapped$1(PoolConnectionClass.prototype.execute)) {
4768
- this._wrap(PoolConnectionClass.prototype, "execute", this._getExecutePatchFn("poolConnection"));
4769
- logger.debug(`[Mysql2Instrumentation] Wrapped PoolConnection.prototype.execute`);
4770
- }
4771
- }
4772
- this.markModuleAsPatched(PoolConnectionClass);
4773
- logger.debug(`[Mysql2Instrumentation] PoolConnection class patching complete`);
5134
+ }
5135
+ _patchPoolConnectionV2(PoolConnectionClass) {
5136
+ logger.debug(`[Mysql2Instrumentation] PoolConnection class (v2) - skipping (inherits from Connection)`);
5137
+ return PoolConnectionClass;
5138
+ }
5139
+ _patchPoolConnectionV3(PoolConnectionClass) {
5140
+ logger.debug(`[Mysql2Instrumentation] PoolConnection class (v3) - skipping (inherits from Connection)`);
4774
5141
  return PoolConnectionClass;
4775
5142
  }
4776
5143
  _getQueryPatchFn(clientType) {
@@ -4792,22 +5159,24 @@ var Mysql2Instrumentation = class extends TdInstrumentationBase {
4792
5159
  values: queryConfig.values || [],
4793
5160
  clientType
4794
5161
  };
4795
- if (self.mode === TuskDriftMode.REPLAY) return handleReplayMode({ replayModeHandler: () => {
4796
- const spanName = `mysql2.${clientType}.query`;
4797
- return SpanUtils.createAndExecuteSpan(self.mode, () => originalQuery.apply(this, args), {
4798
- name: spanName,
4799
- kind: SpanKind.CLIENT,
4800
- submodule: "query",
4801
- packageType: PackageType.MYSQL,
4802
- packageName: "mysql2",
4803
- instrumentationName: self.INSTRUMENTATION_NAME,
4804
- inputValue,
4805
- isPreAppStart: false
4806
- }, (spanInfo) => {
4807
- return self.handleReplayQuery(queryConfig, inputValue, spanInfo, "query");
4808
- });
4809
- } });
4810
- else if (self.mode === TuskDriftMode.RECORD) return handleRecordMode({
5162
+ if (self.mode === TuskDriftMode.REPLAY) {
5163
+ const stackTrace = captureStackTrace(["Mysql2Instrumentation"]);
5164
+ return handleReplayMode({ replayModeHandler: () => {
5165
+ const spanName = `mysql2.${clientType}.query`;
5166
+ return SpanUtils.createAndExecuteSpan(self.mode, () => originalQuery.apply(this, args), {
5167
+ name: spanName,
5168
+ kind: SpanKind.CLIENT,
5169
+ submodule: "query",
5170
+ packageType: PackageType.MYSQL,
5171
+ packageName: "mysql2",
5172
+ instrumentationName: self.INSTRUMENTATION_NAME,
5173
+ inputValue,
5174
+ isPreAppStart: false
5175
+ }, (spanInfo) => {
5176
+ return self.handleReplayQuery(queryConfig, inputValue, spanInfo, "query", stackTrace);
5177
+ });
5178
+ } });
5179
+ } else if (self.mode === TuskDriftMode.RECORD) return handleRecordMode({
4811
5180
  originalFunctionCall: () => originalQuery.apply(this, args),
4812
5181
  recordModeHandler: ({ isPreAppStart }) => {
4813
5182
  const spanName = `mysql2.${clientType}.query`;
@@ -4849,22 +5218,24 @@ var Mysql2Instrumentation = class extends TdInstrumentationBase {
4849
5218
  values: queryConfig.values || [],
4850
5219
  clientType
4851
5220
  };
4852
- if (self.mode === TuskDriftMode.REPLAY) return handleReplayMode({ replayModeHandler: () => {
4853
- const spanName = `mysql2.${clientType}.execute`;
4854
- return SpanUtils.createAndExecuteSpan(self.mode, () => originalExecute.apply(this, args), {
4855
- name: spanName,
4856
- kind: SpanKind.CLIENT,
4857
- submodule: "execute",
4858
- packageType: PackageType.MYSQL,
4859
- packageName: "mysql2",
4860
- instrumentationName: self.INSTRUMENTATION_NAME,
4861
- inputValue,
4862
- isPreAppStart: false
4863
- }, (spanInfo) => {
4864
- return self.handleReplayQuery(queryConfig, inputValue, spanInfo, "execute");
4865
- });
4866
- } });
4867
- else if (self.mode === TuskDriftMode.RECORD) return handleRecordMode({
5221
+ if (self.mode === TuskDriftMode.REPLAY) {
5222
+ const stackTrace = captureStackTrace(["Mysql2Instrumentation"]);
5223
+ return handleReplayMode({ replayModeHandler: () => {
5224
+ const spanName = `mysql2.${clientType}.execute`;
5225
+ return SpanUtils.createAndExecuteSpan(self.mode, () => originalExecute.apply(this, args), {
5226
+ name: spanName,
5227
+ kind: SpanKind.CLIENT,
5228
+ submodule: "execute",
5229
+ packageType: PackageType.MYSQL,
5230
+ packageName: "mysql2",
5231
+ instrumentationName: self.INSTRUMENTATION_NAME,
5232
+ inputValue,
5233
+ isPreAppStart: false
5234
+ }, (spanInfo) => {
5235
+ return self.handleReplayQuery(queryConfig, inputValue, spanInfo, "execute", stackTrace);
5236
+ });
5237
+ } });
5238
+ } else if (self.mode === TuskDriftMode.RECORD) return handleRecordMode({
4868
5239
  originalFunctionCall: () => originalExecute.apply(this, args),
4869
5240
  recordModeHandler: ({ isPreAppStart }) => {
4870
5241
  const spanName = `mysql2.${clientType}.execute`;
@@ -5212,8 +5583,8 @@ var Mysql2Instrumentation = class extends TdInstrumentationBase {
5212
5583
  return result;
5213
5584
  }
5214
5585
  }
5215
- handleReplayQuery(queryConfig, inputValue, spanInfo, submoduleName = "query") {
5216
- return this.queryMock.handleReplayQuery(queryConfig, inputValue, spanInfo, submoduleName);
5586
+ handleReplayQuery(queryConfig, inputValue, spanInfo, submoduleName = "query", stackTrace) {
5587
+ return this.queryMock.handleReplayQuery(queryConfig, inputValue, spanInfo, submoduleName, stackTrace);
5217
5588
  }
5218
5589
  _handleRecordPoolGetConnectionInSpan(spanInfo, originalGetConnection, callback, context$1) {
5219
5590
  if (callback) {
@@ -5276,117 +5647,137 @@ var Mysql2Instrumentation = class extends TdInstrumentationBase {
5276
5647
  return;
5277
5648
  } else return Promise.resolve(mockConnection);
5278
5649
  }
5279
- _addOutputAttributesToSpan(spanInfo, result, fields) {
5280
- if (!result) return;
5281
- let outputValue = {};
5282
- if (Array.isArray(result)) outputValue = {
5283
- rowCount: result.length,
5284
- rows: result,
5285
- fields: fields || []
5286
- };
5287
- else if (result.affectedRows !== void 0) outputValue = {
5288
- affectedRows: result.affectedRows,
5289
- insertId: result.insertId,
5290
- warningCount: result.warningCount
5291
- };
5292
- else outputValue = result;
5293
- SpanUtils.addSpanAttributes(spanInfo.span, { outputValue });
5294
- }
5295
- _patchCreateConnectionFile(createConnectionFn) {
5296
- logger.debug(`[Mysql2Instrumentation] Patching create_connection.js file`);
5650
+ /**
5651
+ * Creates a patched Connection class that intercepts the constructor
5652
+ * to handle connection event recording/replay.
5653
+ *
5654
+ * Wrap the Connection constructor to:
5655
+ * - In RECORD mode: listen for 'connect'/'error' events and record them
5656
+ * - In REPLAY mode: create a MockConnection that fakes the connection and emits recorded events
5657
+ */
5658
+ _getPatchedConnectionClass(OriginalConnection) {
5297
5659
  const self = this;
5298
- const wrappedFn = function(...args) {
5660
+ function TdPatchedConnection(...args) {
5299
5661
  const inputValue = { method: "createConnection" };
5300
- if (self.mode === TuskDriftMode.REPLAY) return handleReplayMode({ replayModeHandler: () => {
5301
- return SpanUtils.createAndExecuteSpan(self.mode, () => createConnectionFn.apply(this, args), {
5302
- name: `mysql2.createConnection`,
5303
- kind: SpanKind.CLIENT,
5304
- submodule: "createConnection",
5305
- packageName: "mysql2",
5306
- packageType: PackageType.MYSQL,
5307
- instrumentationName: self.INSTRUMENTATION_NAME,
5308
- inputValue,
5309
- isPreAppStart: false
5310
- }, (spanInfo) => {
5311
- const connection = createConnectionFn.apply(this, args);
5312
- SpanUtils.addSpanAttributes(spanInfo.span, { outputValue: { created: true } });
5313
- SpanUtils.endSpan(spanInfo.span, { code: SpanStatusCode.OK });
5314
- return connection;
5315
- });
5316
- } });
5317
- else if (self.mode === TuskDriftMode.RECORD) return handleRecordMode({
5318
- originalFunctionCall: () => createConnectionFn.apply(this, args),
5662
+ if (self.mode === TuskDriftMode.RECORD) return handleRecordMode({
5663
+ originalFunctionCall: () => new OriginalConnection(...args),
5319
5664
  recordModeHandler: ({ isPreAppStart }) => {
5320
- return SpanUtils.createAndExecuteSpan(self.mode, () => createConnectionFn.apply(this, args), {
5321
- name: `mysql2.createConnection`,
5665
+ return SpanUtils.createAndExecuteSpan(self.mode, () => new OriginalConnection(...args), {
5666
+ name: `mysql2.connection.create`,
5322
5667
  kind: SpanKind.CLIENT,
5323
- submodule: "createConnection",
5324
- packageName: "mysql2",
5668
+ submodule: "connectEvent",
5325
5669
  packageType: PackageType.MYSQL,
5670
+ packageName: "mysql2",
5326
5671
  instrumentationName: self.INSTRUMENTATION_NAME,
5327
5672
  inputValue,
5328
5673
  isPreAppStart
5329
5674
  }, (spanInfo) => {
5330
- const connection = createConnectionFn.apply(this, args);
5331
- SpanUtils.addSpanAttributes(spanInfo.span, { outputValue: { created: true } });
5332
- SpanUtils.endSpan(spanInfo.span, { code: SpanStatusCode.OK });
5675
+ const connection = new OriginalConnection(...args);
5676
+ connection.on("connect", (connectionObj) => {
5677
+ try {
5678
+ SpanUtils.addSpanAttributes(spanInfo.span, { outputValue: {
5679
+ connected: true,
5680
+ connectionObj
5681
+ } });
5682
+ SpanUtils.endSpan(spanInfo.span, { code: SpanStatusCode.OK });
5683
+ } catch {
5684
+ logger.error(`[Mysql2Instrumentation] error adding span attributes:`);
5685
+ }
5686
+ });
5687
+ connection.on("error", (err) => {
5688
+ try {
5689
+ logger.debug(`[Mysql2Instrumentation] Connection error, recording: ${err.message}`);
5690
+ SpanUtils.endSpan(spanInfo.span, {
5691
+ code: SpanStatusCode.ERROR,
5692
+ message: err.message
5693
+ });
5694
+ } catch {
5695
+ logger.error(`[Mysql2Instrumentation] error ending span`);
5696
+ }
5697
+ });
5333
5698
  return connection;
5334
5699
  });
5335
5700
  },
5336
5701
  spanKind: SpanKind.CLIENT
5337
5702
  });
5338
- else return createConnectionFn.apply(this, args);
5339
- };
5340
- logger.debug(`[Mysql2Instrumentation] Patched create_connection.js file`);
5341
- return wrappedFn;
5342
- }
5343
- _patchCreatePoolFile(createPoolFn) {
5344
- logger.debug(`[Mysql2Instrumentation] Patching create_pool.js file`);
5345
- const self = this;
5346
- const wrappedFn = function(...args) {
5347
- const inputValue = { method: "createPool" };
5348
5703
  if (self.mode === TuskDriftMode.REPLAY) return handleReplayMode({ replayModeHandler: () => {
5349
- return SpanUtils.createAndExecuteSpan(self.mode, () => createPoolFn.apply(this, args), {
5350
- name: `mysql2.createPool`,
5704
+ return SpanUtils.createAndExecuteSpan(self.mode, () => new OriginalConnection(...args), {
5705
+ name: `mysql2.connection.create`,
5351
5706
  kind: SpanKind.CLIENT,
5352
- submodule: "createPool",
5353
- packageName: "mysql2",
5707
+ submodule: "connectEvent",
5354
5708
  packageType: PackageType.MYSQL,
5709
+ packageName: "mysql2",
5355
5710
  instrumentationName: self.INSTRUMENTATION_NAME,
5356
5711
  inputValue,
5357
5712
  isPreAppStart: false
5358
5713
  }, (spanInfo) => {
5359
- const pool = createPoolFn.apply(this, args);
5360
- SpanUtils.addSpanAttributes(spanInfo.span, { outputValue: { created: true } });
5714
+ class MockConnection extends OriginalConnection {
5715
+ constructor(...mockConnectionArgs) {
5716
+ const clonedArgs = JSON.parse(JSON.stringify(mockConnectionArgs));
5717
+ if (clonedArgs[0] && clonedArgs[0].config) {
5718
+ clonedArgs[0].config.host = "127.0.0.1";
5719
+ clonedArgs[0].config.port = 127;
5720
+ } else if (clonedArgs[0]) {
5721
+ clonedArgs[0].host = "127.0.0.1";
5722
+ clonedArgs[0].port = 127;
5723
+ }
5724
+ super(...clonedArgs);
5725
+ this._isConnectOrErrorEmitted = false;
5726
+ this._connectEventMock = new TdMysql2ConnectionEventMock(spanInfo);
5727
+ }
5728
+ on(event, listener) {
5729
+ if (!this._connectEventMock) return super.on(event, listener);
5730
+ if (event === "connect" && !this._isConnectOrErrorEmitted) {
5731
+ this._connectEventMock.getReplayedConnectionEvent(inputValue).then(({ output }) => {
5732
+ if (output !== void 0) process.nextTick(() => {
5733
+ listener.call(this, output);
5734
+ this._isConnectOrErrorEmitted = true;
5735
+ });
5736
+ }).catch((err) => {
5737
+ logger.error(`[Mysql2Instrumentation] Error replaying connection event:`, err);
5738
+ });
5739
+ return this;
5740
+ }
5741
+ if (event === "error" && !this._isConnectOrErrorEmitted) return this;
5742
+ return super.on(event, listener);
5743
+ }
5744
+ }
5745
+ const mockConnection = new MockConnection(...args);
5746
+ mockConnection.addListener("error", (_err) => {});
5747
+ SpanUtils.addSpanAttributes(spanInfo.span, { outputValue: {
5748
+ connected: true,
5749
+ mock: true
5750
+ } });
5361
5751
  SpanUtils.endSpan(spanInfo.span, { code: SpanStatusCode.OK });
5362
- return pool;
5752
+ return mockConnection;
5363
5753
  });
5364
5754
  } });
5365
- else if (self.mode === TuskDriftMode.RECORD) return handleRecordMode({
5366
- originalFunctionCall: () => createPoolFn.apply(this, args),
5367
- recordModeHandler: ({ isPreAppStart }) => {
5368
- return SpanUtils.createAndExecuteSpan(self.mode, () => createPoolFn.apply(this, args), {
5369
- name: `mysql2.createPool`,
5370
- kind: SpanKind.CLIENT,
5371
- submodule: "createPool",
5372
- packageName: "mysql2",
5373
- packageType: PackageType.MYSQL,
5374
- instrumentationName: self.INSTRUMENTATION_NAME,
5375
- inputValue,
5376
- isPreAppStart
5377
- }, (spanInfo) => {
5378
- const pool = createPoolFn.apply(this, args);
5379
- SpanUtils.addSpanAttributes(spanInfo.span, { outputValue: { created: true } });
5380
- SpanUtils.endSpan(spanInfo.span, { code: SpanStatusCode.OK });
5381
- return pool;
5382
- });
5383
- },
5384
- spanKind: SpanKind.CLIENT
5385
- });
5386
- else return createPoolFn.apply(this, args);
5755
+ return new OriginalConnection(...args);
5756
+ }
5757
+ const staticProps = Object.getOwnPropertyNames(OriginalConnection).filter((key) => ![
5758
+ "length",
5759
+ "name",
5760
+ "prototype"
5761
+ ].includes(key));
5762
+ for (const staticProp of staticProps) TdPatchedConnection[staticProp] = OriginalConnection[staticProp];
5763
+ Object.setPrototypeOf(TdPatchedConnection.prototype, OriginalConnection.prototype);
5764
+ return TdPatchedConnection;
5765
+ }
5766
+ _addOutputAttributesToSpan(spanInfo, result, fields) {
5767
+ if (!result) return;
5768
+ let outputValue = {};
5769
+ if (Array.isArray(result)) outputValue = {
5770
+ rowCount: result.length,
5771
+ rows: result,
5772
+ fields: fields || []
5773
+ };
5774
+ else if (result.affectedRows !== void 0) outputValue = {
5775
+ affectedRows: result.affectedRows,
5776
+ insertId: result.insertId,
5777
+ warningCount: result.warningCount
5387
5778
  };
5388
- logger.debug(`[Mysql2Instrumentation] Patched create_pool.js file`);
5389
- return wrappedFn;
5779
+ else outputValue = result;
5780
+ SpanUtils.addSpanAttributes(spanInfo.span, { outputValue });
5390
5781
  }
5391
5782
  _wrap(target, propertyName, wrapper) {
5392
5783
  wrap(target, propertyName, wrapper);
@@ -5536,28 +5927,30 @@ var JsonwebtokenInstrumentation = class extends TdInstrumentationBase {
5536
5927
  token: verifyConfig.token,
5537
5928
  secretOrPublicKey: typeof verifyConfig.secretOrPublicKey === "function" ? "[Function:secretProvider]" : verifyConfig.secretOrPublicKey,
5538
5929
  options: verifyConfig.options
5539
- };
5540
- let inputValue;
5541
- try {
5542
- inputValue = createMockInputValue(rawInputValue);
5543
- } catch (error) {
5544
- logger.error(`[JsonwebtokenInstrumentation] error creating mock input value:`, error);
5545
- return originalVerify.apply(this, args);
5546
- }
5547
- if (self.mode === TuskDriftMode.REPLAY) return handleReplayMode({ replayModeHandler: () => {
5548
- return SpanUtils.createAndExecuteSpan(self.mode, () => originalVerify.apply(this, args), {
5549
- name: "jsonwebtoken.verify",
5550
- kind: SpanKind.CLIENT,
5551
- submodule: "verify",
5552
- packageName: "jsonwebtoken",
5553
- instrumentationName: self.INSTRUMENTATION_NAME,
5554
- inputValue,
5555
- isPreAppStart: false
5556
- }, (spanInfo) => {
5557
- return self.handleReplayVerify(verifyConfig, inputValue, spanInfo);
5558
- });
5559
- } });
5560
- else if (self.mode === TuskDriftMode.RECORD) return handleRecordMode({
5930
+ };
5931
+ let inputValue;
5932
+ try {
5933
+ inputValue = createMockInputValue(rawInputValue);
5934
+ } catch (error) {
5935
+ logger.error(`[JsonwebtokenInstrumentation] error creating mock input value:`, error);
5936
+ return originalVerify.apply(this, args);
5937
+ }
5938
+ if (self.mode === TuskDriftMode.REPLAY) {
5939
+ const stackTrace = captureStackTrace(["JsonwebtokenInstrumentation"]);
5940
+ return handleReplayMode({ replayModeHandler: () => {
5941
+ return SpanUtils.createAndExecuteSpan(self.mode, () => originalVerify.apply(this, args), {
5942
+ name: "jsonwebtoken.verify",
5943
+ kind: SpanKind.CLIENT,
5944
+ submodule: "verify",
5945
+ packageName: "jsonwebtoken",
5946
+ instrumentationName: self.INSTRUMENTATION_NAME,
5947
+ inputValue,
5948
+ isPreAppStart: false
5949
+ }, (spanInfo) => {
5950
+ return self.handleReplayVerify(verifyConfig, inputValue, spanInfo, stackTrace);
5951
+ });
5952
+ } });
5953
+ } else if (self.mode === TuskDriftMode.RECORD) return handleRecordMode({
5561
5954
  originalFunctionCall: () => originalVerify.apply(this, args),
5562
5955
  recordModeHandler: ({ isPreAppStart }) => {
5563
5956
  return SpanUtils.createAndExecuteSpan(self.mode, () => originalVerify.apply(this, args), {
@@ -5597,20 +5990,22 @@ var JsonwebtokenInstrumentation = class extends TdInstrumentationBase {
5597
5990
  secretOrPrivateKey: signConfig.secretOrPrivateKey,
5598
5991
  options: signConfig.options
5599
5992
  };
5600
- if (self.mode === TuskDriftMode.REPLAY) return handleReplayMode({ replayModeHandler: () => {
5601
- return SpanUtils.createAndExecuteSpan(self.mode, () => originalSign.apply(this, args), {
5602
- name: "jsonwebtoken.sign",
5603
- kind: SpanKind.CLIENT,
5604
- submodule: "sign",
5605
- packageName: "jsonwebtoken",
5606
- instrumentationName: self.INSTRUMENTATION_NAME,
5607
- inputValue,
5608
- isPreAppStart: false
5609
- }, (spanInfo) => {
5610
- return self.handleReplaySign(signConfig, inputValue, spanInfo);
5611
- });
5612
- } });
5613
- else if (self.mode === TuskDriftMode.RECORD) return handleRecordMode({
5993
+ if (self.mode === TuskDriftMode.REPLAY) {
5994
+ const stackTrace = captureStackTrace(["JsonwebtokenInstrumentation"]);
5995
+ return handleReplayMode({ replayModeHandler: () => {
5996
+ return SpanUtils.createAndExecuteSpan(self.mode, () => originalSign.apply(this, args), {
5997
+ name: "jsonwebtoken.sign",
5998
+ kind: SpanKind.CLIENT,
5999
+ submodule: "sign",
6000
+ packageName: "jsonwebtoken",
6001
+ instrumentationName: self.INSTRUMENTATION_NAME,
6002
+ inputValue,
6003
+ isPreAppStart: false
6004
+ }, (spanInfo) => {
6005
+ return self.handleReplaySign(signConfig, inputValue, spanInfo, stackTrace);
6006
+ });
6007
+ } });
6008
+ } else if (self.mode === TuskDriftMode.RECORD) return handleRecordMode({
5614
6009
  originalFunctionCall: () => originalSign.apply(this, args),
5615
6010
  recordModeHandler: ({ isPreAppStart }) => {
5616
6011
  return SpanUtils.createAndExecuteSpan(self.mode, () => originalSign.apply(this, args), {
@@ -5778,18 +6173,19 @@ var JsonwebtokenInstrumentation = class extends TdInstrumentationBase {
5778
6173
  throw error;
5779
6174
  }
5780
6175
  }
5781
- async handleReplayVerify(verifyConfig, inputValue, spanInfo) {
6176
+ async handleReplayVerify(verifyConfig, inputValue, spanInfo, stackTrace) {
5782
6177
  logger.debug(`[JsonwebtokenInstrumentation] Replaying JWT verify`);
5783
6178
  const mockData = await findMockResponseAsync({
5784
6179
  mockRequestData: {
5785
6180
  traceId: spanInfo.traceId,
5786
6181
  spanId: spanInfo.spanId,
5787
- name: inputValue.token,
6182
+ name: "jsonwebtoken.verify",
5788
6183
  packageName: "jsonwebtoken",
5789
6184
  instrumentationName: this.INSTRUMENTATION_NAME,
5790
6185
  submoduleName: "verify",
5791
6186
  inputValue,
5792
- kind: SpanKind.CLIENT
6187
+ kind: SpanKind.CLIENT,
6188
+ stackTrace
5793
6189
  },
5794
6190
  tuskDrift: this.tuskDrift
5795
6191
  });
@@ -5824,18 +6220,19 @@ var JsonwebtokenInstrumentation = class extends TdInstrumentationBase {
5824
6220
  return;
5825
6221
  } else return result;
5826
6222
  }
5827
- async handleReplaySign(signConfig, inputValue, spanInfo) {
6223
+ async handleReplaySign(signConfig, inputValue, spanInfo, stackTrace) {
5828
6224
  logger.debug(`[JsonwebtokenInstrumentation] Replaying JWT sign`);
5829
6225
  const mockData = await findMockResponseAsync({
5830
6226
  mockRequestData: {
5831
6227
  traceId: spanInfo?.traceId,
5832
6228
  spanId: spanInfo?.spanId,
5833
- name: JSON.stringify(inputValue.payload),
6229
+ name: "jsonwebtoken.sign",
5834
6230
  packageName: "jsonwebtoken",
5835
6231
  instrumentationName: this.INSTRUMENTATION_NAME,
5836
6232
  submoduleName: "sign",
5837
6233
  inputValue,
5838
- kind: SpanKind.CLIENT
6234
+ kind: SpanKind.CLIENT,
6235
+ stackTrace
5839
6236
  },
5840
6237
  tuskDrift: this.tuskDrift
5841
6238
  });
@@ -6239,11 +6636,12 @@ var FetchInstrumentation = class extends TdInstrumentationBase {
6239
6636
  this.originalFetch = globalThis.fetch;
6240
6637
  const self = this;
6241
6638
  globalThis.fetch = function(input, init) {
6242
- return self._handleFetchRequest(input, init);
6639
+ const stackTrace = captureStackTrace(["FetchInstrumentation"]);
6640
+ return self._handleFetchRequest(input, init, stackTrace);
6243
6641
  };
6244
6642
  logger.debug("Global fetch patching complete");
6245
6643
  }
6246
- async _handleFetchRequest(input, init) {
6644
+ async _handleFetchRequest(input, init, stackTrace) {
6247
6645
  const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
6248
6646
  const method = init?.method || "GET";
6249
6647
  const headers = init?.headers || {};
@@ -6273,7 +6671,7 @@ var FetchInstrumentation = class extends TdInstrumentationBase {
6273
6671
  inputValue,
6274
6672
  isPreAppStart: false
6275
6673
  }, (spanInfo) => {
6276
- return this._handleReplayFetch(inputValue, spanInfo);
6674
+ return this._handleReplayFetch(inputValue, spanInfo, stackTrace);
6277
6675
  });
6278
6676
  } });
6279
6677
  else if (this.mode === TuskDriftMode.RECORD) return handleRecordMode({
@@ -6354,7 +6752,7 @@ var FetchInstrumentation = class extends TdInstrumentationBase {
6354
6752
  });
6355
6753
  });
6356
6754
  }
6357
- async _handleReplayFetch(inputValue, spanInfo) {
6755
+ async _handleReplayFetch(inputValue, spanInfo, stackTrace) {
6358
6756
  const mockData = await findMockResponseAsync({
6359
6757
  mockRequestData: {
6360
6758
  traceId: spanInfo.traceId,
@@ -6365,7 +6763,8 @@ var FetchInstrumentation = class extends TdInstrumentationBase {
6365
6763
  instrumentationName: this.INSTRUMENTATION_NAME,
6366
6764
  submoduleName: inputValue.method,
6367
6765
  inputValue,
6368
- kind: SpanKind.CLIENT
6766
+ kind: SpanKind.CLIENT,
6767
+ stackTrace
6369
6768
  },
6370
6769
  tuskDrift: this.tuskDrift,
6371
6770
  inputValueSchemaMerges: {
@@ -6602,7 +7001,7 @@ var IORedisInstrumentation = class extends TdInstrumentationBase {
6602
7001
  }
6603
7002
  const actualExports = moduleExports[Symbol.toStringTag] === "Module" ? moduleExports.default : moduleExports;
6604
7003
  if (!actualExports || !actualExports.prototype) {
6605
- logger.error(`[IORedisInstrumentation] Invalid Pipeline module exports, cannot patch`);
7004
+ logger.debug(`[IORedisInstrumentation] Invalid Pipeline module exports, cannot patch`);
6606
7005
  return moduleExports;
6607
7006
  }
6608
7007
  if (actualExports.prototype.exec) {
@@ -6640,21 +7039,23 @@ var IORedisInstrumentation = class extends TdInstrumentationBase {
6640
7039
  port: this.options?.port
6641
7040
  }
6642
7041
  };
6643
- if (self.mode === TuskDriftMode.REPLAY) return handleReplayMode({ replayModeHandler: () => {
6644
- return SpanUtils.createAndExecuteSpan(self.mode, () => originalSendCommand.apply(this, arguments), {
6645
- name: `ioredis.${commandName}`,
6646
- kind: SpanKind.CLIENT,
6647
- submodule: commandName,
6648
- packageType: PackageType.REDIS,
6649
- packageName: "ioredis",
6650
- instrumentationName: self.INSTRUMENTATION_NAME,
6651
- inputValue,
6652
- isPreAppStart: false
6653
- }, (spanInfo) => {
6654
- return self._handleReplaySendCommand(spanInfo, cmd, inputValue, commandName);
6655
- });
6656
- } });
6657
- else if (self.mode === TuskDriftMode.RECORD) return handleRecordMode({
7042
+ if (self.mode === TuskDriftMode.REPLAY) {
7043
+ const stackTrace = captureStackTrace(["IORedisInstrumentation"]);
7044
+ return handleReplayMode({ replayModeHandler: () => {
7045
+ return SpanUtils.createAndExecuteSpan(self.mode, () => originalSendCommand.apply(this, arguments), {
7046
+ name: `ioredis.${commandName}`,
7047
+ kind: SpanKind.CLIENT,
7048
+ submodule: commandName,
7049
+ packageType: PackageType.REDIS,
7050
+ packageName: "ioredis",
7051
+ instrumentationName: self.INSTRUMENTATION_NAME,
7052
+ inputValue,
7053
+ isPreAppStart: false
7054
+ }, (spanInfo) => {
7055
+ return self._handleReplaySendCommand(spanInfo, cmd, inputValue, commandName, stackTrace);
7056
+ });
7057
+ } });
7058
+ } else if (self.mode === TuskDriftMode.RECORD) return handleRecordMode({
6658
7059
  originalFunctionCall: () => originalSendCommand.apply(this, arguments),
6659
7060
  recordModeHandler: ({ isPreAppStart }) => {
6660
7061
  return SpanUtils.createAndExecuteSpan(self.mode, () => originalSendCommand.apply(this, arguments), {
@@ -6816,7 +7217,7 @@ var IORedisInstrumentation = class extends TdInstrumentationBase {
6816
7217
  });
6817
7218
  return promise;
6818
7219
  }
6819
- async _handleReplaySendCommand(spanInfo, cmd, inputValue, commandName) {
7220
+ async _handleReplaySendCommand(spanInfo, cmd, inputValue, commandName, stackTrace) {
6820
7221
  logger.debug(`[IORedisInstrumentation] Replaying IORedis command ${cmd.name}`);
6821
7222
  const mockData = await findMockResponseAsync({
6822
7223
  mockRequestData: {
@@ -6827,7 +7228,8 @@ var IORedisInstrumentation = class extends TdInstrumentationBase {
6827
7228
  packageName: "ioredis",
6828
7229
  instrumentationName: this.INSTRUMENTATION_NAME,
6829
7230
  submoduleName: cmd.name,
6830
- kind: SpanKind.CLIENT
7231
+ kind: SpanKind.CLIENT,
7232
+ stackTrace
6831
7233
  },
6832
7234
  tuskDrift: this.tuskDrift
6833
7235
  });
@@ -7276,21 +7678,23 @@ var GrpcInstrumentation = class GrpcInstrumentation extends TdInstrumentationBas
7276
7678
  jsonableStringMap
7277
7679
  }
7278
7680
  };
7279
- if (self.mode === TuskDriftMode.REPLAY) return handleReplayMode({ replayModeHandler: () => {
7280
- return SpanUtils.createAndExecuteSpan(self.mode, () => original.apply(this, args), {
7281
- name: "grpc.client.unary",
7282
- kind: SpanKind.CLIENT,
7283
- submodule: "client",
7284
- packageType: PackageType.GRPC,
7285
- packageName: GRPC_MODULE_NAME,
7286
- instrumentationName: self.INSTRUMENTATION_NAME,
7287
- inputValue,
7288
- isPreAppStart: false
7289
- }, (spanInfo) => {
7290
- return self._handleReplayUnaryRequest(spanInfo, inputValue, callback, MetadataConstructor);
7291
- });
7292
- } });
7293
- else if (self.mode === TuskDriftMode.RECORD) return handleRecordMode({
7681
+ if (self.mode === TuskDriftMode.REPLAY) {
7682
+ const stackTrace = captureStackTrace(["GrpcInstrumentation"]);
7683
+ return handleReplayMode({ replayModeHandler: () => {
7684
+ return SpanUtils.createAndExecuteSpan(self.mode, () => original.apply(this, args), {
7685
+ name: "grpc.client.unary",
7686
+ kind: SpanKind.CLIENT,
7687
+ submodule: "client",
7688
+ packageType: PackageType.GRPC,
7689
+ packageName: GRPC_MODULE_NAME,
7690
+ instrumentationName: self.INSTRUMENTATION_NAME,
7691
+ inputValue,
7692
+ isPreAppStart: false
7693
+ }, (spanInfo) => {
7694
+ return self._handleReplayUnaryRequest(spanInfo, inputValue, callback, MetadataConstructor, stackTrace);
7695
+ });
7696
+ } });
7697
+ } else if (self.mode === TuskDriftMode.RECORD) return handleRecordMode({
7294
7698
  originalFunctionCall: () => original.apply(this, args),
7295
7699
  recordModeHandler: ({ isPreAppStart }) => {
7296
7700
  return SpanUtils.createAndExecuteSpan(self.mode, () => original.apply(this, args), {
@@ -7422,7 +7826,7 @@ var GrpcInstrumentation = class GrpcInstrumentation extends TdInstrumentationBas
7422
7826
  isGrpcErrorOutput(result) {
7423
7827
  return "error" in result;
7424
7828
  }
7425
- async _handleReplayUnaryRequest(spanInfo, inputValue, callback, MetadataConstructor) {
7829
+ async _handleReplayUnaryRequest(spanInfo, inputValue, callback, MetadataConstructor, stackTrace) {
7426
7830
  logger.debug(`[GrpcInstrumentation] Replaying gRPC unary request`);
7427
7831
  const mockData = await findMockResponseAsync({
7428
7832
  mockRequestData: {
@@ -7433,7 +7837,8 @@ var GrpcInstrumentation = class GrpcInstrumentation extends TdInstrumentationBas
7433
7837
  packageName: GRPC_MODULE_NAME,
7434
7838
  instrumentationName: this.INSTRUMENTATION_NAME,
7435
7839
  submoduleName: "client",
7436
- kind: SpanKind.CLIENT
7840
+ kind: SpanKind.CLIENT,
7841
+ stackTrace
7437
7842
  },
7438
7843
  tuskDrift: this.tuskDrift
7439
7844
  });
@@ -7521,6 +7926,372 @@ var GrpcInstrumentation = class GrpcInstrumentation extends TdInstrumentationBas
7521
7926
  };
7522
7927
  GrpcInstrumentation.metadataStore = /* @__PURE__ */ new Map();
7523
7928
 
7929
+ //#endregion
7930
+ //#region src/instrumentation/libraries/nextjs/Instrumentation.ts
7931
+ var NextjsInstrumentation = class extends TdInstrumentationBase {
7932
+ constructor(config = {}) {
7933
+ super("nextjs", config);
7934
+ this.INSTRUMENTATION_NAME = "NextjsInstrumentation";
7935
+ this.mode = config.mode || TuskDriftMode.DISABLED;
7936
+ this.replayHooks = new HttpReplayHooks();
7937
+ this.tuskDrift = TuskDriftCore.getInstance();
7938
+ logger.debug(`[NextjsInstrumentation] Constructor called with mode: ${this.mode}`);
7939
+ this._patchLoadedModules();
7940
+ }
7941
+ init() {
7942
+ logger.debug(`[NextjsInstrumentation] Initializing in ${this.mode} mode`);
7943
+ return [new TdInstrumentationNodeModule({
7944
+ name: "next/dist/server/base-server",
7945
+ supportedVersions: ["*"],
7946
+ patch: (moduleExports) => {
7947
+ return this._patchBaseServer(moduleExports);
7948
+ }
7949
+ })];
7950
+ }
7951
+ _patchLoadedModules() {
7952
+ logger.debug(`[NextjsInstrumentation] Checking for already-loaded Next.js modules`);
7953
+ const pattern = "/next/dist/server/base-server.js";
7954
+ let patchedCount = 0;
7955
+ for (const [modulePath, cached] of Object.entries(__require.cache)) if (modulePath.includes(pattern) && cached && cached.exports) {
7956
+ logger.debug(`[NextjsInstrumentation] Found ${pattern} at ${modulePath}, patching now`);
7957
+ this._patchBaseServer(cached.exports);
7958
+ patchedCount++;
7959
+ break;
7960
+ }
7961
+ if (patchedCount === 0) {
7962
+ logger.debug(`[NextjsInstrumentation] No Next.js server modules found in require.cache yet`);
7963
+ logger.debug(`[NextjsInstrumentation] Will wait for require-in-the-middle hooks to catch them`);
7964
+ } else logger.debug(`[NextjsInstrumentation] Patched ${patchedCount} already-loaded Next.js modules`);
7965
+ }
7966
+ _patchBaseServer(baseServerModule) {
7967
+ logger.debug(`[NextjsInstrumentation] Patching Next.js BaseServer in ${this.mode} mode`);
7968
+ const BaseServer = baseServerModule.default || baseServerModule.BaseServer || baseServerModule;
7969
+ if (BaseServer && BaseServer.prototype) {
7970
+ if (this.isModulePatched(BaseServer.prototype)) {
7971
+ logger.debug(`[NextjsInstrumentation] BaseServer.prototype already patched, skipping`);
7972
+ return baseServerModule;
7973
+ }
7974
+ if (BaseServer.prototype.handleRequest) {
7975
+ this._wrap(BaseServer.prototype, "handleRequest", this._getHandleRequestPatchFn());
7976
+ logger.debug(`[NextjsInstrumentation] Wrapped BaseServer.prototype.handleRequest`);
7977
+ this.markModuleAsPatched(BaseServer.prototype);
7978
+ } else logger.warn(`[NextjsInstrumentation] Could not find BaseServer.prototype.handleRequest`);
7979
+ } else logger.warn(`[NextjsInstrumentation] Could not find BaseServer class`);
7980
+ logger.debug(`[NextjsInstrumentation] Next.js BaseServer patching complete`);
7981
+ return baseServerModule;
7982
+ }
7983
+ _getHandleRequestPatchFn() {
7984
+ const self = this;
7985
+ return (originalHandleRequest) => {
7986
+ return async function(req, res, parsedUrl) {
7987
+ const method = req.method || "GET";
7988
+ const url = req.url || "/";
7989
+ logger.debug(`[NextjsInstrumentation] Intercepted Next.js request: ${method} ${url}`);
7990
+ if (self.mode === TuskDriftMode.REPLAY) return handleReplayMode({ replayModeHandler: () => {
7991
+ const inputValue = {
7992
+ method,
7993
+ url,
7994
+ target: url,
7995
+ headers: self._normalizeHeaders(req.headers || {})
7996
+ };
7997
+ const replayTraceId = self.replayHooks.extractTraceIdFromHeaders(req);
7998
+ if (!replayTraceId) {
7999
+ logger.debug(`[NextjsInstrumentation] No trace ID found, calling original handler`);
8000
+ return originalHandleRequest.call(this, req, res, parsedUrl);
8001
+ }
8002
+ const envVars = self.replayHooks.extractEnvVarsFromHeaders(req);
8003
+ if (envVars) EnvVarTracker.setEnvVars(replayTraceId, envVars);
8004
+ const ctxWithReplayTraceId = SpanUtils.setCurrentReplayTraceId(replayTraceId);
8005
+ if (!ctxWithReplayTraceId) throw new Error("Error setting current replay trace id");
8006
+ return context.with(ctxWithReplayTraceId, () => {
8007
+ return SpanUtils.createAndExecuteSpan(self.mode, () => originalHandleRequest.call(this, req, res, parsedUrl), {
8008
+ name: url,
8009
+ kind: SpanKind.SERVER,
8010
+ packageName: "nextjs",
8011
+ submodule: method,
8012
+ packageType: PackageType.HTTP,
8013
+ instrumentationName: self.INSTRUMENTATION_NAME,
8014
+ inputValue,
8015
+ inputSchemaMerges: { headers: { matchImportance: 0 } },
8016
+ isPreAppStart: false
8017
+ }, (spanInfo) => {
8018
+ return self._handleNextjsRequestInSpan({
8019
+ req,
8020
+ res,
8021
+ parsedUrl,
8022
+ originalHandleRequest,
8023
+ spanInfo,
8024
+ inputValue,
8025
+ thisContext: this
8026
+ });
8027
+ });
8028
+ });
8029
+ } });
8030
+ else if (self.mode === TuskDriftMode.RECORD) {
8031
+ if (method.toUpperCase() === "OPTIONS" || !!req.headers["access-control-request-method"]) return originalHandleRequest.call(this, req, res, parsedUrl);
8032
+ if (!shouldSample({
8033
+ samplingRate: self.tuskDrift.getSamplingRate(),
8034
+ isAppReady: self.tuskDrift.isAppReady()
8035
+ })) {
8036
+ logger.debug(`[NextjsInstrumentation] Skipping server span due to sampling rate: ${url}`);
8037
+ return originalHandleRequest.call(this, req, res, parsedUrl);
8038
+ }
8039
+ logger.debug(`[NextjsInstrumentation] Creating server span for ${method} ${url}`);
8040
+ return handleRecordMode({
8041
+ originalFunctionCall: () => originalHandleRequest.call(this, req, res, parsedUrl),
8042
+ recordModeHandler: ({ isPreAppStart }) => {
8043
+ const inputValue = {
8044
+ method,
8045
+ url,
8046
+ target: url,
8047
+ headers: self._normalizeHeaders(req.headers || {})
8048
+ };
8049
+ return SpanUtils.createAndExecuteSpan(self.mode, () => originalHandleRequest.call(this, req, res, parsedUrl), {
8050
+ name: url,
8051
+ kind: SpanKind.SERVER,
8052
+ packageName: "nextjs",
8053
+ packageType: PackageType.HTTP,
8054
+ submodule: method,
8055
+ instrumentationName: self.INSTRUMENTATION_NAME,
8056
+ inputValue,
8057
+ inputSchemaMerges: { headers: { matchImportance: 0 } },
8058
+ isPreAppStart
8059
+ }, (spanInfo) => {
8060
+ return self._handleNextjsRequestInSpan({
8061
+ req,
8062
+ res,
8063
+ parsedUrl,
8064
+ originalHandleRequest,
8065
+ spanInfo,
8066
+ inputValue,
8067
+ thisContext: this
8068
+ });
8069
+ });
8070
+ },
8071
+ spanKind: SpanKind.SERVER
8072
+ });
8073
+ } else return originalHandleRequest.call(this, req, res, parsedUrl);
8074
+ };
8075
+ };
8076
+ }
8077
+ async _handleNextjsRequestInSpan({ req, res, parsedUrl, originalHandleRequest, spanInfo, inputValue, thisContext }) {
8078
+ const self = this;
8079
+ context.bind(spanInfo.context, req);
8080
+ context.bind(spanInfo.context, res);
8081
+ let completeInputValue = inputValue;
8082
+ this._captureRequestBody(req, spanInfo, inputValue, (updatedInputValue) => {
8083
+ completeInputValue = updatedInputValue;
8084
+ });
8085
+ let capturedStatusCode;
8086
+ let capturedStatusMessage;
8087
+ let capturedHeaders = {};
8088
+ const responseChunks = [];
8089
+ const actualRes = res.originalResponse || res;
8090
+ const originalWriteHead = actualRes.writeHead?.bind(actualRes);
8091
+ if (originalWriteHead) actualRes.writeHead = function(statusCode, statusMessage, headers) {
8092
+ capturedStatusCode = statusCode;
8093
+ if (typeof statusMessage === "object") capturedHeaders = self._normalizeHeaders(statusMessage);
8094
+ else {
8095
+ capturedStatusMessage = statusMessage;
8096
+ if (headers) capturedHeaders = self._normalizeHeaders(headers);
8097
+ }
8098
+ return originalWriteHead.call(this, statusCode, statusMessage, headers);
8099
+ };
8100
+ const originalSetHeader = actualRes.setHeader?.bind(actualRes);
8101
+ if (originalSetHeader) actualRes.setHeader = function(name, value) {
8102
+ capturedHeaders[name.toLowerCase()] = Array.isArray(value) ? value.join(", ") : value;
8103
+ return originalSetHeader.call(this, name, value);
8104
+ };
8105
+ const originalWrite = actualRes.write?.bind(actualRes);
8106
+ if (originalWrite) actualRes.write = function(chunk, encoding, callback) {
8107
+ if (chunk) responseChunks.push(chunk);
8108
+ return originalWrite.call(this, chunk, encoding, callback);
8109
+ };
8110
+ const originalEnd = actualRes.end?.bind(actualRes);
8111
+ if (originalEnd) actualRes.end = function(chunk, encoding, callback) {
8112
+ if (chunk) responseChunks.push(chunk);
8113
+ return originalEnd.call(this, chunk, encoding, callback);
8114
+ };
8115
+ try {
8116
+ await originalHandleRequest.call(thisContext, req, res, parsedUrl);
8117
+ if (!capturedStatusCode) {
8118
+ capturedStatusCode = res.statusCode;
8119
+ capturedStatusMessage = res.statusMessage;
8120
+ }
8121
+ if (Object.keys(capturedHeaders).length === 0 && res.getHeaders) {
8122
+ const rawHeaders = res.getHeaders();
8123
+ capturedHeaders = self._normalizeHeaders(rawHeaders);
8124
+ }
8125
+ logger.debug(`[NextjsInstrumentation] Next.js request completed: ${capturedStatusCode} (${SpanUtils.getTraceInfo()})`);
8126
+ const outputValue = {
8127
+ statusCode: capturedStatusCode,
8128
+ statusMessage: capturedStatusMessage,
8129
+ headers: capturedHeaders
8130
+ };
8131
+ if (responseChunks.length > 0) try {
8132
+ const responseBuffer = combineChunks(responseChunks);
8133
+ const contentEncoding = outputValue.headers?.["content-encoding"];
8134
+ outputValue.body = await httpBodyEncoder({
8135
+ bodyBuffer: responseBuffer,
8136
+ contentEncoding
8137
+ });
8138
+ outputValue.bodySize = responseBuffer.length;
8139
+ } catch (error) {
8140
+ logger.error(`[NextjsInstrumentation] Error processing response body:`, error);
8141
+ outputValue.bodyProcessingError = error instanceof Error ? error.message : String(error);
8142
+ }
8143
+ SpanUtils.addSpanAttributes(spanInfo.span, {
8144
+ inputValue: completeInputValue,
8145
+ outputValue,
8146
+ outputSchemaMerges: {
8147
+ body: {
8148
+ encoding: EncodingType.BASE64,
8149
+ decodedType: getDecodedType(outputValue.headers?.["content-type"] || "")
8150
+ },
8151
+ headers: { matchImportance: 0 }
8152
+ },
8153
+ metadata: { ENV_VARS: EnvVarTracker.getEnvVars(spanInfo.traceId) }
8154
+ });
8155
+ EnvVarTracker.clearEnvVars(spanInfo.traceId);
8156
+ const status = (capturedStatusCode || 200) >= 400 ? {
8157
+ code: SpanStatusCode.ERROR,
8158
+ message: `HTTP ${capturedStatusCode}`
8159
+ } : { code: SpanStatusCode.OK };
8160
+ SpanUtils.setStatus(spanInfo.span, status);
8161
+ SpanUtils.endSpan(spanInfo.span);
8162
+ if (self.mode === TuskDriftMode.REPLAY) try {
8163
+ const now = OriginalGlobalUtils.getOriginalDate();
8164
+ const replayTraceId = SpanUtils.getCurrentReplayTraceId() || spanInfo.traceId;
8165
+ const { schema: inputSchema, decodedValueHash: inputValueHash } = JsonSchemaHelper.generateSchemaAndHash(completeInputValue, {
8166
+ body: {
8167
+ encoding: EncodingType.BASE64,
8168
+ decodedType: getDecodedType(completeInputValue.headers && completeInputValue.headers["content-type"] || "")
8169
+ },
8170
+ headers: { matchImportance: 0 }
8171
+ });
8172
+ const { schema: outputSchema, decodedValueHash: outputValueHash } = JsonSchemaHelper.generateSchemaAndHash(outputValue, {
8173
+ body: {
8174
+ encoding: EncodingType.BASE64,
8175
+ decodedType: getDecodedType(outputValue.headers["content-type"] || "")
8176
+ },
8177
+ headers: { matchImportance: 0 }
8178
+ });
8179
+ const cleanSpan = {
8180
+ traceId: replayTraceId,
8181
+ spanId: spanInfo.spanId,
8182
+ parentSpanId: "",
8183
+ name: completeInputValue.url,
8184
+ packageName: "nextjs",
8185
+ instrumentationName: self.INSTRUMENTATION_NAME,
8186
+ submoduleName: completeInputValue.method,
8187
+ inputValue: completeInputValue,
8188
+ outputValue,
8189
+ inputSchema,
8190
+ outputSchema,
8191
+ inputSchemaHash: JsonSchemaHelper.generateDeterministicHash(inputSchema),
8192
+ outputSchemaHash: JsonSchemaHelper.generateDeterministicHash(outputSchema),
8193
+ inputValueHash,
8194
+ outputValueHash,
8195
+ kind: SpanKind.SERVER,
8196
+ packageType: PackageType.HTTP,
8197
+ status: {
8198
+ code: (capturedStatusCode || 200) >= 400 ? StatusCode.ERROR : StatusCode.OK,
8199
+ message: (capturedStatusCode || 200) >= 400 ? `HTTP ${capturedStatusCode}` : ""
8200
+ },
8201
+ timestamp: {
8202
+ seconds: Math.floor(now.getTime() / 1e3),
8203
+ nanos: now.getTime() % 1e3 * 1e6
8204
+ },
8205
+ duration: {
8206
+ seconds: 0,
8207
+ nanos: 0
8208
+ },
8209
+ isRootSpan: true,
8210
+ isPreAppStart: false,
8211
+ metadata: void 0
8212
+ };
8213
+ await self.tuskDrift.sendInboundSpanForReplay(cleanSpan);
8214
+ } catch (e) {
8215
+ logger.error("[NextjsInstrumentation] Failed to build/send inbound replay span:", e);
8216
+ }
8217
+ } catch (error) {
8218
+ logger.error(`[NextjsInstrumentation] Error in Next.js request: ${error instanceof Error ? error.message : String(error)}`);
8219
+ SpanUtils.endSpan(spanInfo.span, {
8220
+ code: SpanStatusCode.ERROR,
8221
+ message: error instanceof Error ? error.message : String(error)
8222
+ });
8223
+ throw error;
8224
+ }
8225
+ }
8226
+ /**
8227
+ * Captures the request body from an IncomingMessage stream by patching req.read() and listening for data events.
8228
+ * Similar to HTTP instrumentation's request body capture, but adapted for Next.js.
8229
+ */
8230
+ _captureRequestBody(req, spanInfo, inputValue, onBodyCaptured) {
8231
+ const actualReq = req.originalRequest || req._req || req;
8232
+ const requestBodyChunks = [];
8233
+ let streamConsumptionMode = "NOT_CONSUMING";
8234
+ const originalRead = actualReq.read?.bind(actualReq);
8235
+ if (originalRead) actualReq.read = function read(size) {
8236
+ const chunk = originalRead(size);
8237
+ if (chunk && (streamConsumptionMode === "READ" || streamConsumptionMode === "NOT_CONSUMING")) {
8238
+ streamConsumptionMode = "READ";
8239
+ requestBodyChunks.push(Buffer.from(chunk));
8240
+ }
8241
+ return chunk;
8242
+ };
8243
+ if (typeof actualReq.once !== "function" || typeof actualReq.addListener !== "function") {
8244
+ logger.debug(`[NextjsInstrumentation] Request object doesn't have event emitter methods, skipping body capture`);
8245
+ return;
8246
+ }
8247
+ actualReq.once("resume", () => {
8248
+ actualReq.addListener("data", (chunk) => {
8249
+ if (chunk && (streamConsumptionMode === "PIPE" || streamConsumptionMode === "NOT_CONSUMING")) {
8250
+ streamConsumptionMode = "PIPE";
8251
+ requestBodyChunks.push(Buffer.from(chunk));
8252
+ }
8253
+ });
8254
+ });
8255
+ actualReq.addListener("end", async (chunk) => {
8256
+ if (chunk) requestBodyChunks.push(Buffer.from(chunk));
8257
+ if (requestBodyChunks.length > 0) try {
8258
+ const bodyBuffer = Buffer.concat(requestBodyChunks);
8259
+ const encodedBody = await httpBodyEncoder({
8260
+ bodyBuffer,
8261
+ contentEncoding: actualReq.headers["content-encoding"]
8262
+ });
8263
+ const updatedInputValue = {
8264
+ ...inputValue,
8265
+ body: encodedBody,
8266
+ bodySize: bodyBuffer.length
8267
+ };
8268
+ if (onBodyCaptured) onBodyCaptured(updatedInputValue);
8269
+ SpanUtils.addSpanAttributes(spanInfo.span, {
8270
+ inputValue: updatedInputValue,
8271
+ inputSchemaMerges: {
8272
+ body: {
8273
+ encoding: EncodingType.BASE64,
8274
+ decodedType: getDecodedType(actualReq.headers["content-type"] || "")
8275
+ },
8276
+ headers: { matchImportance: 0 }
8277
+ }
8278
+ });
8279
+ logger.debug(`[NextjsInstrumentation] Captured request body for ${actualReq.method} ${actualReq.url}: ${bodyBuffer.length} bytes`);
8280
+ } catch (error) {
8281
+ logger.error(`[NextjsInstrumentation] Error processing request body:`, error);
8282
+ }
8283
+ });
8284
+ }
8285
+ _normalizeHeaders(headers) {
8286
+ const normalized = {};
8287
+ for (const [key, value] of Object.entries(headers)) if (value !== void 0) normalized[key.toLowerCase()] = Array.isArray(value) ? value.join(", ") : String(value);
8288
+ return normalized;
8289
+ }
8290
+ _wrap(target, propertyName, wrapper) {
8291
+ wrap(target, propertyName, wrapper);
8292
+ }
8293
+ };
8294
+
7524
8295
  //#endregion
7525
8296
  //#region node_modules/@opentelemetry/core/build/src/trace/suppress-tracing.js
7526
8297
  var require_suppress_tracing = /* @__PURE__ */ __commonJS({ "node_modules/@opentelemetry/core/build/src/trace/suppress-tracing.js": ((exports) => {
@@ -9697,7 +10468,7 @@ var SpanTransformer = class SpanTransformer {
9697
10468
  if (transformMetadataString) try {
9698
10469
  transformMetadata = JSON.parse(transformMetadataString);
9699
10470
  } catch (error) {
9700
- logger.warn("Failed to parse transform metadata", error);
10471
+ logger.warn("[SpanTransformer] Failed to parse transform metadata", error);
9701
10472
  }
9702
10473
  const originalDate = OriginalGlobalUtils.getOriginalDate();
9703
10474
  return {
@@ -9957,7 +10728,18 @@ var TdSpanExporter = class {
9957
10728
  */
9958
10729
  export(spans, resultCallback) {
9959
10730
  logger.debug(`TdSpanExporter.export() called with ${spans.length} span(s)`);
9960
- const cleanSpans = spans.map((span) => SpanTransformer.transformSpanToCleanJSON(span));
10731
+ const filteredSpans = spans.filter((span) => {
10732
+ if (span.instrumentationLibrary?.name === "next.js") return false;
10733
+ return true;
10734
+ });
10735
+ logger.debug(`After filtering: ${filteredSpans.length} span(s) remaining`);
10736
+ let cleanSpans;
10737
+ try {
10738
+ cleanSpans = filteredSpans.map((span) => SpanTransformer.transformSpanToCleanJSON(span));
10739
+ } catch (error) {
10740
+ logger.error("Error transforming spans to CleanSpanData", error);
10741
+ throw error;
10742
+ }
9961
10743
  if (this.adapters.length === 0) {
9962
10744
  logger.debug("No adapters configured");
9963
10745
  resultCallback({ code: import_src.ExportResultCode.SUCCESS });
@@ -10072,7 +10854,7 @@ var ProtobufCommunicator = class {
10072
10854
  requestId,
10073
10855
  tags: {},
10074
10856
  outboundSpan: cleanSpan,
10075
- stackTrace: this.getStackTrace()
10857
+ stackTrace: cleanSpan?.stackTrace
10076
10858
  });
10077
10859
  const sdkMessage = SDKMessage.create({
10078
10860
  type: MessageType.MOCK_REQUEST,
@@ -10113,7 +10895,7 @@ var ProtobufCommunicator = class {
10113
10895
  requestId,
10114
10896
  tags: {},
10115
10897
  outboundSpan: cleanSpan,
10116
- stackTrace: this.getStackTrace()
10898
+ stackTrace: cleanSpan?.stackTrace
10117
10899
  });
10118
10900
  const sdkMessage = SDKMessage.create({
10119
10901
  type: MessageType.MOCK_REQUEST,
@@ -10405,7 +11187,7 @@ var TuskDriftCore = class TuskDriftCore {
10405
11187
  const packageName = this.getPackageName(modulePath);
10406
11188
  if (packageName && TuskDriftInstrumentationModuleNames.includes(packageName)) alreadyRequiredModuleNames.add(packageName);
10407
11189
  }
10408
- } else logger.warn("Running in ES Module mode. Cannot detect pre-loaded instrumentation modules.");
11190
+ } else logger.debug("Running in ES Module mode. Cannot detect pre-loaded instrumentation modules.");
10409
11191
  return alreadyRequiredModuleNames;
10410
11192
  }
10411
11193
  static getInstance() {
@@ -10477,6 +11259,10 @@ var TuskDriftCore = class TuskDriftCore {
10477
11259
  enabled: true,
10478
11260
  mode: this.mode
10479
11261
  });
11262
+ new NextjsInstrumentation({
11263
+ enabled: true,
11264
+ mode: this.mode
11265
+ });
10480
11266
  }
10481
11267
  initializeTracing({ baseDirectory }) {
10482
11268
  const serviceName = this.config.service?.name || "unknown";
@@ -10506,17 +11292,17 @@ var TuskDriftCore = class TuskDriftCore {
10506
11292
  return `sdk-${OriginalGlobalUtils.getOriginalDate().getTime()}-${Math.random().toString(36).substr(2, 9)}`;
10507
11293
  }
10508
11294
  initialize(initParams) {
11295
+ initializeGlobalLogger({
11296
+ logLevel: initParams.logLevel || "info",
11297
+ prefix: "TuskDrift"
11298
+ });
10509
11299
  this.samplingRate = this.config.recording?.sampling_rate ?? 1;
10510
11300
  this.initParams = initParams;
10511
11301
  if (!this.initParams.env) {
10512
- const nodeEnv = OriginalGlobalUtils.getOriginalProcessEnvVar("NODE_ENV") || "unknown";
11302
+ const nodeEnv = OriginalGlobalUtils.getOriginalProcessEnvVar("NODE_ENV") || "development";
10513
11303
  logger.warn(`Environment not provided in initialization parameters. Using '${nodeEnv}' as the environment.`);
10514
11304
  this.initParams.env = nodeEnv;
10515
11305
  }
10516
- initializeGlobalLogger({
10517
- logLevel: initParams.logLevel || "silent",
10518
- prefix: "TuskDrift"
10519
- });
10520
11306
  if (this.initialized) {
10521
11307
  logger.debug("Already initialized, skipping...");
10522
11308
  return;
@@ -10591,8 +11377,8 @@ var TuskDriftCore = class TuskDriftCore {
10591
11377
  }
10592
11378
  this.appReady = true;
10593
11379
  logger.debug("Application marked as ready");
10594
- if (this.mode === TuskDriftMode.REPLAY) logger.info("Replay mode active - ready to serve mocked responses");
10595
- else if (this.mode === TuskDriftMode.RECORD) logger.info("Record mode active - capturing requests and responses");
11380
+ if (this.mode === TuskDriftMode.REPLAY) logger.debug("Replay mode active - ready to serve mocked responses");
11381
+ else if (this.mode === TuskDriftMode.RECORD) logger.debug("Record mode active - capturing inbound requests and responses");
10596
11382
  }
10597
11383
  async sendInboundSpanForReplay(span) {
10598
11384
  try {
@@ -10712,5 +11498,5 @@ var TuskDriftSDK = class {
10712
11498
  const TuskDrift = new TuskDriftSDK();
10713
11499
 
10714
11500
  //#endregion
10715
- export { TuskDrift };
11501
+ export { TuskDrift, withTuskDrift };
10716
11502
  //# sourceMappingURL=index.js.map