@use-tusk/drift-node-sdk 0.1.11 → 0.1.13
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/README.md +1 -0
- package/dist/index.cjs +608 -231
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +609 -232
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -25,7 +25,7 @@ import { SpanExportServiceClient } from "@use-tusk/drift-schemas/backend/span_ex
|
|
|
25
25
|
import { TwirpFetchTransport } from "@protobuf-ts/twirp-transport";
|
|
26
26
|
import { BatchSpanProcessor, NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
|
|
27
27
|
import { execSync } from "child_process";
|
|
28
|
-
import { CLIMessage, ConnectRequest, GetMockRequest, MessageType, SDKMessage, SendInboundSpanForReplayRequest } from "@use-tusk/drift-schemas/core/communication";
|
|
28
|
+
import { CLIMessage, ConnectRequest, EnvVarRequest, GetMockRequest, InstrumentationVersionMismatchAlert, MessageType, SDKMessage, SendAlertRequest, SendInboundSpanForReplayRequest, UnpatchedDependencyAlert } from "@use-tusk/drift-schemas/core/communication";
|
|
29
29
|
import { Resource } from "@opentelemetry/resources";
|
|
30
30
|
|
|
31
31
|
//#region rolldown:runtime
|
|
@@ -254,15 +254,6 @@ var TdInstrumentationAbstract = class {
|
|
|
254
254
|
}
|
|
255
255
|
};
|
|
256
256
|
|
|
257
|
-
//#endregion
|
|
258
|
-
//#region package.json
|
|
259
|
-
var version = "0.1.11";
|
|
260
|
-
|
|
261
|
-
//#endregion
|
|
262
|
-
//#region src/version.ts
|
|
263
|
-
const SDK_VERSION = version;
|
|
264
|
-
const MIN_CLI_VERSION = "0.1.0";
|
|
265
|
-
|
|
266
257
|
//#endregion
|
|
267
258
|
//#region src/core/utils/dataNormalizationUtils.ts
|
|
268
259
|
/**
|
|
@@ -542,51 +533,40 @@ const logger = {
|
|
|
542
533
|
|
|
543
534
|
//#endregion
|
|
544
535
|
//#region src/core/analytics/analyticsUtils.ts
|
|
545
|
-
function sendAnalyticsPayload(payload) {
|
|
546
|
-
try {
|
|
547
|
-
if (TuskDriftCore.getInstance().getConfig().recording?.enable_analytics) {}
|
|
548
|
-
} catch (e) {
|
|
549
|
-
logger.error("Error sending analytics event:", e);
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
function sendTdAnalytics(eventName, properties = {}) {
|
|
553
|
-
const serviceId = TuskDriftCore.getInstance().getConfig().service?.id || "unknown-service";
|
|
554
|
-
const payload = {
|
|
555
|
-
distinctId: `tusk-drift:${serviceId}`,
|
|
556
|
-
event: `${eventName}`,
|
|
557
|
-
properties: {
|
|
558
|
-
serviceId,
|
|
559
|
-
tdMode: TuskDriftCore.getInstance().getMode(),
|
|
560
|
-
sdkVersion: SDK_VERSION,
|
|
561
|
-
...properties
|
|
562
|
-
}
|
|
563
|
-
};
|
|
564
|
-
sendAnalyticsPayload(payload);
|
|
565
|
-
}
|
|
566
536
|
/**
|
|
567
|
-
*
|
|
537
|
+
* Send version mismatch alert to CLI (only in REPLAY mode)
|
|
568
538
|
*/
|
|
569
539
|
function sendVersionMismatchAlert({ moduleName, foundVersion, supportedVersions }) {
|
|
540
|
+
logger.info("Sending version mismatch alert", {
|
|
541
|
+
moduleName,
|
|
542
|
+
foundVersion,
|
|
543
|
+
supportedVersions
|
|
544
|
+
});
|
|
570
545
|
try {
|
|
571
|
-
|
|
546
|
+
if (TuskDriftCore.getInstance().getMode() !== TuskDriftMode.REPLAY) return;
|
|
547
|
+
const protobufComm = TuskDriftCore.getInstance().getProtobufCommunicator();
|
|
548
|
+
if (protobufComm) protobufComm.sendInstrumentationVersionMismatchAlert({
|
|
572
549
|
moduleName,
|
|
573
|
-
|
|
574
|
-
supportedVersions
|
|
550
|
+
requestedVersion: foundVersion,
|
|
551
|
+
supportedVersions
|
|
575
552
|
});
|
|
576
553
|
} catch (e) {
|
|
577
554
|
logger.error("Error sending version mismatch alert:", e);
|
|
578
555
|
}
|
|
579
556
|
}
|
|
580
557
|
/**
|
|
581
|
-
*
|
|
558
|
+
* Send unpatched dependency alert to CLI
|
|
582
559
|
*/
|
|
583
|
-
function sendUnpatchedDependencyAlert({
|
|
560
|
+
function sendUnpatchedDependencyAlert({ traceTestServerSpanId, stackTrace }) {
|
|
561
|
+
logger.info("Sending unpatched dependency alert", {
|
|
562
|
+
traceTestServerSpanId,
|
|
563
|
+
stackTrace
|
|
564
|
+
});
|
|
584
565
|
try {
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
stackTrace: stackTrace ? stackTrace.split("\n").slice(0, 10).join("\n") : void 0
|
|
566
|
+
const protobufComm = TuskDriftCore.getInstance().getProtobufCommunicator();
|
|
567
|
+
if (protobufComm && stackTrace) protobufComm.sendUnpatchedDependencyAlert({
|
|
568
|
+
stackTrace,
|
|
569
|
+
traceTestServerSpanId
|
|
590
570
|
});
|
|
591
571
|
} catch (e) {
|
|
592
572
|
logger.error("Error sending unpatched dependency alert:", e);
|
|
@@ -2017,9 +1997,12 @@ var HttpReplayHooks = class {
|
|
|
2017
1997
|
const traceIdHeader = req.headers["x-td-trace-id"] || req.headers["X-TD-TRACE-ID"];
|
|
2018
1998
|
return traceIdHeader ? String(traceIdHeader) : null;
|
|
2019
1999
|
}
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2000
|
+
/**
|
|
2001
|
+
* Check if we should fetch env vars from CLI
|
|
2002
|
+
*/
|
|
2003
|
+
extractShouldFetchEnvVars(req) {
|
|
2004
|
+
const fetchHeader = req.headers["x-td-fetch-env-vars"] || req.headers["X-TD-FETCH-ENV-VARS"];
|
|
2005
|
+
return fetchHeader === "true" || fetchHeader === true;
|
|
2023
2006
|
}
|
|
2024
2007
|
/**
|
|
2025
2008
|
* Handle outbound HTTP requests in replay mode
|
|
@@ -2038,7 +2021,7 @@ var HttpReplayHooks = class {
|
|
|
2038
2021
|
auth: requestOptions.auth || void 0,
|
|
2039
2022
|
agent: requestOptions.agent || void 0,
|
|
2040
2023
|
protocol,
|
|
2041
|
-
hostname: requestOptions.hostname || void 0,
|
|
2024
|
+
hostname: requestOptions.hostname || requestOptions.host || void 0,
|
|
2042
2025
|
port: requestOptions.port ? Number(requestOptions.port) : void 0,
|
|
2043
2026
|
method
|
|
2044
2027
|
};
|
|
@@ -2632,8 +2615,13 @@ var HttpInstrumentation = class extends TdInstrumentationBase {
|
|
|
2632
2615
|
return originalHandler.call(this);
|
|
2633
2616
|
}
|
|
2634
2617
|
logger.debug(`[HttpInstrumentation] Setting replay trace id`, replayTraceId);
|
|
2635
|
-
|
|
2636
|
-
|
|
2618
|
+
if (this.replayHooks.extractShouldFetchEnvVars(req)) try {
|
|
2619
|
+
const envVars = this.tuskDrift.requestEnvVarsSync(replayTraceId);
|
|
2620
|
+
EnvVarTracker.setEnvVars(replayTraceId, envVars);
|
|
2621
|
+
logger.debug(`[HttpInstrumentation] Fetched env vars from CLI for trace ${replayTraceId}`);
|
|
2622
|
+
} catch (error) {
|
|
2623
|
+
logger.error(`[HttpInstrumentation] Failed to fetch env vars from CLI:`, error);
|
|
2624
|
+
}
|
|
2637
2625
|
const ctxWithReplayTraceId = SpanUtils.setCurrentReplayTraceId(replayTraceId);
|
|
2638
2626
|
if (!ctxWithReplayTraceId) throw new Error("Error setting current replay trace id");
|
|
2639
2627
|
return context.with(ctxWithReplayTraceId, () => {
|
|
@@ -3007,13 +2995,29 @@ var HttpInstrumentation = class extends TdInstrumentationBase {
|
|
|
3007
2995
|
readable: res.readable
|
|
3008
2996
|
};
|
|
3009
2997
|
const responseChunks = [];
|
|
3010
|
-
|
|
2998
|
+
let streamConsumptionMode = "NOT_CONSUMING";
|
|
2999
|
+
const originalRead = res.read?.bind(res);
|
|
3000
|
+
if (originalRead) res.read = function read(size) {
|
|
3001
|
+
const chunk = originalRead(size);
|
|
3002
|
+
if (chunk && (streamConsumptionMode === "READ" || streamConsumptionMode === "NOT_CONSUMING")) {
|
|
3003
|
+
streamConsumptionMode = "READ";
|
|
3004
|
+
responseChunks.push(Buffer.from(chunk));
|
|
3005
|
+
}
|
|
3006
|
+
return chunk;
|
|
3007
|
+
};
|
|
3008
|
+
res.once("resume", () => {
|
|
3011
3009
|
res.on("data", (chunk) => {
|
|
3012
|
-
|
|
3010
|
+
if (chunk && (streamConsumptionMode === "PIPE" || streamConsumptionMode === "NOT_CONSUMING")) {
|
|
3011
|
+
streamConsumptionMode = "PIPE";
|
|
3012
|
+
responseChunks.push(Buffer.from(chunk));
|
|
3013
|
+
}
|
|
3013
3014
|
});
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3015
|
+
});
|
|
3016
|
+
res.on("end", async (chunk) => {
|
|
3017
|
+
if (chunk && typeof chunk !== "function") responseChunks.push(Buffer.from(chunk));
|
|
3018
|
+
try {
|
|
3019
|
+
if (responseChunks.length > 0) {
|
|
3020
|
+
const responseBuffer = Buffer.concat(responseChunks);
|
|
3017
3021
|
const rawHeaders = this._captureHeadersFromRawHeaders(res.rawHeaders);
|
|
3018
3022
|
outputValue.headers = rawHeaders;
|
|
3019
3023
|
const contentEncoding = rawHeaders["content-encoding"];
|
|
@@ -3022,40 +3026,24 @@ var HttpInstrumentation = class extends TdInstrumentationBase {
|
|
|
3022
3026
|
contentEncoding
|
|
3023
3027
|
});
|
|
3024
3028
|
outputValue.bodySize = responseBuffer.length;
|
|
3025
|
-
this._addOutputAttributesToSpan({
|
|
3026
|
-
spanInfo,
|
|
3027
|
-
outputValue,
|
|
3028
|
-
statusCode: res.statusCode || 1,
|
|
3029
|
-
outputSchemaMerges: {
|
|
3030
|
-
body: {
|
|
3031
|
-
encoding: EncodingType.BASE64,
|
|
3032
|
-
decodedType: getDecodedType(outputValue.headers["content-type"] || "")
|
|
3033
|
-
},
|
|
3034
|
-
headers: { matchImportance: 0 }
|
|
3035
|
-
},
|
|
3036
|
-
inputValue: completeInputValue
|
|
3037
|
-
});
|
|
3038
|
-
} catch (error) {
|
|
3039
|
-
logger.error(`[HttpInstrumentation] Error processing response body:`, error);
|
|
3040
3029
|
}
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3030
|
+
this._addOutputAttributesToSpan({
|
|
3031
|
+
spanInfo,
|
|
3032
|
+
outputValue,
|
|
3033
|
+
statusCode: res.statusCode || 1,
|
|
3034
|
+
outputSchemaMerges: {
|
|
3035
|
+
body: {
|
|
3036
|
+
encoding: EncodingType.BASE64,
|
|
3037
|
+
decodedType: getDecodedType(outputValue.headers["content-type"] || "")
|
|
3038
|
+
},
|
|
3039
|
+
headers: { matchImportance: 0 }
|
|
3051
3040
|
},
|
|
3052
|
-
|
|
3053
|
-
}
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
}
|
|
3041
|
+
inputValue: completeInputValue
|
|
3042
|
+
});
|
|
3043
|
+
} catch (error) {
|
|
3044
|
+
logger.error(`[HttpInstrumentation] Error processing response body:`, error);
|
|
3045
|
+
}
|
|
3046
|
+
});
|
|
3059
3047
|
});
|
|
3060
3048
|
req.on("error", (error) => {
|
|
3061
3049
|
try {
|
|
@@ -6167,12 +6155,13 @@ var TcpInstrumentation = class extends TdInstrumentationBase {
|
|
|
6167
6155
|
traceId: currentSpanInfo.traceId,
|
|
6168
6156
|
socketContext
|
|
6169
6157
|
});
|
|
6170
|
-
|
|
6158
|
+
const stackTrace = (/* @__PURE__ */ new Error()).stack || "";
|
|
6159
|
+
const traceTestServerSpanId = SpanUtils.getCurrentReplayTraceId();
|
|
6160
|
+
logger.warn(`[TcpInstrumentation] Full stack trace:\n${stackTrace}`, { traceTestServerSpanId });
|
|
6171
6161
|
Error.stackTraceLimit = 10;
|
|
6172
6162
|
sendUnpatchedDependencyAlert({
|
|
6173
|
-
|
|
6174
|
-
|
|
6175
|
-
traceId: currentSpanInfo.traceId
|
|
6163
|
+
traceTestServerSpanId: traceTestServerSpanId || "",
|
|
6164
|
+
stackTrace
|
|
6176
6165
|
});
|
|
6177
6166
|
if (this.loggedSpans.size > 1e3) {
|
|
6178
6167
|
logger.debug(`[TcpInstrumentation] Cleaning up logged spans cache (${this.loggedSpans.size} entries)`);
|
|
@@ -9611,8 +9600,13 @@ var NextjsInstrumentation = class extends TdInstrumentationBase {
|
|
|
9611
9600
|
return originalHandleRequest.call(this, req, res, parsedUrl);
|
|
9612
9601
|
}
|
|
9613
9602
|
logger.debug(`[NextjsInstrumentation] Setting replay trace id`, replayTraceId);
|
|
9614
|
-
|
|
9615
|
-
|
|
9603
|
+
if (self.replayHooks.extractShouldFetchEnvVars(req)) try {
|
|
9604
|
+
const envVars = self.tuskDrift.requestEnvVarsSync(replayTraceId);
|
|
9605
|
+
EnvVarTracker.setEnvVars(replayTraceId, envVars);
|
|
9606
|
+
logger.debug(`[NextjsInstrumentation] Fetched env vars from CLI for trace ${replayTraceId}`);
|
|
9607
|
+
} catch (error) {
|
|
9608
|
+
logger.error(`[NextjsInstrumentation] Failed to fetch env vars from CLI:`, error);
|
|
9609
|
+
}
|
|
9616
9610
|
const ctxWithReplayTraceId = SpanUtils.setCurrentReplayTraceId(replayTraceId);
|
|
9617
9611
|
if (!ctxWithReplayTraceId) throw new Error("Error setting current replay trace id");
|
|
9618
9612
|
return context.with(ctxWithReplayTraceId, () => {
|
|
@@ -9918,6 +9912,239 @@ var NextjsInstrumentation = class extends TdInstrumentationBase {
|
|
|
9918
9912
|
}
|
|
9919
9913
|
};
|
|
9920
9914
|
|
|
9915
|
+
//#endregion
|
|
9916
|
+
//#region src/instrumentation/libraries/prisma/types.ts
|
|
9917
|
+
/**
|
|
9918
|
+
* Prisma error class names for proper error handling
|
|
9919
|
+
*/
|
|
9920
|
+
let PrismaErrorClassName = /* @__PURE__ */ function(PrismaErrorClassName$1) {
|
|
9921
|
+
PrismaErrorClassName$1["PrismaClientKnownRequestError"] = "PrismaClientKnownRequestError";
|
|
9922
|
+
PrismaErrorClassName$1["PrismaClientUnknownRequestError"] = "PrismaClientUnknownRequestError";
|
|
9923
|
+
PrismaErrorClassName$1["PrismaClientInitializationError"] = "PrismaClientInitializationError";
|
|
9924
|
+
PrismaErrorClassName$1["PrismaClientValidationError"] = "PrismaClientValidationError";
|
|
9925
|
+
PrismaErrorClassName$1["PrismaClientRustPanicError"] = "PrismaClientRustPanicError";
|
|
9926
|
+
PrismaErrorClassName$1["NotFoundError"] = "NotFoundError";
|
|
9927
|
+
return PrismaErrorClassName$1;
|
|
9928
|
+
}({});
|
|
9929
|
+
|
|
9930
|
+
//#endregion
|
|
9931
|
+
//#region src/instrumentation/libraries/prisma/Instrumentation.ts
|
|
9932
|
+
var PrismaInstrumentation = class extends TdInstrumentationBase {
|
|
9933
|
+
constructor(config = {}) {
|
|
9934
|
+
super("@prisma/client", config);
|
|
9935
|
+
this.INSTRUMENTATION_NAME = "PrismaInstrumentation";
|
|
9936
|
+
this.prismaErrorClasses = [];
|
|
9937
|
+
this.mode = config.mode || TuskDriftMode.DISABLED;
|
|
9938
|
+
this.tuskDrift = TuskDriftCore.getInstance();
|
|
9939
|
+
}
|
|
9940
|
+
init() {
|
|
9941
|
+
return [new TdInstrumentationNodeModule({
|
|
9942
|
+
name: "@prisma/client",
|
|
9943
|
+
supportedVersions: ["5.*", "6.*"],
|
|
9944
|
+
patch: (moduleExports) => this._patchPrismaModule(moduleExports)
|
|
9945
|
+
})];
|
|
9946
|
+
}
|
|
9947
|
+
_patchPrismaModule(prismaModule) {
|
|
9948
|
+
if (this.isModulePatched(prismaModule)) {
|
|
9949
|
+
logger.debug(`[PrismaInstrumentation] Prisma module already patched, skipping`);
|
|
9950
|
+
return prismaModule;
|
|
9951
|
+
}
|
|
9952
|
+
this._storePrismaErrorClasses(prismaModule);
|
|
9953
|
+
logger.debug(`[PrismaInstrumentation] Wrapping PrismaClient constructor`);
|
|
9954
|
+
this._wrap(prismaModule, "PrismaClient", (OriginalPrismaClient) => {
|
|
9955
|
+
const self = this;
|
|
9956
|
+
logger.debug(`[PrismaInstrumentation] PrismaClient wrapper called`);
|
|
9957
|
+
return class TdPrismaClient {
|
|
9958
|
+
constructor(...args) {
|
|
9959
|
+
logger.debug(`[PrismaInstrumentation] Creating patched PrismaClient instance`);
|
|
9960
|
+
return new OriginalPrismaClient(...args).$extends({ query: { async $allOperations({ model, operation, args: operationArgs, query }) {
|
|
9961
|
+
logger.debug(`[PrismaInstrumentation] $allOperations intercepted: ${model}.${operation}`);
|
|
9962
|
+
return self._handlePrismaOperation({
|
|
9963
|
+
model,
|
|
9964
|
+
operation,
|
|
9965
|
+
args: operationArgs,
|
|
9966
|
+
query
|
|
9967
|
+
});
|
|
9968
|
+
} } });
|
|
9969
|
+
}
|
|
9970
|
+
};
|
|
9971
|
+
});
|
|
9972
|
+
this.markModuleAsPatched(prismaModule);
|
|
9973
|
+
logger.debug(`[PrismaInstrumentation] Prisma module patching complete`);
|
|
9974
|
+
return prismaModule;
|
|
9975
|
+
}
|
|
9976
|
+
_storePrismaErrorClasses(moduleExports) {
|
|
9977
|
+
const prismaNamespace = moduleExports.Prisma || {};
|
|
9978
|
+
this.prismaErrorClasses = [
|
|
9979
|
+
{
|
|
9980
|
+
name: PrismaErrorClassName.PrismaClientKnownRequestError,
|
|
9981
|
+
errorClass: moduleExports.PrismaClientKnownRequestError || prismaNamespace.PrismaClientKnownRequestError
|
|
9982
|
+
},
|
|
9983
|
+
{
|
|
9984
|
+
name: PrismaErrorClassName.PrismaClientUnknownRequestError,
|
|
9985
|
+
errorClass: moduleExports.PrismaClientUnknownRequestError || prismaNamespace.PrismaClientUnknownRequestError
|
|
9986
|
+
},
|
|
9987
|
+
{
|
|
9988
|
+
name: PrismaErrorClassName.PrismaClientInitializationError,
|
|
9989
|
+
errorClass: moduleExports.PrismaClientInitializationError || prismaNamespace.PrismaClientInitializationError
|
|
9990
|
+
},
|
|
9991
|
+
{
|
|
9992
|
+
name: PrismaErrorClassName.PrismaClientValidationError,
|
|
9993
|
+
errorClass: moduleExports.PrismaClientValidationError || prismaNamespace.PrismaClientValidationError
|
|
9994
|
+
},
|
|
9995
|
+
{
|
|
9996
|
+
name: PrismaErrorClassName.PrismaClientRustPanicError,
|
|
9997
|
+
errorClass: moduleExports.PrismaClientRustPanicError || prismaNamespace.PrismaClientRustPanicError
|
|
9998
|
+
},
|
|
9999
|
+
{
|
|
10000
|
+
name: PrismaErrorClassName.NotFoundError,
|
|
10001
|
+
errorClass: moduleExports.NotFoundError || prismaNamespace.NotFoundError
|
|
10002
|
+
}
|
|
10003
|
+
];
|
|
10004
|
+
}
|
|
10005
|
+
_handlePrismaOperation({ model, operation, args, query }) {
|
|
10006
|
+
const inputValue = {
|
|
10007
|
+
model,
|
|
10008
|
+
operation,
|
|
10009
|
+
args
|
|
10010
|
+
};
|
|
10011
|
+
logger.debug(`[PrismaInstrumentation] Intercepted Prisma operation: ${model}.${operation} in ${this.mode} mode`);
|
|
10012
|
+
if (this.mode === TuskDriftMode.RECORD) return handleRecordMode({
|
|
10013
|
+
originalFunctionCall: () => query(args),
|
|
10014
|
+
recordModeHandler: ({ isPreAppStart }) => {
|
|
10015
|
+
return SpanUtils.createAndExecuteSpan(this.mode, () => query(args), {
|
|
10016
|
+
name: `prisma.${operation}`,
|
|
10017
|
+
kind: SpanKind.CLIENT,
|
|
10018
|
+
submodule: model,
|
|
10019
|
+
packageType: PackageType.UNSPECIFIED,
|
|
10020
|
+
packageName: "@prisma/client",
|
|
10021
|
+
instrumentationName: this.INSTRUMENTATION_NAME,
|
|
10022
|
+
inputValue,
|
|
10023
|
+
isPreAppStart
|
|
10024
|
+
}, (spanInfo) => {
|
|
10025
|
+
return this._handleRecordPrismaOperation(spanInfo, query, args);
|
|
10026
|
+
});
|
|
10027
|
+
},
|
|
10028
|
+
spanKind: SpanKind.CLIENT
|
|
10029
|
+
});
|
|
10030
|
+
else if (this.mode === TuskDriftMode.REPLAY) {
|
|
10031
|
+
const stackTrace = captureStackTrace(["PrismaInstrumentation"]);
|
|
10032
|
+
return handleReplayMode({
|
|
10033
|
+
noOpRequestHandler: () => query(args),
|
|
10034
|
+
isServerRequest: false,
|
|
10035
|
+
replayModeHandler: () => {
|
|
10036
|
+
return SpanUtils.createAndExecuteSpan(this.mode, () => query(args), {
|
|
10037
|
+
name: `prisma.${operation}`,
|
|
10038
|
+
kind: SpanKind.CLIENT,
|
|
10039
|
+
submodule: model,
|
|
10040
|
+
packageType: PackageType.UNSPECIFIED,
|
|
10041
|
+
packageName: "@prisma/client",
|
|
10042
|
+
instrumentationName: this.INSTRUMENTATION_NAME,
|
|
10043
|
+
inputValue,
|
|
10044
|
+
isPreAppStart: false
|
|
10045
|
+
}, (spanInfo) => {
|
|
10046
|
+
return this._handleReplayPrismaOperation(spanInfo, inputValue, stackTrace);
|
|
10047
|
+
});
|
|
10048
|
+
}
|
|
10049
|
+
});
|
|
10050
|
+
} else return query(args);
|
|
10051
|
+
}
|
|
10052
|
+
async _handleRecordPrismaOperation(spanInfo, query, args) {
|
|
10053
|
+
try {
|
|
10054
|
+
logger.debug(`[PrismaInstrumentation] Recording Prisma operation`);
|
|
10055
|
+
const result = await query(args);
|
|
10056
|
+
const outputValue = {
|
|
10057
|
+
prismaResult: result,
|
|
10058
|
+
_tdOriginalFormat: "result"
|
|
10059
|
+
};
|
|
10060
|
+
try {
|
|
10061
|
+
SpanUtils.addSpanAttributes(spanInfo.span, { outputValue });
|
|
10062
|
+
SpanUtils.endSpan(spanInfo.span, { code: SpanStatusCode.OK });
|
|
10063
|
+
} catch (spanError) {
|
|
10064
|
+
logger.error(`[PrismaInstrumentation] error adding span attributes:`, spanError);
|
|
10065
|
+
}
|
|
10066
|
+
return result;
|
|
10067
|
+
} catch (error) {
|
|
10068
|
+
logger.debug(`[PrismaInstrumentation] Prisma operation error: ${error.message}`);
|
|
10069
|
+
try {
|
|
10070
|
+
const errorClassName = this._getPrismaErrorClassName(error);
|
|
10071
|
+
const errorWithClassName = this._cloneError(error);
|
|
10072
|
+
if (errorClassName) errorWithClassName.customTdName = errorClassName;
|
|
10073
|
+
const outputValue = {
|
|
10074
|
+
prismaResult: errorWithClassName,
|
|
10075
|
+
_tdOriginalFormat: "error"
|
|
10076
|
+
};
|
|
10077
|
+
SpanUtils.addSpanAttributes(spanInfo.span, { outputValue });
|
|
10078
|
+
SpanUtils.endSpan(spanInfo.span, {
|
|
10079
|
+
code: SpanStatusCode.ERROR,
|
|
10080
|
+
message: error.message
|
|
10081
|
+
});
|
|
10082
|
+
} catch (spanError) {
|
|
10083
|
+
logger.error(`[PrismaInstrumentation] error extracting error and adding span attributes:`, spanError);
|
|
10084
|
+
}
|
|
10085
|
+
throw error;
|
|
10086
|
+
}
|
|
10087
|
+
}
|
|
10088
|
+
async _handleReplayPrismaOperation(spanInfo, inputValue, stackTrace) {
|
|
10089
|
+
const mockData = await findMockResponseAsync({
|
|
10090
|
+
mockRequestData: {
|
|
10091
|
+
traceId: spanInfo.traceId,
|
|
10092
|
+
spanId: spanInfo.spanId,
|
|
10093
|
+
name: `prisma.${inputValue.operation}`,
|
|
10094
|
+
inputValue,
|
|
10095
|
+
packageName: "@prisma/client",
|
|
10096
|
+
instrumentationName: this.INSTRUMENTATION_NAME,
|
|
10097
|
+
submoduleName: inputValue.model,
|
|
10098
|
+
kind: SpanKind.CLIENT,
|
|
10099
|
+
stackTrace
|
|
10100
|
+
},
|
|
10101
|
+
tuskDrift: this.tuskDrift
|
|
10102
|
+
});
|
|
10103
|
+
if (!mockData) {
|
|
10104
|
+
logger.warn(`[PrismaInstrumentation] No mock data found for Prisma operation: ${inputValue.model}.${inputValue.operation}`);
|
|
10105
|
+
throw new Error(`[PrismaInstrumentation] No matching mock found for Prisma operation: ${inputValue.model}.${inputValue.operation}`);
|
|
10106
|
+
}
|
|
10107
|
+
logger.debug(`[PrismaInstrumentation] Found mock data for Prisma operation: ${inputValue.model}.${inputValue.operation}`);
|
|
10108
|
+
const outputValue = mockData.result;
|
|
10109
|
+
if (outputValue._tdOriginalFormat === "error") {
|
|
10110
|
+
const errorObj = outputValue.prismaResult;
|
|
10111
|
+
if (errorObj.customTdName) {
|
|
10112
|
+
const errorClass = this._getPrismaErrorClassFromName(errorObj.customTdName);
|
|
10113
|
+
if (errorClass) Object.setPrototypeOf(errorObj, errorClass.prototype);
|
|
10114
|
+
}
|
|
10115
|
+
SpanUtils.endSpan(spanInfo.span, {
|
|
10116
|
+
code: SpanStatusCode.ERROR,
|
|
10117
|
+
message: errorObj.message || "Prisma error"
|
|
10118
|
+
});
|
|
10119
|
+
throw errorObj;
|
|
10120
|
+
}
|
|
10121
|
+
SpanUtils.endSpan(spanInfo.span, { code: SpanStatusCode.OK });
|
|
10122
|
+
return outputValue.prismaResult;
|
|
10123
|
+
}
|
|
10124
|
+
_getPrismaErrorClassName(error) {
|
|
10125
|
+
for (const errorInfo of this.prismaErrorClasses) if (error instanceof errorInfo.errorClass) return errorInfo.name;
|
|
10126
|
+
}
|
|
10127
|
+
_getPrismaErrorClassFromName(className) {
|
|
10128
|
+
for (const errorInfo of this.prismaErrorClasses) if (errorInfo.name === className) return errorInfo.errorClass;
|
|
10129
|
+
return null;
|
|
10130
|
+
}
|
|
10131
|
+
/**
|
|
10132
|
+
* Deep clone an error object to make it serializable
|
|
10133
|
+
*/
|
|
10134
|
+
_cloneError(error) {
|
|
10135
|
+
const cloned = new Error(error.message);
|
|
10136
|
+
cloned.name = error.name;
|
|
10137
|
+
cloned.stack = error.stack;
|
|
10138
|
+
for (const key in error) if (error.hasOwnProperty(key)) try {
|
|
10139
|
+
cloned[key] = error[key];
|
|
10140
|
+
} catch (e) {}
|
|
10141
|
+
return cloned;
|
|
10142
|
+
}
|
|
10143
|
+
_wrap(target, propertyName, wrapper) {
|
|
10144
|
+
wrap(target, propertyName, wrapper);
|
|
10145
|
+
}
|
|
10146
|
+
};
|
|
10147
|
+
|
|
9921
10148
|
//#endregion
|
|
9922
10149
|
//#region node_modules/@opentelemetry/core/build/src/trace/suppress-tracing.js
|
|
9923
10150
|
var require_suppress_tracing = /* @__PURE__ */ __commonJS({ "node_modules/@opentelemetry/core/build/src/trace/suppress-tracing.js": ((exports) => {
|
|
@@ -12418,9 +12645,18 @@ var TdSpanExporter = class {
|
|
|
12418
12645
|
}
|
|
12419
12646
|
};
|
|
12420
12647
|
|
|
12648
|
+
//#endregion
|
|
12649
|
+
//#region package.json
|
|
12650
|
+
var version = "0.1.13";
|
|
12651
|
+
|
|
12652
|
+
//#endregion
|
|
12653
|
+
//#region src/version.ts
|
|
12654
|
+
const SDK_VERSION = version;
|
|
12655
|
+
const MIN_CLI_VERSION = "0.1.0";
|
|
12656
|
+
|
|
12421
12657
|
//#endregion
|
|
12422
12658
|
//#region src/core/ProtobufCommunicator.ts
|
|
12423
|
-
var ProtobufCommunicator = class {
|
|
12659
|
+
var ProtobufCommunicator = class ProtobufCommunicator {
|
|
12424
12660
|
constructor() {
|
|
12425
12661
|
this.client = null;
|
|
12426
12662
|
this.pendingRequests = /* @__PURE__ */ new Map();
|
|
@@ -12518,43 +12754,18 @@ var ProtobufCommunicator = class {
|
|
|
12518
12754
|
});
|
|
12519
12755
|
}
|
|
12520
12756
|
/**
|
|
12521
|
-
*
|
|
12522
|
-
*
|
|
12523
|
-
*
|
|
12524
|
-
*
|
|
12525
|
-
* Since this function blocks the main thread, there is a performance impact. We should use requestMockAsync whenever possible and only use this function
|
|
12526
|
-
* for instrumentations that require fetching mocks synchronously.
|
|
12757
|
+
* Generic synchronous request handler that spawns a child process.
|
|
12758
|
+
* @param sdkMessage The SDK message to send
|
|
12759
|
+
* @param filePrefix Prefix for temporary files (e.g., 'envvar', 'mock')
|
|
12760
|
+
* @param responseHandler Function to extract and return the desired response
|
|
12527
12761
|
*/
|
|
12528
|
-
|
|
12529
|
-
const requestId =
|
|
12530
|
-
const cleanSpan = mockRequest.outboundSpan ? this.cleanSpan(mockRequest.outboundSpan) : void 0;
|
|
12531
|
-
if (cleanSpan?.inputValue) cleanSpan.inputValue = objectToProtobufStruct(cleanSpan.inputValue);
|
|
12532
|
-
if (cleanSpan?.inputSchema) cleanSpan.inputSchema = objectToProtobufStruct(cleanSpan.inputSchema);
|
|
12533
|
-
if (cleanSpan?.kind) cleanSpan.kind = mapOtToPb(cleanSpan.kind);
|
|
12534
|
-
const protoMockRequest = GetMockRequest.create({
|
|
12535
|
-
...mockRequest,
|
|
12536
|
-
requestId,
|
|
12537
|
-
tags: {},
|
|
12538
|
-
outboundSpan: cleanSpan,
|
|
12539
|
-
stackTrace: cleanSpan?.stackTrace
|
|
12540
|
-
});
|
|
12541
|
-
const sdkMessage = SDKMessage.create({
|
|
12542
|
-
type: MessageType.MOCK_REQUEST,
|
|
12543
|
-
requestId,
|
|
12544
|
-
payload: {
|
|
12545
|
-
oneofKind: "getMockRequest",
|
|
12546
|
-
getMockRequest: protoMockRequest
|
|
12547
|
-
}
|
|
12548
|
-
});
|
|
12549
|
-
logger.debug("Sending protobuf request to CLI (sync)", {
|
|
12550
|
-
outboundSpan: mockRequest.outboundSpan,
|
|
12551
|
-
testId: mockRequest.testId
|
|
12552
|
-
});
|
|
12762
|
+
executeSyncRequest(sdkMessage, filePrefix, responseHandler) {
|
|
12763
|
+
const requestId = sdkMessage.requestId;
|
|
12553
12764
|
const messageBytes = SDKMessage.toBinary(sdkMessage);
|
|
12554
12765
|
const tempDir = os.tmpdir();
|
|
12555
|
-
const requestFile = path.join(tempDir, `tusk-sync-request-${requestId}.bin`);
|
|
12556
|
-
const responseFile = path.join(tempDir, `tusk-sync-response-${requestId}.bin`);
|
|
12557
|
-
const scriptFile = path.join(tempDir, `tusk-sync-script-${requestId}.js`);
|
|
12766
|
+
const requestFile = path.join(tempDir, `tusk-sync-${filePrefix}-request-${requestId}.bin`);
|
|
12767
|
+
const responseFile = path.join(tempDir, `tusk-sync-${filePrefix}-response-${requestId}.bin`);
|
|
12768
|
+
const scriptFile = path.join(tempDir, `tusk-sync-${filePrefix}-script-${requestId}.js`);
|
|
12558
12769
|
try {
|
|
12559
12770
|
const lengthBuffer = Buffer.allocUnsafe(4);
|
|
12560
12771
|
lengthBuffer.writeUInt32BE(messageBytes.length, 0);
|
|
@@ -12577,95 +12788,7 @@ var ProtobufCommunicator = class {
|
|
|
12577
12788
|
type: "unix",
|
|
12578
12789
|
path: path.join(os.tmpdir(), "tusk-connect.sock")
|
|
12579
12790
|
};
|
|
12580
|
-
fs.writeFileSync(scriptFile,
|
|
12581
|
-
const net = require('net');
|
|
12582
|
-
const fs = require('fs');
|
|
12583
|
-
|
|
12584
|
-
const requestFile = process.argv[2];
|
|
12585
|
-
const responseFile = process.argv[3];
|
|
12586
|
-
const config = JSON.parse(process.argv[4]);
|
|
12587
|
-
|
|
12588
|
-
let responseReceived = false;
|
|
12589
|
-
let timeoutId;
|
|
12590
|
-
|
|
12591
|
-
function cleanup(exitCode) {
|
|
12592
|
-
if (timeoutId) clearTimeout(timeoutId);
|
|
12593
|
-
process.exit(exitCode);
|
|
12594
|
-
}
|
|
12595
|
-
|
|
12596
|
-
try {
|
|
12597
|
-
// Read the request data
|
|
12598
|
-
const requestData = fs.readFileSync(requestFile);
|
|
12599
|
-
|
|
12600
|
-
// Create connection based on config
|
|
12601
|
-
const client = config.type === 'unix'
|
|
12602
|
-
? net.createConnection({ path: config.path })
|
|
12603
|
-
: net.createConnection({ host: config.host, port: config.port });
|
|
12604
|
-
|
|
12605
|
-
const incomingChunks = [];
|
|
12606
|
-
let incomingBuffer = Buffer.alloc(0);
|
|
12607
|
-
|
|
12608
|
-
// Set timeout
|
|
12609
|
-
timeoutId = setTimeout(() => {
|
|
12610
|
-
if (!responseReceived) {
|
|
12611
|
-
console.error('Timeout waiting for response');
|
|
12612
|
-
client.destroy();
|
|
12613
|
-
cleanup(1);
|
|
12614
|
-
}
|
|
12615
|
-
}, 10000);
|
|
12616
|
-
|
|
12617
|
-
client.on('connect', () => {
|
|
12618
|
-
// Send the request
|
|
12619
|
-
client.write(requestData);
|
|
12620
|
-
});
|
|
12621
|
-
|
|
12622
|
-
client.on('data', (data) => {
|
|
12623
|
-
incomingBuffer = Buffer.concat([incomingBuffer, data]);
|
|
12624
|
-
|
|
12625
|
-
// Try to parse complete message (4 byte length prefix + message)
|
|
12626
|
-
while (incomingBuffer.length >= 4) {
|
|
12627
|
-
const messageLength = incomingBuffer.readUInt32BE(0);
|
|
12628
|
-
|
|
12629
|
-
if (incomingBuffer.length < 4 + messageLength) {
|
|
12630
|
-
// Incomplete message, wait for more data
|
|
12631
|
-
break;
|
|
12632
|
-
}
|
|
12633
|
-
|
|
12634
|
-
// We have a complete message
|
|
12635
|
-
const messageData = incomingBuffer.slice(4, 4 + messageLength);
|
|
12636
|
-
incomingBuffer = incomingBuffer.slice(4 + messageLength);
|
|
12637
|
-
|
|
12638
|
-
// Write the complete response (including length prefix)
|
|
12639
|
-
const lengthPrefix = Buffer.allocUnsafe(4);
|
|
12640
|
-
lengthPrefix.writeUInt32BE(messageLength, 0);
|
|
12641
|
-
fs.writeFileSync(responseFile, Buffer.concat([lengthPrefix, messageData]));
|
|
12642
|
-
|
|
12643
|
-
responseReceived = true;
|
|
12644
|
-
client.destroy();
|
|
12645
|
-
cleanup(0);
|
|
12646
|
-
break;
|
|
12647
|
-
}
|
|
12648
|
-
});
|
|
12649
|
-
|
|
12650
|
-
client.on('error', (err) => {
|
|
12651
|
-
if (!responseReceived) {
|
|
12652
|
-
console.error('Connection error:', err.message);
|
|
12653
|
-
cleanup(1);
|
|
12654
|
-
}
|
|
12655
|
-
});
|
|
12656
|
-
|
|
12657
|
-
client.on('close', () => {
|
|
12658
|
-
if (!responseReceived) {
|
|
12659
|
-
console.error('Connection closed without response');
|
|
12660
|
-
cleanup(1);
|
|
12661
|
-
}
|
|
12662
|
-
});
|
|
12663
|
-
|
|
12664
|
-
} catch (err) {
|
|
12665
|
-
console.error('Script error:', err.message);
|
|
12666
|
-
cleanup(1);
|
|
12667
|
-
}
|
|
12668
|
-
`);
|
|
12791
|
+
fs.writeFileSync(scriptFile, ProtobufCommunicator.SYNC_CHILD_SCRIPT);
|
|
12669
12792
|
try {
|
|
12670
12793
|
execSync(`node "${scriptFile}" "${requestFile}" "${responseFile}" '${JSON.stringify(connectionConfig)}'`, {
|
|
12671
12794
|
timeout: 12e3,
|
|
@@ -12677,27 +12800,13 @@ try {
|
|
|
12677
12800
|
if (responseBuffer.length < 4 + responseLength) throw new Error("Invalid response: incomplete message");
|
|
12678
12801
|
const responseData = responseBuffer.slice(4, 4 + responseLength);
|
|
12679
12802
|
const cliMessage = CLIMessage.fromBinary(responseData);
|
|
12680
|
-
|
|
12681
|
-
const mockResponse = cliMessage.payload.getMockResponse;
|
|
12682
|
-
if (!mockResponse) throw new Error("No mock response received");
|
|
12683
|
-
if (mockResponse.found) try {
|
|
12684
|
-
return {
|
|
12685
|
-
found: true,
|
|
12686
|
-
response: this.extractResponseData(mockResponse)
|
|
12687
|
-
};
|
|
12688
|
-
} catch (error) {
|
|
12689
|
-
throw new Error(`Failed to extract response data: ${error}`);
|
|
12690
|
-
}
|
|
12691
|
-
else return {
|
|
12692
|
-
found: false,
|
|
12693
|
-
error: mockResponse.error || "Mock not found"
|
|
12694
|
-
};
|
|
12803
|
+
return responseHandler(cliMessage);
|
|
12695
12804
|
} catch (error) {
|
|
12696
|
-
logger.error(
|
|
12805
|
+
logger.error(`[ProtobufCommunicator] error in sync ${filePrefix} request child process:`, error);
|
|
12697
12806
|
throw error;
|
|
12698
12807
|
}
|
|
12699
12808
|
} catch (error) {
|
|
12700
|
-
throw new Error(`Sync request failed: ${error.message}`);
|
|
12809
|
+
throw new Error(`Sync ${filePrefix} request failed: ${error.message}`);
|
|
12701
12810
|
} finally {
|
|
12702
12811
|
try {
|
|
12703
12812
|
if (fs.existsSync(requestFile)) fs.unlinkSync(requestFile);
|
|
@@ -12716,6 +12825,86 @@ try {
|
|
|
12716
12825
|
}
|
|
12717
12826
|
}
|
|
12718
12827
|
}
|
|
12828
|
+
/**
|
|
12829
|
+
* Request environment variables from CLI synchronously using a child process.
|
|
12830
|
+
* This blocks the main thread, so it should be used carefully.
|
|
12831
|
+
* Similar to requestMockSync but for environment variables.
|
|
12832
|
+
*/
|
|
12833
|
+
requestEnvVarsSync(traceTestServerSpanId) {
|
|
12834
|
+
const requestId = this.generateRequestId();
|
|
12835
|
+
const envVarRequest = EnvVarRequest.create({ traceTestServerSpanId });
|
|
12836
|
+
const sdkMessage = SDKMessage.create({
|
|
12837
|
+
type: MessageType.ENV_VAR_REQUEST,
|
|
12838
|
+
requestId,
|
|
12839
|
+
payload: {
|
|
12840
|
+
oneofKind: "envVarRequest",
|
|
12841
|
+
envVarRequest
|
|
12842
|
+
}
|
|
12843
|
+
});
|
|
12844
|
+
logger.debug(`[ProtobufCommunicator] Requesting env vars (sync) for trace: ${traceTestServerSpanId}`);
|
|
12845
|
+
return this.executeSyncRequest(sdkMessage, "envvar", (cliMessage) => {
|
|
12846
|
+
if (cliMessage.payload.oneofKind !== "envVarResponse") throw new Error(`Unexpected response type: ${cliMessage.type}`);
|
|
12847
|
+
const envVarResponse = cliMessage.payload.envVarResponse;
|
|
12848
|
+
if (!envVarResponse) throw new Error("No env var response received");
|
|
12849
|
+
const envVars = {};
|
|
12850
|
+
if (envVarResponse.envVars) Object.entries(envVarResponse.envVars).forEach(([key, value]) => {
|
|
12851
|
+
envVars[key] = value;
|
|
12852
|
+
});
|
|
12853
|
+
logger.debug(`[ProtobufCommunicator] Received env vars (sync), count: ${Object.keys(envVars).length}`);
|
|
12854
|
+
return envVars;
|
|
12855
|
+
});
|
|
12856
|
+
}
|
|
12857
|
+
/**
|
|
12858
|
+
* This function uses a separate Node.js child process to communicate with the CLI over a socket.
|
|
12859
|
+
* The child process creates its own connection and event loop, allowing proper async socket handling.
|
|
12860
|
+
* The parent process blocks synchronously waiting for the child to complete.
|
|
12861
|
+
*
|
|
12862
|
+
* Since this function blocks the main thread, there is a performance impact. We should use requestMockAsync whenever possible and only use this function
|
|
12863
|
+
* for instrumentations that require fetching mocks synchronously.
|
|
12864
|
+
*/
|
|
12865
|
+
requestMockSync(mockRequest) {
|
|
12866
|
+
const requestId = this.generateRequestId();
|
|
12867
|
+
const cleanSpan = mockRequest.outboundSpan ? this.cleanSpan(mockRequest.outboundSpan) : void 0;
|
|
12868
|
+
if (cleanSpan?.inputValue) cleanSpan.inputValue = objectToProtobufStruct(cleanSpan.inputValue);
|
|
12869
|
+
if (cleanSpan?.inputSchema) cleanSpan.inputSchema = objectToProtobufStruct(cleanSpan.inputSchema);
|
|
12870
|
+
if (cleanSpan?.kind) cleanSpan.kind = mapOtToPb(cleanSpan.kind);
|
|
12871
|
+
const protoMockRequest = GetMockRequest.create({
|
|
12872
|
+
...mockRequest,
|
|
12873
|
+
requestId,
|
|
12874
|
+
tags: {},
|
|
12875
|
+
outboundSpan: cleanSpan,
|
|
12876
|
+
stackTrace: cleanSpan?.stackTrace
|
|
12877
|
+
});
|
|
12878
|
+
const sdkMessage = SDKMessage.create({
|
|
12879
|
+
type: MessageType.MOCK_REQUEST,
|
|
12880
|
+
requestId,
|
|
12881
|
+
payload: {
|
|
12882
|
+
oneofKind: "getMockRequest",
|
|
12883
|
+
getMockRequest: protoMockRequest
|
|
12884
|
+
}
|
|
12885
|
+
});
|
|
12886
|
+
logger.debug("Sending protobuf request to CLI (sync)", {
|
|
12887
|
+
outboundSpan: mockRequest.outboundSpan,
|
|
12888
|
+
testId: mockRequest.testId
|
|
12889
|
+
});
|
|
12890
|
+
return this.executeSyncRequest(sdkMessage, "mock", (cliMessage) => {
|
|
12891
|
+
if (cliMessage.payload.oneofKind !== "getMockResponse") throw new Error(`Unexpected response type: ${cliMessage.type}`);
|
|
12892
|
+
const mockResponse = cliMessage.payload.getMockResponse;
|
|
12893
|
+
if (!mockResponse) throw new Error("No mock response received");
|
|
12894
|
+
if (mockResponse.found) try {
|
|
12895
|
+
return {
|
|
12896
|
+
found: true,
|
|
12897
|
+
response: this.extractResponseData(mockResponse)
|
|
12898
|
+
};
|
|
12899
|
+
} catch (error) {
|
|
12900
|
+
throw new Error(`Failed to extract response data: ${error}`);
|
|
12901
|
+
}
|
|
12902
|
+
else return {
|
|
12903
|
+
found: false,
|
|
12904
|
+
error: mockResponse.error || "Mock not found"
|
|
12905
|
+
};
|
|
12906
|
+
});
|
|
12907
|
+
}
|
|
12719
12908
|
async sendProtobufMessage(message) {
|
|
12720
12909
|
if (!this.client || !this.protobufContext) throw new Error("Not connected to CLI");
|
|
12721
12910
|
const messageBytes = SDKMessage.toBinary(message);
|
|
@@ -12768,6 +12957,58 @@ try {
|
|
|
12768
12957
|
});
|
|
12769
12958
|
await this.sendProtobufMessage(sdkMessage);
|
|
12770
12959
|
}
|
|
12960
|
+
/**
|
|
12961
|
+
* Send an alert to the CLI (fire-and-forget, no response expected)
|
|
12962
|
+
*/
|
|
12963
|
+
async sendAlert(alert) {
|
|
12964
|
+
if (!this.client || !this.protobufContext) {
|
|
12965
|
+
logger.debug("[ProtobufCommunicator] Not connected to CLI, skipping alert");
|
|
12966
|
+
return;
|
|
12967
|
+
}
|
|
12968
|
+
const sdkMessage = SDKMessage.create({
|
|
12969
|
+
type: MessageType.ALERT,
|
|
12970
|
+
requestId: this.generateRequestId(),
|
|
12971
|
+
payload: {
|
|
12972
|
+
oneofKind: "sendAlertRequest",
|
|
12973
|
+
sendAlertRequest: alert
|
|
12974
|
+
}
|
|
12975
|
+
});
|
|
12976
|
+
try {
|
|
12977
|
+
await this.sendProtobufMessage(sdkMessage);
|
|
12978
|
+
logger.debug("[ProtobufCommunicator] Alert sent to CLI");
|
|
12979
|
+
} catch (error) {
|
|
12980
|
+
logger.debug("[ProtobufCommunicator] Failed to send alert to CLI:", error);
|
|
12981
|
+
}
|
|
12982
|
+
}
|
|
12983
|
+
/**
|
|
12984
|
+
* Send instrumentation version mismatch alert to CLI
|
|
12985
|
+
*/
|
|
12986
|
+
async sendInstrumentationVersionMismatchAlert(params) {
|
|
12987
|
+
const alert = SendAlertRequest.create({ alert: {
|
|
12988
|
+
oneofKind: "versionMismatch",
|
|
12989
|
+
versionMismatch: InstrumentationVersionMismatchAlert.create({
|
|
12990
|
+
moduleName: params.moduleName,
|
|
12991
|
+
requestedVersion: params.requestedVersion || "",
|
|
12992
|
+
supportedVersions: params.supportedVersions,
|
|
12993
|
+
sdkVersion: SDK_VERSION
|
|
12994
|
+
})
|
|
12995
|
+
} });
|
|
12996
|
+
await this.sendAlert(alert);
|
|
12997
|
+
}
|
|
12998
|
+
/**
|
|
12999
|
+
* Send unpatched dependency alert to CLI
|
|
13000
|
+
*/
|
|
13001
|
+
async sendUnpatchedDependencyAlert(params) {
|
|
13002
|
+
const alert = SendAlertRequest.create({ alert: {
|
|
13003
|
+
oneofKind: "unpatchedDependency",
|
|
13004
|
+
unpatchedDependency: UnpatchedDependencyAlert.create({
|
|
13005
|
+
stackTrace: params.stackTrace,
|
|
13006
|
+
traceTestServerSpanId: params.traceTestServerSpanId,
|
|
13007
|
+
sdkVersion: SDK_VERSION
|
|
13008
|
+
})
|
|
13009
|
+
} });
|
|
13010
|
+
await this.sendAlert(alert);
|
|
13011
|
+
}
|
|
12771
13012
|
handleIncomingData(data) {
|
|
12772
13013
|
this.incomingBuffer = Buffer.concat([this.incomingBuffer, data]);
|
|
12773
13014
|
logger.debug(`[ProtobufCommunicator] Processing buffer, length: ${this.incomingBuffer.length}`);
|
|
@@ -12825,6 +13066,22 @@ try {
|
|
|
12825
13066
|
error: mockResponse.error || "Mock not found"
|
|
12826
13067
|
});
|
|
12827
13068
|
}
|
|
13069
|
+
if (message.payload.oneofKind === "envVarResponse") {
|
|
13070
|
+
const envVarResponse = message.payload.envVarResponse;
|
|
13071
|
+
logger.debug(`[ProtobufCommunicator] Received env var response for requestId: ${requestId}`);
|
|
13072
|
+
const pendingRequest = this.pendingRequests.get(requestId);
|
|
13073
|
+
if (!pendingRequest) {
|
|
13074
|
+
logger.warn("[ProtobufCommunicator] received env var response for unknown request:", requestId);
|
|
13075
|
+
return;
|
|
13076
|
+
}
|
|
13077
|
+
this.pendingRequests.delete(requestId);
|
|
13078
|
+
const envVars = {};
|
|
13079
|
+
if (envVarResponse?.envVars) Object.entries(envVarResponse.envVars).forEach(([key, value]) => {
|
|
13080
|
+
envVars[key] = value;
|
|
13081
|
+
});
|
|
13082
|
+
pendingRequest.resolve(envVars);
|
|
13083
|
+
return;
|
|
13084
|
+
}
|
|
12828
13085
|
}
|
|
12829
13086
|
/**
|
|
12830
13087
|
* Extract response data from MockResponse
|
|
@@ -12867,6 +13124,95 @@ try {
|
|
|
12867
13124
|
}
|
|
12868
13125
|
}
|
|
12869
13126
|
};
|
|
13127
|
+
ProtobufCommunicator.SYNC_CHILD_SCRIPT = `
|
|
13128
|
+
const net = require('net');
|
|
13129
|
+
const fs = require('fs');
|
|
13130
|
+
|
|
13131
|
+
const requestFile = process.argv[2];
|
|
13132
|
+
const responseFile = process.argv[3];
|
|
13133
|
+
const config = JSON.parse(process.argv[4]);
|
|
13134
|
+
|
|
13135
|
+
let responseReceived = false;
|
|
13136
|
+
let timeoutId;
|
|
13137
|
+
|
|
13138
|
+
function cleanup(exitCode) {
|
|
13139
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
13140
|
+
process.exit(exitCode);
|
|
13141
|
+
}
|
|
13142
|
+
|
|
13143
|
+
try {
|
|
13144
|
+
// Read the request data
|
|
13145
|
+
const requestData = fs.readFileSync(requestFile);
|
|
13146
|
+
|
|
13147
|
+
// Create connection based on config
|
|
13148
|
+
const client = config.type === 'unix'
|
|
13149
|
+
? net.createConnection({ path: config.path })
|
|
13150
|
+
: net.createConnection({ host: config.host, port: config.port });
|
|
13151
|
+
|
|
13152
|
+
const incomingChunks = [];
|
|
13153
|
+
let incomingBuffer = Buffer.alloc(0);
|
|
13154
|
+
|
|
13155
|
+
// Set timeout
|
|
13156
|
+
timeoutId = setTimeout(() => {
|
|
13157
|
+
if (!responseReceived) {
|
|
13158
|
+
console.error('Timeout waiting for response');
|
|
13159
|
+
client.destroy();
|
|
13160
|
+
cleanup(1);
|
|
13161
|
+
}
|
|
13162
|
+
}, 10000);
|
|
13163
|
+
|
|
13164
|
+
client.on('connect', () => {
|
|
13165
|
+
// Send the request
|
|
13166
|
+
client.write(requestData);
|
|
13167
|
+
});
|
|
13168
|
+
|
|
13169
|
+
client.on('data', (data) => {
|
|
13170
|
+
incomingBuffer = Buffer.concat([incomingBuffer, data]);
|
|
13171
|
+
|
|
13172
|
+
// Try to parse complete message (4 byte length prefix + message)
|
|
13173
|
+
while (incomingBuffer.length >= 4) {
|
|
13174
|
+
const messageLength = incomingBuffer.readUInt32BE(0);
|
|
13175
|
+
|
|
13176
|
+
if (incomingBuffer.length < 4 + messageLength) {
|
|
13177
|
+
// Incomplete message, wait for more data
|
|
13178
|
+
break;
|
|
13179
|
+
}
|
|
13180
|
+
|
|
13181
|
+
// We have a complete message
|
|
13182
|
+
const messageData = incomingBuffer.slice(4, 4 + messageLength);
|
|
13183
|
+
incomingBuffer = incomingBuffer.slice(4 + messageLength);
|
|
13184
|
+
|
|
13185
|
+
// Write the complete response (including length prefix)
|
|
13186
|
+
const lengthPrefix = Buffer.allocUnsafe(4);
|
|
13187
|
+
lengthPrefix.writeUInt32BE(messageLength, 0);
|
|
13188
|
+
fs.writeFileSync(responseFile, Buffer.concat([lengthPrefix, messageData]));
|
|
13189
|
+
|
|
13190
|
+
responseReceived = true;
|
|
13191
|
+
client.destroy();
|
|
13192
|
+
cleanup(0);
|
|
13193
|
+
break;
|
|
13194
|
+
}
|
|
13195
|
+
});
|
|
13196
|
+
|
|
13197
|
+
client.on('error', (err) => {
|
|
13198
|
+
if (!responseReceived) {
|
|
13199
|
+
console.error('Connection error:', err.message);
|
|
13200
|
+
cleanup(1);
|
|
13201
|
+
}
|
|
13202
|
+
});
|
|
13203
|
+
|
|
13204
|
+
client.on('close', () => {
|
|
13205
|
+
if (!responseReceived) {
|
|
13206
|
+
console.error('Connection closed without response');
|
|
13207
|
+
cleanup(1);
|
|
13208
|
+
}
|
|
13209
|
+
});
|
|
13210
|
+
|
|
13211
|
+
} catch (err) {
|
|
13212
|
+
console.error('Script error:', err.message);
|
|
13213
|
+
cleanup(1);
|
|
13214
|
+
}
|
|
13215
|
+
`;
|
|
12870
13216
|
|
|
12871
13217
|
//#endregion
|
|
12872
13218
|
//#region src/core/TuskDriftInstrumentationModuleNames.ts
|
|
@@ -13047,6 +13393,10 @@ var TuskDriftCore = class TuskDriftCore {
|
|
|
13047
13393
|
enabled: true,
|
|
13048
13394
|
mode: this.mode
|
|
13049
13395
|
});
|
|
13396
|
+
new PrismaInstrumentation({
|
|
13397
|
+
enabled: true,
|
|
13398
|
+
mode: this.mode
|
|
13399
|
+
});
|
|
13050
13400
|
}
|
|
13051
13401
|
initializeTracing({ baseDirectory }) {
|
|
13052
13402
|
const serviceName = this.config.service?.name || "unknown";
|
|
@@ -13215,6 +13565,30 @@ var TuskDriftCore = class TuskDriftCore {
|
|
|
13215
13565
|
};
|
|
13216
13566
|
}
|
|
13217
13567
|
}
|
|
13568
|
+
/**
|
|
13569
|
+
* Request environment variables from CLI for a specific trace (synchronously).
|
|
13570
|
+
* This blocks the main thread, so it should be used carefully.
|
|
13571
|
+
*/
|
|
13572
|
+
requestEnvVarsSync(traceTestServerSpanId) {
|
|
13573
|
+
if (!this.isConnectedWithCLI) {
|
|
13574
|
+
logger.error("Requesting sync env vars but CLI is not ready yet");
|
|
13575
|
+
throw new Error("Requesting sync env vars but CLI is not ready yet");
|
|
13576
|
+
}
|
|
13577
|
+
if (!this.communicator || this.mode !== TuskDriftMode.REPLAY) {
|
|
13578
|
+
logger.debug("Cannot request env vars: not in replay mode or no CLI connection");
|
|
13579
|
+
return {};
|
|
13580
|
+
}
|
|
13581
|
+
try {
|
|
13582
|
+
logger.debug(`Requesting env vars (sync) for trace: ${traceTestServerSpanId}`);
|
|
13583
|
+
const envVars = this.communicator.requestEnvVarsSync(traceTestServerSpanId);
|
|
13584
|
+
logger.debug(`Received env vars from CLI, count: ${Object.keys(envVars).length}`);
|
|
13585
|
+
logger.debug(`First 10 env vars: ${JSON.stringify(Object.keys(envVars).slice(0, 10), null, 2)}`);
|
|
13586
|
+
return envVars;
|
|
13587
|
+
} catch (error) {
|
|
13588
|
+
logger.error(`[TuskDrift] Error requesting env vars from CLI:`, error);
|
|
13589
|
+
return {};
|
|
13590
|
+
}
|
|
13591
|
+
}
|
|
13218
13592
|
requestMockSync(mockRequest) {
|
|
13219
13593
|
if (!this.isConnectedWithCLI) {
|
|
13220
13594
|
logger.error("Requesting sync mock but CLI is not ready yet");
|
|
@@ -13269,6 +13643,9 @@ var TuskDriftCore = class TuskDriftCore {
|
|
|
13269
13643
|
getTracer() {
|
|
13270
13644
|
return trace.getTracer(TD_INSTRUMENTATION_LIBRARY_NAME);
|
|
13271
13645
|
}
|
|
13646
|
+
getProtobufCommunicator() {
|
|
13647
|
+
return this.communicator;
|
|
13648
|
+
}
|
|
13272
13649
|
};
|
|
13273
13650
|
var TuskDriftSDK = class {
|
|
13274
13651
|
constructor() {
|