convex-tracer 0.1.0
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/LICENSE +201 -0
- package/README.md +592 -0
- package/dist/client/_generated/_ignore.d.ts +1 -0
- package/dist/client/_generated/_ignore.d.ts.map +1 -0
- package/dist/client/_generated/_ignore.js +3 -0
- package/dist/client/_generated/_ignore.js.map +1 -0
- package/dist/client/helpers.d.ts +31 -0
- package/dist/client/helpers.d.ts.map +1 -0
- package/dist/client/helpers.js +177 -0
- package/dist/client/helpers.js.map +1 -0
- package/dist/client/index.d.ts +210 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +355 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/tracer-api/index.d.ts +27 -0
- package/dist/client/tracer-api/index.d.ts.map +1 -0
- package/dist/client/tracer-api/index.js +177 -0
- package/dist/client/tracer-api/index.js.map +1 -0
- package/dist/client/tracer-api/types.d.ts +143 -0
- package/dist/client/tracer-api/types.d.ts.map +1 -0
- package/dist/client/tracer-api/types.js +2 -0
- package/dist/client/tracer-api/types.js.map +1 -0
- package/dist/client/types.d.ts +168 -0
- package/dist/client/types.d.ts.map +1 -0
- package/dist/client/types.js +2 -0
- package/dist/client/types.js.map +1 -0
- package/dist/component/_generated/api.d.ts +36 -0
- package/dist/component/_generated/api.d.ts.map +1 -0
- package/dist/component/_generated/api.js +31 -0
- package/dist/component/_generated/api.js.map +1 -0
- package/dist/component/_generated/component.d.ts +139 -0
- package/dist/component/_generated/component.d.ts.map +1 -0
- package/dist/component/_generated/component.js +11 -0
- package/dist/component/_generated/component.js.map +1 -0
- package/dist/component/_generated/dataModel.d.ts +46 -0
- package/dist/component/_generated/dataModel.d.ts.map +1 -0
- package/dist/component/_generated/dataModel.js +11 -0
- package/dist/component/_generated/dataModel.js.map +1 -0
- package/dist/component/_generated/server.d.ts +121 -0
- package/dist/component/_generated/server.d.ts.map +1 -0
- package/dist/component/_generated/server.js +78 -0
- package/dist/component/_generated/server.js.map +1 -0
- package/dist/component/convex.config.d.ts +3 -0
- package/dist/component/convex.config.d.ts.map +1 -0
- package/dist/component/convex.config.js +3 -0
- package/dist/component/convex.config.js.map +1 -0
- package/dist/component/lib.d.ts +161 -0
- package/dist/component/lib.d.ts.map +1 -0
- package/dist/component/lib.js +349 -0
- package/dist/component/lib.js.map +1 -0
- package/dist/component/schema.d.ts +75 -0
- package/dist/component/schema.d.ts.map +1 -0
- package/dist/component/schema.js +46 -0
- package/dist/component/schema.js.map +1 -0
- package/dist/component/types.d.ts +286 -0
- package/dist/component/types.d.ts.map +1 -0
- package/dist/component/types.js +28 -0
- package/dist/component/types.js.map +1 -0
- package/dist/react/index.d.ts +6 -0
- package/dist/react/index.d.ts.map +1 -0
- package/dist/react/index.js +11 -0
- package/dist/react/index.js.map +1 -0
- package/dist/react/types.d.ts +8 -0
- package/dist/react/types.d.ts.map +1 -0
- package/dist/react/types.js +2 -0
- package/dist/react/types.js.map +1 -0
- package/package.json +121 -0
- package/src/client/_generated/_ignore.ts +1 -0
- package/src/client/helpers.ts +278 -0
- package/src/client/index.ts +593 -0
- package/src/client/setup.test.ts +26 -0
- package/src/client/tracer-api/index.ts +235 -0
- package/src/client/tracer-api/types.ts +168 -0
- package/src/client/types.ts +257 -0
- package/src/component/_generated/api.ts +52 -0
- package/src/component/_generated/component.ts +199 -0
- package/src/component/_generated/dataModel.ts +60 -0
- package/src/component/_generated/server.ts +161 -0
- package/src/component/convex.config.ts +3 -0
- package/src/component/lib.ts +399 -0
- package/src/component/schema.ts +62 -0
- package/src/component/setup.test.ts +11 -0
- package/src/component/types.ts +38 -0
- package/src/react/index.ts +36 -0
- package/src/react/types.ts +15 -0
- package/src/test.ts +18 -0
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import type { GenericDataModel } from "convex/server";
|
|
2
|
+
import type { ObjectType, PropertyValidators } from "convex/values";
|
|
3
|
+
import type { ComponentApi } from "../component/_generated/component";
|
|
4
|
+
import TracerAPI from "./tracer-api/index";
|
|
5
|
+
import type {
|
|
6
|
+
ArgsWithTraceContext,
|
|
7
|
+
LogArgs,
|
|
8
|
+
OptionalArgsObject,
|
|
9
|
+
StrippedGenericFunctionContext,
|
|
10
|
+
TraceContext,
|
|
11
|
+
TracedFunctionOptions,
|
|
12
|
+
TracedResult,
|
|
13
|
+
TracerConfig,
|
|
14
|
+
TracerHandler,
|
|
15
|
+
} from "./types";
|
|
16
|
+
|
|
17
|
+
function pick<T extends Record<string, any>, Keys extends (keyof T)[]>(
|
|
18
|
+
obj: T,
|
|
19
|
+
keys: Keys,
|
|
20
|
+
) {
|
|
21
|
+
return Object.fromEntries(
|
|
22
|
+
Object.entries(obj).filter(([k]) => keys.includes(k as Keys[number])),
|
|
23
|
+
) as {
|
|
24
|
+
[K in Keys[number]]: T[K];
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function extractTraceContext<Args extends Record<string, unknown>>(
|
|
29
|
+
allArgs: ArgsWithTraceContext<Args>,
|
|
30
|
+
): { existingContext?: TraceContext; args: Args } {
|
|
31
|
+
const existingContext = allArgs.__traceContext;
|
|
32
|
+
const args = { ...allArgs };
|
|
33
|
+
delete (args as ArgsWithTraceContext<Args>).__traceContext;
|
|
34
|
+
return { existingContext, args };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function prepareLogArgs<Args extends PropertyValidators>(
|
|
38
|
+
args: ObjectType<Args>,
|
|
39
|
+
logArgs: LogArgs<Args>,
|
|
40
|
+
): unknown | undefined {
|
|
41
|
+
if (!logArgs) return undefined;
|
|
42
|
+
if (logArgs === true) return args;
|
|
43
|
+
if (Array.isArray(logArgs)) {
|
|
44
|
+
return pick(args, logArgs);
|
|
45
|
+
}
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export async function setupTraceContext(
|
|
50
|
+
ctx: StrippedGenericFunctionContext<GenericDataModel>,
|
|
51
|
+
component: ComponentApi,
|
|
52
|
+
existingContext: TraceContext | undefined,
|
|
53
|
+
startTime: number,
|
|
54
|
+
functionName: string,
|
|
55
|
+
sampleRate: number,
|
|
56
|
+
retentionMinutes: number,
|
|
57
|
+
preserveErrors: boolean,
|
|
58
|
+
spanData: { functionName?: string; args?: unknown },
|
|
59
|
+
): Promise<{
|
|
60
|
+
traceId: string;
|
|
61
|
+
spanId: string;
|
|
62
|
+
traceContext: TraceContext;
|
|
63
|
+
isRoot: boolean;
|
|
64
|
+
}> {
|
|
65
|
+
if (existingContext?.traceId) {
|
|
66
|
+
const spanId = await ctx.runMutation(component.lib.createSpan, {
|
|
67
|
+
traceId: existingContext.traceId,
|
|
68
|
+
span: {
|
|
69
|
+
parentSpanId: existingContext.spanId,
|
|
70
|
+
spanName: functionName,
|
|
71
|
+
source: "backend",
|
|
72
|
+
startTime,
|
|
73
|
+
status: "pending",
|
|
74
|
+
functionName: spanData.functionName,
|
|
75
|
+
args: spanData.args,
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
traceId: existingContext.traceId,
|
|
81
|
+
spanId,
|
|
82
|
+
traceContext: {
|
|
83
|
+
traceId: existingContext.traceId,
|
|
84
|
+
spanId,
|
|
85
|
+
sampleRate: existingContext.sampleRate,
|
|
86
|
+
retentionMinutes: existingContext.retentionMinutes,
|
|
87
|
+
preserveErrors: existingContext.preserveErrors,
|
|
88
|
+
},
|
|
89
|
+
isRoot: false,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const traceId = await ctx.runMutation(component.lib.createTrace, {
|
|
94
|
+
status: "pending",
|
|
95
|
+
sampleRate,
|
|
96
|
+
metadata: {},
|
|
97
|
+
source: "backend",
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const spanId = await ctx.runMutation(component.lib.createSpan, {
|
|
101
|
+
traceId,
|
|
102
|
+
span: {
|
|
103
|
+
spanName: functionName,
|
|
104
|
+
source: "backend",
|
|
105
|
+
startTime: Date.now(),
|
|
106
|
+
status: "pending",
|
|
107
|
+
functionName: spanData.functionName,
|
|
108
|
+
args: spanData.args,
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
traceId,
|
|
114
|
+
spanId,
|
|
115
|
+
traceContext: {
|
|
116
|
+
traceId,
|
|
117
|
+
spanId,
|
|
118
|
+
sampleRate,
|
|
119
|
+
retentionMinutes,
|
|
120
|
+
preserveErrors,
|
|
121
|
+
},
|
|
122
|
+
isRoot: true,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export async function executeTracedHandler<
|
|
127
|
+
Args extends PropertyValidators,
|
|
128
|
+
Output,
|
|
129
|
+
EnhancedCtx,
|
|
130
|
+
>(params: {
|
|
131
|
+
ctx: StrippedGenericFunctionContext<GenericDataModel>;
|
|
132
|
+
component: ComponentApi;
|
|
133
|
+
traceId: string;
|
|
134
|
+
spanId: string;
|
|
135
|
+
startTime: number;
|
|
136
|
+
config: TracedFunctionOptions<EnhancedCtx, Args, Output> & TracerConfig;
|
|
137
|
+
args: OptionalArgsObject<Args>;
|
|
138
|
+
handler: TracerHandler<EnhancedCtx, Args>;
|
|
139
|
+
enhancedCtx: EnhancedCtx;
|
|
140
|
+
isRoot: boolean;
|
|
141
|
+
}): Promise<TracedResult<Output>> {
|
|
142
|
+
const {
|
|
143
|
+
ctx,
|
|
144
|
+
component,
|
|
145
|
+
traceId,
|
|
146
|
+
spanId,
|
|
147
|
+
startTime,
|
|
148
|
+
config,
|
|
149
|
+
args,
|
|
150
|
+
handler,
|
|
151
|
+
enhancedCtx,
|
|
152
|
+
isRoot,
|
|
153
|
+
} = params;
|
|
154
|
+
|
|
155
|
+
const defaultConfig = (enhancedCtx as any).tracer
|
|
156
|
+
.config as Required<TracerConfig>;
|
|
157
|
+
|
|
158
|
+
try {
|
|
159
|
+
// Reject any functions that pass a traceId that doesn't exist
|
|
160
|
+
// These params can be passed from the frontend to break tracing
|
|
161
|
+
// Running a tracedFunction without passing ids will create a new trace
|
|
162
|
+
if (traceId) {
|
|
163
|
+
const traceExists = await ctx.runQuery(component.lib.verifyTrace, {
|
|
164
|
+
traceId,
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
if (!traceExists)
|
|
168
|
+
throw new Error("Cannot pass a traceId for a trace that doesn't exist");
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Reject any functions that pass a spanId that doesn't exist
|
|
172
|
+
// These params can be passed from the frontend to break tracing
|
|
173
|
+
if (spanId) {
|
|
174
|
+
const spanExists = await ctx.runQuery(component.lib.verifySpan, {
|
|
175
|
+
spanId,
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
if (!spanExists)
|
|
179
|
+
throw new Error("Cannot pass a spanId for a span that doesn't exist");
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (config.onStart) {
|
|
183
|
+
await config.onStart(enhancedCtx, args);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const result = await handler(enhancedCtx, args);
|
|
187
|
+
|
|
188
|
+
if (config.onSuccess) {
|
|
189
|
+
await config.onSuccess(enhancedCtx, args, result);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const now = Date.now();
|
|
193
|
+
|
|
194
|
+
await ctx
|
|
195
|
+
.runMutation(component.lib.completeSpan, {
|
|
196
|
+
spanId,
|
|
197
|
+
endTime: now,
|
|
198
|
+
duration: now - startTime,
|
|
199
|
+
status: "success",
|
|
200
|
+
result: config.logReturn ? result : undefined,
|
|
201
|
+
})
|
|
202
|
+
.catch((err) =>
|
|
203
|
+
console.error("[Tracer] Failed to complete span with success:", err),
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
if (isRoot) {
|
|
207
|
+
await ctx
|
|
208
|
+
.runMutation(component.lib.updateTraceStatus, {
|
|
209
|
+
traceId,
|
|
210
|
+
status: "success",
|
|
211
|
+
})
|
|
212
|
+
.catch((err) =>
|
|
213
|
+
console.error("[Tracer] Failed to update trace status:", err),
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return { success: true, data: result, error: undefined };
|
|
218
|
+
} catch (e) {
|
|
219
|
+
const error = e as unknown as Error;
|
|
220
|
+
|
|
221
|
+
if (config.onError) {
|
|
222
|
+
await config.onError(enhancedCtx, args, error);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const preserveErrors =
|
|
226
|
+
config.preserveErrors ?? defaultConfig.preserveErrors;
|
|
227
|
+
|
|
228
|
+
if (preserveErrors) {
|
|
229
|
+
const tracerAPI = new TracerAPI(ctx, component, traceId, spanId, config);
|
|
230
|
+
await tracerAPI.preserve();
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
await ctx
|
|
234
|
+
.runMutation(component.lib.completeSpan, {
|
|
235
|
+
spanId,
|
|
236
|
+
endTime: Date.now(),
|
|
237
|
+
duration: Date.now() - startTime,
|
|
238
|
+
status: "error",
|
|
239
|
+
error: error.message,
|
|
240
|
+
})
|
|
241
|
+
.catch((err) =>
|
|
242
|
+
console.error("[Tracer] Failed to complete span with error:", err),
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
if (isRoot) {
|
|
246
|
+
await ctx
|
|
247
|
+
.runMutation(component.lib.updateTraceStatus, {
|
|
248
|
+
traceId,
|
|
249
|
+
status: "error",
|
|
250
|
+
})
|
|
251
|
+
.catch((err) =>
|
|
252
|
+
console.error("[Tracer] Failed to update trace status:", err),
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return { success: false, data: undefined, error: error.message };
|
|
257
|
+
} finally {
|
|
258
|
+
const retMins = config.retentionMinutes ?? defaultConfig.retentionMinutes;
|
|
259
|
+
|
|
260
|
+
if (!retMins) {
|
|
261
|
+
console.error("[Tracer] retentionMinutes is not defined");
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const sampleRate = config.sampleRate ?? defaultConfig.sampleRate;
|
|
265
|
+
if (!sampleRate) {
|
|
266
|
+
console.error("[Tracer] sampleRate is not defined");
|
|
267
|
+
} else if (sampleRate && sampleRate < 1) {
|
|
268
|
+
const MINUTE = 60 * 1000;
|
|
269
|
+
const delayMins = retMins ?? 120;
|
|
270
|
+
|
|
271
|
+
const delay = delayMins * MINUTE;
|
|
272
|
+
|
|
273
|
+
await ctx.scheduler.runAfter(delay, component.lib.cleanupTrace, {
|
|
274
|
+
traceId,
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|