autotel-tanstack 1.13.34 → 1.13.36

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.
@@ -1,204 +0,0 @@
1
- /**
2
- * Error reporting utilities for TanStack Start
3
- *
4
- * Provides basic error reporting without external dependencies,
5
- * following the patterns from TanStack Start observability guide.
6
- */
7
-
8
- /**
9
- * Error report data structure
10
- */
11
- export interface ErrorReport {
12
- id: string;
13
- count: number;
14
- lastSeen: Date;
15
- error: {
16
- name: string;
17
- message: string;
18
- stack?: string;
19
- context?: unknown;
20
- };
21
- }
22
-
23
- /**
24
- * Error store for in-memory error tracking
25
- *
26
- * Stores error reports with deduplication by error name + message.
27
- * Thread-safe for concurrent access.
28
- */
29
- class ErrorStore {
30
- private errors = new Map<string, ErrorReport>();
31
- private readonly maxErrors = 100; // Limit memory usage
32
-
33
- /**
34
- * Report an error
35
- */
36
- reportError(error: Error, context?: unknown): string {
37
- const key = `${error.name}:${error.message}`;
38
- const existing = this.errors.get(key);
39
-
40
- if (existing) {
41
- existing.count++;
42
- existing.lastSeen = new Date();
43
- if (context) {
44
- // Merge context
45
- existing.error.context = {
46
- ...(existing.error.context as Record<string, unknown>),
47
- ...(context as Record<string, unknown>),
48
- };
49
- }
50
- return key;
51
- }
52
-
53
- // Add new error
54
- const report: ErrorReport = {
55
- id: key,
56
- count: 1,
57
- lastSeen: new Date(),
58
- error: {
59
- name: error.name,
60
- message: error.message,
61
- stack: error.stack,
62
- context,
63
- },
64
- };
65
-
66
- this.errors.set(key, report);
67
-
68
- // Limit stored errors
69
- if (this.errors.size > this.maxErrors) {
70
- // Remove oldest error
71
- const entries = [...this.errors.entries()];
72
- const oldest = entries.toSorted(
73
- (a: [string, ErrorReport], b: [string, ErrorReport]) =>
74
- a[1].lastSeen.getTime() - b[1].lastSeen.getTime(),
75
- )[0];
76
- this.errors.delete(oldest[0]);
77
- }
78
-
79
- // Log immediately
80
- console.error('[ERROR REPORTED]:', {
81
- error: error.message,
82
- count: 1,
83
- context,
84
- });
85
-
86
- return key;
87
- }
88
-
89
- /**
90
- * Get all error reports
91
- */
92
- getAllErrors(): ErrorReport[] {
93
- return [...this.errors.values()];
94
- }
95
-
96
- /**
97
- * Get a specific error by ID
98
- */
99
- getError(id: string): ErrorReport | undefined {
100
- return this.errors.get(id);
101
- }
102
-
103
- /**
104
- * Clear all errors
105
- */
106
- clear(): void {
107
- this.errors.clear();
108
- }
109
-
110
- /**
111
- * Clear a specific error
112
- */
113
- clearError(id: string): void {
114
- this.errors.delete(id);
115
- }
116
- }
117
-
118
- /**
119
- * Global error store instance
120
- */
121
- export const errorStore = new ErrorStore();
122
-
123
- /**
124
- * Report an error to the error store
125
- *
126
- * @example
127
- * ```typescript
128
- * import { reportError } from 'autotel-tanstack/error-reporting';
129
- *
130
- * try {
131
- * await riskyOperation();
132
- * } catch (error) {
133
- * reportError(error as Error, {
134
- * userId: context.userId,
135
- * operation: 'riskyOperation',
136
- * });
137
- * throw error;
138
- * }
139
- * ```
140
- */
141
- export function reportError(error: Error, context?: unknown): string {
142
- return errorStore.reportError(error, context);
143
- }
144
-
145
- /**
146
- * Create an error reporting endpoint handler
147
- *
148
- * Returns a handler that exposes error reports in JSON format.
149
- * Use this to create an `/admin/errors` endpoint.
150
- *
151
- * @example
152
- * ```typescript
153
- * // routes/admin/errors.ts
154
- * import { createFileRoute } from '@tanstack/react-router';
155
- * import { json } from '@tanstack/react-start';
156
- * import { createErrorReportingHandler } from 'autotel-tanstack/error-reporting';
157
- *
158
- * export const Route = createFileRoute('/admin/errors')({
159
- * server: {
160
- * handlers: {
161
- * GET: createErrorReportingHandler(),
162
- * },
163
- * },
164
- * });
165
- * ```
166
- */
167
- export function createErrorReportingHandler() {
168
- return async () => {
169
- const { json } = await import('@tanstack/react-start');
170
-
171
- return json({
172
- errors: errorStore.getAllErrors(),
173
- });
174
- };
175
- }
176
-
177
- /**
178
- * Wrap a function with automatic error reporting
179
- *
180
- * Automatically reports errors to the error store.
181
- *
182
- * @example
183
- * ```typescript
184
- * import { withErrorReporting } from 'autotel-tanstack/error-reporting';
185
- *
186
- * const riskyOperation = createServerFn()
187
- * .handler(withErrorReporting(async () => {
188
- * return await performOperation();
189
- * }, { operation: 'riskyOperation' }));
190
- * ```
191
- */
192
- export function withErrorReporting<TArgs extends unknown[], TReturn>(
193
- fn: (...args: TArgs) => Promise<TReturn>,
194
- context?: Record<string, unknown>,
195
- ): (...args: TArgs) => Promise<TReturn> {
196
- return async (...args: TArgs): Promise<TReturn> => {
197
- try {
198
- return await fn(...args);
199
- } catch (error) {
200
- reportError(error as Error, context);
201
- throw error;
202
- }
203
- };
204
- }
package/src/handlers.ts DELETED
@@ -1,306 +0,0 @@
1
- import { context, SpanStatusCode } from '@opentelemetry/api';
2
- import { trace, init, type TraceContext } from 'autotel';
3
- import { extractContextFromRequest } from './context';
4
- import { isExcludedPath } from './route-filter';
5
- import {
6
- type WrapStartHandlerConfig,
7
- DEFAULT_CONFIG,
8
- SPAN_ATTRIBUTES,
9
- } from './types';
10
-
11
- /**
12
- * Request handler type (compatible with TanStack Start handlers)
13
- */
14
- type RequestHandler = (
15
- request: Request,
16
- opts?: { context?: Record<string, unknown> },
17
- ) => Promise<Response> | Response;
18
-
19
- /**
20
- * Wrap a TanStack Start handler with OpenTelemetry tracing
21
- *
22
- * This function wraps the entire request handler to automatically create
23
- * spans for all incoming requests. It initializes OpenTelemetry and
24
- * provides comprehensive request tracing.
25
- *
26
- * @param config - Configuration options including OTLP endpoint and headers
27
- * @returns Function that wraps a request handler
28
- *
29
- * @example
30
- * ```typescript
31
- * // server.ts
32
- * import { createStartHandler, defaultStreamHandler } from '@tanstack/react-start/server';
33
- * import { wrapStartHandler } from 'autotel-tanstack/handlers';
34
- *
35
- * export default wrapStartHandler({
36
- * service: 'my-app',
37
- * endpoint: process.env.OTEL_EXPORTER_OTLP_ENDPOINT,
38
- * headers: { 'x-honeycomb-team': process.env.HONEYCOMB_API_KEY },
39
- * })(createStartHandler(defaultStreamHandler));
40
- * ```
41
- *
42
- * @example
43
- * ```typescript
44
- * // With env var configuration (recommended for production)
45
- * // Set OTEL_SERVICE_NAME, OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_EXPORTER_OTLP_HEADERS
46
- * export default wrapStartHandler()(createStartHandler(defaultStreamHandler));
47
- * ```
48
- */
49
- export function wrapStartHandler(
50
- config: WrapStartHandlerConfig = {},
51
- ): (handler: RequestHandler) => RequestHandler {
52
- const mergedConfig = { ...DEFAULT_CONFIG, ...config };
53
-
54
- // Initialize autotel with provided configuration
55
- const service =
56
- config.service || process.env.OTEL_SERVICE_NAME || 'tanstack-start';
57
- const endpoint = config.endpoint || process.env.OTEL_EXPORTER_OTLP_ENDPOINT;
58
-
59
- // Parse headers from env if not provided
60
- let headers = config.headers;
61
- if (!headers && process.env.OTEL_EXPORTER_OTLP_HEADERS) {
62
- headers = {};
63
- const pairs = process.env.OTEL_EXPORTER_OTLP_HEADERS.split(',');
64
- for (const pair of pairs) {
65
- const [key, value] = pair.split('=');
66
- if (key && value) {
67
- headers[key.trim()] = value.trim();
68
- }
69
- }
70
- }
71
-
72
- // Initialize OpenTelemetry
73
- init({
74
- service,
75
- endpoint,
76
- headers,
77
- });
78
-
79
- return function wrapHandler(handler: RequestHandler): RequestHandler {
80
- return async function tracedHandler(
81
- request: Request,
82
- opts?: { context?: Record<string, unknown> },
83
- ): Promise<Response> {
84
- const url = new URL(request.url);
85
-
86
- // Check if path should be excluded
87
- if (isExcludedPath(url.pathname, mergedConfig.excludePaths)) {
88
- return handler(request, opts);
89
- }
90
-
91
- // Extract parent context from request headers
92
- const parentContext = extractContextFromRequest(request);
93
-
94
- // Run within parent context
95
- return context.with(parentContext, async () => {
96
- const spanName = `${request.method} ${url.pathname}`;
97
-
98
- return trace(spanName, async (ctx: TraceContext) => {
99
- // Set HTTP semantic attributes
100
- ctx.setAttributes({
101
- [SPAN_ATTRIBUTES.HTTP_REQUEST_METHOD]: request.method,
102
- [SPAN_ATTRIBUTES.URL_PATH]: url.pathname,
103
- [SPAN_ATTRIBUTES.URL_FULL]: request.url,
104
- [SPAN_ATTRIBUTES.TANSTACK_TYPE]: 'request',
105
- });
106
-
107
- if (url.search) {
108
- ctx.setAttribute(SPAN_ATTRIBUTES.URL_QUERY, url.search);
109
- }
110
-
111
- // Capture configured headers
112
- if (mergedConfig.captureHeaders) {
113
- for (const header of mergedConfig.captureHeaders) {
114
- const value = request.headers.get(header);
115
- if (value) {
116
- ctx.setAttribute(
117
- `http.request.header.${header.toLowerCase()}`,
118
- value,
119
- );
120
- }
121
- }
122
- }
123
-
124
- // Add custom attributes
125
- if (config.customAttributes) {
126
- const customAttrs = config.customAttributes({
127
- type: 'request',
128
- name: spanName,
129
- request,
130
- });
131
- ctx.setAttributes(
132
- customAttrs as Record<string, string | number | boolean>,
133
- );
134
- }
135
-
136
- const startTime = Date.now();
137
-
138
- try {
139
- const response = await handler(request, opts);
140
- const duration = Date.now() - startTime;
141
-
142
- ctx.setAttribute(
143
- SPAN_ATTRIBUTES.TANSTACK_REQUEST_DURATION_MS,
144
- duration,
145
- );
146
- ctx.setAttribute(
147
- SPAN_ATTRIBUTES.HTTP_RESPONSE_STATUS_CODE,
148
- response.status,
149
- );
150
-
151
- // Set status based on HTTP status code
152
- if (response.status >= 400) {
153
- ctx.setStatus({
154
- code: SpanStatusCode.ERROR,
155
- message: `HTTP ${response.status}`,
156
- });
157
- } else {
158
- ctx.setStatus({ code: SpanStatusCode.OK });
159
- }
160
-
161
- return response;
162
- } catch (error) {
163
- const duration = Date.now() - startTime;
164
- ctx.setAttribute(
165
- SPAN_ATTRIBUTES.TANSTACK_REQUEST_DURATION_MS,
166
- duration,
167
- );
168
-
169
- if (mergedConfig.captureErrors) {
170
- ctx.recordError(error);
171
- }
172
-
173
- throw error;
174
- }
175
- });
176
- });
177
- };
178
- };
179
- }
180
-
181
- /**
182
- * Create a traced handler without auto-initialization
183
- *
184
- * Use this when you want to initialize autotel separately
185
- * (e.g., with more advanced configuration).
186
- *
187
- * @param config - Configuration options (excluding endpoint/headers)
188
- * @returns Function that wraps a request handler
189
- *
190
- * @example
191
- * ```typescript
192
- * import { init } from 'autotel';
193
- * import { createTracedHandler } from 'autotel-tanstack/handlers';
194
- *
195
- * // Initialize autotel with custom configuration
196
- * init({
197
- * service: 'my-app',
198
- * endpoint: 'https://api.honeycomb.io',
199
- * instrumentations: [/* custom instrumentations *\/],
200
- * });
201
- *
202
- * // Wrap handler without re-initializing
203
- * export default createTracedHandler({
204
- * captureHeaders: ['x-request-id'],
205
- * })(createStartHandler(defaultStreamHandler));
206
- * ```
207
- */
208
- export function createTracedHandler(
209
- config: Omit<WrapStartHandlerConfig, 'endpoint' | 'headers' | 'service'> = {},
210
- ): (handler: RequestHandler) => RequestHandler {
211
- const mergedConfig = { ...DEFAULT_CONFIG, ...config };
212
-
213
- return function wrapHandler(handler: RequestHandler): RequestHandler {
214
- return async function tracedHandler(
215
- request: Request,
216
- opts?: { context?: Record<string, unknown> },
217
- ): Promise<Response> {
218
- const url = new URL(request.url);
219
-
220
- // Check if path should be excluded
221
- if (isExcludedPath(url.pathname, mergedConfig.excludePaths)) {
222
- return handler(request, opts);
223
- }
224
-
225
- const parentContext = extractContextFromRequest(request);
226
-
227
- return context.with(parentContext, async () => {
228
- const spanName = `${request.method} ${url.pathname}`;
229
-
230
- return trace(spanName, async (ctx: TraceContext) => {
231
- ctx.setAttributes({
232
- [SPAN_ATTRIBUTES.HTTP_REQUEST_METHOD]: request.method,
233
- [SPAN_ATTRIBUTES.URL_PATH]: url.pathname,
234
- [SPAN_ATTRIBUTES.TANSTACK_TYPE]: 'request',
235
- });
236
-
237
- if (url.search) {
238
- ctx.setAttribute(SPAN_ATTRIBUTES.URL_QUERY, url.search);
239
- }
240
-
241
- if (mergedConfig.captureHeaders) {
242
- for (const header of mergedConfig.captureHeaders) {
243
- const value = request.headers.get(header);
244
- if (value) {
245
- ctx.setAttribute(
246
- `http.request.header.${header.toLowerCase()}`,
247
- value,
248
- );
249
- }
250
- }
251
- }
252
-
253
- if (config.customAttributes) {
254
- const customAttrs = config.customAttributes({
255
- type: 'request',
256
- name: spanName,
257
- request,
258
- });
259
- ctx.setAttributes(
260
- customAttrs as Record<string, string | number | boolean>,
261
- );
262
- }
263
-
264
- const startTime = Date.now();
265
-
266
- try {
267
- const response = await handler(request, opts);
268
- const duration = Date.now() - startTime;
269
-
270
- ctx.setAttribute(
271
- SPAN_ATTRIBUTES.TANSTACK_REQUEST_DURATION_MS,
272
- duration,
273
- );
274
- ctx.setAttribute(
275
- SPAN_ATTRIBUTES.HTTP_RESPONSE_STATUS_CODE,
276
- response.status,
277
- );
278
-
279
- if (response.status >= 400) {
280
- ctx.setStatus({
281
- code: SpanStatusCode.ERROR,
282
- message: `HTTP ${response.status}`,
283
- });
284
- } else {
285
- ctx.setStatus({ code: SpanStatusCode.OK });
286
- }
287
-
288
- return response;
289
- } catch (error) {
290
- const duration = Date.now() - startTime;
291
- ctx.setAttribute(
292
- SPAN_ATTRIBUTES.TANSTACK_REQUEST_DURATION_MS,
293
- duration,
294
- );
295
-
296
- if (mergedConfig.captureErrors) {
297
- ctx.recordError(error);
298
- }
299
-
300
- throw error;
301
- }
302
- });
303
- });
304
- };
305
- };
306
- }
package/src/index.ts DELETED
@@ -1,97 +0,0 @@
1
- /**
2
- * autotel-tanstack
3
- *
4
- * OpenTelemetry instrumentation for TanStack Start applications.
5
- * Provides automatic tracing for server functions, middleware, and route loaders.
6
- *
7
- * @example
8
- * ```typescript
9
- * // Quick start with middleware
10
- * import { createStart } from '@tanstack/react-start';
11
- * import { tracingMiddleware } from 'autotel-tanstack';
12
- *
13
- * export const startInstance = createStart(() => ({
14
- * requestMiddleware: [tracingMiddleware()],
15
- * }));
16
- * ```
17
- *
18
- * @example
19
- * ```typescript
20
- * // Zero-config auto-init
21
- * import 'autotel-tanstack/auto';
22
- * ```
23
- *
24
- * @packageDocumentation
25
- */
26
-
27
- // Types
28
- export type {
29
- TanStackInstrumentationConfig,
30
- TracingMiddlewareConfig,
31
- TraceServerFnConfig,
32
- TraceLoaderConfig,
33
- WrapStartHandlerConfig,
34
- } from './types';
35
- export { DEFAULT_CONFIG, SPAN_ATTRIBUTES } from './types';
36
-
37
- // Middleware
38
- export {
39
- createTracingMiddleware,
40
- tracingMiddleware,
41
- functionTracingMiddleware,
42
- createTracingServerHandler,
43
- type MiddlewareHandler,
44
- } from './middleware';
45
-
46
- // Server Functions
47
- export { traceServerFn, createTracedServerFnFactory } from './server-functions';
48
-
49
- // Loaders
50
- export { traceLoader, traceBeforeLoad, createTracedRoute } from './loaders';
51
-
52
- // Handlers
53
- export { wrapStartHandler, createTracedHandler } from './handlers';
54
-
55
- // Context
56
- export {
57
- extractContextFromRequest,
58
- injectContextToHeaders,
59
- createTracedHeaders,
60
- runInContext,
61
- getActiveContext,
62
- } from './context';
63
-
64
- // Debug Headers
65
- export {
66
- debugHeadersMiddleware,
67
- type DebugHeadersConfig,
68
- } from './debug-headers';
69
-
70
- // Metrics
71
- export {
72
- metricsCollector,
73
- createMetricsHandler,
74
- recordTiming,
75
- type TimingStats,
76
- } from './metrics';
77
-
78
- // Error Reporting
79
- export {
80
- errorStore,
81
- reportError,
82
- createErrorReportingHandler,
83
- withErrorReporting,
84
- type ErrorReport,
85
- } from './error-reporting';
86
-
87
- // Configurable TanStack Start tracing setup (the configurable form of the
88
- // zero-config `autotel-tanstack/auto` import).
89
- export { instrument, type InstrumentOptions } from './instrument';
90
-
91
- // Re-export autotel core utilities for convenience
92
- // Note: These should only be used on the server side
93
- // They use Node.js APIs (AsyncLocalStorage) that don't exist in the browser
94
- export { trace, span, init, shutdown, flush } from 'autotel';
95
-
96
- // Export environment utilities
97
- export { isBrowser, isNode, isServerSide } from './env';