@use-tusk/drift-node-sdk 0.1.7 → 0.1.8
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 +1617 -139
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1617 -140
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -18,11 +18,12 @@ import { IncomingMessage } from "http";
|
|
|
18
18
|
import * as crypto from "crypto";
|
|
19
19
|
import { SpanExportService } from "@use-tusk/drift-schemas/backend/span_export_service";
|
|
20
20
|
import jp from "jsonpath";
|
|
21
|
+
import net from "net";
|
|
22
|
+
import { Readable } from "stream";
|
|
21
23
|
import { ATTR_SERVICE_NAME } from "@opentelemetry/semantic-conventions";
|
|
22
24
|
import { SpanExportServiceClient } from "@use-tusk/drift-schemas/backend/span_export_service.client";
|
|
23
25
|
import { TwirpFetchTransport } from "@protobuf-ts/twirp-transport";
|
|
24
26
|
import { BatchSpanProcessor, NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
|
|
25
|
-
import net from "net";
|
|
26
27
|
import { execSync } from "child_process";
|
|
27
28
|
import { CLIMessage, ConnectRequest, GetMockRequest, MessageType, SDKMessage, SendInboundSpanForReplayRequest } from "@use-tusk/drift-schemas/core/communication";
|
|
28
29
|
import { Resource } from "@opentelemetry/resources";
|
|
@@ -252,7 +253,7 @@ var TdInstrumentationAbstract = class {
|
|
|
252
253
|
|
|
253
254
|
//#endregion
|
|
254
255
|
//#region package.json
|
|
255
|
-
var version = "0.1.
|
|
256
|
+
var version = "0.1.8";
|
|
256
257
|
|
|
257
258
|
//#endregion
|
|
258
259
|
//#region src/version.ts
|
|
@@ -449,6 +450,20 @@ function loadTuskConfig() {
|
|
|
449
450
|
}
|
|
450
451
|
}
|
|
451
452
|
|
|
453
|
+
//#endregion
|
|
454
|
+
//#region src/core/utils/runtimeDetectionUtils.ts
|
|
455
|
+
function isNextJsRuntime() {
|
|
456
|
+
return process.env.NEXT_RUNTIME !== void 0 || typeof global.__NEXT_DATA__ !== "undefined";
|
|
457
|
+
}
|
|
458
|
+
function isEsm(moduleExports) {
|
|
459
|
+
if (!moduleExports || typeof moduleExports !== "object") return false;
|
|
460
|
+
try {
|
|
461
|
+
return moduleExports[Symbol.toStringTag] === "Module";
|
|
462
|
+
} catch (error) {
|
|
463
|
+
return false;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
452
467
|
//#endregion
|
|
453
468
|
//#region src/core/utils/logger.ts
|
|
454
469
|
var Logger = class {
|
|
@@ -728,9 +743,11 @@ var TdInstrumentationNodeModule = class {
|
|
|
728
743
|
|
|
729
744
|
//#endregion
|
|
730
745
|
//#region src/core/types.ts
|
|
746
|
+
const TD_INSTRUMENTATION_LIBRARY_NAME = "tusk-drift-sdk";
|
|
731
747
|
const REPLAY_TRACE_ID_CONTEXT_KEY = createContextKey("td.replayTraceId");
|
|
732
748
|
const SPAN_KIND_CONTEXT_KEY = createContextKey("td.spanKind");
|
|
733
749
|
const IS_PRE_APP_START_CONTEXT_KEY = createContextKey("td.isPreAppStart");
|
|
750
|
+
const STOP_RECORDING_CHILD_SPANS_CONTEXT_KEY = createContextKey("td.stopRecordingChildSpans");
|
|
734
751
|
const CALLING_LIBRARY_CONTEXT_KEY = createContextKey("td.callingLibrary");
|
|
735
752
|
let TdSpanAttributes = /* @__PURE__ */ function(TdSpanAttributes$1) {
|
|
736
753
|
/**
|
|
@@ -773,6 +790,74 @@ let TdSpanAttributes = /* @__PURE__ */ function(TdSpanAttributes$1) {
|
|
|
773
790
|
return TdSpanAttributes$1;
|
|
774
791
|
}({});
|
|
775
792
|
|
|
793
|
+
//#endregion
|
|
794
|
+
//#region src/core/tracing/TraceBlockingManager.ts
|
|
795
|
+
/**
|
|
796
|
+
* Manages blocked trace IDs to prevent creation and export of spans
|
|
797
|
+
* that belong to traces exceeding size limits.
|
|
798
|
+
*
|
|
799
|
+
* This class uses an in-memory Set for O(1) lookup performance and
|
|
800
|
+
* automatically cleans up old entries to prevent memory leaks.
|
|
801
|
+
*/
|
|
802
|
+
var TraceBlockingManager = class TraceBlockingManager {
|
|
803
|
+
constructor() {
|
|
804
|
+
this.blockedTraceIds = /* @__PURE__ */ new Set();
|
|
805
|
+
this.traceTimestamps = /* @__PURE__ */ new Map();
|
|
806
|
+
this.cleanupIntervalId = null;
|
|
807
|
+
this.DEFAULT_TTL_MS = 600 * 1e3;
|
|
808
|
+
this.CLEANUP_INTERVAL_MS = 120 * 1e3;
|
|
809
|
+
this.startCleanupInterval();
|
|
810
|
+
}
|
|
811
|
+
/**
|
|
812
|
+
* Get singleton instance
|
|
813
|
+
*/
|
|
814
|
+
static getInstance() {
|
|
815
|
+
if (!TraceBlockingManager.instance) TraceBlockingManager.instance = new TraceBlockingManager();
|
|
816
|
+
return TraceBlockingManager.instance;
|
|
817
|
+
}
|
|
818
|
+
/**
|
|
819
|
+
* Check if a trace ID is blocked
|
|
820
|
+
*/
|
|
821
|
+
isTraceBlocked(traceId) {
|
|
822
|
+
return this.blockedTraceIds.has(traceId);
|
|
823
|
+
}
|
|
824
|
+
/**
|
|
825
|
+
* Block a trace ID and all future spans for this trace
|
|
826
|
+
*/
|
|
827
|
+
blockTrace(traceId) {
|
|
828
|
+
if (!this.blockedTraceIds.has(traceId)) {
|
|
829
|
+
this.blockedTraceIds.add(traceId);
|
|
830
|
+
const originalDate = OriginalGlobalUtils.getOriginalDate();
|
|
831
|
+
this.traceTimestamps.set(traceId, originalDate.getTime());
|
|
832
|
+
logger.debug(`[TraceBlockingManager] Blocked trace: ${traceId}`);
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
/**
|
|
836
|
+
* Start periodic cleanup of old blocked trace IDs
|
|
837
|
+
*/
|
|
838
|
+
startCleanupInterval() {
|
|
839
|
+
if (this.cleanupIntervalId) return;
|
|
840
|
+
this.cleanupIntervalId = setInterval(() => {
|
|
841
|
+
this.cleanupOldTraces();
|
|
842
|
+
}, this.CLEANUP_INTERVAL_MS);
|
|
843
|
+
if (this.cleanupIntervalId.unref) this.cleanupIntervalId.unref();
|
|
844
|
+
}
|
|
845
|
+
/**
|
|
846
|
+
* Clean up trace IDs older than TTL
|
|
847
|
+
*/
|
|
848
|
+
cleanupOldTraces() {
|
|
849
|
+
const now = OriginalGlobalUtils.getOriginalDate().getTime();
|
|
850
|
+
const expiredTraces = [];
|
|
851
|
+
for (const [traceId, timestamp] of this.traceTimestamps.entries()) if (now - timestamp > this.DEFAULT_TTL_MS) expiredTraces.push(traceId);
|
|
852
|
+
for (const traceId of expiredTraces) {
|
|
853
|
+
this.blockedTraceIds.delete(traceId);
|
|
854
|
+
this.traceTimestamps.delete(traceId);
|
|
855
|
+
}
|
|
856
|
+
if (expiredTraces.length > 0) logger.debug(`[TraceBlockingManager] Cleaned up ${expiredTraces.length} expired blocked trace(s)`);
|
|
857
|
+
}
|
|
858
|
+
};
|
|
859
|
+
TraceBlockingManager.instance = null;
|
|
860
|
+
|
|
776
861
|
//#endregion
|
|
777
862
|
//#region src/core/tracing/SpanUtils.ts
|
|
778
863
|
var SpanUtils = class SpanUtils {
|
|
@@ -783,6 +868,14 @@ var SpanUtils = class SpanUtils {
|
|
|
783
868
|
try {
|
|
784
869
|
const tracer = TuskDriftCore.getInstance().getTracer();
|
|
785
870
|
const parentContext = options.parentContext || context.active();
|
|
871
|
+
const activeSpan = trace.getSpan(parentContext);
|
|
872
|
+
if (activeSpan) {
|
|
873
|
+
const parentTraceId = activeSpan.spanContext().traceId;
|
|
874
|
+
if (TraceBlockingManager.getInstance().isTraceBlocked(parentTraceId)) {
|
|
875
|
+
logger.debug(`[SpanUtils] Skipping span creation for '${options.name}' - trace ${parentTraceId} is blocked`);
|
|
876
|
+
return null;
|
|
877
|
+
}
|
|
878
|
+
}
|
|
786
879
|
const span = tracer.startSpan(options.name, {
|
|
787
880
|
kind: options.kind || SpanKind.CLIENT,
|
|
788
881
|
attributes: options.attributes || {}
|
|
@@ -822,7 +915,14 @@ var SpanUtils = class SpanUtils {
|
|
|
822
915
|
* @returns The result of the function execution
|
|
823
916
|
*/
|
|
824
917
|
static createAndExecuteSpan(mode, originalFunctionCall, options, fn) {
|
|
825
|
-
const
|
|
918
|
+
const spanContext = trace.getActiveSpan()?.spanContext();
|
|
919
|
+
if (spanContext) {
|
|
920
|
+
if (context.active().getValue(STOP_RECORDING_CHILD_SPANS_CONTEXT_KEY)) {
|
|
921
|
+
logger.debug(`[SpanUtils] Stopping recording of child spans for span ${spanContext.spanId}, packageName: ${options.packageName}, instrumentationName: ${options.instrumentationName}`);
|
|
922
|
+
return originalFunctionCall();
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
const { name, kind, packageName, instrumentationName, packageType, submodule, inputValue, inputSchemaMerges, isPreAppStart, metadata, stopRecordingChildSpans } = options;
|
|
826
926
|
let spanInfo = null;
|
|
827
927
|
try {
|
|
828
928
|
spanInfo = SpanUtils.createSpan({
|
|
@@ -847,6 +947,7 @@ var SpanUtils = class SpanUtils {
|
|
|
847
947
|
}
|
|
848
948
|
if (!spanInfo) if (mode === TuskDriftMode.REPLAY) throw new Error("Error creating span in replay mode");
|
|
849
949
|
else return originalFunctionCall();
|
|
950
|
+
if (stopRecordingChildSpans) spanInfo.context = spanInfo.context.setValue(STOP_RECORDING_CHILD_SPANS_CONTEXT_KEY, true);
|
|
850
951
|
return SpanUtils.withSpan(spanInfo, () => fn(spanInfo));
|
|
851
952
|
}
|
|
852
953
|
/**
|
|
@@ -1102,6 +1203,22 @@ function getDecodedType(contentType) {
|
|
|
1102
1203
|
const mainType = contentTypeString.toLowerCase().split(";")[0].trim();
|
|
1103
1204
|
return CONTENT_TYPE_MAPPING[mainType];
|
|
1104
1205
|
}
|
|
1206
|
+
const STATIC_ASSET_TYPES = new Set([
|
|
1207
|
+
DecodedType.HTML,
|
|
1208
|
+
DecodedType.CSS,
|
|
1209
|
+
DecodedType.JAVASCRIPT,
|
|
1210
|
+
DecodedType.JPEG,
|
|
1211
|
+
DecodedType.PNG,
|
|
1212
|
+
DecodedType.GIF,
|
|
1213
|
+
DecodedType.WEBP,
|
|
1214
|
+
DecodedType.SVG,
|
|
1215
|
+
DecodedType.PDF,
|
|
1216
|
+
DecodedType.AUDIO,
|
|
1217
|
+
DecodedType.VIDEO,
|
|
1218
|
+
DecodedType.BINARY,
|
|
1219
|
+
DecodedType.ZIP,
|
|
1220
|
+
DecodedType.GZIP
|
|
1221
|
+
]);
|
|
1105
1222
|
|
|
1106
1223
|
//#endregion
|
|
1107
1224
|
//#region src/instrumentation/libraries/http/mocks/TdHttpMockSocket.ts
|
|
@@ -1576,6 +1693,28 @@ async function findMockResponseAsync({ mockRequestData, tuskDrift, inputValueSch
|
|
|
1576
1693
|
return null;
|
|
1577
1694
|
}
|
|
1578
1695
|
}
|
|
1696
|
+
function findMockResponseSync({ mockRequestData, tuskDrift, inputValueSchemaMerges }) {
|
|
1697
|
+
const outboundSpan = convertMockRequestDataToCleanSpanData(mockRequestData, tuskDrift, inputValueSchemaMerges);
|
|
1698
|
+
try {
|
|
1699
|
+
const replayTraceId = SpanUtils.getCurrentReplayTraceId();
|
|
1700
|
+
logger.debug(`Finding ${outboundSpan.traceId} mock for replay trace ID: ${replayTraceId}`);
|
|
1701
|
+
const mockResponse = tuskDrift.requestMockSync({
|
|
1702
|
+
outboundSpan,
|
|
1703
|
+
testId: replayTraceId || ""
|
|
1704
|
+
});
|
|
1705
|
+
if (!mockResponse || !mockResponse.found) {
|
|
1706
|
+
logger.debug(`No matching mock found for ${outboundSpan.traceId} with input value: ${JSON.stringify(outboundSpan.inputValue)}`, replayTraceId);
|
|
1707
|
+
return null;
|
|
1708
|
+
}
|
|
1709
|
+
const responseBody = mockResponse.response?.response?.body;
|
|
1710
|
+
logger.debug(`Found ${outboundSpan.traceId} mock response and timestamp:`, responseBody, { timestamp: mockResponse.response?.timestamp });
|
|
1711
|
+
if (mockResponse.response?.timestamp) DateTracker.updateLatestTimestamp(replayTraceId || "", mockResponse.response.timestamp);
|
|
1712
|
+
return { result: responseBody };
|
|
1713
|
+
} catch (error) {
|
|
1714
|
+
logger.error(`Error finding ${outboundSpan.traceId} mock response:`, error);
|
|
1715
|
+
return null;
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1579
1718
|
|
|
1580
1719
|
//#endregion
|
|
1581
1720
|
//#region src/instrumentation/libraries/http/mocks/TdMockClientRequest.ts
|
|
@@ -1988,7 +2127,7 @@ function wrap(target, propertyName, wrapper) {
|
|
|
1988
2127
|
* Helper functions for capturing stack traces in replay mode
|
|
1989
2128
|
*
|
|
1990
2129
|
* TODO: Consider using a structured format for stack frames:
|
|
1991
|
-
*
|
|
2130
|
+
*
|
|
1992
2131
|
* {
|
|
1993
2132
|
* "frames": [
|
|
1994
2133
|
* {
|
|
@@ -2004,7 +2143,7 @@ function wrap(target, propertyName, wrapper) {
|
|
|
2004
2143
|
* It would also allow for more accurate stack trace reconstruction in replay mode.
|
|
2005
2144
|
*/
|
|
2006
2145
|
/**
|
|
2007
|
-
*
|
|
2146
|
+
*
|
|
2008
2147
|
* @param excludeClassNames - Class names to exclude from the stack trace
|
|
2009
2148
|
* @returns The stack trace as a string
|
|
2010
2149
|
*/
|
|
@@ -2417,7 +2556,7 @@ var HttpInstrumentation = class extends TdInstrumentationBase {
|
|
|
2417
2556
|
logger.debug(`[HttpInstrumentation] ${protocolUpper} module already patched, skipping`);
|
|
2418
2557
|
return httpModule;
|
|
2419
2558
|
}
|
|
2420
|
-
if (httpModule
|
|
2559
|
+
if (isEsm(httpModule)) {
|
|
2421
2560
|
if (httpModule.default) {
|
|
2422
2561
|
this._wrap(httpModule.default, "request", this._getRequestPatchFn(protocol));
|
|
2423
2562
|
this._wrap(httpModule.default, "get", this._getGetPatchFn(protocol));
|
|
@@ -2436,11 +2575,18 @@ var HttpInstrumentation = class extends TdInstrumentationBase {
|
|
|
2436
2575
|
return httpModule;
|
|
2437
2576
|
}
|
|
2438
2577
|
_createServerSpan({ req, res, originalHandler, protocol }) {
|
|
2578
|
+
if (isNextJsRuntime()) {
|
|
2579
|
+
logger.debug(`[HttpInstrumentation] Skipping recording/replaying for nextJs runtime, handled by nextJs instrumentation`);
|
|
2580
|
+
return originalHandler.call(this);
|
|
2581
|
+
}
|
|
2439
2582
|
const method = req.method || "GET";
|
|
2440
2583
|
const url = req.url || "/";
|
|
2441
2584
|
const target = req.url || "/";
|
|
2442
2585
|
const spanProtocol = this._normalizeProtocol(protocol, "http");
|
|
2443
|
-
if (isTuskDriftIngestionUrl(url) || isTuskDriftIngestionUrl(target))
|
|
2586
|
+
if (isTuskDriftIngestionUrl(url) || isTuskDriftIngestionUrl(target)) {
|
|
2587
|
+
logger.debug(`[HttpInstrumentation] Ignoring drift ingestion endpoints`);
|
|
2588
|
+
return originalHandler.call(this);
|
|
2589
|
+
}
|
|
2444
2590
|
if (this.transformEngine.shouldDropInboundRequest(method, url, req.headers)) {
|
|
2445
2591
|
logger.debug(`[HttpInstrumentation] Dropping inbound request due to transforms: ${method} ${url}`);
|
|
2446
2592
|
return originalHandler.call(this);
|
|
@@ -2458,7 +2604,10 @@ var HttpInstrumentation = class extends TdInstrumentationBase {
|
|
|
2458
2604
|
remotePort: req.socket?.remotePort
|
|
2459
2605
|
};
|
|
2460
2606
|
const replayTraceId = this.replayHooks.extractTraceIdFromHeaders(req);
|
|
2461
|
-
if (!replayTraceId)
|
|
2607
|
+
if (!replayTraceId) {
|
|
2608
|
+
logger.debug(`[HttpInstrumentation] No trace id found in headers`, req.headers);
|
|
2609
|
+
return originalHandler.call(this);
|
|
2610
|
+
}
|
|
2462
2611
|
logger.debug(`[HttpInstrumentation] Setting replay trace id`, replayTraceId);
|
|
2463
2612
|
const envVars = this.replayHooks.extractEnvVarsFromHeaders(req);
|
|
2464
2613
|
if (envVars) EnvVarTracker.setEnvVars(replayTraceId, envVars);
|
|
@@ -2490,13 +2639,6 @@ var HttpInstrumentation = class extends TdInstrumentationBase {
|
|
|
2490
2639
|
} });
|
|
2491
2640
|
else if (this.mode === TuskDriftMode.RECORD) {
|
|
2492
2641
|
if (method.toUpperCase() === "OPTIONS" || !!req.headers["access-control-request-method"]) return originalHandler.call(this);
|
|
2493
|
-
if (!shouldSample({
|
|
2494
|
-
samplingRate: this.tuskDrift.getSamplingRate(),
|
|
2495
|
-
isAppReady: this.tuskDrift.isAppReady()
|
|
2496
|
-
})) {
|
|
2497
|
-
logger.debug(`Skipping server span due to sampling rate`, url, this.tuskDrift.getSamplingRate());
|
|
2498
|
-
return originalHandler.call(this);
|
|
2499
|
-
}
|
|
2500
2642
|
logger.debug(`[HttpInstrumentation] Creating server span for ${method} ${url}`);
|
|
2501
2643
|
return handleRecordMode({
|
|
2502
2644
|
originalFunctionCall: () => originalHandler.call(this),
|
|
@@ -2590,7 +2732,6 @@ var HttpInstrumentation = class extends TdInstrumentationBase {
|
|
|
2590
2732
|
outputValue.bodySize = responseBuffer.length;
|
|
2591
2733
|
} catch (error) {
|
|
2592
2734
|
logger.error(`[HttpInstrumentation] Error processing server response body:`, error);
|
|
2593
|
-
outputValue.bodyProcessingError = error instanceof Error ? error.message : String(error);
|
|
2594
2735
|
}
|
|
2595
2736
|
try {
|
|
2596
2737
|
const spanData = {
|
|
@@ -2608,7 +2749,7 @@ var HttpInstrumentation = class extends TdInstrumentationBase {
|
|
|
2608
2749
|
outputSchemaMerges: {
|
|
2609
2750
|
body: {
|
|
2610
2751
|
encoding: EncodingType.BASE64,
|
|
2611
|
-
decodedType: getDecodedType(spanData.outputValue
|
|
2752
|
+
decodedType: getDecodedType(spanData.outputValue?.headers?.["content-type"] || "")
|
|
2612
2753
|
},
|
|
2613
2754
|
headers: { matchImportance: 0 }
|
|
2614
2755
|
},
|
|
@@ -2621,6 +2762,11 @@ var HttpInstrumentation = class extends TdInstrumentationBase {
|
|
|
2621
2762
|
message: `HTTP ${statusCode}`
|
|
2622
2763
|
} : { code: SpanStatusCode.OK };
|
|
2623
2764
|
SpanUtils.setStatus(spanInfo.span, status);
|
|
2765
|
+
const decodedType = getDecodedType(outputValue.headers?.["content-type"] || "");
|
|
2766
|
+
if (decodedType && STATIC_ASSET_TYPES.has(decodedType)) {
|
|
2767
|
+
TraceBlockingManager.getInstance().blockTrace(spanInfo.traceId);
|
|
2768
|
+
logger.debug(`[HttpInstrumentation] Blocking trace ${spanInfo.traceId} because it is an static asset response. Decoded type: ${decodedType}`);
|
|
2769
|
+
}
|
|
2624
2770
|
SpanUtils.endSpan(spanInfo.span);
|
|
2625
2771
|
} catch (error) {
|
|
2626
2772
|
logger.error(`[HttpInstrumentation] Error adding response attributes to span:`, error);
|
|
@@ -3122,6 +3268,12 @@ var HttpInstrumentation = class extends TdInstrumentationBase {
|
|
|
3122
3268
|
return (originalEmit) => {
|
|
3123
3269
|
return function(eventName, ...args) {
|
|
3124
3270
|
if (eventName === "request") {
|
|
3271
|
+
if (self.mode === TuskDriftMode.RECORD) {
|
|
3272
|
+
if (!shouldSample({
|
|
3273
|
+
samplingRate: self.tuskDrift.getSamplingRate(),
|
|
3274
|
+
isAppReady: self.tuskDrift.isAppReady()
|
|
3275
|
+
})) return originalEmit.apply(this, [eventName, ...args]);
|
|
3276
|
+
}
|
|
3125
3277
|
const req = args[0];
|
|
3126
3278
|
const res = args[1];
|
|
3127
3279
|
return self._createServerSpan({
|
|
@@ -4034,7 +4186,7 @@ var PostgresInstrumentation = class extends TdInstrumentationBase {
|
|
|
4034
4186
|
return postgresModule;
|
|
4035
4187
|
}
|
|
4036
4188
|
const self = this;
|
|
4037
|
-
if (postgresModule
|
|
4189
|
+
if (isEsm(postgresModule)) {
|
|
4038
4190
|
logger.debug(`[PostgresInstrumentation] Wrapping ESM default export`);
|
|
4039
4191
|
this._wrap(postgresModule, "default", (originalFunction) => {
|
|
4040
4192
|
return function(...args) {
|
|
@@ -5800,6 +5952,7 @@ var TcpInstrumentation = class extends TdInstrumentationBase {
|
|
|
5800
5952
|
super("tcp", config);
|
|
5801
5953
|
this.loggedSpans = /* @__PURE__ */ new Set();
|
|
5802
5954
|
this.mode = config.mode || TuskDriftMode.DISABLED;
|
|
5955
|
+
this._patchLoadedModules();
|
|
5803
5956
|
}
|
|
5804
5957
|
init() {
|
|
5805
5958
|
return [new TdInstrumentationNodeModule({
|
|
@@ -5808,6 +5961,17 @@ var TcpInstrumentation = class extends TdInstrumentationBase {
|
|
|
5808
5961
|
patch: (moduleExports) => this._patchNetModule(moduleExports)
|
|
5809
5962
|
})];
|
|
5810
5963
|
}
|
|
5964
|
+
_patchLoadedModules() {
|
|
5965
|
+
if (isNextJsRuntime()) {
|
|
5966
|
+
logger.debug(`[TcpInstrumentation] Next.js environment detected - force-loading net module to ensure patching`);
|
|
5967
|
+
try {
|
|
5968
|
+
__require("net");
|
|
5969
|
+
logger.debug(`[TcpInstrumentation] net module force-loaded`);
|
|
5970
|
+
} catch (err) {
|
|
5971
|
+
logger.error(`[TcpInstrumentation] Error force-loading net module:`, err);
|
|
5972
|
+
}
|
|
5973
|
+
} else logger.debug(`[TcpInstrumentation] Regular Node.js environment - hooks will catch net module on first require`);
|
|
5974
|
+
}
|
|
5811
5975
|
_patchNetModule(netModule) {
|
|
5812
5976
|
logger.debug(`[TcpInstrumentation] Patching NET module in ${this.mode} mode`);
|
|
5813
5977
|
if (this.isModulePatched(netModule)) {
|
|
@@ -6960,7 +7124,7 @@ var IORedisInstrumentation = class extends TdInstrumentationBase {
|
|
|
6960
7124
|
logger.debug(`[IORedisInstrumentation] IORedis module already patched, skipping`);
|
|
6961
7125
|
return moduleExports;
|
|
6962
7126
|
}
|
|
6963
|
-
const actualExports = moduleExports
|
|
7127
|
+
const actualExports = isEsm(moduleExports) ? moduleExports.default : moduleExports;
|
|
6964
7128
|
if (!actualExports || !actualExports.prototype) {
|
|
6965
7129
|
logger.error(`[IORedisInstrumentation] Invalid module exports, cannot patch`);
|
|
6966
7130
|
return moduleExports;
|
|
@@ -6999,7 +7163,7 @@ var IORedisInstrumentation = class extends TdInstrumentationBase {
|
|
|
6999
7163
|
logger.debug(`[IORedisInstrumentation] Pipeline module already patched, skipping`);
|
|
7000
7164
|
return moduleExports;
|
|
7001
7165
|
}
|
|
7002
|
-
const actualExports = moduleExports
|
|
7166
|
+
const actualExports = isEsm(moduleExports) ? moduleExports.default : moduleExports;
|
|
7003
7167
|
if (!actualExports || !actualExports.prototype) {
|
|
7004
7168
|
logger.debug(`[IORedisInstrumentation] Invalid Pipeline module exports, cannot patch`);
|
|
7005
7169
|
return moduleExports;
|
|
@@ -7592,6 +7756,7 @@ var GrpcInstrumentation = class GrpcInstrumentation extends TdInstrumentationBas
|
|
|
7592
7756
|
return;
|
|
7593
7757
|
}
|
|
7594
7758
|
this._wrap(clientPrototype, "makeUnaryRequest", this._getMakeUnaryRequestPatchFn(version$1));
|
|
7759
|
+
this._wrap(clientPrototype, "makeServerStreamRequest", this._getMakeServerStreamRequestPatchFn(version$1));
|
|
7595
7760
|
this._wrap(clientPrototype, "waitForReady", this._getWaitForReadyPatchFn());
|
|
7596
7761
|
this._wrap(clientPrototype, "close", this._getClosePatchFn());
|
|
7597
7762
|
this._wrap(clientPrototype, "getChannel", this._getGetChannelPatchFn());
|
|
@@ -7641,6 +7806,7 @@ var GrpcInstrumentation = class GrpcInstrumentation extends TdInstrumentationBas
|
|
|
7641
7806
|
logger.debug(`[GrpcInstrumentation] _getMakeUnaryRequestPatchFn called for version: ${version$1}`);
|
|
7642
7807
|
return (original) => {
|
|
7643
7808
|
return function makeUnaryRequest(...args) {
|
|
7809
|
+
logger.debug(`[GrpcInstrumentation] makeUnaryRequest called! args length: ${args.length}`);
|
|
7644
7810
|
const MetadataConstructor = GrpcInstrumentation.metadataStore.get(version$1) || self.Metadata;
|
|
7645
7811
|
if (!MetadataConstructor) {
|
|
7646
7812
|
logger.warn(`[GrpcInstrumentation] Metadata constructor not found for version ${version$1}`);
|
|
@@ -7716,6 +7882,115 @@ var GrpcInstrumentation = class GrpcInstrumentation extends TdInstrumentationBas
|
|
|
7716
7882
|
};
|
|
7717
7883
|
};
|
|
7718
7884
|
}
|
|
7885
|
+
_getMakeServerStreamRequestPatchFn(version$1) {
|
|
7886
|
+
const self = this;
|
|
7887
|
+
logger.debug(`[GrpcInstrumentation] _getMakeServerStreamRequestPatchFn called for version: ${version$1}`);
|
|
7888
|
+
return (original) => {
|
|
7889
|
+
return function makeServerStreamRequest(...args) {
|
|
7890
|
+
logger.debug(`[GrpcInstrumentation] makeServerStreamRequest called! args length: ${args.length}`);
|
|
7891
|
+
const MetadataConstructor = GrpcInstrumentation.metadataStore.get(version$1) || self.Metadata;
|
|
7892
|
+
if (!MetadataConstructor) {
|
|
7893
|
+
logger.warn(`[GrpcInstrumentation] Metadata constructor not found for version ${version$1}`);
|
|
7894
|
+
return original.apply(this, args);
|
|
7895
|
+
}
|
|
7896
|
+
let parsedParams;
|
|
7897
|
+
try {
|
|
7898
|
+
parsedParams = self.extractServerStreamRequestParameters(args, MetadataConstructor);
|
|
7899
|
+
} catch (error) {
|
|
7900
|
+
logger.error(`[GrpcInstrumentation] Error parsing makeServerStreamRequest arguments:`, error);
|
|
7901
|
+
return original.apply(this, args);
|
|
7902
|
+
}
|
|
7903
|
+
const { method: path$2, argument, metadata, options } = parsedParams;
|
|
7904
|
+
let method;
|
|
7905
|
+
let service;
|
|
7906
|
+
let readableBody;
|
|
7907
|
+
let bufferMap;
|
|
7908
|
+
let jsonableStringMap;
|
|
7909
|
+
let readableMetadata;
|
|
7910
|
+
try {
|
|
7911
|
+
({method, service} = parseGrpcPath(path$2));
|
|
7912
|
+
({readableBody, bufferMap, jsonableStringMap} = serializeGrpcPayload(argument));
|
|
7913
|
+
readableMetadata = serializeGrpcMetadata(metadata);
|
|
7914
|
+
} catch (error) {
|
|
7915
|
+
logger.error(`[GrpcInstrumentation] Error parsing makeServerStreamRequest arguments:`, error);
|
|
7916
|
+
return original.apply(this, args);
|
|
7917
|
+
}
|
|
7918
|
+
const inputValue = {
|
|
7919
|
+
method,
|
|
7920
|
+
service,
|
|
7921
|
+
body: readableBody,
|
|
7922
|
+
metadata: readableMetadata,
|
|
7923
|
+
inputMeta: {
|
|
7924
|
+
bufferMap,
|
|
7925
|
+
jsonableStringMap
|
|
7926
|
+
}
|
|
7927
|
+
};
|
|
7928
|
+
if (self.mode === TuskDriftMode.REPLAY) return handleReplayMode({ replayModeHandler: () => {
|
|
7929
|
+
return SpanUtils.createAndExecuteSpan(self.mode, () => original.apply(this, args), {
|
|
7930
|
+
name: "grpc.client.server_stream",
|
|
7931
|
+
kind: SpanKind.CLIENT,
|
|
7932
|
+
submodule: "client",
|
|
7933
|
+
packageType: PackageType.GRPC,
|
|
7934
|
+
packageName: GRPC_MODULE_NAME,
|
|
7935
|
+
instrumentationName: self.INSTRUMENTATION_NAME,
|
|
7936
|
+
inputValue,
|
|
7937
|
+
isPreAppStart: false
|
|
7938
|
+
}, (spanInfo) => {
|
|
7939
|
+
return self._handleReplayServerStreamRequest(spanInfo, inputValue, MetadataConstructor);
|
|
7940
|
+
});
|
|
7941
|
+
} });
|
|
7942
|
+
else if (self.mode === TuskDriftMode.RECORD) return handleRecordMode({
|
|
7943
|
+
originalFunctionCall: () => original.apply(this, args),
|
|
7944
|
+
recordModeHandler: ({ isPreAppStart }) => {
|
|
7945
|
+
return SpanUtils.createAndExecuteSpan(self.mode, () => original.apply(this, args), {
|
|
7946
|
+
name: "grpc.client.server_stream",
|
|
7947
|
+
kind: SpanKind.CLIENT,
|
|
7948
|
+
submodule: "client",
|
|
7949
|
+
packageType: PackageType.GRPC,
|
|
7950
|
+
packageName: GRPC_MODULE_NAME,
|
|
7951
|
+
instrumentationName: self.INSTRUMENTATION_NAME,
|
|
7952
|
+
inputValue,
|
|
7953
|
+
isPreAppStart
|
|
7954
|
+
}, (spanInfo) => {
|
|
7955
|
+
return self._handleRecordServerStreamRequest(spanInfo, original, this, parsedParams);
|
|
7956
|
+
});
|
|
7957
|
+
},
|
|
7958
|
+
spanKind: SpanKind.CLIENT
|
|
7959
|
+
});
|
|
7960
|
+
else return original.apply(this, args);
|
|
7961
|
+
};
|
|
7962
|
+
};
|
|
7963
|
+
}
|
|
7964
|
+
extractServerStreamRequestParameters(args, MetadataConstructor) {
|
|
7965
|
+
const method = args[0];
|
|
7966
|
+
const serialize = args[1];
|
|
7967
|
+
const deserialize = args[2];
|
|
7968
|
+
const argument = args[3];
|
|
7969
|
+
let metadata;
|
|
7970
|
+
let options;
|
|
7971
|
+
if (args.length === 6) {
|
|
7972
|
+
metadata = args[4];
|
|
7973
|
+
options = args[5];
|
|
7974
|
+
} else if (args.length === 5) if (args[4] instanceof MetadataConstructor) {
|
|
7975
|
+
metadata = args[4];
|
|
7976
|
+
options = {};
|
|
7977
|
+
} else {
|
|
7978
|
+
metadata = new MetadataConstructor();
|
|
7979
|
+
options = args[4] || {};
|
|
7980
|
+
}
|
|
7981
|
+
else {
|
|
7982
|
+
metadata = new MetadataConstructor();
|
|
7983
|
+
options = {};
|
|
7984
|
+
}
|
|
7985
|
+
return {
|
|
7986
|
+
method,
|
|
7987
|
+
serialize,
|
|
7988
|
+
deserialize,
|
|
7989
|
+
argument,
|
|
7990
|
+
metadata,
|
|
7991
|
+
options
|
|
7992
|
+
};
|
|
7993
|
+
}
|
|
7719
7994
|
_handleRecordUnaryRequest(spanInfo, original, context$1, parsedParams, callback) {
|
|
7720
7995
|
let isResponseReceived = false;
|
|
7721
7996
|
let isStatusEmitted = false;
|
|
@@ -7826,105 +8101,1160 @@ var GrpcInstrumentation = class GrpcInstrumentation extends TdInstrumentationBas
|
|
|
7826
8101
|
isGrpcErrorOutput(result) {
|
|
7827
8102
|
return "error" in result;
|
|
7828
8103
|
}
|
|
7829
|
-
|
|
8104
|
+
_handleReplayUnaryRequest(spanInfo, inputValue, callback, MetadataConstructor, stackTrace) {
|
|
7830
8105
|
logger.debug(`[GrpcInstrumentation] Replaying gRPC unary request`);
|
|
7831
|
-
const
|
|
8106
|
+
const emitter = Object.assign(new EventEmitter(), {
|
|
8107
|
+
cancel() {},
|
|
8108
|
+
getPeer: () => "0.0.0.0:0000",
|
|
8109
|
+
call: void 0
|
|
8110
|
+
});
|
|
8111
|
+
findMockResponseAsync({
|
|
7832
8112
|
mockRequestData: {
|
|
7833
8113
|
traceId: spanInfo.traceId,
|
|
7834
8114
|
spanId: spanInfo.spanId,
|
|
7835
8115
|
name: "grpc.client.unary",
|
|
7836
8116
|
inputValue,
|
|
7837
8117
|
packageName: GRPC_MODULE_NAME,
|
|
8118
|
+
packageType: PackageType.GRPC,
|
|
7838
8119
|
instrumentationName: this.INSTRUMENTATION_NAME,
|
|
7839
8120
|
submoduleName: "client",
|
|
7840
8121
|
kind: SpanKind.CLIENT,
|
|
7841
8122
|
stackTrace
|
|
7842
8123
|
},
|
|
7843
8124
|
tuskDrift: this.tuskDrift
|
|
7844
|
-
})
|
|
7845
|
-
|
|
7846
|
-
|
|
7847
|
-
|
|
7848
|
-
|
|
7849
|
-
|
|
7850
|
-
|
|
8125
|
+
}).then((mockData) => {
|
|
8126
|
+
if (!mockData) {
|
|
8127
|
+
logger.warn(`[GrpcInstrumentation] No mock data found for gRPC request: ${inputValue.service}/${inputValue.method}`, inputValue);
|
|
8128
|
+
callback(/* @__PURE__ */ new Error("No mock data found"));
|
|
8129
|
+
SpanUtils.endSpan(spanInfo.span, { code: SpanStatusCode.ERROR });
|
|
8130
|
+
return;
|
|
8131
|
+
}
|
|
8132
|
+
const mockResult = mockData.result;
|
|
8133
|
+
let status;
|
|
8134
|
+
if (this.isGrpcErrorOutput(mockResult)) {
|
|
8135
|
+
const { error, status: errorStatus } = mockResult;
|
|
8136
|
+
status = {
|
|
8137
|
+
code: errorStatus.code,
|
|
8138
|
+
details: errorStatus.details,
|
|
8139
|
+
metadata: deserializeGrpcMetadata(MetadataConstructor, errorStatus.metadata)
|
|
8140
|
+
};
|
|
8141
|
+
const errorObj = Object.assign(new Error(error.message), {
|
|
8142
|
+
name: error.name,
|
|
8143
|
+
stack: error.stack,
|
|
8144
|
+
...status
|
|
8145
|
+
});
|
|
8146
|
+
callback(errorObj);
|
|
8147
|
+
} else {
|
|
8148
|
+
const { body, status: successStatus, bufferMap, jsonableStringMap } = mockResult;
|
|
8149
|
+
const bufferMapToUse = bufferMap || {};
|
|
8150
|
+
const jsonableStringMapToUse = jsonableStringMap || {};
|
|
8151
|
+
status = {
|
|
8152
|
+
code: successStatus.code,
|
|
8153
|
+
details: successStatus.details,
|
|
8154
|
+
metadata: deserializeGrpcMetadata(MetadataConstructor, successStatus.metadata)
|
|
8155
|
+
};
|
|
8156
|
+
const realResponse = deserializeGrpcPayload(body, bufferMapToUse, jsonableStringMapToUse);
|
|
8157
|
+
callback(null, realResponse);
|
|
8158
|
+
}
|
|
8159
|
+
process.nextTick(() => {
|
|
8160
|
+
if (mockResult.metadata) emitter.emit("metadata", deserializeGrpcMetadata(MetadataConstructor, mockResult.metadata));
|
|
8161
|
+
emitter.emit("status", status);
|
|
7851
8162
|
});
|
|
7852
|
-
|
|
7853
|
-
|
|
7854
|
-
|
|
8163
|
+
SpanUtils.addSpanAttributes(spanInfo.span, { outputValue: mockResult });
|
|
8164
|
+
SpanUtils.endSpan(spanInfo.span, { code: mockResult.error ? SpanStatusCode.ERROR : SpanStatusCode.OK });
|
|
8165
|
+
}).catch((error) => {
|
|
8166
|
+
logger.error(`[GrpcInstrumentation] Error fetching mock data:`, error);
|
|
8167
|
+
callback(error);
|
|
8168
|
+
SpanUtils.endSpan(spanInfo.span, { code: SpanStatusCode.ERROR });
|
|
8169
|
+
});
|
|
8170
|
+
return emitter;
|
|
8171
|
+
}
|
|
8172
|
+
_handleRecordServerStreamRequest(spanInfo, original, context$1, parsedParams) {
|
|
8173
|
+
let hasErrorOccurred = false;
|
|
8174
|
+
let isSpanCompleted = false;
|
|
8175
|
+
let streamResponses = [];
|
|
7855
8176
|
let status;
|
|
7856
|
-
|
|
7857
|
-
|
|
7858
|
-
|
|
7859
|
-
|
|
7860
|
-
|
|
7861
|
-
|
|
7862
|
-
|
|
7863
|
-
|
|
7864
|
-
|
|
7865
|
-
|
|
7866
|
-
|
|
7867
|
-
})
|
|
7868
|
-
|
|
7869
|
-
|
|
7870
|
-
|
|
7871
|
-
|
|
7872
|
-
|
|
8177
|
+
let responseMetadataInitial = {};
|
|
8178
|
+
let serviceError;
|
|
8179
|
+
/**
|
|
8180
|
+
* Completes the span exactly once
|
|
8181
|
+
*/
|
|
8182
|
+
const completeSpan = (output, statusCode, errorMessage) => {
|
|
8183
|
+
if (isSpanCompleted) return;
|
|
8184
|
+
isSpanCompleted = true;
|
|
8185
|
+
try {
|
|
8186
|
+
SpanUtils.addSpanAttributes(spanInfo.span, { outputValue: output });
|
|
8187
|
+
SpanUtils.endSpan(spanInfo.span, { code: statusCode });
|
|
8188
|
+
} catch (e) {
|
|
8189
|
+
logger.error(`[GrpcInstrumentation] Error completing span:`, e);
|
|
8190
|
+
}
|
|
8191
|
+
};
|
|
8192
|
+
const inputArgs = [
|
|
8193
|
+
parsedParams.method,
|
|
8194
|
+
parsedParams.serialize,
|
|
8195
|
+
parsedParams.deserialize,
|
|
8196
|
+
parsedParams.argument,
|
|
8197
|
+
parsedParams.metadata,
|
|
8198
|
+
parsedParams.options
|
|
8199
|
+
];
|
|
8200
|
+
const stream = original.apply(context$1, inputArgs);
|
|
8201
|
+
stream.on("data", (data) => {
|
|
8202
|
+
try {
|
|
8203
|
+
const { readableBody, bufferMap, jsonableStringMap } = serializeGrpcPayload(data);
|
|
8204
|
+
streamResponses.push({
|
|
8205
|
+
body: readableBody,
|
|
8206
|
+
bufferMap,
|
|
8207
|
+
jsonableStringMap
|
|
8208
|
+
});
|
|
8209
|
+
} catch (e) {
|
|
8210
|
+
logger.error(`[GrpcInstrumentation] Error serializing stream data:`, e);
|
|
8211
|
+
}
|
|
8212
|
+
});
|
|
8213
|
+
stream.on("metadata", (initialMetadata) => {
|
|
8214
|
+
responseMetadataInitial = serializeGrpcMetadata(initialMetadata);
|
|
8215
|
+
});
|
|
8216
|
+
stream.on("error", (err) => {
|
|
8217
|
+
serviceError = err;
|
|
8218
|
+
hasErrorOccurred = true;
|
|
8219
|
+
});
|
|
8220
|
+
stream.on("status", (responseStatus) => {
|
|
7873
8221
|
status = {
|
|
7874
|
-
code:
|
|
7875
|
-
details:
|
|
7876
|
-
metadata:
|
|
8222
|
+
code: responseStatus.code,
|
|
8223
|
+
details: responseStatus.details,
|
|
8224
|
+
metadata: serializeGrpcMetadata(responseStatus.metadata)
|
|
7877
8225
|
};
|
|
7878
|
-
|
|
7879
|
-
|
|
7880
|
-
|
|
7881
|
-
|
|
8226
|
+
if (!hasErrorOccurred && streamResponses.length > 0) completeSpan({
|
|
8227
|
+
body: streamResponses,
|
|
8228
|
+
metadata: responseMetadataInitial,
|
|
8229
|
+
status,
|
|
8230
|
+
bufferMap: {},
|
|
8231
|
+
jsonableStringMap: {}
|
|
8232
|
+
}, SpanStatusCode.OK);
|
|
8233
|
+
else if (!hasErrorOccurred && streamResponses.length === 0) completeSpan({
|
|
8234
|
+
body: [],
|
|
8235
|
+
metadata: responseMetadataInitial,
|
|
8236
|
+
status,
|
|
8237
|
+
bufferMap: {},
|
|
8238
|
+
jsonableStringMap: {}
|
|
8239
|
+
}, SpanStatusCode.OK);
|
|
8240
|
+
else if (hasErrorOccurred) {
|
|
8241
|
+
const errorOutput = {
|
|
8242
|
+
error: {
|
|
8243
|
+
message: serviceError.message,
|
|
8244
|
+
name: serviceError.name,
|
|
8245
|
+
stack: serviceError.stack
|
|
8246
|
+
},
|
|
8247
|
+
status,
|
|
8248
|
+
metadata: responseMetadataInitial
|
|
8249
|
+
};
|
|
8250
|
+
completeSpan(errorOutput, SpanStatusCode.ERROR, serviceError.message);
|
|
8251
|
+
}
|
|
8252
|
+
});
|
|
8253
|
+
return stream;
|
|
8254
|
+
}
|
|
8255
|
+
_handleReplayServerStreamRequest(spanInfo, inputValue, MetadataConstructor) {
|
|
8256
|
+
logger.debug(`[GrpcInstrumentation] Replaying gRPC server stream request`);
|
|
8257
|
+
const stream = new Readable({
|
|
8258
|
+
objectMode: true,
|
|
8259
|
+
read() {}
|
|
8260
|
+
});
|
|
8261
|
+
Object.assign(stream, {
|
|
7882
8262
|
cancel() {},
|
|
7883
8263
|
getPeer: () => "0.0.0.0:0000",
|
|
7884
8264
|
call: void 0
|
|
7885
8265
|
});
|
|
7886
|
-
|
|
7887
|
-
|
|
7888
|
-
|
|
7889
|
-
|
|
7890
|
-
|
|
7891
|
-
|
|
7892
|
-
|
|
7893
|
-
|
|
7894
|
-
|
|
7895
|
-
|
|
7896
|
-
|
|
7897
|
-
|
|
7898
|
-
|
|
7899
|
-
|
|
7900
|
-
|
|
7901
|
-
|
|
8266
|
+
findMockResponseAsync({
|
|
8267
|
+
mockRequestData: {
|
|
8268
|
+
traceId: spanInfo.traceId,
|
|
8269
|
+
spanId: spanInfo.spanId,
|
|
8270
|
+
name: "grpc.client.server_stream",
|
|
8271
|
+
inputValue,
|
|
8272
|
+
packageName: GRPC_MODULE_NAME,
|
|
8273
|
+
packageType: PackageType.GRPC,
|
|
8274
|
+
instrumentationName: this.INSTRUMENTATION_NAME,
|
|
8275
|
+
submoduleName: "client",
|
|
8276
|
+
kind: SpanKind.CLIENT
|
|
8277
|
+
},
|
|
8278
|
+
tuskDrift: this.tuskDrift
|
|
8279
|
+
}).then((mockData) => {
|
|
8280
|
+
if (!mockData) {
|
|
8281
|
+
logger.warn(`[GrpcInstrumentation] No mock data found for gRPC server stream request: ${inputValue.service}/${inputValue.method}`, inputValue);
|
|
8282
|
+
const error = /* @__PURE__ */ new Error("No mock data found");
|
|
8283
|
+
process.nextTick(() => {
|
|
8284
|
+
stream.emit("error", error);
|
|
8285
|
+
stream.emit("status", {
|
|
8286
|
+
code: 2,
|
|
8287
|
+
details: "No mock data found",
|
|
8288
|
+
metadata: new MetadataConstructor()
|
|
8289
|
+
});
|
|
8290
|
+
stream.push(null);
|
|
8291
|
+
});
|
|
8292
|
+
SpanUtils.endSpan(spanInfo.span, {
|
|
8293
|
+
code: SpanStatusCode.ERROR,
|
|
8294
|
+
message: "No mock data found"
|
|
8295
|
+
});
|
|
8296
|
+
return;
|
|
8297
|
+
}
|
|
8298
|
+
const mockResult = mockData.result;
|
|
8299
|
+
process.nextTick(() => {
|
|
8300
|
+
if (this.isGrpcErrorOutput(mockResult)) {
|
|
8301
|
+
const { error, status: errorStatus } = mockResult;
|
|
8302
|
+
const status = {
|
|
8303
|
+
code: errorStatus.code,
|
|
8304
|
+
details: errorStatus.details,
|
|
8305
|
+
metadata: deserializeGrpcMetadata(MetadataConstructor, errorStatus.metadata)
|
|
8306
|
+
};
|
|
8307
|
+
if (mockResult.metadata) stream.emit("metadata", deserializeGrpcMetadata(MetadataConstructor, mockResult.metadata));
|
|
8308
|
+
const errorObj = Object.assign(new Error(error.message), {
|
|
8309
|
+
name: error.name,
|
|
8310
|
+
stack: error.stack,
|
|
8311
|
+
...status
|
|
8312
|
+
});
|
|
8313
|
+
stream.emit("error", errorObj);
|
|
8314
|
+
stream.emit("status", status);
|
|
8315
|
+
stream.push(null);
|
|
8316
|
+
SpanUtils.addSpanAttributes(spanInfo.span, { outputValue: mockResult });
|
|
8317
|
+
SpanUtils.endSpan(spanInfo.span, {
|
|
8318
|
+
code: SpanStatusCode.ERROR,
|
|
8319
|
+
message: error.message
|
|
8320
|
+
});
|
|
8321
|
+
} else {
|
|
8322
|
+
const { body, status: successStatus } = mockResult;
|
|
8323
|
+
const status = {
|
|
8324
|
+
code: successStatus.code,
|
|
8325
|
+
details: successStatus.details,
|
|
8326
|
+
metadata: deserializeGrpcMetadata(MetadataConstructor, successStatus.metadata)
|
|
8327
|
+
};
|
|
8328
|
+
if (mockResult.metadata) stream.emit("metadata", deserializeGrpcMetadata(MetadataConstructor, mockResult.metadata));
|
|
8329
|
+
if (Array.isArray(body)) body.forEach((item) => {
|
|
8330
|
+
const bufferMapToUse = item.bufferMap || {};
|
|
8331
|
+
const jsonableStringMapToUse = item.jsonableStringMap || {};
|
|
8332
|
+
const realResponse = deserializeGrpcPayload(item.body, bufferMapToUse, jsonableStringMapToUse);
|
|
8333
|
+
stream.push(realResponse);
|
|
8334
|
+
});
|
|
8335
|
+
stream.push(null);
|
|
8336
|
+
stream.emit("status", status);
|
|
8337
|
+
SpanUtils.addSpanAttributes(spanInfo.span, { outputValue: mockResult });
|
|
8338
|
+
SpanUtils.endSpan(spanInfo.span, { code: SpanStatusCode.OK });
|
|
8339
|
+
}
|
|
8340
|
+
});
|
|
8341
|
+
}).catch((error) => {
|
|
8342
|
+
logger.error(`[GrpcInstrumentation] Error fetching mock data for server stream:`, error);
|
|
8343
|
+
process.nextTick(() => {
|
|
8344
|
+
stream.emit("error", error);
|
|
8345
|
+
stream.emit("status", {
|
|
8346
|
+
code: 2,
|
|
8347
|
+
details: error.message,
|
|
8348
|
+
metadata: new MetadataConstructor()
|
|
8349
|
+
});
|
|
8350
|
+
stream.push(null);
|
|
8351
|
+
});
|
|
8352
|
+
SpanUtils.endSpan(spanInfo.span, { code: SpanStatusCode.ERROR });
|
|
8353
|
+
});
|
|
8354
|
+
return stream;
|
|
8355
|
+
}
|
|
8356
|
+
_getWaitForReadyPatchFn() {
|
|
8357
|
+
const self = this;
|
|
8358
|
+
return (original) => {
|
|
8359
|
+
return function waitForReady(deadline, callback) {
|
|
8360
|
+
if (self.mode === TuskDriftMode.REPLAY) {
|
|
8361
|
+
process.nextTick(() => callback());
|
|
8362
|
+
return;
|
|
8363
|
+
} else return original.apply(this, [deadline, callback]);
|
|
8364
|
+
};
|
|
8365
|
+
};
|
|
8366
|
+
}
|
|
8367
|
+
_getClosePatchFn() {
|
|
8368
|
+
const self = this;
|
|
8369
|
+
return (original) => {
|
|
8370
|
+
return function close() {
|
|
8371
|
+
if (self.mode === TuskDriftMode.REPLAY) return;
|
|
8372
|
+
else return original.apply(this, arguments);
|
|
8373
|
+
};
|
|
8374
|
+
};
|
|
8375
|
+
}
|
|
8376
|
+
_getGetChannelPatchFn() {
|
|
8377
|
+
const self = this;
|
|
8378
|
+
return (original) => {
|
|
8379
|
+
return function getChannel() {
|
|
8380
|
+
if (self.mode === TuskDriftMode.REPLAY) return {};
|
|
8381
|
+
else return original.apply(this, arguments);
|
|
8382
|
+
};
|
|
8383
|
+
};
|
|
8384
|
+
}
|
|
8385
|
+
_wrap(target, propertyName, wrapper) {
|
|
8386
|
+
wrap(target, propertyName, wrapper);
|
|
8387
|
+
}
|
|
8388
|
+
};
|
|
8389
|
+
GrpcInstrumentation.metadataStore = /* @__PURE__ */ new Map();
|
|
8390
|
+
|
|
8391
|
+
//#endregion
|
|
8392
|
+
//#region src/instrumentation/libraries/firestore/mocks/TdFirestoreDocumentMock.ts
|
|
8393
|
+
/**
|
|
8394
|
+
* Mock Firestore DocumentSnapshot for replay mode
|
|
8395
|
+
* Mimics the Firestore DocumentSnapshot API
|
|
8396
|
+
*/
|
|
8397
|
+
var TdFirestoreDocumentMock = class {
|
|
8398
|
+
constructor(documentData) {
|
|
8399
|
+
this.documentData = documentData;
|
|
8400
|
+
}
|
|
8401
|
+
/**
|
|
8402
|
+
* The document's identifier within its collection
|
|
8403
|
+
*/
|
|
8404
|
+
get id() {
|
|
8405
|
+
return this.documentData.id;
|
|
8406
|
+
}
|
|
8407
|
+
/**
|
|
8408
|
+
* Whether the document exists
|
|
8409
|
+
*/
|
|
8410
|
+
get exists() {
|
|
8411
|
+
return this.documentData.exists;
|
|
8412
|
+
}
|
|
8413
|
+
/**
|
|
8414
|
+
* A DocumentReference to the document location
|
|
8415
|
+
*/
|
|
8416
|
+
get ref() {
|
|
8417
|
+
return {
|
|
8418
|
+
id: this.documentData.id,
|
|
8419
|
+
path: this.documentData.path
|
|
8420
|
+
};
|
|
8421
|
+
}
|
|
8422
|
+
/**
|
|
8423
|
+
* The time the document was created (if available)
|
|
8424
|
+
*/
|
|
8425
|
+
get createTime() {
|
|
8426
|
+
return this.documentData.createTime ? {
|
|
8427
|
+
seconds: this.documentData.createTime.seconds,
|
|
8428
|
+
nanoseconds: this.documentData.createTime.nanoseconds,
|
|
8429
|
+
toDate: () => /* @__PURE__ */ new Date(this.documentData.createTime.seconds * 1e3 + this.documentData.createTime.nanoseconds / 1e6)
|
|
8430
|
+
} : null;
|
|
8431
|
+
}
|
|
8432
|
+
/**
|
|
8433
|
+
* The time the document was last updated (if available)
|
|
8434
|
+
*/
|
|
8435
|
+
get updateTime() {
|
|
8436
|
+
return this.documentData.updateTime ? {
|
|
8437
|
+
seconds: this.documentData.updateTime.seconds,
|
|
8438
|
+
nanoseconds: this.documentData.updateTime.nanoseconds,
|
|
8439
|
+
toDate: () => /* @__PURE__ */ new Date(this.documentData.updateTime.seconds * 1e3 + this.documentData.updateTime.nanoseconds / 1e6)
|
|
8440
|
+
} : null;
|
|
8441
|
+
}
|
|
8442
|
+
/**
|
|
8443
|
+
* The time the document was read (if available)
|
|
8444
|
+
*/
|
|
8445
|
+
get readTime() {
|
|
8446
|
+
return this.documentData.readTime ? {
|
|
8447
|
+
seconds: this.documentData.readTime.seconds,
|
|
8448
|
+
nanoseconds: this.documentData.readTime.nanoseconds,
|
|
8449
|
+
toDate: () => /* @__PURE__ */ new Date(this.documentData.readTime.seconds * 1e3 + this.documentData.readTime.nanoseconds / 1e6)
|
|
8450
|
+
} : null;
|
|
8451
|
+
}
|
|
8452
|
+
/**
|
|
8453
|
+
* Retrieves all fields in the document as an object
|
|
8454
|
+
*/
|
|
8455
|
+
data() {
|
|
8456
|
+
return this.documentData.data;
|
|
8457
|
+
}
|
|
8458
|
+
/**
|
|
8459
|
+
* Retrieves the field specified by fieldPath
|
|
8460
|
+
*/
|
|
8461
|
+
get(fieldPath) {
|
|
8462
|
+
if (!this.documentData.data) return;
|
|
8463
|
+
return this.documentData.data[fieldPath];
|
|
8464
|
+
}
|
|
8465
|
+
/**
|
|
8466
|
+
* Returns true if the document's data and path are equal to the provided value
|
|
8467
|
+
*/
|
|
8468
|
+
isEqual(other) {
|
|
8469
|
+
return this.documentData.path === other.documentData.path;
|
|
8470
|
+
}
|
|
8471
|
+
};
|
|
8472
|
+
|
|
8473
|
+
//#endregion
|
|
8474
|
+
//#region src/instrumentation/libraries/firestore/mocks/TdFirestoreQueryMock.ts
|
|
8475
|
+
/**
|
|
8476
|
+
* Mock Firestore QuerySnapshot for replay mode
|
|
8477
|
+
* Mimics the Firestore QuerySnapshot API
|
|
8478
|
+
*/
|
|
8479
|
+
var TdFirestoreQueryMock = class {
|
|
8480
|
+
constructor(queryResult) {
|
|
8481
|
+
this.queryResult = queryResult;
|
|
8482
|
+
this._docs = queryResult.docs.map((doc) => new TdFirestoreDocumentMock(doc));
|
|
8483
|
+
}
|
|
8484
|
+
/**
|
|
8485
|
+
* An array of all the documents in the QuerySnapshot
|
|
8486
|
+
*/
|
|
8487
|
+
get docs() {
|
|
8488
|
+
return this._docs;
|
|
8489
|
+
}
|
|
8490
|
+
/**
|
|
8491
|
+
* The number of documents in the QuerySnapshot
|
|
8492
|
+
*/
|
|
8493
|
+
get size() {
|
|
8494
|
+
return this.queryResult.size;
|
|
8495
|
+
}
|
|
8496
|
+
/**
|
|
8497
|
+
* True if there are no documents in the QuerySnapshot
|
|
8498
|
+
*/
|
|
8499
|
+
get empty() {
|
|
8500
|
+
return this.queryResult.empty;
|
|
8501
|
+
}
|
|
8502
|
+
/**
|
|
8503
|
+
* The time the query snapshot was read
|
|
8504
|
+
*/
|
|
8505
|
+
get readTime() {
|
|
8506
|
+
return this.queryResult.readTime ? {
|
|
8507
|
+
seconds: this.queryResult.readTime.seconds,
|
|
8508
|
+
nanoseconds: this.queryResult.readTime.nanoseconds,
|
|
8509
|
+
toDate: () => /* @__PURE__ */ new Date(this.queryResult.readTime.seconds * 1e3 + this.queryResult.readTime.nanoseconds / 1e6)
|
|
8510
|
+
} : null;
|
|
8511
|
+
}
|
|
8512
|
+
/**
|
|
8513
|
+
* The query on which you called get or onSnapshot
|
|
8514
|
+
*/
|
|
8515
|
+
get query() {
|
|
8516
|
+
return {};
|
|
8517
|
+
}
|
|
8518
|
+
/**
|
|
8519
|
+
* Enumerates all of the documents in the QuerySnapshot
|
|
8520
|
+
*/
|
|
8521
|
+
forEach(callback, thisArg) {
|
|
8522
|
+
this._docs.forEach(callback, thisArg);
|
|
8523
|
+
}
|
|
8524
|
+
/**
|
|
8525
|
+
* Returns an array of the documents changes since the last snapshot
|
|
8526
|
+
*/
|
|
8527
|
+
docChanges() {
|
|
8528
|
+
return [];
|
|
8529
|
+
}
|
|
8530
|
+
/**
|
|
8531
|
+
* Returns true if the document data and path are equal to this QuerySnapshot
|
|
8532
|
+
*/
|
|
8533
|
+
isEqual(other) {
|
|
8534
|
+
if (this.size !== other.size) return false;
|
|
8535
|
+
return this.size === other.size && this.empty === other.empty;
|
|
8536
|
+
}
|
|
8537
|
+
};
|
|
8538
|
+
|
|
8539
|
+
//#endregion
|
|
8540
|
+
//#region src/instrumentation/libraries/firestore/mocks/TdFirestoreWriteResultMock.ts
|
|
8541
|
+
/**
|
|
8542
|
+
* Mock Firestore WriteResult for replay mode
|
|
8543
|
+
* Mimics the Firestore WriteResult API
|
|
8544
|
+
*/
|
|
8545
|
+
var TdFirestoreWriteResultMock = class {
|
|
8546
|
+
constructor(result) {
|
|
8547
|
+
this.result = result;
|
|
8548
|
+
}
|
|
8549
|
+
/**
|
|
8550
|
+
* The write time as reported by the server
|
|
8551
|
+
*/
|
|
8552
|
+
get writeTime() {
|
|
8553
|
+
return this.result.writeTime ? {
|
|
8554
|
+
seconds: this.result.writeTime.seconds,
|
|
8555
|
+
nanoseconds: this.result.writeTime.nanoseconds,
|
|
8556
|
+
toDate: () => /* @__PURE__ */ new Date(this.result.writeTime.seconds * 1e3 + this.result.writeTime.nanoseconds / 1e6)
|
|
8557
|
+
} : null;
|
|
8558
|
+
}
|
|
8559
|
+
/**
|
|
8560
|
+
* Returns true if this WriteResult is equal to the provided one
|
|
8561
|
+
*/
|
|
8562
|
+
isEqual(other) {
|
|
8563
|
+
if (!this.writeTime || !other.writeTime) return this.writeTime === other.writeTime;
|
|
8564
|
+
return this.writeTime.seconds === other.writeTime.seconds && this.writeTime.nanoseconds === other.writeTime.nanoseconds;
|
|
8565
|
+
}
|
|
8566
|
+
};
|
|
8567
|
+
|
|
8568
|
+
//#endregion
|
|
8569
|
+
//#region src/instrumentation/libraries/firestore/Instrumentation.ts
|
|
8570
|
+
const FIRESTORE_VERSION = "7.*";
|
|
8571
|
+
const PACKAGE_NAME = "@google-cloud/firestore";
|
|
8572
|
+
var FirestoreInstrumentation = class extends TdInstrumentationBase {
|
|
8573
|
+
constructor(config = {}) {
|
|
8574
|
+
super(PACKAGE_NAME, config);
|
|
8575
|
+
this.INSTRUMENTATION_NAME = "FirestoreInstrumentation";
|
|
8576
|
+
this.originalCollectionDocFn = null;
|
|
8577
|
+
this.mode = config.mode || TuskDriftMode.DISABLED;
|
|
8578
|
+
this.tuskDrift = TuskDriftCore.getInstance();
|
|
8579
|
+
}
|
|
8580
|
+
init() {
|
|
8581
|
+
return [new TdInstrumentationNodeModule({
|
|
8582
|
+
name: PACKAGE_NAME,
|
|
8583
|
+
supportedVersions: [FIRESTORE_VERSION],
|
|
8584
|
+
files: [
|
|
8585
|
+
new TdInstrumentationNodeModuleFile({
|
|
8586
|
+
name: "@google-cloud/firestore/build/src/reference/document-reference.js",
|
|
8587
|
+
supportedVersions: [FIRESTORE_VERSION],
|
|
8588
|
+
patch: (moduleExports) => this._patchDocumentReference(moduleExports)
|
|
8589
|
+
}),
|
|
8590
|
+
new TdInstrumentationNodeModuleFile({
|
|
8591
|
+
name: "@google-cloud/firestore/build/src/reference/collection-reference.js",
|
|
8592
|
+
supportedVersions: [FIRESTORE_VERSION],
|
|
8593
|
+
patch: (moduleExports) => this._patchCollectionReference(moduleExports)
|
|
8594
|
+
}),
|
|
8595
|
+
new TdInstrumentationNodeModuleFile({
|
|
8596
|
+
name: "@google-cloud/firestore/build/src/reference/query.js",
|
|
8597
|
+
supportedVersions: [FIRESTORE_VERSION],
|
|
8598
|
+
patch: (moduleExports) => this._patchQuery(moduleExports)
|
|
8599
|
+
})
|
|
8600
|
+
]
|
|
8601
|
+
})];
|
|
8602
|
+
}
|
|
8603
|
+
_patchDocumentReference(moduleExports) {
|
|
8604
|
+
logger.debug(`[FirestoreInstrumentation] Patching DocumentReference in ${this.mode} mode`);
|
|
8605
|
+
if (this.isModulePatched(moduleExports)) {
|
|
8606
|
+
logger.debug(`[FirestoreInstrumentation] DocumentReference already patched, skipping`);
|
|
8607
|
+
return moduleExports;
|
|
8608
|
+
}
|
|
8609
|
+
const DocumentReference = moduleExports.DocumentReference;
|
|
8610
|
+
if (!DocumentReference || !DocumentReference.prototype) {
|
|
8611
|
+
logger.warn(`[FirestoreInstrumentation] DocumentReference.prototype not found`);
|
|
8612
|
+
return moduleExports;
|
|
8613
|
+
}
|
|
8614
|
+
this._wrap(DocumentReference.prototype, "get", this._getDocumentGetPatchFn());
|
|
8615
|
+
this._wrap(DocumentReference.prototype, "create", this._getDocumentCreatePatchFn());
|
|
8616
|
+
this._wrap(DocumentReference.prototype, "set", this._getDocumentSetPatchFn());
|
|
8617
|
+
this._wrap(DocumentReference.prototype, "update", this._getDocumentUpdatePatchFn());
|
|
8618
|
+
this._wrap(DocumentReference.prototype, "delete", this._getDocumentDeletePatchFn());
|
|
8619
|
+
this.markModuleAsPatched(moduleExports);
|
|
8620
|
+
logger.debug(`[FirestoreInstrumentation] DocumentReference patching complete`);
|
|
8621
|
+
return moduleExports;
|
|
8622
|
+
}
|
|
8623
|
+
_patchCollectionReference(moduleExports) {
|
|
8624
|
+
logger.debug(`[FirestoreInstrumentation] Patching CollectionReference in ${this.mode} mode`);
|
|
8625
|
+
if (this.isModulePatched(moduleExports)) {
|
|
8626
|
+
logger.debug(`[FirestoreInstrumentation] CollectionReference already patched, skipping`);
|
|
8627
|
+
return moduleExports;
|
|
8628
|
+
}
|
|
8629
|
+
const CollectionReference = moduleExports.CollectionReference;
|
|
8630
|
+
if (!CollectionReference || !CollectionReference.prototype) {
|
|
8631
|
+
logger.warn(`[FirestoreInstrumentation] CollectionReference.prototype not found`);
|
|
8632
|
+
return moduleExports;
|
|
8633
|
+
}
|
|
8634
|
+
this.originalCollectionDocFn = CollectionReference.prototype.doc;
|
|
8635
|
+
this._wrap(CollectionReference.prototype, "add", this._getCollectionAddPatchFn());
|
|
8636
|
+
this._wrap(CollectionReference.prototype, "doc", this._getCollectionDocPatchFn());
|
|
8637
|
+
this.markModuleAsPatched(moduleExports);
|
|
8638
|
+
logger.debug(`[FirestoreInstrumentation] CollectionReference patching complete`);
|
|
8639
|
+
return moduleExports;
|
|
8640
|
+
}
|
|
8641
|
+
_patchQuery(moduleExports) {
|
|
8642
|
+
logger.debug(`[FirestoreInstrumentation] Patching Query in ${this.mode} mode`);
|
|
8643
|
+
if (this.isModulePatched(moduleExports)) {
|
|
8644
|
+
logger.debug(`[FirestoreInstrumentation] Query already patched, skipping`);
|
|
8645
|
+
return moduleExports;
|
|
8646
|
+
}
|
|
8647
|
+
const Query = moduleExports.Query;
|
|
8648
|
+
if (!Query || !Query.prototype) {
|
|
8649
|
+
logger.warn(`[FirestoreInstrumentation] Query.prototype not found`);
|
|
8650
|
+
return moduleExports;
|
|
8651
|
+
}
|
|
8652
|
+
this._wrap(Query.prototype, "get", this._getQueryGetPatchFn());
|
|
8653
|
+
this.markModuleAsPatched(moduleExports);
|
|
8654
|
+
logger.debug(`[FirestoreInstrumentation] Query patching complete`);
|
|
8655
|
+
return moduleExports;
|
|
8656
|
+
}
|
|
8657
|
+
_getDocumentGetPatchFn() {
|
|
8658
|
+
const self = this;
|
|
8659
|
+
return (originalGet) => {
|
|
8660
|
+
return function() {
|
|
8661
|
+
const inputValue = {
|
|
8662
|
+
operation: "document.get",
|
|
8663
|
+
path: this.path
|
|
8664
|
+
};
|
|
8665
|
+
if (self.mode === TuskDriftMode.REPLAY) return handleReplayMode({ replayModeHandler: () => {
|
|
8666
|
+
return SpanUtils.createAndExecuteSpan(self.mode, () => originalGet.call(this), {
|
|
8667
|
+
name: "firestore.document.get",
|
|
8668
|
+
kind: SpanKind.CLIENT,
|
|
8669
|
+
submodule: "document",
|
|
8670
|
+
packageType: PackageType.FIRESTORE,
|
|
8671
|
+
packageName: PACKAGE_NAME,
|
|
8672
|
+
instrumentationName: self.INSTRUMENTATION_NAME,
|
|
8673
|
+
inputValue,
|
|
8674
|
+
isPreAppStart: false,
|
|
8675
|
+
stopRecordingChildSpans: true
|
|
8676
|
+
}, (spanInfo) => {
|
|
8677
|
+
return self._handleReplayDocumentGet(spanInfo, inputValue);
|
|
8678
|
+
});
|
|
8679
|
+
} });
|
|
8680
|
+
else if (self.mode === TuskDriftMode.RECORD) return handleRecordMode({
|
|
8681
|
+
originalFunctionCall: () => originalGet.call(this),
|
|
8682
|
+
recordModeHandler: ({ isPreAppStart }) => {
|
|
8683
|
+
return SpanUtils.createAndExecuteSpan(self.mode, () => originalGet.call(this), {
|
|
8684
|
+
name: "firestore.document.get",
|
|
8685
|
+
kind: SpanKind.CLIENT,
|
|
8686
|
+
submodule: "document",
|
|
8687
|
+
packageType: PackageType.FIRESTORE,
|
|
8688
|
+
packageName: PACKAGE_NAME,
|
|
8689
|
+
instrumentationName: self.INSTRUMENTATION_NAME,
|
|
8690
|
+
inputValue,
|
|
8691
|
+
isPreAppStart,
|
|
8692
|
+
stopRecordingChildSpans: true
|
|
8693
|
+
}, (spanInfo) => {
|
|
8694
|
+
return self._handleRecordDocumentGet(spanInfo, originalGet, this);
|
|
8695
|
+
});
|
|
8696
|
+
},
|
|
8697
|
+
spanKind: SpanKind.CLIENT
|
|
8698
|
+
});
|
|
8699
|
+
else return originalGet.call(this);
|
|
8700
|
+
};
|
|
8701
|
+
};
|
|
8702
|
+
}
|
|
8703
|
+
async _handleRecordDocumentGet(spanInfo, originalGet, context$1) {
|
|
8704
|
+
const snapshot = await originalGet.call(context$1);
|
|
8705
|
+
const documentResult = {
|
|
8706
|
+
id: snapshot.id,
|
|
8707
|
+
path: snapshot.ref.path,
|
|
8708
|
+
exists: snapshot.exists,
|
|
8709
|
+
data: snapshot.exists ? snapshot.data() : void 0,
|
|
8710
|
+
createTime: snapshot.createTime ? {
|
|
8711
|
+
seconds: snapshot.createTime.seconds,
|
|
8712
|
+
nanoseconds: snapshot.createTime.nanoseconds
|
|
8713
|
+
} : void 0,
|
|
8714
|
+
updateTime: snapshot.updateTime ? {
|
|
8715
|
+
seconds: snapshot.updateTime.seconds,
|
|
8716
|
+
nanoseconds: snapshot.updateTime.nanoseconds
|
|
8717
|
+
} : void 0,
|
|
8718
|
+
readTime: snapshot.readTime ? {
|
|
8719
|
+
seconds: snapshot.readTime.seconds,
|
|
8720
|
+
nanoseconds: snapshot.readTime.nanoseconds
|
|
8721
|
+
} : void 0
|
|
8722
|
+
};
|
|
8723
|
+
try {
|
|
8724
|
+
SpanUtils.addSpanAttributes(spanInfo.span, { outputValue: documentResult });
|
|
8725
|
+
SpanUtils.endSpan(spanInfo.span, { code: SpanStatusCode.OK });
|
|
8726
|
+
} catch {
|
|
8727
|
+
logger.error(`[FirestoreInstrumentation] Error updating span attributes for document.get`);
|
|
8728
|
+
}
|
|
8729
|
+
return snapshot;
|
|
8730
|
+
}
|
|
8731
|
+
async _handleReplayDocumentGet(spanInfo, inputValue) {
|
|
8732
|
+
logger.debug(`[FirestoreInstrumentation] Replaying document.get`);
|
|
8733
|
+
const mockData = await findMockResponseAsync({
|
|
8734
|
+
mockRequestData: {
|
|
8735
|
+
traceId: spanInfo.traceId,
|
|
8736
|
+
spanId: spanInfo.spanId,
|
|
8737
|
+
name: "firestore.document.get",
|
|
8738
|
+
inputValue: createMockInputValue(inputValue),
|
|
8739
|
+
packageName: PACKAGE_NAME,
|
|
8740
|
+
instrumentationName: this.INSTRUMENTATION_NAME,
|
|
8741
|
+
submoduleName: "document",
|
|
8742
|
+
kind: SpanKind.CLIENT
|
|
8743
|
+
},
|
|
8744
|
+
tuskDrift: this.tuskDrift
|
|
8745
|
+
});
|
|
8746
|
+
if (!mockData) {
|
|
8747
|
+
logger.warn(`[FirestoreInstrumentation] No mock data found for document.get: ${inputValue.path}`);
|
|
8748
|
+
return Promise.reject(/* @__PURE__ */ new Error("No mock data found"));
|
|
8749
|
+
}
|
|
8750
|
+
const documentResult = mockData.result;
|
|
8751
|
+
return new TdFirestoreDocumentMock(documentResult);
|
|
8752
|
+
}
|
|
8753
|
+
_getDocumentCreatePatchFn() {
|
|
8754
|
+
const self = this;
|
|
8755
|
+
return (originalCreate) => {
|
|
8756
|
+
return function(data) {
|
|
8757
|
+
const inputValue = {
|
|
8758
|
+
operation: "document.create",
|
|
8759
|
+
path: this.path,
|
|
8760
|
+
data
|
|
8761
|
+
};
|
|
8762
|
+
if (self.mode === TuskDriftMode.REPLAY) return handleReplayMode({ replayModeHandler: () => {
|
|
8763
|
+
return SpanUtils.createAndExecuteSpan(self.mode, () => originalCreate.call(this, data), {
|
|
8764
|
+
name: "firestore.document.create",
|
|
8765
|
+
kind: SpanKind.CLIENT,
|
|
8766
|
+
submodule: "document",
|
|
8767
|
+
packageType: PackageType.FIRESTORE,
|
|
8768
|
+
packageName: PACKAGE_NAME,
|
|
8769
|
+
instrumentationName: self.INSTRUMENTATION_NAME,
|
|
8770
|
+
inputValue,
|
|
8771
|
+
isPreAppStart: false,
|
|
8772
|
+
stopRecordingChildSpans: true
|
|
8773
|
+
}, (spanInfo) => {
|
|
8774
|
+
return self._handleReplayDocumentWrite(spanInfo, inputValue);
|
|
8775
|
+
});
|
|
8776
|
+
} });
|
|
8777
|
+
else if (self.mode === TuskDriftMode.RECORD) return handleRecordMode({
|
|
8778
|
+
originalFunctionCall: () => originalCreate.call(this, data),
|
|
8779
|
+
recordModeHandler: ({ isPreAppStart }) => {
|
|
8780
|
+
return SpanUtils.createAndExecuteSpan(self.mode, () => originalCreate.call(this, data), {
|
|
8781
|
+
name: "firestore.document.create",
|
|
8782
|
+
kind: SpanKind.CLIENT,
|
|
8783
|
+
submodule: "document",
|
|
8784
|
+
packageType: PackageType.FIRESTORE,
|
|
8785
|
+
packageName: PACKAGE_NAME,
|
|
8786
|
+
instrumentationName: self.INSTRUMENTATION_NAME,
|
|
8787
|
+
inputValue,
|
|
8788
|
+
isPreAppStart,
|
|
8789
|
+
stopRecordingChildSpans: true
|
|
8790
|
+
}, (spanInfo) => {
|
|
8791
|
+
return self._handleRecordDocumentWrite(spanInfo, originalCreate, this, data);
|
|
8792
|
+
});
|
|
8793
|
+
},
|
|
8794
|
+
spanKind: SpanKind.CLIENT
|
|
8795
|
+
});
|
|
8796
|
+
else return originalCreate.call(this, data);
|
|
8797
|
+
};
|
|
8798
|
+
};
|
|
8799
|
+
}
|
|
8800
|
+
_getDocumentSetPatchFn() {
|
|
8801
|
+
const self = this;
|
|
8802
|
+
return (originalSet) => {
|
|
8803
|
+
return function(data, options) {
|
|
8804
|
+
const inputValue = {
|
|
8805
|
+
operation: "document.set",
|
|
8806
|
+
path: this.path,
|
|
8807
|
+
data,
|
|
8808
|
+
options
|
|
8809
|
+
};
|
|
8810
|
+
if (self.mode === TuskDriftMode.REPLAY) return handleReplayMode({ replayModeHandler: () => {
|
|
8811
|
+
return SpanUtils.createAndExecuteSpan(self.mode, () => originalSet.call(this, data, options), {
|
|
8812
|
+
name: "firestore.document.set",
|
|
8813
|
+
kind: SpanKind.CLIENT,
|
|
8814
|
+
submodule: "document",
|
|
8815
|
+
packageType: PackageType.FIRESTORE,
|
|
8816
|
+
packageName: PACKAGE_NAME,
|
|
8817
|
+
instrumentationName: self.INSTRUMENTATION_NAME,
|
|
8818
|
+
inputValue,
|
|
8819
|
+
isPreAppStart: false,
|
|
8820
|
+
stopRecordingChildSpans: true
|
|
8821
|
+
}, (spanInfo) => {
|
|
8822
|
+
return self._handleReplayDocumentWrite(spanInfo, inputValue);
|
|
8823
|
+
});
|
|
8824
|
+
} });
|
|
8825
|
+
else if (self.mode === TuskDriftMode.RECORD) return handleRecordMode({
|
|
8826
|
+
originalFunctionCall: () => originalSet.call(this, data, options),
|
|
8827
|
+
recordModeHandler: ({ isPreAppStart }) => {
|
|
8828
|
+
return SpanUtils.createAndExecuteSpan(self.mode, () => originalSet.call(this, data, options), {
|
|
8829
|
+
name: "firestore.document.set",
|
|
8830
|
+
kind: SpanKind.CLIENT,
|
|
8831
|
+
submodule: "document",
|
|
8832
|
+
packageType: PackageType.FIRESTORE,
|
|
8833
|
+
packageName: PACKAGE_NAME,
|
|
8834
|
+
instrumentationName: self.INSTRUMENTATION_NAME,
|
|
8835
|
+
inputValue,
|
|
8836
|
+
isPreAppStart,
|
|
8837
|
+
stopRecordingChildSpans: true
|
|
8838
|
+
}, (spanInfo) => {
|
|
8839
|
+
return self._handleRecordDocumentWrite(spanInfo, originalSet, this, data, options);
|
|
8840
|
+
});
|
|
8841
|
+
},
|
|
8842
|
+
spanKind: SpanKind.CLIENT
|
|
8843
|
+
});
|
|
8844
|
+
else return originalSet.call(this, data, options);
|
|
8845
|
+
};
|
|
8846
|
+
};
|
|
8847
|
+
}
|
|
8848
|
+
_getDocumentUpdatePatchFn() {
|
|
8849
|
+
const self = this;
|
|
8850
|
+
return (originalUpdate) => {
|
|
8851
|
+
return function(...args) {
|
|
8852
|
+
const inputValue = {
|
|
8853
|
+
operation: "document.update",
|
|
8854
|
+
path: this.path,
|
|
8855
|
+
data: args.length === 1 ? args[0] : args
|
|
8856
|
+
};
|
|
8857
|
+
if (self.mode === TuskDriftMode.REPLAY) return handleReplayMode({ replayModeHandler: () => {
|
|
8858
|
+
return SpanUtils.createAndExecuteSpan(self.mode, () => originalUpdate.apply(this, args), {
|
|
8859
|
+
name: "firestore.document.update",
|
|
8860
|
+
kind: SpanKind.CLIENT,
|
|
8861
|
+
submodule: "document",
|
|
8862
|
+
packageType: PackageType.FIRESTORE,
|
|
8863
|
+
packageName: PACKAGE_NAME,
|
|
8864
|
+
instrumentationName: self.INSTRUMENTATION_NAME,
|
|
8865
|
+
inputValue,
|
|
8866
|
+
isPreAppStart: false,
|
|
8867
|
+
stopRecordingChildSpans: true
|
|
8868
|
+
}, (spanInfo) => {
|
|
8869
|
+
return self._handleReplayDocumentWrite(spanInfo, inputValue);
|
|
8870
|
+
});
|
|
8871
|
+
} });
|
|
8872
|
+
else if (self.mode === TuskDriftMode.RECORD) return handleRecordMode({
|
|
8873
|
+
originalFunctionCall: () => originalUpdate.apply(this, args),
|
|
8874
|
+
recordModeHandler: ({ isPreAppStart }) => {
|
|
8875
|
+
return SpanUtils.createAndExecuteSpan(self.mode, () => originalUpdate.apply(this, args), {
|
|
8876
|
+
name: "firestore.document.update",
|
|
8877
|
+
kind: SpanKind.CLIENT,
|
|
8878
|
+
submodule: "document",
|
|
8879
|
+
packageType: PackageType.FIRESTORE,
|
|
8880
|
+
packageName: PACKAGE_NAME,
|
|
8881
|
+
instrumentationName: self.INSTRUMENTATION_NAME,
|
|
8882
|
+
inputValue,
|
|
8883
|
+
isPreAppStart,
|
|
8884
|
+
stopRecordingChildSpans: true
|
|
8885
|
+
}, (spanInfo) => {
|
|
8886
|
+
return self._handleRecordDocumentWrite(spanInfo, originalUpdate, this, ...args);
|
|
8887
|
+
});
|
|
8888
|
+
},
|
|
8889
|
+
spanKind: SpanKind.CLIENT
|
|
8890
|
+
});
|
|
8891
|
+
else return originalUpdate.apply(this, args);
|
|
7902
8892
|
};
|
|
7903
8893
|
};
|
|
7904
8894
|
}
|
|
7905
|
-
|
|
8895
|
+
_getDocumentDeletePatchFn() {
|
|
7906
8896
|
const self = this;
|
|
7907
|
-
return (
|
|
7908
|
-
return function
|
|
7909
|
-
|
|
7910
|
-
|
|
8897
|
+
return (originalDelete) => {
|
|
8898
|
+
return function(precondition) {
|
|
8899
|
+
const inputValue = {
|
|
8900
|
+
operation: "document.delete",
|
|
8901
|
+
path: this.path,
|
|
8902
|
+
options: precondition
|
|
8903
|
+
};
|
|
8904
|
+
if (self.mode === TuskDriftMode.REPLAY) return handleReplayMode({ replayModeHandler: () => {
|
|
8905
|
+
return SpanUtils.createAndExecuteSpan(self.mode, () => originalDelete.call(this, precondition), {
|
|
8906
|
+
name: "firestore.document.delete",
|
|
8907
|
+
kind: SpanKind.CLIENT,
|
|
8908
|
+
submodule: "document",
|
|
8909
|
+
packageType: PackageType.FIRESTORE,
|
|
8910
|
+
packageName: PACKAGE_NAME,
|
|
8911
|
+
instrumentationName: self.INSTRUMENTATION_NAME,
|
|
8912
|
+
inputValue,
|
|
8913
|
+
isPreAppStart: false,
|
|
8914
|
+
stopRecordingChildSpans: true
|
|
8915
|
+
}, (spanInfo) => {
|
|
8916
|
+
return self._handleReplayDocumentWrite(spanInfo, inputValue);
|
|
8917
|
+
});
|
|
8918
|
+
} });
|
|
8919
|
+
else if (self.mode === TuskDriftMode.RECORD) return handleRecordMode({
|
|
8920
|
+
originalFunctionCall: () => originalDelete.call(this, precondition),
|
|
8921
|
+
recordModeHandler: ({ isPreAppStart }) => {
|
|
8922
|
+
return SpanUtils.createAndExecuteSpan(self.mode, () => originalDelete.call(this, precondition), {
|
|
8923
|
+
name: "firestore.document.delete",
|
|
8924
|
+
kind: SpanKind.CLIENT,
|
|
8925
|
+
submodule: "document",
|
|
8926
|
+
packageType: PackageType.FIRESTORE,
|
|
8927
|
+
packageName: PACKAGE_NAME,
|
|
8928
|
+
instrumentationName: self.INSTRUMENTATION_NAME,
|
|
8929
|
+
inputValue,
|
|
8930
|
+
isPreAppStart,
|
|
8931
|
+
stopRecordingChildSpans: true
|
|
8932
|
+
}, (spanInfo) => {
|
|
8933
|
+
return self._handleRecordDocumentWrite(spanInfo, originalDelete, this, precondition);
|
|
8934
|
+
});
|
|
8935
|
+
},
|
|
8936
|
+
spanKind: SpanKind.CLIENT
|
|
8937
|
+
});
|
|
8938
|
+
else return originalDelete.call(this, precondition);
|
|
7911
8939
|
};
|
|
7912
8940
|
};
|
|
7913
8941
|
}
|
|
7914
|
-
|
|
8942
|
+
async _handleRecordDocumentWrite(spanInfo, originalWrite, context$1, ...args) {
|
|
8943
|
+
const writeResult = await originalWrite.apply(context$1, args);
|
|
8944
|
+
const result = { writeTime: writeResult.writeTime ? {
|
|
8945
|
+
seconds: writeResult.writeTime.seconds,
|
|
8946
|
+
nanoseconds: writeResult.writeTime.nanoseconds
|
|
8947
|
+
} : void 0 };
|
|
8948
|
+
try {
|
|
8949
|
+
SpanUtils.addSpanAttributes(spanInfo.span, { outputValue: result });
|
|
8950
|
+
SpanUtils.endSpan(spanInfo.span, { code: SpanStatusCode.OK });
|
|
8951
|
+
} catch {
|
|
8952
|
+
logger.error(`[FirestoreInstrumentation] Error updating span attributes for document.write`);
|
|
8953
|
+
}
|
|
8954
|
+
return writeResult;
|
|
8955
|
+
}
|
|
8956
|
+
async _handleReplayDocumentWrite(spanInfo, inputValue) {
|
|
8957
|
+
logger.debug(`[FirestoreInstrumentation] Replaying document write: ${inputValue.operation}`);
|
|
8958
|
+
const mockData = await findMockResponseAsync({
|
|
8959
|
+
mockRequestData: {
|
|
8960
|
+
traceId: spanInfo.traceId,
|
|
8961
|
+
spanId: spanInfo.spanId,
|
|
8962
|
+
name: `firestore.${inputValue.operation}`,
|
|
8963
|
+
inputValue: createMockInputValue(inputValue),
|
|
8964
|
+
packageName: PACKAGE_NAME,
|
|
8965
|
+
instrumentationName: this.INSTRUMENTATION_NAME,
|
|
8966
|
+
submoduleName: "document",
|
|
8967
|
+
kind: SpanKind.CLIENT
|
|
8968
|
+
},
|
|
8969
|
+
tuskDrift: this.tuskDrift
|
|
8970
|
+
});
|
|
8971
|
+
if (!mockData) {
|
|
8972
|
+
logger.warn(`[FirestoreInstrumentation] No mock data found for ${inputValue.operation}: ${inputValue.path}`);
|
|
8973
|
+
return Promise.reject(/* @__PURE__ */ new Error("No mock data found"));
|
|
8974
|
+
}
|
|
8975
|
+
const writeResult = mockData.result;
|
|
8976
|
+
return new TdFirestoreWriteResultMock(writeResult);
|
|
8977
|
+
}
|
|
8978
|
+
_getCollectionAddPatchFn() {
|
|
7915
8979
|
const self = this;
|
|
7916
|
-
return (
|
|
7917
|
-
return function
|
|
7918
|
-
|
|
7919
|
-
|
|
8980
|
+
return (originalAdd) => {
|
|
8981
|
+
return function(data) {
|
|
8982
|
+
const inputValue = {
|
|
8983
|
+
operation: "collection.add",
|
|
8984
|
+
path: this.path,
|
|
8985
|
+
data
|
|
8986
|
+
};
|
|
8987
|
+
if (self.mode === TuskDriftMode.REPLAY) return handleReplayMode({ replayModeHandler: () => {
|
|
8988
|
+
return SpanUtils.createAndExecuteSpan(self.mode, () => originalAdd.call(this, data), {
|
|
8989
|
+
name: "firestore.collection.add",
|
|
8990
|
+
kind: SpanKind.CLIENT,
|
|
8991
|
+
submodule: "collection",
|
|
8992
|
+
packageType: PackageType.FIRESTORE,
|
|
8993
|
+
packageName: PACKAGE_NAME,
|
|
8994
|
+
instrumentationName: self.INSTRUMENTATION_NAME,
|
|
8995
|
+
inputValue,
|
|
8996
|
+
isPreAppStart: false,
|
|
8997
|
+
stopRecordingChildSpans: true
|
|
8998
|
+
}, (spanInfo) => {
|
|
8999
|
+
return self._handleReplayCollectionAdd(spanInfo, inputValue, this);
|
|
9000
|
+
});
|
|
9001
|
+
} });
|
|
9002
|
+
else if (self.mode === TuskDriftMode.RECORD) return handleRecordMode({
|
|
9003
|
+
originalFunctionCall: () => originalAdd.call(this, data),
|
|
9004
|
+
recordModeHandler: ({ isPreAppStart }) => {
|
|
9005
|
+
return SpanUtils.createAndExecuteSpan(self.mode, () => originalAdd.call(this, data), {
|
|
9006
|
+
name: "firestore.collection.add",
|
|
9007
|
+
kind: SpanKind.CLIENT,
|
|
9008
|
+
submodule: "collection",
|
|
9009
|
+
packageType: PackageType.FIRESTORE,
|
|
9010
|
+
packageName: PACKAGE_NAME,
|
|
9011
|
+
instrumentationName: self.INSTRUMENTATION_NAME,
|
|
9012
|
+
inputValue,
|
|
9013
|
+
isPreAppStart,
|
|
9014
|
+
stopRecordingChildSpans: true
|
|
9015
|
+
}, (spanInfo) => {
|
|
9016
|
+
return self._handleRecordCollectionAdd(spanInfo, originalAdd, this, data);
|
|
9017
|
+
});
|
|
9018
|
+
},
|
|
9019
|
+
spanKind: SpanKind.CLIENT
|
|
9020
|
+
});
|
|
9021
|
+
else return originalAdd.call(this, data);
|
|
9022
|
+
};
|
|
9023
|
+
};
|
|
9024
|
+
}
|
|
9025
|
+
async _handleRecordCollectionAdd(spanInfo, originalAdd, context$1, data) {
|
|
9026
|
+
const docRef = await originalAdd.call(context$1, data);
|
|
9027
|
+
const result = {
|
|
9028
|
+
id: docRef.id,
|
|
9029
|
+
path: docRef.path
|
|
9030
|
+
};
|
|
9031
|
+
try {
|
|
9032
|
+
SpanUtils.addSpanAttributes(spanInfo.span, { outputValue: result });
|
|
9033
|
+
SpanUtils.endSpan(spanInfo.span, { code: SpanStatusCode.OK });
|
|
9034
|
+
} catch {
|
|
9035
|
+
logger.error(`[FirestoreInstrumentation] Error updating span attributes for collection.add`);
|
|
9036
|
+
}
|
|
9037
|
+
return docRef;
|
|
9038
|
+
}
|
|
9039
|
+
async _handleReplayCollectionAdd(spanInfo, inputValue, collectionRef) {
|
|
9040
|
+
logger.debug(`[FirestoreInstrumentation] Replaying collection.add`);
|
|
9041
|
+
const mockData = await findMockResponseAsync({
|
|
9042
|
+
mockRequestData: {
|
|
9043
|
+
traceId: spanInfo.traceId,
|
|
9044
|
+
spanId: spanInfo.spanId,
|
|
9045
|
+
name: "firestore.collection.add",
|
|
9046
|
+
inputValue: createMockInputValue(inputValue),
|
|
9047
|
+
packageName: PACKAGE_NAME,
|
|
9048
|
+
instrumentationName: this.INSTRUMENTATION_NAME,
|
|
9049
|
+
submoduleName: "collection",
|
|
9050
|
+
kind: SpanKind.CLIENT
|
|
9051
|
+
},
|
|
9052
|
+
tuskDrift: this.tuskDrift
|
|
9053
|
+
});
|
|
9054
|
+
if (!mockData) {
|
|
9055
|
+
logger.warn(`[FirestoreInstrumentation] No mock data found for collection.add: ${inputValue.path}`);
|
|
9056
|
+
return Promise.reject(/* @__PURE__ */ new Error("No mock data found"));
|
|
9057
|
+
}
|
|
9058
|
+
const recordedId = mockData.result.id;
|
|
9059
|
+
if (!this.originalCollectionDocFn) {
|
|
9060
|
+
logger.error(`[FirestoreInstrumentation] Original doc function not available`);
|
|
9061
|
+
return Promise.reject(/* @__PURE__ */ new Error("Original doc function not available"));
|
|
9062
|
+
}
|
|
9063
|
+
return this.originalCollectionDocFn.call(collectionRef, recordedId);
|
|
9064
|
+
}
|
|
9065
|
+
_getCollectionDocPatchFn() {
|
|
9066
|
+
const self = this;
|
|
9067
|
+
return (originalDoc) => {
|
|
9068
|
+
return function(documentPath) {
|
|
9069
|
+
const collectionPath = this.path;
|
|
9070
|
+
const inputValue = {
|
|
9071
|
+
operation: "collection.doc",
|
|
9072
|
+
path: collectionPath,
|
|
9073
|
+
documentId: documentPath
|
|
9074
|
+
};
|
|
9075
|
+
if (self.mode === TuskDriftMode.REPLAY) return handleReplayMode({ replayModeHandler: () => {
|
|
9076
|
+
return SpanUtils.createAndExecuteSpan(self.mode, () => documentPath ? originalDoc.call(this, documentPath) : originalDoc.call(this), {
|
|
9077
|
+
name: "firestore.collection.doc",
|
|
9078
|
+
kind: SpanKind.CLIENT,
|
|
9079
|
+
submodule: "collection",
|
|
9080
|
+
packageType: PackageType.FIRESTORE,
|
|
9081
|
+
packageName: PACKAGE_NAME,
|
|
9082
|
+
instrumentationName: self.INSTRUMENTATION_NAME,
|
|
9083
|
+
inputValue,
|
|
9084
|
+
isPreAppStart: false,
|
|
9085
|
+
stopRecordingChildSpans: true
|
|
9086
|
+
}, (spanInfo) => {
|
|
9087
|
+
const mockData = findMockResponseSync({
|
|
9088
|
+
mockRequestData: {
|
|
9089
|
+
traceId: spanInfo.traceId,
|
|
9090
|
+
spanId: spanInfo.spanId,
|
|
9091
|
+
name: "firestore.collection.doc",
|
|
9092
|
+
inputValue: createMockInputValue(inputValue),
|
|
9093
|
+
packageName: PACKAGE_NAME,
|
|
9094
|
+
instrumentationName: self.INSTRUMENTATION_NAME,
|
|
9095
|
+
submoduleName: "collection",
|
|
9096
|
+
kind: SpanKind.CLIENT
|
|
9097
|
+
},
|
|
9098
|
+
tuskDrift: self.tuskDrift
|
|
9099
|
+
});
|
|
9100
|
+
if (!mockData) {
|
|
9101
|
+
logger.warn(`[FirestoreInstrumentation] No mock data found for collection.doc: ${collectionPath}`);
|
|
9102
|
+
throw new Error("No mock data found for collection.doc");
|
|
9103
|
+
}
|
|
9104
|
+
const recordedId = mockData.result.id;
|
|
9105
|
+
logger.debug(`[FirestoreInstrumentation] replaying doc call with recorded id: ${recordedId}`);
|
|
9106
|
+
const docRef = originalDoc.call(this, recordedId);
|
|
9107
|
+
logger.debug(`[FirestoreInstrumentation] doc ref, id`, docRef, recordedId);
|
|
9108
|
+
return docRef;
|
|
9109
|
+
});
|
|
9110
|
+
} });
|
|
9111
|
+
else if (self.mode === TuskDriftMode.RECORD) return handleRecordMode({
|
|
9112
|
+
originalFunctionCall: () => documentPath ? originalDoc.call(this, documentPath) : originalDoc.call(this),
|
|
9113
|
+
recordModeHandler: ({ isPreAppStart }) => {
|
|
9114
|
+
return SpanUtils.createAndExecuteSpan(self.mode, () => documentPath ? originalDoc.call(this, documentPath) : originalDoc.call(this), {
|
|
9115
|
+
name: "firestore.collection.doc",
|
|
9116
|
+
kind: SpanKind.CLIENT,
|
|
9117
|
+
submodule: "collection",
|
|
9118
|
+
packageType: PackageType.FIRESTORE,
|
|
9119
|
+
packageName: PACKAGE_NAME,
|
|
9120
|
+
instrumentationName: self.INSTRUMENTATION_NAME,
|
|
9121
|
+
inputValue,
|
|
9122
|
+
isPreAppStart,
|
|
9123
|
+
stopRecordingChildSpans: true
|
|
9124
|
+
}, (spanInfo) => {
|
|
9125
|
+
const docRef = documentPath ? originalDoc.call(this, documentPath) : originalDoc.call(this);
|
|
9126
|
+
const result = {
|
|
9127
|
+
id: docRef.id,
|
|
9128
|
+
path: docRef.path
|
|
9129
|
+
};
|
|
9130
|
+
try {
|
|
9131
|
+
SpanUtils.addSpanAttributes(spanInfo.span, { outputValue: result });
|
|
9132
|
+
SpanUtils.endSpan(spanInfo.span, { code: SpanStatusCode.OK });
|
|
9133
|
+
} catch {
|
|
9134
|
+
logger.error(`[FirestoreInstrumentation] Error updating span attributes for collection.doc`);
|
|
9135
|
+
}
|
|
9136
|
+
return docRef;
|
|
9137
|
+
});
|
|
9138
|
+
},
|
|
9139
|
+
spanKind: SpanKind.CLIENT
|
|
9140
|
+
});
|
|
9141
|
+
else return documentPath ? originalDoc.call(this, documentPath) : originalDoc.call(this);
|
|
9142
|
+
};
|
|
9143
|
+
};
|
|
9144
|
+
}
|
|
9145
|
+
_getQueryGetPatchFn() {
|
|
9146
|
+
const self = this;
|
|
9147
|
+
return (originalGet) => {
|
|
9148
|
+
return function() {
|
|
9149
|
+
const inputValue = {
|
|
9150
|
+
operation: "query.get",
|
|
9151
|
+
path: this._queryOptions?.parentPath?.formattedName || "unknown"
|
|
9152
|
+
};
|
|
9153
|
+
if (self.mode === TuskDriftMode.REPLAY) return handleReplayMode({ replayModeHandler: () => {
|
|
9154
|
+
return SpanUtils.createAndExecuteSpan(self.mode, () => originalGet.call(this), {
|
|
9155
|
+
name: "firestore.query.get",
|
|
9156
|
+
kind: SpanKind.CLIENT,
|
|
9157
|
+
submodule: "query",
|
|
9158
|
+
packageType: PackageType.FIRESTORE,
|
|
9159
|
+
packageName: PACKAGE_NAME,
|
|
9160
|
+
instrumentationName: self.INSTRUMENTATION_NAME,
|
|
9161
|
+
inputValue,
|
|
9162
|
+
isPreAppStart: false,
|
|
9163
|
+
stopRecordingChildSpans: true
|
|
9164
|
+
}, (spanInfo) => {
|
|
9165
|
+
return self._handleReplayQueryGet(spanInfo, inputValue);
|
|
9166
|
+
});
|
|
9167
|
+
} });
|
|
9168
|
+
else if (self.mode === TuskDriftMode.RECORD) return handleRecordMode({
|
|
9169
|
+
originalFunctionCall: () => originalGet.call(this),
|
|
9170
|
+
recordModeHandler: ({ isPreAppStart }) => {
|
|
9171
|
+
return SpanUtils.createAndExecuteSpan(self.mode, () => originalGet.call(this), {
|
|
9172
|
+
name: "firestore.query.get",
|
|
9173
|
+
kind: SpanKind.CLIENT,
|
|
9174
|
+
submodule: "query",
|
|
9175
|
+
packageType: PackageType.FIRESTORE,
|
|
9176
|
+
packageName: PACKAGE_NAME,
|
|
9177
|
+
instrumentationName: self.INSTRUMENTATION_NAME,
|
|
9178
|
+
inputValue,
|
|
9179
|
+
isPreAppStart,
|
|
9180
|
+
stopRecordingChildSpans: true
|
|
9181
|
+
}, (spanInfo) => {
|
|
9182
|
+
return self._handleRecordQueryGet(spanInfo, originalGet, this);
|
|
9183
|
+
});
|
|
9184
|
+
},
|
|
9185
|
+
spanKind: SpanKind.CLIENT
|
|
9186
|
+
});
|
|
9187
|
+
else return originalGet.call(this);
|
|
7920
9188
|
};
|
|
7921
9189
|
};
|
|
7922
9190
|
}
|
|
9191
|
+
async _handleRecordQueryGet(spanInfo, originalGet, context$1) {
|
|
9192
|
+
const querySnapshot = await originalGet.call(context$1);
|
|
9193
|
+
const queryResult = {
|
|
9194
|
+
docs: querySnapshot.docs.map((doc) => ({
|
|
9195
|
+
id: doc.id,
|
|
9196
|
+
path: doc.ref.path,
|
|
9197
|
+
exists: doc.exists,
|
|
9198
|
+
data: doc.exists ? doc.data() : void 0,
|
|
9199
|
+
createTime: doc.createTime ? {
|
|
9200
|
+
seconds: doc.createTime.seconds,
|
|
9201
|
+
nanoseconds: doc.createTime.nanoseconds
|
|
9202
|
+
} : void 0,
|
|
9203
|
+
updateTime: doc.updateTime ? {
|
|
9204
|
+
seconds: doc.updateTime.seconds,
|
|
9205
|
+
nanoseconds: doc.updateTime.nanoseconds
|
|
9206
|
+
} : void 0,
|
|
9207
|
+
readTime: doc.readTime ? {
|
|
9208
|
+
seconds: doc.readTime.seconds,
|
|
9209
|
+
nanoseconds: doc.readTime.nanoseconds
|
|
9210
|
+
} : void 0
|
|
9211
|
+
})),
|
|
9212
|
+
size: querySnapshot.size,
|
|
9213
|
+
empty: querySnapshot.empty,
|
|
9214
|
+
readTime: querySnapshot.readTime ? {
|
|
9215
|
+
seconds: querySnapshot.readTime.seconds,
|
|
9216
|
+
nanoseconds: querySnapshot.readTime.nanoseconds
|
|
9217
|
+
} : void 0
|
|
9218
|
+
};
|
|
9219
|
+
try {
|
|
9220
|
+
SpanUtils.addSpanAttributes(spanInfo.span, { outputValue: queryResult });
|
|
9221
|
+
SpanUtils.endSpan(spanInfo.span, { code: SpanStatusCode.OK });
|
|
9222
|
+
} catch {
|
|
9223
|
+
logger.error(`[FirestoreInstrumentation] Error updating span attributes for query.get`);
|
|
9224
|
+
}
|
|
9225
|
+
return querySnapshot;
|
|
9226
|
+
}
|
|
9227
|
+
async _handleReplayQueryGet(spanInfo, inputValue) {
|
|
9228
|
+
logger.debug(`[FirestoreInstrumentation] Replaying query.get`);
|
|
9229
|
+
const mockData = await findMockResponseAsync({
|
|
9230
|
+
mockRequestData: {
|
|
9231
|
+
traceId: spanInfo.traceId,
|
|
9232
|
+
spanId: spanInfo.spanId,
|
|
9233
|
+
name: "firestore.query.get",
|
|
9234
|
+
inputValue: createMockInputValue(inputValue),
|
|
9235
|
+
packageName: PACKAGE_NAME,
|
|
9236
|
+
instrumentationName: this.INSTRUMENTATION_NAME,
|
|
9237
|
+
submoduleName: "query",
|
|
9238
|
+
kind: SpanKind.CLIENT
|
|
9239
|
+
},
|
|
9240
|
+
tuskDrift: this.tuskDrift
|
|
9241
|
+
});
|
|
9242
|
+
if (!mockData) {
|
|
9243
|
+
logger.warn(`[FirestoreInstrumentation] No mock data found for query.get: ${inputValue.path}`);
|
|
9244
|
+
return Promise.reject(/* @__PURE__ */ new Error("No mock data found"));
|
|
9245
|
+
}
|
|
9246
|
+
const queryResult = mockData.result;
|
|
9247
|
+
return new TdFirestoreQueryMock(queryResult);
|
|
9248
|
+
}
|
|
7923
9249
|
_wrap(target, propertyName, wrapper) {
|
|
7924
|
-
|
|
9250
|
+
if (!target || typeof target[propertyName] !== "function") {
|
|
9251
|
+
logger.warn(`[FirestoreInstrumentation] Cannot wrap ${propertyName}: not a function or target is undefined`);
|
|
9252
|
+
return;
|
|
9253
|
+
}
|
|
9254
|
+
const original = target[propertyName];
|
|
9255
|
+
target[propertyName] = wrapper(original);
|
|
7925
9256
|
}
|
|
7926
9257
|
};
|
|
7927
|
-
GrpcInstrumentation.metadataStore = /* @__PURE__ */ new Map();
|
|
7928
9258
|
|
|
7929
9259
|
//#endregion
|
|
7930
9260
|
//#region src/instrumentation/libraries/nextjs/Instrumentation.ts
|
|
@@ -7984,6 +9314,12 @@ var NextjsInstrumentation = class extends TdInstrumentationBase {
|
|
|
7984
9314
|
const self = this;
|
|
7985
9315
|
return (originalHandleRequest) => {
|
|
7986
9316
|
return async function(req, res, parsedUrl) {
|
|
9317
|
+
if (self.mode === TuskDriftMode.RECORD) {
|
|
9318
|
+
if (!shouldSample({
|
|
9319
|
+
samplingRate: self.tuskDrift.getSamplingRate(),
|
|
9320
|
+
isAppReady: self.tuskDrift.isAppReady()
|
|
9321
|
+
})) return originalHandleRequest.call(this, req, res, parsedUrl);
|
|
9322
|
+
}
|
|
7987
9323
|
const method = req.method || "GET";
|
|
7988
9324
|
const url = req.url || "/";
|
|
7989
9325
|
logger.debug(`[NextjsInstrumentation] Intercepted Next.js request: ${method} ${url}`);
|
|
@@ -7999,6 +9335,7 @@ var NextjsInstrumentation = class extends TdInstrumentationBase {
|
|
|
7999
9335
|
logger.debug(`[NextjsInstrumentation] No trace ID found, calling original handler`);
|
|
8000
9336
|
return originalHandleRequest.call(this, req, res, parsedUrl);
|
|
8001
9337
|
}
|
|
9338
|
+
logger.debug(`[NextjsInstrumentation] Setting replay trace id`, replayTraceId);
|
|
8002
9339
|
const envVars = self.replayHooks.extractEnvVarsFromHeaders(req);
|
|
8003
9340
|
if (envVars) EnvVarTracker.setEnvVars(replayTraceId, envVars);
|
|
8004
9341
|
const ctxWithReplayTraceId = SpanUtils.setCurrentReplayTraceId(replayTraceId);
|
|
@@ -8029,13 +9366,6 @@ var NextjsInstrumentation = class extends TdInstrumentationBase {
|
|
|
8029
9366
|
} });
|
|
8030
9367
|
else if (self.mode === TuskDriftMode.RECORD) {
|
|
8031
9368
|
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
9369
|
logger.debug(`[NextjsInstrumentation] Creating server span for ${method} ${url}`);
|
|
8040
9370
|
return handleRecordMode({
|
|
8041
9371
|
originalFunctionCall: () => originalHandleRequest.call(this, req, res, parsedUrl),
|
|
@@ -8114,6 +9444,19 @@ var NextjsInstrumentation = class extends TdInstrumentationBase {
|
|
|
8114
9444
|
};
|
|
8115
9445
|
try {
|
|
8116
9446
|
await originalHandleRequest.call(thisContext, req, res, parsedUrl);
|
|
9447
|
+
} catch (error) {
|
|
9448
|
+
logger.error(`[NextjsInstrumentation] Error in Next.js request: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
9449
|
+
try {
|
|
9450
|
+
SpanUtils.endSpan(spanInfo.span, {
|
|
9451
|
+
code: SpanStatusCode.ERROR,
|
|
9452
|
+
message: error instanceof Error ? error.message : "Unknown error"
|
|
9453
|
+
});
|
|
9454
|
+
} catch (e) {
|
|
9455
|
+
logger.error(`[NextjsInstrumentation] Error ending span:`, e);
|
|
9456
|
+
}
|
|
9457
|
+
throw error;
|
|
9458
|
+
}
|
|
9459
|
+
try {
|
|
8117
9460
|
if (!capturedStatusCode) {
|
|
8118
9461
|
capturedStatusCode = res.statusCode;
|
|
8119
9462
|
capturedStatusMessage = res.statusMessage;
|
|
@@ -8138,7 +9481,6 @@ var NextjsInstrumentation = class extends TdInstrumentationBase {
|
|
|
8138
9481
|
outputValue.bodySize = responseBuffer.length;
|
|
8139
9482
|
} catch (error) {
|
|
8140
9483
|
logger.error(`[NextjsInstrumentation] Error processing response body:`, error);
|
|
8141
|
-
outputValue.bodyProcessingError = error instanceof Error ? error.message : String(error);
|
|
8142
9484
|
}
|
|
8143
9485
|
SpanUtils.addSpanAttributes(spanInfo.span, {
|
|
8144
9486
|
inputValue: completeInputValue,
|
|
@@ -8158,6 +9500,11 @@ var NextjsInstrumentation = class extends TdInstrumentationBase {
|
|
|
8158
9500
|
message: `HTTP ${capturedStatusCode}`
|
|
8159
9501
|
} : { code: SpanStatusCode.OK };
|
|
8160
9502
|
SpanUtils.setStatus(spanInfo.span, status);
|
|
9503
|
+
const decodedType = getDecodedType(outputValue.headers?.["content-type"] || "");
|
|
9504
|
+
if (decodedType && STATIC_ASSET_TYPES.has(decodedType)) {
|
|
9505
|
+
TraceBlockingManager.getInstance().blockTrace(spanInfo.traceId);
|
|
9506
|
+
logger.debug(`[NextjsInstrumentation] Blocking trace ${spanInfo.traceId} because it is an static asset response. Decoded type: ${decodedType}`);
|
|
9507
|
+
}
|
|
8161
9508
|
SpanUtils.endSpan(spanInfo.span);
|
|
8162
9509
|
if (self.mode === TuskDriftMode.REPLAY) try {
|
|
8163
9510
|
const now = OriginalGlobalUtils.getOriginalDate();
|
|
@@ -8215,12 +9562,15 @@ var NextjsInstrumentation = class extends TdInstrumentationBase {
|
|
|
8215
9562
|
logger.error("[NextjsInstrumentation] Failed to build/send inbound replay span:", e);
|
|
8216
9563
|
}
|
|
8217
9564
|
} catch (error) {
|
|
8218
|
-
logger.error(`[NextjsInstrumentation] Error in Next.js request: ${error instanceof Error ? error.message :
|
|
8219
|
-
|
|
8220
|
-
|
|
8221
|
-
|
|
8222
|
-
|
|
8223
|
-
|
|
9565
|
+
logger.error(`[NextjsInstrumentation] Error in Next.js request: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
9566
|
+
try {
|
|
9567
|
+
SpanUtils.endSpan(spanInfo.span, {
|
|
9568
|
+
code: SpanStatusCode.ERROR,
|
|
9569
|
+
message: error instanceof Error ? error.message : "Unknown error"
|
|
9570
|
+
});
|
|
9571
|
+
} catch (e) {
|
|
9572
|
+
logger.error(`[NextjsInstrumentation] Error ending span:`, e);
|
|
9573
|
+
}
|
|
8224
9574
|
}
|
|
8225
9575
|
}
|
|
8226
9576
|
/**
|
|
@@ -10447,20 +11797,21 @@ var SpanTransformer = class SpanTransformer {
|
|
|
10447
11797
|
const inputData = JSON.parse(inputValueString);
|
|
10448
11798
|
const inputSchemaMergesString = attributes[TdSpanAttributes.INPUT_SCHEMA_MERGES];
|
|
10449
11799
|
const inputSchemaMerges = inputSchemaMergesString ? JSON.parse(inputSchemaMergesString) : void 0;
|
|
10450
|
-
const { schema: inputSchema, decodedValueHash: inputValueHash } = JsonSchemaHelper.generateSchemaAndHash(inputData, inputSchemaMerges);
|
|
11800
|
+
const { schema: inputSchema, decodedValueHash: inputValueHash, decodedSchemaHash: inputSchemaHash } = JsonSchemaHelper.generateSchemaAndHash(inputData, inputSchemaMerges);
|
|
10451
11801
|
let outputData = {};
|
|
10452
11802
|
let outputSchema = {
|
|
10453
11803
|
type: JsonSchemaType.OBJECT,
|
|
10454
11804
|
properties: {}
|
|
10455
11805
|
};
|
|
10456
11806
|
let outputValueHash = "";
|
|
11807
|
+
let outputSchemaHash = "";
|
|
10457
11808
|
if (attributes[TdSpanAttributes.OUTPUT_VALUE]) {
|
|
10458
11809
|
const outputValueString = attributes[TdSpanAttributes.OUTPUT_VALUE];
|
|
10459
11810
|
outputData = JSON.parse(outputValueString);
|
|
10460
11811
|
const outputSchemaMergesString = attributes[TdSpanAttributes.OUTPUT_SCHEMA_MERGES];
|
|
10461
11812
|
const outputSchemaMerges = outputSchemaMergesString ? JSON.parse(outputSchemaMergesString) : void 0;
|
|
10462
|
-
({schema: outputSchema, decodedValueHash: outputValueHash} = JsonSchemaHelper.generateSchemaAndHash(outputData, outputSchemaMerges));
|
|
10463
|
-
} else ({schema: outputSchema, decodedSchemaHash:
|
|
11813
|
+
({schema: outputSchema, decodedValueHash: outputValueHash, decodedSchemaHash: outputSchemaHash} = JsonSchemaHelper.generateSchemaAndHash(outputData, outputSchemaMerges));
|
|
11814
|
+
} else ({schema: outputSchema, decodedValueHash: outputValueHash, decodedSchemaHash: outputSchemaHash} = JsonSchemaHelper.generateSchemaAndHash(outputData));
|
|
10464
11815
|
let metadata = void 0;
|
|
10465
11816
|
if (attributes[TdSpanAttributes.METADATA]) metadata = JSON.parse(attributes[TdSpanAttributes.METADATA]);
|
|
10466
11817
|
let transformMetadata;
|
|
@@ -10484,8 +11835,8 @@ var SpanTransformer = class SpanTransformer {
|
|
|
10484
11835
|
outputValue: outputData,
|
|
10485
11836
|
inputSchema,
|
|
10486
11837
|
outputSchema,
|
|
10487
|
-
inputSchemaHash
|
|
10488
|
-
outputSchemaHash
|
|
11838
|
+
inputSchemaHash,
|
|
11839
|
+
outputSchemaHash,
|
|
10489
11840
|
inputValueHash,
|
|
10490
11841
|
outputValueHash,
|
|
10491
11842
|
kind: span.kind,
|
|
@@ -10728,18 +12079,35 @@ var TdSpanExporter = class {
|
|
|
10728
12079
|
*/
|
|
10729
12080
|
export(spans, resultCallback) {
|
|
10730
12081
|
logger.debug(`TdSpanExporter.export() called with ${spans.length} span(s)`);
|
|
10731
|
-
const
|
|
10732
|
-
|
|
12082
|
+
const traceBlockingManager = TraceBlockingManager.getInstance();
|
|
12083
|
+
const filteredSpansBasedOnLibraryName = spans.filter((span) => {
|
|
12084
|
+
if (span.instrumentationLibrary.name === TD_INSTRUMENTATION_LIBRARY_NAME) return true;
|
|
12085
|
+
return false;
|
|
12086
|
+
});
|
|
12087
|
+
logger.debug(`After filtering based on library name: ${filteredSpansBasedOnLibraryName.length} span(s) remaining`);
|
|
12088
|
+
const MAX_SPAN_SIZE_MB = 1;
|
|
12089
|
+
const MAX_SPAN_SIZE_BYTES = MAX_SPAN_SIZE_MB * 1024 * 1024;
|
|
12090
|
+
const filteredSpansBasedOnSize = filteredSpansBasedOnLibraryName.filter((span) => {
|
|
12091
|
+
const traceId = span.spanContext().traceId;
|
|
12092
|
+
if (traceBlockingManager.isTraceBlocked(traceId)) {
|
|
12093
|
+
logger.debug(`Skipping span '${span.name}' (${span.spanContext().spanId}) - trace ${traceId} is blocked`);
|
|
12094
|
+
return false;
|
|
12095
|
+
}
|
|
12096
|
+
const inputValueString = span.attributes[TdSpanAttributes.INPUT_VALUE] || "";
|
|
12097
|
+
const outputValueString = span.attributes[TdSpanAttributes.OUTPUT_VALUE] || "";
|
|
12098
|
+
const inputSize = Buffer.byteLength(inputValueString, "utf8");
|
|
12099
|
+
const outputSize = Buffer.byteLength(outputValueString, "utf8");
|
|
12100
|
+
const estimatedTotalSize = inputSize + outputSize + 5e4;
|
|
12101
|
+
const estimatedSizeMB = estimatedTotalSize / (1024 * 1024);
|
|
12102
|
+
if (estimatedTotalSize > MAX_SPAN_SIZE_BYTES) {
|
|
12103
|
+
traceBlockingManager.blockTrace(traceId);
|
|
12104
|
+
logger.warn(`Blocking trace ${traceId} - span '${span.name}' (${span.spanContext().spanId}) has estimated size ${estimatedSizeMB.toFixed(2)} MB exceeding limit of ${MAX_SPAN_SIZE_MB} MB. Future spans for this trace will be prevented.`);
|
|
12105
|
+
return false;
|
|
12106
|
+
}
|
|
10733
12107
|
return true;
|
|
10734
12108
|
});
|
|
10735
|
-
logger.debug(`
|
|
10736
|
-
|
|
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
|
-
}
|
|
12109
|
+
logger.debug(`Filtered ${filteredSpansBasedOnLibraryName.length - filteredSpansBasedOnSize.length} oversized span(s), ${filteredSpansBasedOnSize.length} remaining`);
|
|
12110
|
+
const cleanSpans = filteredSpansBasedOnSize.map((span) => SpanTransformer.transformSpanToCleanJSON(span));
|
|
10743
12111
|
if (this.adapters.length === 0) {
|
|
10744
12112
|
logger.debug("No adapters configured");
|
|
10745
12113
|
resultCallback({ code: import_src.ExportResultCode.SUCCESS });
|
|
@@ -10874,15 +12242,12 @@ var ProtobufCommunicator = class {
|
|
|
10874
12242
|
});
|
|
10875
12243
|
}
|
|
10876
12244
|
/**
|
|
10877
|
-
* This function uses a Node.js
|
|
10878
|
-
* The
|
|
10879
|
-
*
|
|
10880
|
-
* Since this function blocks the main thread, there is a perfomance impact for using this. We should use requestMockAsync whenever possilbe and only use this function
|
|
10881
|
-
* for instrumentations that request fetching mocks synchronously.
|
|
12245
|
+
* This function uses a separate Node.js child process to communicate with the CLI over a socket.
|
|
12246
|
+
* The child process creates its own connection and event loop, allowing proper async socket handling.
|
|
12247
|
+
* The parent process blocks synchronously waiting for the child to complete.
|
|
10882
12248
|
*
|
|
10883
|
-
*
|
|
10884
|
-
*
|
|
10885
|
-
* Better approach is replacing nc command with pure Node.js implementation
|
|
12249
|
+
* Since this function blocks the main thread, there is a performance impact. We should use requestMockAsync whenever possible and only use this function
|
|
12250
|
+
* for instrumentations that require fetching mocks synchronously.
|
|
10886
12251
|
*/
|
|
10887
12252
|
requestMockSync(mockRequest) {
|
|
10888
12253
|
const requestId = this.generateRequestId();
|
|
@@ -10905,10 +12270,15 @@ var ProtobufCommunicator = class {
|
|
|
10905
12270
|
getMockRequest: protoMockRequest
|
|
10906
12271
|
}
|
|
10907
12272
|
});
|
|
12273
|
+
logger.debug("Sending protobuf request to CLI (sync)", {
|
|
12274
|
+
outboundSpan: mockRequest.outboundSpan,
|
|
12275
|
+
testId: mockRequest.testId
|
|
12276
|
+
});
|
|
10908
12277
|
const messageBytes = SDKMessage.toBinary(sdkMessage);
|
|
10909
12278
|
const tempDir = os.tmpdir();
|
|
10910
|
-
const requestFile = path.join(tempDir, `tusk-request-${requestId}.bin`);
|
|
10911
|
-
const responseFile = path.join(tempDir, `tusk-response-${requestId}.bin`);
|
|
12279
|
+
const requestFile = path.join(tempDir, `tusk-sync-request-${requestId}.bin`);
|
|
12280
|
+
const responseFile = path.join(tempDir, `tusk-sync-response-${requestId}.bin`);
|
|
12281
|
+
const scriptFile = path.join(tempDir, `tusk-sync-script-${requestId}.js`);
|
|
10912
12282
|
try {
|
|
10913
12283
|
const lengthBuffer = Buffer.allocUnsafe(4);
|
|
10914
12284
|
lengthBuffer.writeUInt32BE(messageBytes.length, 0);
|
|
@@ -10917,19 +12287,112 @@ var ProtobufCommunicator = class {
|
|
|
10917
12287
|
const mockSocket = OriginalGlobalUtils.getOriginalProcessEnvVar("TUSK_MOCK_SOCKET");
|
|
10918
12288
|
const mockHost = OriginalGlobalUtils.getOriginalProcessEnvVar("TUSK_MOCK_HOST");
|
|
10919
12289
|
const mockPort = OriginalGlobalUtils.getOriginalProcessEnvVar("TUSK_MOCK_PORT");
|
|
10920
|
-
let
|
|
10921
|
-
if (mockSocket) {
|
|
10922
|
-
|
|
10923
|
-
|
|
10924
|
-
}
|
|
10925
|
-
else {
|
|
10926
|
-
|
|
10927
|
-
|
|
10928
|
-
|
|
10929
|
-
}
|
|
12290
|
+
let connectionConfig;
|
|
12291
|
+
if (mockSocket) connectionConfig = {
|
|
12292
|
+
type: "unix",
|
|
12293
|
+
path: mockSocket
|
|
12294
|
+
};
|
|
12295
|
+
else if (mockHost && mockPort) connectionConfig = {
|
|
12296
|
+
type: "tcp",
|
|
12297
|
+
host: mockHost,
|
|
12298
|
+
port: parseInt(mockPort, 10)
|
|
12299
|
+
};
|
|
12300
|
+
else connectionConfig = {
|
|
12301
|
+
type: "unix",
|
|
12302
|
+
path: path.join(os.tmpdir(), "tusk-connect.sock")
|
|
12303
|
+
};
|
|
12304
|
+
fs.writeFileSync(scriptFile, `
|
|
12305
|
+
const net = require('net');
|
|
12306
|
+
const fs = require('fs');
|
|
12307
|
+
|
|
12308
|
+
const requestFile = process.argv[2];
|
|
12309
|
+
const responseFile = process.argv[3];
|
|
12310
|
+
const config = JSON.parse(process.argv[4]);
|
|
12311
|
+
|
|
12312
|
+
let responseReceived = false;
|
|
12313
|
+
let timeoutId;
|
|
12314
|
+
|
|
12315
|
+
function cleanup(exitCode) {
|
|
12316
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
12317
|
+
process.exit(exitCode);
|
|
12318
|
+
}
|
|
12319
|
+
|
|
12320
|
+
try {
|
|
12321
|
+
// Read the request data
|
|
12322
|
+
const requestData = fs.readFileSync(requestFile);
|
|
12323
|
+
|
|
12324
|
+
// Create connection based on config
|
|
12325
|
+
const client = config.type === 'unix'
|
|
12326
|
+
? net.createConnection({ path: config.path })
|
|
12327
|
+
: net.createConnection({ host: config.host, port: config.port });
|
|
12328
|
+
|
|
12329
|
+
const incomingChunks = [];
|
|
12330
|
+
let incomingBuffer = Buffer.alloc(0);
|
|
12331
|
+
|
|
12332
|
+
// Set timeout
|
|
12333
|
+
timeoutId = setTimeout(() => {
|
|
12334
|
+
if (!responseReceived) {
|
|
12335
|
+
console.error('Timeout waiting for response');
|
|
12336
|
+
client.destroy();
|
|
12337
|
+
cleanup(1);
|
|
12338
|
+
}
|
|
12339
|
+
}, 10000);
|
|
12340
|
+
|
|
12341
|
+
client.on('connect', () => {
|
|
12342
|
+
// Send the request
|
|
12343
|
+
client.write(requestData);
|
|
12344
|
+
});
|
|
12345
|
+
|
|
12346
|
+
client.on('data', (data) => {
|
|
12347
|
+
incomingBuffer = Buffer.concat([incomingBuffer, data]);
|
|
12348
|
+
|
|
12349
|
+
// Try to parse complete message (4 byte length prefix + message)
|
|
12350
|
+
while (incomingBuffer.length >= 4) {
|
|
12351
|
+
const messageLength = incomingBuffer.readUInt32BE(0);
|
|
12352
|
+
|
|
12353
|
+
if (incomingBuffer.length < 4 + messageLength) {
|
|
12354
|
+
// Incomplete message, wait for more data
|
|
12355
|
+
break;
|
|
12356
|
+
}
|
|
12357
|
+
|
|
12358
|
+
// We have a complete message
|
|
12359
|
+
const messageData = incomingBuffer.slice(4, 4 + messageLength);
|
|
12360
|
+
incomingBuffer = incomingBuffer.slice(4 + messageLength);
|
|
12361
|
+
|
|
12362
|
+
// Write the complete response (including length prefix)
|
|
12363
|
+
const lengthPrefix = Buffer.allocUnsafe(4);
|
|
12364
|
+
lengthPrefix.writeUInt32BE(messageLength, 0);
|
|
12365
|
+
fs.writeFileSync(responseFile, Buffer.concat([lengthPrefix, messageData]));
|
|
12366
|
+
|
|
12367
|
+
responseReceived = true;
|
|
12368
|
+
client.destroy();
|
|
12369
|
+
cleanup(0);
|
|
12370
|
+
break;
|
|
12371
|
+
}
|
|
12372
|
+
});
|
|
12373
|
+
|
|
12374
|
+
client.on('error', (err) => {
|
|
12375
|
+
if (!responseReceived) {
|
|
12376
|
+
console.error('Connection error:', err.message);
|
|
12377
|
+
cleanup(1);
|
|
12378
|
+
}
|
|
12379
|
+
});
|
|
12380
|
+
|
|
12381
|
+
client.on('close', () => {
|
|
12382
|
+
if (!responseReceived) {
|
|
12383
|
+
console.error('Connection closed without response');
|
|
12384
|
+
cleanup(1);
|
|
12385
|
+
}
|
|
12386
|
+
});
|
|
12387
|
+
|
|
12388
|
+
} catch (err) {
|
|
12389
|
+
console.error('Script error:', err.message);
|
|
12390
|
+
cleanup(1);
|
|
12391
|
+
}
|
|
12392
|
+
`);
|
|
10930
12393
|
try {
|
|
10931
|
-
execSync(
|
|
10932
|
-
timeout:
|
|
12394
|
+
execSync(`node "${scriptFile}" "${requestFile}" "${responseFile}" '${JSON.stringify(connectionConfig)}'`, {
|
|
12395
|
+
timeout: 12e3,
|
|
10933
12396
|
stdio: "pipe"
|
|
10934
12397
|
});
|
|
10935
12398
|
const responseBuffer = fs.readFileSync(responseFile);
|
|
@@ -10954,22 +12417,27 @@ var ProtobufCommunicator = class {
|
|
|
10954
12417
|
error: mockResponse.error || "Mock not found"
|
|
10955
12418
|
};
|
|
10956
12419
|
} catch (error) {
|
|
10957
|
-
logger.error("[ProtobufCommunicator] error
|
|
12420
|
+
logger.error("[ProtobufCommunicator] error in sync request child process:", error);
|
|
10958
12421
|
throw error;
|
|
10959
12422
|
}
|
|
10960
12423
|
} catch (error) {
|
|
10961
12424
|
throw new Error(`Sync request failed: ${error.message}`);
|
|
10962
12425
|
} finally {
|
|
10963
12426
|
try {
|
|
10964
|
-
fs.unlinkSync(requestFile);
|
|
12427
|
+
if (fs.existsSync(requestFile)) fs.unlinkSync(requestFile);
|
|
10965
12428
|
} catch (e) {
|
|
10966
12429
|
logger.error("[ProtobufCommunicator] error cleaning up request file:", e);
|
|
10967
12430
|
}
|
|
10968
12431
|
try {
|
|
10969
|
-
fs.unlinkSync(responseFile);
|
|
12432
|
+
if (fs.existsSync(responseFile)) fs.unlinkSync(responseFile);
|
|
10970
12433
|
} catch (e) {
|
|
10971
12434
|
logger.error("[ProtobufCommunicator] error cleaning up response file:", e);
|
|
10972
12435
|
}
|
|
12436
|
+
try {
|
|
12437
|
+
if (fs.existsSync(scriptFile)) fs.unlinkSync(scriptFile);
|
|
12438
|
+
} catch (e) {
|
|
12439
|
+
logger.error("[ProtobufCommunicator] error cleaning up script file:", e);
|
|
12440
|
+
}
|
|
10973
12441
|
}
|
|
10974
12442
|
}
|
|
10975
12443
|
async sendProtobufMessage(message) {
|
|
@@ -11137,7 +12605,8 @@ const TuskDriftInstrumentationModuleNames = [
|
|
|
11137
12605
|
"jwks-rsa",
|
|
11138
12606
|
"mysql2",
|
|
11139
12607
|
"ioredis",
|
|
11140
|
-
"@grpc/grpc-js"
|
|
12608
|
+
"@grpc/grpc-js",
|
|
12609
|
+
"@google-cloud/firestore"
|
|
11141
12610
|
];
|
|
11142
12611
|
|
|
11143
12612
|
//#endregion
|
|
@@ -11259,6 +12728,10 @@ var TuskDriftCore = class TuskDriftCore {
|
|
|
11259
12728
|
enabled: true,
|
|
11260
12729
|
mode: this.mode
|
|
11261
12730
|
});
|
|
12731
|
+
new FirestoreInstrumentation({
|
|
12732
|
+
enabled: true,
|
|
12733
|
+
mode: this.mode
|
|
12734
|
+
});
|
|
11262
12735
|
new NextjsInstrumentation({
|
|
11263
12736
|
enabled: true,
|
|
11264
12737
|
mode: this.mode
|
|
@@ -11431,6 +12904,10 @@ var TuskDriftCore = class TuskDriftCore {
|
|
|
11431
12904
|
}
|
|
11432
12905
|
}
|
|
11433
12906
|
requestMockSync(mockRequest) {
|
|
12907
|
+
if (!this.isConnectedWithCLI) {
|
|
12908
|
+
logger.error("Requesting sync mock but CLI is not ready yet");
|
|
12909
|
+
throw new Error("Requesting sync mock but CLI is not ready yet");
|
|
12910
|
+
}
|
|
11434
12911
|
const mockRequestCore = this.createMockRequestCore(mockRequest);
|
|
11435
12912
|
if (mockRequestCore) return mockRequestCore;
|
|
11436
12913
|
return this.requestMockFromCLISync(mockRequest);
|
|
@@ -11478,7 +12955,7 @@ var TuskDriftCore = class TuskDriftCore {
|
|
|
11478
12955
|
return this.initParams;
|
|
11479
12956
|
}
|
|
11480
12957
|
getTracer() {
|
|
11481
|
-
return trace.getTracer(
|
|
12958
|
+
return trace.getTracer(TD_INSTRUMENTATION_LIBRARY_NAME);
|
|
11482
12959
|
}
|
|
11483
12960
|
};
|
|
11484
12961
|
var TuskDriftSDK = class {
|