autotel-tanstack 1.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 +21 -0
- package/README.md +294 -0
- package/dist/auto.d.ts +44 -0
- package/dist/auto.js +47 -0
- package/dist/auto.js.map +1 -0
- package/dist/browser/context.d.ts +48 -0
- package/dist/browser/context.js +3 -0
- package/dist/browser/context.js.map +1 -0
- package/dist/browser/debug-headers.d.ts +16 -0
- package/dist/browser/debug-headers.js +3 -0
- package/dist/browser/debug-headers.js.map +1 -0
- package/dist/browser/error-reporting.d.ts +37 -0
- package/dist/browser/error-reporting.js +3 -0
- package/dist/browser/error-reporting.js.map +1 -0
- package/dist/browser/handlers.d.ts +19 -0
- package/dist/browser/handlers.js +3 -0
- package/dist/browser/handlers.js.map +1 -0
- package/dist/browser/index.d.ts +10 -0
- package/dist/browser/index.js +12 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/browser/loaders.d.ts +36 -0
- package/dist/browser/loaders.js +3 -0
- package/dist/browser/loaders.js.map +1 -0
- package/dist/browser/metrics.d.ts +54 -0
- package/dist/browser/metrics.js +3 -0
- package/dist/browser/metrics.js.map +1 -0
- package/dist/browser/middleware.d.ts +39 -0
- package/dist/browser/middleware.js +3 -0
- package/dist/browser/middleware.js.map +1 -0
- package/dist/browser/server-functions.d.ts +19 -0
- package/dist/browser/server-functions.js +3 -0
- package/dist/browser/server-functions.js.map +1 -0
- package/dist/browser/testing.d.ts +45 -0
- package/dist/browser/testing.js +3 -0
- package/dist/browser/testing.js.map +1 -0
- package/dist/browser/types.d.ts +85 -0
- package/dist/browser/types.js +3 -0
- package/dist/browser/types.js.map +1 -0
- package/dist/chunk-4C7T5ZIM.js +20 -0
- package/dist/chunk-4C7T5ZIM.js.map +1 -0
- package/dist/chunk-CSFIPJC2.js +11 -0
- package/dist/chunk-CSFIPJC2.js.map +1 -0
- package/dist/chunk-DTZCOB4W.js +32 -0
- package/dist/chunk-DTZCOB4W.js.map +1 -0
- package/dist/chunk-EGRHWZRV.js +3 -0
- package/dist/chunk-EGRHWZRV.js.map +1 -0
- package/dist/chunk-EUYFVNYE.js +16 -0
- package/dist/chunk-EUYFVNYE.js.map +1 -0
- package/dist/chunk-HIQYW2HB.js +20 -0
- package/dist/chunk-HIQYW2HB.js.map +1 -0
- package/dist/chunk-HKM7LMO6.js +129 -0
- package/dist/chunk-HKM7LMO6.js.map +1 -0
- package/dist/chunk-I4LX3LOG.js +35 -0
- package/dist/chunk-I4LX3LOG.js.map +1 -0
- package/dist/chunk-JSI6QG7M.js +96 -0
- package/dist/chunk-JSI6QG7M.js.map +1 -0
- package/dist/chunk-JXO7H6KO.js +10 -0
- package/dist/chunk-JXO7H6KO.js.map +1 -0
- package/dist/chunk-MFYOV2SF.js +32 -0
- package/dist/chunk-MFYOV2SF.js.map +1 -0
- package/dist/chunk-MNP65ZX7.js +21 -0
- package/dist/chunk-MNP65ZX7.js.map +1 -0
- package/dist/chunk-NTY64BKS.js +38 -0
- package/dist/chunk-NTY64BKS.js.map +1 -0
- package/dist/chunk-OLBHLVLE.js +220 -0
- package/dist/chunk-OLBHLVLE.js.map +1 -0
- package/dist/chunk-TNOQTZ3N.js +92 -0
- package/dist/chunk-TNOQTZ3N.js.map +1 -0
- package/dist/chunk-UMEJU65Q.js +34 -0
- package/dist/chunk-UMEJU65Q.js.map +1 -0
- package/dist/chunk-UTPW3QRT.js +52 -0
- package/dist/chunk-UTPW3QRT.js.map +1 -0
- package/dist/chunk-V3RO5N2M.js +8 -0
- package/dist/chunk-V3RO5N2M.js.map +1 -0
- package/dist/chunk-XXBHZR3M.js +99 -0
- package/dist/chunk-XXBHZR3M.js.map +1 -0
- package/dist/chunk-Z3MJ3GZ6.js +18 -0
- package/dist/chunk-Z3MJ3GZ6.js.map +1 -0
- package/dist/chunk-Z5D2V4DU.js +216 -0
- package/dist/chunk-Z5D2V4DU.js.map +1 -0
- package/dist/context.d.ts +94 -0
- package/dist/context.js +4 -0
- package/dist/context.js.map +1 -0
- package/dist/debug-headers.d.ts +43 -0
- package/dist/debug-headers.js +5 -0
- package/dist/debug-headers.js.map +1 -0
- package/dist/error-reporting.d.ts +118 -0
- package/dist/error-reporting.js +4 -0
- package/dist/error-reporting.js.map +1 -0
- package/dist/handlers.d.ts +70 -0
- package/dist/handlers.js +6 -0
- package/dist/handlers.js.map +1 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/loaders.d.ts +124 -0
- package/dist/loaders.js +6 -0
- package/dist/loaders.js.map +1 -0
- package/dist/metrics.d.ts +113 -0
- package/dist/metrics.js +4 -0
- package/dist/metrics.js.map +1 -0
- package/dist/middleware.d.ts +104 -0
- package/dist/middleware.js +7 -0
- package/dist/middleware.js.map +1 -0
- package/dist/server-functions.d.ts +71 -0
- package/dist/server-functions.js +6 -0
- package/dist/server-functions.js.map +1 -0
- package/dist/testing.d.ts +128 -0
- package/dist/testing.js +110 -0
- package/dist/testing.js.map +1 -0
- package/dist/types-C37KSxMN.d.ts +152 -0
- package/package.json +166 -0
- package/src/auto.ts +86 -0
- package/src/browser/context.ts +88 -0
- package/src/browser/debug-headers.ts +19 -0
- package/src/browser/error-reporting.ts +63 -0
- package/src/browser/handlers.ts +23 -0
- package/src/browser/index.ts +65 -0
- package/src/browser/loaders.ts +62 -0
- package/src/browser/metrics.ts +86 -0
- package/src/browser/middleware.ts +61 -0
- package/src/browser/server-functions.ts +31 -0
- package/src/browser/testing.ts +67 -0
- package/src/browser/types.ts +100 -0
- package/src/context.test.ts +90 -0
- package/src/context.ts +145 -0
- package/src/debug-headers.ts +109 -0
- package/src/env.ts +56 -0
- package/src/error-reporting.ts +204 -0
- package/src/handlers.ts +339 -0
- package/src/index.ts +92 -0
- package/src/loaders.test.ts +123 -0
- package/src/loaders.ts +267 -0
- package/src/metrics.ts +183 -0
- package/src/middleware.test.ts +191 -0
- package/src/middleware.ts +400 -0
- package/src/server-functions.test.ts +86 -0
- package/src/server-functions.ts +184 -0
- package/src/testing.test.ts +72 -0
- package/src/testing.ts +276 -0
- package/src/types.test.ts +46 -0
- package/src/types.ts +182 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { createMockRequest, generateTraceparent } from './testing';
|
|
3
|
+
|
|
4
|
+
describe('testing utilities', () => {
|
|
5
|
+
describe('createMockRequest', () => {
|
|
6
|
+
it('should create a GET request', () => {
|
|
7
|
+
const request = createMockRequest('GET', '/api/users');
|
|
8
|
+
|
|
9
|
+
expect(request.method).toBe('GET');
|
|
10
|
+
expect(request.url).toBe('http://localhost/api/users');
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('should create a POST request', () => {
|
|
14
|
+
const request = createMockRequest('POST', '/api/users', {
|
|
15
|
+
body: JSON.stringify({ name: 'Test' }),
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
expect(request.method).toBe('POST');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('should include custom headers', () => {
|
|
22
|
+
const request = createMockRequest('GET', '/api/users', {
|
|
23
|
+
headers: { 'x-request-id': 'test-123' },
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
expect(request.headers.get('x-request-id')).toBe('test-123');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('should include traceparent header', () => {
|
|
30
|
+
const traceparent =
|
|
31
|
+
'00-12345678901234567890123456789012-1234567890123456-01';
|
|
32
|
+
const request = createMockRequest('GET', '/api/users', { traceparent });
|
|
33
|
+
|
|
34
|
+
expect(request.headers.get('traceparent')).toBe(traceparent);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe('generateTraceparent', () => {
|
|
39
|
+
it('should generate valid traceparent format', () => {
|
|
40
|
+
const traceparent = generateTraceparent();
|
|
41
|
+
|
|
42
|
+
// Format: version-traceId-spanId-flags
|
|
43
|
+
const parts = traceparent.split('-');
|
|
44
|
+
expect(parts).toHaveLength(4);
|
|
45
|
+
expect(parts[0]).toBe('00'); // version
|
|
46
|
+
expect(parts[1]).toHaveLength(32); // trace ID
|
|
47
|
+
expect(parts[2]).toHaveLength(16); // span ID
|
|
48
|
+
expect(parts[3]).toBe('01'); // sampled flag
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('should use provided trace ID', () => {
|
|
52
|
+
const traceId = '12345678901234567890123456789012';
|
|
53
|
+
const traceparent = generateTraceparent(traceId);
|
|
54
|
+
|
|
55
|
+
expect(traceparent).toContain(traceId);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('should use provided span ID', () => {
|
|
59
|
+
const spanId = '1234567890123456';
|
|
60
|
+
const traceparent = generateTraceparent(undefined, spanId);
|
|
61
|
+
|
|
62
|
+
expect(traceparent).toContain(spanId);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should generate different values each time', () => {
|
|
66
|
+
const tp1 = generateTraceparent();
|
|
67
|
+
const tp2 = generateTraceparent();
|
|
68
|
+
|
|
69
|
+
expect(tp1).not.toBe(tp2);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
});
|
package/src/testing.ts
ADDED
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import { type ReadableSpan } from '@opentelemetry/sdk-trace-base';
|
|
2
|
+
import { InMemorySpanExporter } from 'autotel/exporters';
|
|
3
|
+
import { SimpleSpanProcessor } from 'autotel/processors';
|
|
4
|
+
import { init } from 'autotel';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Test harness for TanStack instrumentation testing
|
|
8
|
+
*
|
|
9
|
+
* Provides utilities for testing TanStack Start applications
|
|
10
|
+
* with autotel-tanstack instrumentation.
|
|
11
|
+
*/
|
|
12
|
+
export interface TestHarness {
|
|
13
|
+
/**
|
|
14
|
+
* The in-memory span exporter
|
|
15
|
+
*/
|
|
16
|
+
exporter: {
|
|
17
|
+
getFinishedSpans(): ReadableSpan[];
|
|
18
|
+
reset(): void;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Get all finished spans
|
|
23
|
+
*/
|
|
24
|
+
getSpans(): ReadableSpan[];
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Get spans by name (exact match or regex)
|
|
28
|
+
*/
|
|
29
|
+
getSpansByName(name: string | RegExp): ReadableSpan[];
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Get spans by TanStack type
|
|
33
|
+
*/
|
|
34
|
+
getSpansByType(
|
|
35
|
+
type: 'request' | 'serverFn' | 'loader' | 'beforeLoad',
|
|
36
|
+
): ReadableSpan[];
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Reset collected spans
|
|
40
|
+
*/
|
|
41
|
+
reset(): void;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Assert a span exists
|
|
45
|
+
*/
|
|
46
|
+
assertSpanExists(name: string | RegExp): void;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Assert a span has a specific attribute
|
|
50
|
+
*/
|
|
51
|
+
assertSpanHasAttribute(
|
|
52
|
+
name: string | RegExp,
|
|
53
|
+
attr: string,
|
|
54
|
+
value?: unknown,
|
|
55
|
+
): void;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Assert a server function was traced
|
|
59
|
+
*/
|
|
60
|
+
assertServerFnTraced(name: string): void;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Assert a loader was traced
|
|
64
|
+
*/
|
|
65
|
+
assertLoaderTraced(routeId: string): void;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Assert a beforeLoad was traced
|
|
69
|
+
*/
|
|
70
|
+
assertBeforeLoadTraced(routeId: string): void;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Assert an HTTP request was traced
|
|
74
|
+
*/
|
|
75
|
+
assertRequestTraced(method: string, path: string): void;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Create a test harness for TanStack instrumentation testing
|
|
80
|
+
*
|
|
81
|
+
* This sets up autotel with an in-memory exporter for testing.
|
|
82
|
+
* Call this in your test setup to capture and assert on spans.
|
|
83
|
+
*
|
|
84
|
+
* @returns Test harness with assertion helpers
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* ```typescript
|
|
88
|
+
* import { describe, it, beforeEach } from 'vitest';
|
|
89
|
+
* import { createTestHarness } from 'autotel-tanstack/testing';
|
|
90
|
+
*
|
|
91
|
+
* describe('MyServerFunction', () => {
|
|
92
|
+
* let harness: ReturnType<typeof createTestHarness>;
|
|
93
|
+
*
|
|
94
|
+
* beforeEach(() => {
|
|
95
|
+
* harness = createTestHarness();
|
|
96
|
+
* });
|
|
97
|
+
*
|
|
98
|
+
* afterEach(() => {
|
|
99
|
+
* harness.reset();
|
|
100
|
+
* });
|
|
101
|
+
*
|
|
102
|
+
* it('should trace the server function', async () => {
|
|
103
|
+
* await myServerFunction({ id: '123' });
|
|
104
|
+
*
|
|
105
|
+
* harness.assertServerFnTraced('myServerFunction');
|
|
106
|
+
* harness.assertSpanHasAttribute(
|
|
107
|
+
* /tanstack\.serverFn/,
|
|
108
|
+
* 'tanstack.server_function.name',
|
|
109
|
+
* 'myServerFunction'
|
|
110
|
+
* );
|
|
111
|
+
* });
|
|
112
|
+
* });
|
|
113
|
+
* ```
|
|
114
|
+
*/
|
|
115
|
+
export function createTestHarness(): TestHarness {
|
|
116
|
+
const exporter = new InMemorySpanExporter();
|
|
117
|
+
|
|
118
|
+
init({
|
|
119
|
+
service: 'test',
|
|
120
|
+
spanProcessors: [new SimpleSpanProcessor(exporter)],
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
function getSpans(): ReadableSpan[] {
|
|
124
|
+
return exporter.getFinishedSpans() as ReadableSpan[];
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function getSpansByName(name: string | RegExp): ReadableSpan[] {
|
|
128
|
+
const spans = getSpans();
|
|
129
|
+
if (typeof name === 'string') {
|
|
130
|
+
return spans.filter((s) => s.name === name);
|
|
131
|
+
}
|
|
132
|
+
return spans.filter((s) => name.test(s.name));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function getSpansByType(
|
|
136
|
+
type: 'request' | 'serverFn' | 'loader' | 'beforeLoad',
|
|
137
|
+
): ReadableSpan[] {
|
|
138
|
+
return getSpans().filter((s) => s.attributes['tanstack.type'] === type);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function reset(): void {
|
|
142
|
+
exporter.reset();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function assertSpanExists(name: string | RegExp): void {
|
|
146
|
+
const spans = getSpansByName(name);
|
|
147
|
+
if (spans.length === 0) {
|
|
148
|
+
const allSpanNames = getSpans().map((s) => s.name);
|
|
149
|
+
throw new Error(
|
|
150
|
+
`Expected span "${name}" to exist. Found spans: ${JSON.stringify(allSpanNames)}`,
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function assertSpanHasAttribute(
|
|
156
|
+
name: string | RegExp,
|
|
157
|
+
attr: string,
|
|
158
|
+
value?: unknown,
|
|
159
|
+
): void {
|
|
160
|
+
const spans = getSpansByName(name);
|
|
161
|
+
if (spans.length === 0) {
|
|
162
|
+
throw new Error(`Span "${name}" not found`);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const span = spans[0];
|
|
166
|
+
const attrValue = span.attributes[attr];
|
|
167
|
+
|
|
168
|
+
if (attrValue === undefined) {
|
|
169
|
+
throw new Error(
|
|
170
|
+
`Attribute "${attr}" not found on span "${span.name}". ` +
|
|
171
|
+
`Available attributes: ${JSON.stringify(Object.keys(span.attributes))}`,
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (value !== undefined && attrValue !== value) {
|
|
176
|
+
throw new Error(
|
|
177
|
+
`Expected attribute "${attr}" to be "${value}", got "${attrValue}"`,
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function assertServerFnTraced(name: string): void {
|
|
183
|
+
assertSpanExists(`tanstack.serverFn.${name}`);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function assertLoaderTraced(routeId: string): void {
|
|
187
|
+
assertSpanExists(`tanstack.loader.${routeId}`);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function assertBeforeLoadTraced(routeId: string): void {
|
|
191
|
+
assertSpanExists(`tanstack.beforeLoad.${routeId}`);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function assertRequestTraced(method: string, path: string): void {
|
|
195
|
+
assertSpanExists(`${method} ${path}`);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return {
|
|
199
|
+
exporter,
|
|
200
|
+
getSpans,
|
|
201
|
+
getSpansByName,
|
|
202
|
+
getSpansByType,
|
|
203
|
+
reset,
|
|
204
|
+
assertSpanExists,
|
|
205
|
+
assertSpanHasAttribute,
|
|
206
|
+
assertServerFnTraced,
|
|
207
|
+
assertLoaderTraced,
|
|
208
|
+
assertBeforeLoadTraced,
|
|
209
|
+
assertRequestTraced,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Mock request factory for testing
|
|
215
|
+
*
|
|
216
|
+
* Creates mock Request objects for testing middleware and handlers.
|
|
217
|
+
*
|
|
218
|
+
* @example
|
|
219
|
+
* ```typescript
|
|
220
|
+
* const request = createMockRequest('GET', '/api/users', {
|
|
221
|
+
* headers: { 'x-request-id': 'test-123' },
|
|
222
|
+
* });
|
|
223
|
+
* ```
|
|
224
|
+
*/
|
|
225
|
+
export function createMockRequest(
|
|
226
|
+
method: string,
|
|
227
|
+
path: string,
|
|
228
|
+
options: {
|
|
229
|
+
headers?: Record<string, string>;
|
|
230
|
+
body?: string;
|
|
231
|
+
traceparent?: string;
|
|
232
|
+
} = {},
|
|
233
|
+
): Request {
|
|
234
|
+
const headers = new Headers(options.headers);
|
|
235
|
+
|
|
236
|
+
if (options.traceparent) {
|
|
237
|
+
headers.set('traceparent', options.traceparent);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return new Request(`http://localhost${path}`, {
|
|
241
|
+
method,
|
|
242
|
+
headers,
|
|
243
|
+
body: options.body,
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Generate a valid W3C traceparent header for testing
|
|
249
|
+
*
|
|
250
|
+
* @param traceId - Optional 32-char hex trace ID
|
|
251
|
+
* @param spanId - Optional 16-char hex span ID
|
|
252
|
+
* @returns Valid traceparent header string
|
|
253
|
+
*
|
|
254
|
+
* @example
|
|
255
|
+
* ```typescript
|
|
256
|
+
* const traceparent = generateTraceparent();
|
|
257
|
+
* const request = createMockRequest('GET', '/api/users', { traceparent });
|
|
258
|
+
* ```
|
|
259
|
+
*/
|
|
260
|
+
export function generateTraceparent(traceId?: string, spanId?: string): string {
|
|
261
|
+
const version = '00';
|
|
262
|
+
const trace = traceId || generateHex(32);
|
|
263
|
+
const span = spanId || generateHex(16);
|
|
264
|
+
const flags = '01'; // Sampled
|
|
265
|
+
|
|
266
|
+
return `${version}-${trace}-${span}-${flags}`;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function generateHex(length: number): string {
|
|
270
|
+
const chars = '0123456789abcdef';
|
|
271
|
+
let result = '';
|
|
272
|
+
for (let i = 0; i < length; i++) {
|
|
273
|
+
result += chars[Math.floor(Math.random() * 16)];
|
|
274
|
+
}
|
|
275
|
+
return result;
|
|
276
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { DEFAULT_CONFIG, SPAN_ATTRIBUTES } from './types';
|
|
3
|
+
|
|
4
|
+
describe('types', () => {
|
|
5
|
+
describe('DEFAULT_CONFIG', () => {
|
|
6
|
+
it('should have sensible defaults', () => {
|
|
7
|
+
expect(DEFAULT_CONFIG.captureArgs).toBe(true);
|
|
8
|
+
expect(DEFAULT_CONFIG.captureResults).toBe(false);
|
|
9
|
+
expect(DEFAULT_CONFIG.captureErrors).toBe(true);
|
|
10
|
+
expect(DEFAULT_CONFIG.sampling).toBe('adaptive');
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('should capture x-request-id by default', () => {
|
|
14
|
+
expect(DEFAULT_CONFIG.captureHeaders).toContain('x-request-id');
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('should not exclude any paths by default', () => {
|
|
18
|
+
expect(DEFAULT_CONFIG.excludePaths).toEqual([]);
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe('SPAN_ATTRIBUTES', () => {
|
|
23
|
+
it('should have HTTP semantic convention attributes', () => {
|
|
24
|
+
expect(SPAN_ATTRIBUTES.HTTP_REQUEST_METHOD).toBe('http.request.method');
|
|
25
|
+
expect(SPAN_ATTRIBUTES.HTTP_RESPONSE_STATUS_CODE).toBe(
|
|
26
|
+
'http.response.status_code',
|
|
27
|
+
);
|
|
28
|
+
expect(SPAN_ATTRIBUTES.URL_PATH).toBe('url.path');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should have RPC semantic convention attributes', () => {
|
|
32
|
+
expect(SPAN_ATTRIBUTES.RPC_SYSTEM).toBe('rpc.system');
|
|
33
|
+
expect(SPAN_ATTRIBUTES.RPC_METHOD).toBe('rpc.method');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should have TanStack-specific attributes', () => {
|
|
37
|
+
expect(SPAN_ATTRIBUTES.TANSTACK_TYPE).toBe('tanstack.type');
|
|
38
|
+
expect(SPAN_ATTRIBUTES.TANSTACK_SERVER_FN_NAME).toBe(
|
|
39
|
+
'tanstack.server_function.name',
|
|
40
|
+
);
|
|
41
|
+
expect(SPAN_ATTRIBUTES.TANSTACK_LOADER_ROUTE_ID).toBe(
|
|
42
|
+
'tanstack.loader.route_id',
|
|
43
|
+
);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
});
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import type { Attributes } from '@opentelemetry/api';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Configuration options for TanStack Start instrumentation
|
|
5
|
+
*/
|
|
6
|
+
export interface TanStackInstrumentationConfig {
|
|
7
|
+
/**
|
|
8
|
+
* Service name for spans
|
|
9
|
+
* @default process.env.OTEL_SERVICE_NAME || 'tanstack-start'
|
|
10
|
+
*/
|
|
11
|
+
service?: string;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Whether to capture function arguments as span attributes
|
|
15
|
+
* Warning: May contain PII, review before enabling in production
|
|
16
|
+
* @default true
|
|
17
|
+
*/
|
|
18
|
+
captureArgs?: boolean;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Whether to capture function results as span attributes
|
|
22
|
+
* Warning: May contain PII, disable in production
|
|
23
|
+
* @default false
|
|
24
|
+
*/
|
|
25
|
+
captureResults?: boolean;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Whether to capture errors and record exceptions
|
|
29
|
+
* @default true
|
|
30
|
+
*/
|
|
31
|
+
captureErrors?: boolean;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* HTTP headers to capture as span attributes
|
|
35
|
+
* @default ['x-request-id']
|
|
36
|
+
*/
|
|
37
|
+
captureHeaders?: string[];
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* URL paths to exclude from tracing (glob patterns)
|
|
41
|
+
* @default []
|
|
42
|
+
*/
|
|
43
|
+
excludePaths?: (string | RegExp)[];
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Sampling strategy
|
|
47
|
+
* - 'always': Sample all requests (100%)
|
|
48
|
+
* - 'adaptive': Use autotel's adaptive sampling (errors + slow = 100%, baseline 10%)
|
|
49
|
+
* - 'never': Disable sampling (for testing/debugging)
|
|
50
|
+
* @default 'adaptive'
|
|
51
|
+
*/
|
|
52
|
+
sampling?: 'always' | 'adaptive' | 'never';
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Custom function to extract additional span attributes
|
|
56
|
+
*/
|
|
57
|
+
customAttributes?: (context: {
|
|
58
|
+
type: 'request' | 'serverFn' | 'loader' | 'beforeLoad' | 'middleware';
|
|
59
|
+
name: string;
|
|
60
|
+
request?: Request;
|
|
61
|
+
args?: unknown;
|
|
62
|
+
result?: unknown;
|
|
63
|
+
}) => Attributes;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Configuration specific to tracing middleware
|
|
68
|
+
*/
|
|
69
|
+
export interface TracingMiddlewareConfig extends TanStackInstrumentationConfig {
|
|
70
|
+
/**
|
|
71
|
+
* Type of middleware
|
|
72
|
+
* - 'request': For global request middleware (routes, SSR)
|
|
73
|
+
* - 'function': For server function middleware
|
|
74
|
+
* @default 'request'
|
|
75
|
+
*/
|
|
76
|
+
type?: 'request' | 'function';
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Configuration for server function tracing
|
|
81
|
+
*/
|
|
82
|
+
export interface TraceServerFnConfig {
|
|
83
|
+
/**
|
|
84
|
+
* Explicit name for the span
|
|
85
|
+
* If not provided, will attempt to infer from function name
|
|
86
|
+
*/
|
|
87
|
+
name?: string;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Whether to capture function arguments
|
|
91
|
+
* @default true
|
|
92
|
+
*/
|
|
93
|
+
captureArgs?: boolean;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Whether to capture function results
|
|
97
|
+
* @default false
|
|
98
|
+
*/
|
|
99
|
+
captureResults?: boolean;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Configuration for loader tracing
|
|
104
|
+
*/
|
|
105
|
+
export interface TraceLoaderConfig {
|
|
106
|
+
/**
|
|
107
|
+
* Explicit name for the span
|
|
108
|
+
* If not provided, will use route ID
|
|
109
|
+
*/
|
|
110
|
+
name?: string;
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Whether to capture route params
|
|
114
|
+
* @default true
|
|
115
|
+
*/
|
|
116
|
+
captureParams?: boolean;
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Whether to capture loader result
|
|
120
|
+
* @default false
|
|
121
|
+
*/
|
|
122
|
+
captureResult?: boolean;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Configuration for handler wrapper
|
|
127
|
+
*/
|
|
128
|
+
export interface WrapStartHandlerConfig extends TanStackInstrumentationConfig {
|
|
129
|
+
/**
|
|
130
|
+
* OTLP endpoint URL
|
|
131
|
+
* @default process.env.OTEL_EXPORTER_OTLP_ENDPOINT
|
|
132
|
+
*/
|
|
133
|
+
endpoint?: string;
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* OTLP headers (e.g., for authentication)
|
|
137
|
+
* @default parsed from process.env.OTEL_EXPORTER_OTLP_HEADERS
|
|
138
|
+
*/
|
|
139
|
+
headers?: Record<string, string>;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Default configuration values
|
|
144
|
+
*/
|
|
145
|
+
export const DEFAULT_CONFIG: Required<
|
|
146
|
+
Omit<TanStackInstrumentationConfig, 'customAttributes' | 'service'>
|
|
147
|
+
> = {
|
|
148
|
+
captureArgs: true,
|
|
149
|
+
captureResults: false,
|
|
150
|
+
captureErrors: true,
|
|
151
|
+
captureHeaders: ['x-request-id'],
|
|
152
|
+
excludePaths: [],
|
|
153
|
+
sampling: 'adaptive',
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Span attribute keys following OpenTelemetry semantic conventions
|
|
158
|
+
*/
|
|
159
|
+
export const SPAN_ATTRIBUTES = {
|
|
160
|
+
// HTTP semantic conventions
|
|
161
|
+
HTTP_REQUEST_METHOD: 'http.request.method',
|
|
162
|
+
HTTP_RESPONSE_STATUS_CODE: 'http.response.status_code',
|
|
163
|
+
URL_PATH: 'url.path',
|
|
164
|
+
URL_QUERY: 'url.query',
|
|
165
|
+
URL_FULL: 'url.full',
|
|
166
|
+
|
|
167
|
+
// RPC semantic conventions (for server functions)
|
|
168
|
+
RPC_SYSTEM: 'rpc.system',
|
|
169
|
+
RPC_METHOD: 'rpc.method',
|
|
170
|
+
|
|
171
|
+
// TanStack-specific attributes
|
|
172
|
+
TANSTACK_TYPE: 'tanstack.type',
|
|
173
|
+
TANSTACK_SERVER_FN_NAME: 'tanstack.server_function.name',
|
|
174
|
+
TANSTACK_SERVER_FN_METHOD: 'tanstack.server_function.method',
|
|
175
|
+
TANSTACK_SERVER_FN_ARGS: 'tanstack.server_function.args',
|
|
176
|
+
TANSTACK_SERVER_FN_RESULT: 'tanstack.server_function.result',
|
|
177
|
+
TANSTACK_LOADER_ROUTE_ID: 'tanstack.loader.route_id',
|
|
178
|
+
TANSTACK_LOADER_TYPE: 'tanstack.loader.type',
|
|
179
|
+
TANSTACK_LOADER_PARAMS: 'tanstack.loader.params',
|
|
180
|
+
TANSTACK_MIDDLEWARE_NAME: 'tanstack.middleware.name',
|
|
181
|
+
TANSTACK_REQUEST_DURATION_MS: 'tanstack.request.duration_ms',
|
|
182
|
+
} as const;
|