autotel-adapters 0.3.11 → 0.3.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -3
- package/src/cloudflare.test.ts +0 -56
- package/src/cloudflare.ts +0 -186
- package/src/core.test.ts +0 -38
- package/src/core.ts +0 -225
- package/src/express.test.ts +0 -85
- package/src/express.ts +0 -169
- package/src/fastify.test.ts +0 -79
- package/src/fastify.ts +0 -151
- package/src/hono.ts +0 -20
- package/src/index.ts +0 -14
- package/src/next.test.ts +0 -47
- package/src/next.ts +0 -137
- package/src/nitro.test.ts +0 -49
- package/src/nitro.ts +0 -121
- package/src/resolve-adapter-config.test.ts +0 -110
- package/src/tanstack.ts +0 -23
package/src/express.ts
DELETED
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
2
|
-
import {
|
|
3
|
-
createDrainPipeline,
|
|
4
|
-
createStructuredError,
|
|
5
|
-
parseError,
|
|
6
|
-
type DrainPipelineOptions,
|
|
7
|
-
type ParsedError,
|
|
8
|
-
type PipelineDrainFn,
|
|
9
|
-
type RequestLogger,
|
|
10
|
-
type RequestLoggerOptions,
|
|
11
|
-
type StructuredError,
|
|
12
|
-
type StructuredErrorInput,
|
|
13
|
-
} from 'autotel';
|
|
14
|
-
import {
|
|
15
|
-
createAdapterToolkit,
|
|
16
|
-
createRequestRunner,
|
|
17
|
-
createUseLogger,
|
|
18
|
-
} from './core';
|
|
19
|
-
|
|
20
|
-
export interface ExpressRequestLike {
|
|
21
|
-
method?: string;
|
|
22
|
-
originalUrl?: string;
|
|
23
|
-
url?: string;
|
|
24
|
-
path?: string;
|
|
25
|
-
route?: { path?: string };
|
|
26
|
-
headers?: Record<string, string | string[] | undefined>;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export interface ExpressResponseLike {
|
|
30
|
-
statusCode?: number;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export type ExpressNext = (err?: unknown) => void;
|
|
34
|
-
|
|
35
|
-
export interface ExpressWithAutotelOptions {
|
|
36
|
-
spanName?: string | ((request: ExpressRequestLike) => string);
|
|
37
|
-
requestLoggerOptions?: RequestLoggerOptions;
|
|
38
|
-
enrich?: (request: ExpressRequestLike) => Record<string, unknown> | undefined;
|
|
39
|
-
/** Emit one wide event automatically when the handler settles. Default `true`. */
|
|
40
|
-
autoEmit?: boolean;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const expressLoggerStorage = new AsyncLocalStorage<RequestLogger>();
|
|
44
|
-
|
|
45
|
-
function headerValue(
|
|
46
|
-
headers: ExpressRequestLike['headers'],
|
|
47
|
-
name: string,
|
|
48
|
-
): string | undefined {
|
|
49
|
-
if (!headers) return undefined;
|
|
50
|
-
const value = headers[name] ?? headers[name.toLowerCase()];
|
|
51
|
-
if (Array.isArray(value)) return value[0];
|
|
52
|
-
return typeof value === 'string' ? value : undefined;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function enrichFromRequest(
|
|
56
|
-
request?: ExpressRequestLike,
|
|
57
|
-
): Record<string, unknown> | undefined {
|
|
58
|
-
if (!request) return undefined;
|
|
59
|
-
|
|
60
|
-
const url = request.originalUrl ?? request.url;
|
|
61
|
-
const route = request.route?.path ?? request.path ?? url;
|
|
62
|
-
const requestId = headerValue(request.headers, 'x-request-id');
|
|
63
|
-
|
|
64
|
-
return {
|
|
65
|
-
...(request.method ? { 'http.request.method': request.method } : {}),
|
|
66
|
-
...(url ? { 'url.full': url } : {}),
|
|
67
|
-
...(route ? { 'http.route': route } : {}),
|
|
68
|
-
...(requestId ? { 'http.request.id': requestId } : {}),
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const baseUseLogger = createUseLogger<ExpressRequestLike>({
|
|
73
|
-
adapterName: 'express',
|
|
74
|
-
enrich: enrichFromRequest,
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
export function useLogger(
|
|
78
|
-
request?: ExpressRequestLike,
|
|
79
|
-
requestLoggerOptions?: RequestLoggerOptions,
|
|
80
|
-
): RequestLogger {
|
|
81
|
-
const stored = expressLoggerStorage.getStore();
|
|
82
|
-
if (stored) return stored;
|
|
83
|
-
return baseUseLogger(request, requestLoggerOptions);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const runRequest = createRequestRunner(expressLoggerStorage);
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Wrap an Express route handler. Each request opens a span, gets a
|
|
90
|
-
* request-scoped logger (via `useLogger(req)`), and emits one wide event when
|
|
91
|
-
* the handler settles. Thrown errors are recorded and forwarded to `next`.
|
|
92
|
-
*/
|
|
93
|
-
export function withAutotel<
|
|
94
|
-
TReq extends ExpressRequestLike,
|
|
95
|
-
TRes extends ExpressResponseLike,
|
|
96
|
-
TReturn,
|
|
97
|
-
>(
|
|
98
|
-
handler: (req: TReq, res: TRes, next?: ExpressNext) => TReturn | Promise<TReturn>,
|
|
99
|
-
options?: ExpressWithAutotelOptions,
|
|
100
|
-
): (req: TReq, res: TRes, next?: ExpressNext) => Promise<TReturn | undefined> {
|
|
101
|
-
return async (
|
|
102
|
-
req: TReq,
|
|
103
|
-
res: TRes,
|
|
104
|
-
next?: ExpressNext,
|
|
105
|
-
): Promise<TReturn | undefined> => {
|
|
106
|
-
const spanName =
|
|
107
|
-
typeof options?.spanName === 'function'
|
|
108
|
-
? options.spanName(req)
|
|
109
|
-
: (options?.spanName ?? `express.${req.method ?? 'request'}`);
|
|
110
|
-
|
|
111
|
-
try {
|
|
112
|
-
return await runRequest<TReturn>(
|
|
113
|
-
spanName,
|
|
114
|
-
(log) => {
|
|
115
|
-
const auto = enrichFromRequest(req);
|
|
116
|
-
if (auto && Object.keys(auto).length > 0) log.set(auto);
|
|
117
|
-
const custom = options?.enrich?.(req);
|
|
118
|
-
if (custom && Object.keys(custom).length > 0) log.set(custom);
|
|
119
|
-
},
|
|
120
|
-
() => handler(req, res, next),
|
|
121
|
-
{
|
|
122
|
-
requestLoggerOptions: options?.requestLoggerOptions,
|
|
123
|
-
autoEmit: options?.autoEmit,
|
|
124
|
-
finalize: () =>
|
|
125
|
-
res.statusCode
|
|
126
|
-
? { 'http.response.status_code': res.statusCode }
|
|
127
|
-
: undefined,
|
|
128
|
-
},
|
|
129
|
-
);
|
|
130
|
-
} catch (error) {
|
|
131
|
-
if (typeof next === 'function') {
|
|
132
|
-
next(error);
|
|
133
|
-
return undefined;
|
|
134
|
-
}
|
|
135
|
-
throw error;
|
|
136
|
-
}
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
export function createExpressAdapter(options?: ExpressWithAutotelOptions) {
|
|
141
|
-
return {
|
|
142
|
-
withAutotel: <
|
|
143
|
-
TReq extends ExpressRequestLike,
|
|
144
|
-
TRes extends ExpressResponseLike,
|
|
145
|
-
TReturn,
|
|
146
|
-
>(
|
|
147
|
-
handler: (
|
|
148
|
-
req: TReq,
|
|
149
|
-
res: TRes,
|
|
150
|
-
next?: ExpressNext,
|
|
151
|
-
) => TReturn | Promise<TReturn>,
|
|
152
|
-
) => withAutotel(handler, options),
|
|
153
|
-
useLogger,
|
|
154
|
-
parseError: (error: unknown): ParsedError => parseError(error),
|
|
155
|
-
createStructuredError: (input: StructuredErrorInput): StructuredError =>
|
|
156
|
-
createStructuredError(input),
|
|
157
|
-
createDrainPipeline: <T = unknown>(
|
|
158
|
-
drainOptions?: DrainPipelineOptions<T>,
|
|
159
|
-
): ((batchDrain: (batch: T[]) => void | Promise<void>) => PipelineDrainFn<T>) =>
|
|
160
|
-
createDrainPipeline(drainOptions),
|
|
161
|
-
};
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
export const expressToolkit = createAdapterToolkit<ExpressRequestLike>({
|
|
165
|
-
adapterName: 'express',
|
|
166
|
-
enrich: enrichFromRequest,
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
export { parseError, createDrainPipeline, createStructuredError };
|
package/src/fastify.test.ts
DELETED
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
-
import { useLogger, withAutotel } from './fastify';
|
|
3
|
-
import type { FastifyRequestLike, FastifyReplyLike } from './fastify';
|
|
4
|
-
|
|
5
|
-
const req = (over: Partial<FastifyRequestLike> = {}): FastifyRequestLike => ({
|
|
6
|
-
method: 'GET',
|
|
7
|
-
url: '/api/orders',
|
|
8
|
-
routeOptions: { url: '/api/orders/:id' },
|
|
9
|
-
id: 'req-1',
|
|
10
|
-
...over,
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
describe('fastify adapter', () => {
|
|
14
|
-
it('throws a clear error when useLogger is called outside a traced context', () => {
|
|
15
|
-
expect(() => useLogger(req())).toThrow(
|
|
16
|
-
'[autotel-adapters/fastify] No active trace context.',
|
|
17
|
-
);
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
it('runs the handler with a request-scoped logger', async () => {
|
|
21
|
-
const handler = withAutotel((request, reply: FastifyReplyLike) => {
|
|
22
|
-
useLogger(request).set({ feature: 'checkout' });
|
|
23
|
-
reply.statusCode = 201;
|
|
24
|
-
return { ok: true };
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
await expect(handler(req(), { statusCode: 200 })).resolves.toEqual({
|
|
28
|
-
ok: true,
|
|
29
|
-
});
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
it('auto-emits one wide event with route, request id, and status', async () => {
|
|
33
|
-
const onEmit = vi.fn();
|
|
34
|
-
const handler = withAutotel(
|
|
35
|
-
(request, reply: FastifyReplyLike) => {
|
|
36
|
-
useLogger(request).set({ user: 'u1' });
|
|
37
|
-
reply.statusCode = 200;
|
|
38
|
-
return 'done';
|
|
39
|
-
},
|
|
40
|
-
{ requestLoggerOptions: { onEmit } },
|
|
41
|
-
);
|
|
42
|
-
|
|
43
|
-
await handler(req({ method: 'POST' }), { statusCode: 200 });
|
|
44
|
-
|
|
45
|
-
expect(onEmit).toHaveBeenCalledTimes(1);
|
|
46
|
-
const snapshot = onEmit.mock.calls[0]?.[0] as {
|
|
47
|
-
context: Record<string, unknown>;
|
|
48
|
-
};
|
|
49
|
-
expect(snapshot.context.user).toBe('u1');
|
|
50
|
-
expect(snapshot.context['http.route']).toBe('/api/orders/:id');
|
|
51
|
-
expect(snapshot.context['http.request.id']).toBe('req-1');
|
|
52
|
-
expect(snapshot.context['http.response.status_code']).toBe(200);
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it('does not emit when autoEmit is false', async () => {
|
|
56
|
-
const onEmit = vi.fn();
|
|
57
|
-
const handler = withAutotel(() => 'x', {
|
|
58
|
-
autoEmit: false,
|
|
59
|
-
requestLoggerOptions: { onEmit },
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
await handler(req(), { statusCode: 200 });
|
|
63
|
-
expect(onEmit).not.toHaveBeenCalled();
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
it('records the error, emits, and rethrows for fastify to handle', async () => {
|
|
67
|
-
const onEmit = vi.fn();
|
|
68
|
-
const boom = new Error('boom');
|
|
69
|
-
const handler = withAutotel(
|
|
70
|
-
() => {
|
|
71
|
-
throw boom;
|
|
72
|
-
},
|
|
73
|
-
{ requestLoggerOptions: { onEmit } },
|
|
74
|
-
);
|
|
75
|
-
|
|
76
|
-
await expect(handler(req(), { statusCode: 500 })).rejects.toThrow('boom');
|
|
77
|
-
expect(onEmit).toHaveBeenCalledTimes(1);
|
|
78
|
-
});
|
|
79
|
-
});
|
package/src/fastify.ts
DELETED
|
@@ -1,151 +0,0 @@
|
|
|
1
|
-
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
2
|
-
import {
|
|
3
|
-
createDrainPipeline,
|
|
4
|
-
createStructuredError,
|
|
5
|
-
parseError,
|
|
6
|
-
type DrainPipelineOptions,
|
|
7
|
-
type ParsedError,
|
|
8
|
-
type PipelineDrainFn,
|
|
9
|
-
type RequestLogger,
|
|
10
|
-
type RequestLoggerOptions,
|
|
11
|
-
type StructuredError,
|
|
12
|
-
type StructuredErrorInput,
|
|
13
|
-
} from 'autotel';
|
|
14
|
-
import {
|
|
15
|
-
createAdapterToolkit,
|
|
16
|
-
createRequestRunner,
|
|
17
|
-
createUseLogger,
|
|
18
|
-
} from './core';
|
|
19
|
-
|
|
20
|
-
export interface FastifyRequestLike {
|
|
21
|
-
method?: string;
|
|
22
|
-
url?: string;
|
|
23
|
-
routeOptions?: { url?: string };
|
|
24
|
-
routerPath?: string;
|
|
25
|
-
id?: string;
|
|
26
|
-
headers?: Record<string, string | string[] | undefined>;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export interface FastifyReplyLike {
|
|
30
|
-
statusCode?: number;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export interface FastifyWithAutotelOptions {
|
|
34
|
-
spanName?: string | ((request: FastifyRequestLike) => string);
|
|
35
|
-
requestLoggerOptions?: RequestLoggerOptions;
|
|
36
|
-
enrich?: (request: FastifyRequestLike) => Record<string, unknown> | undefined;
|
|
37
|
-
/** Emit one wide event automatically when the handler settles. Default `true`. */
|
|
38
|
-
autoEmit?: boolean;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const fastifyLoggerStorage = new AsyncLocalStorage<RequestLogger>();
|
|
42
|
-
|
|
43
|
-
function headerValue(
|
|
44
|
-
headers: FastifyRequestLike['headers'],
|
|
45
|
-
name: string,
|
|
46
|
-
): string | undefined {
|
|
47
|
-
if (!headers) return undefined;
|
|
48
|
-
const value = headers[name] ?? headers[name.toLowerCase()];
|
|
49
|
-
if (Array.isArray(value)) return value[0];
|
|
50
|
-
return typeof value === 'string' ? value : undefined;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function enrichFromRequest(
|
|
54
|
-
request?: FastifyRequestLike,
|
|
55
|
-
): Record<string, unknown> | undefined {
|
|
56
|
-
if (!request) return undefined;
|
|
57
|
-
|
|
58
|
-
const route = request.routeOptions?.url ?? request.routerPath ?? request.url;
|
|
59
|
-
const requestId = request.id ?? headerValue(request.headers, 'x-request-id');
|
|
60
|
-
|
|
61
|
-
return {
|
|
62
|
-
...(request.method ? { 'http.request.method': request.method } : {}),
|
|
63
|
-
...(request.url ? { 'url.full': request.url } : {}),
|
|
64
|
-
...(route ? { 'http.route': route } : {}),
|
|
65
|
-
...(requestId ? { 'http.request.id': requestId } : {}),
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const baseUseLogger = createUseLogger<FastifyRequestLike>({
|
|
70
|
-
adapterName: 'fastify',
|
|
71
|
-
enrich: enrichFromRequest,
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
export function useLogger(
|
|
75
|
-
request?: FastifyRequestLike,
|
|
76
|
-
requestLoggerOptions?: RequestLoggerOptions,
|
|
77
|
-
): RequestLogger {
|
|
78
|
-
const stored = fastifyLoggerStorage.getStore();
|
|
79
|
-
if (stored) return stored;
|
|
80
|
-
return baseUseLogger(request, requestLoggerOptions);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const runRequest = createRequestRunner(fastifyLoggerStorage);
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Wrap a Fastify route handler. Each request opens a span, gets a
|
|
87
|
-
* request-scoped logger (via `useLogger(request)`), and emits one wide event
|
|
88
|
-
* when the handler settles. Thrown errors are recorded, then rethrown for
|
|
89
|
-
* Fastify's error handling.
|
|
90
|
-
*/
|
|
91
|
-
export function withAutotel<
|
|
92
|
-
TReq extends FastifyRequestLike,
|
|
93
|
-
TRes extends FastifyReplyLike,
|
|
94
|
-
TReturn,
|
|
95
|
-
>(
|
|
96
|
-
handler: (request: TReq, reply: TRes) => TReturn | Promise<TReturn>,
|
|
97
|
-
options?: FastifyWithAutotelOptions,
|
|
98
|
-
): (request: TReq, reply: TRes) => Promise<TReturn> {
|
|
99
|
-
return (request: TReq, reply: TRes): Promise<TReturn> => {
|
|
100
|
-
const spanName =
|
|
101
|
-
typeof options?.spanName === 'function'
|
|
102
|
-
? options.spanName(request)
|
|
103
|
-
: (options?.spanName ?? `fastify.${request.method ?? 'request'}`);
|
|
104
|
-
|
|
105
|
-
return runRequest<TReturn>(
|
|
106
|
-
spanName,
|
|
107
|
-
(log) => {
|
|
108
|
-
const auto = enrichFromRequest(request);
|
|
109
|
-
if (auto && Object.keys(auto).length > 0) log.set(auto);
|
|
110
|
-
const custom = options?.enrich?.(request);
|
|
111
|
-
if (custom && Object.keys(custom).length > 0) log.set(custom);
|
|
112
|
-
},
|
|
113
|
-
() => handler(request, reply),
|
|
114
|
-
{
|
|
115
|
-
requestLoggerOptions: options?.requestLoggerOptions,
|
|
116
|
-
autoEmit: options?.autoEmit,
|
|
117
|
-
finalize: () =>
|
|
118
|
-
reply.statusCode
|
|
119
|
-
? { 'http.response.status_code': reply.statusCode }
|
|
120
|
-
: undefined,
|
|
121
|
-
},
|
|
122
|
-
);
|
|
123
|
-
};
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
export function createFastifyAdapter(options?: FastifyWithAutotelOptions) {
|
|
127
|
-
return {
|
|
128
|
-
withAutotel: <
|
|
129
|
-
TReq extends FastifyRequestLike,
|
|
130
|
-
TRes extends FastifyReplyLike,
|
|
131
|
-
TReturn,
|
|
132
|
-
>(
|
|
133
|
-
handler: (request: TReq, reply: TRes) => TReturn | Promise<TReturn>,
|
|
134
|
-
) => withAutotel(handler, options),
|
|
135
|
-
useLogger,
|
|
136
|
-
parseError: (error: unknown): ParsedError => parseError(error),
|
|
137
|
-
createStructuredError: (input: StructuredErrorInput): StructuredError =>
|
|
138
|
-
createStructuredError(input),
|
|
139
|
-
createDrainPipeline: <T = unknown>(
|
|
140
|
-
drainOptions?: DrainPipelineOptions<T>,
|
|
141
|
-
): ((batchDrain: (batch: T[]) => void | Promise<void>) => PipelineDrainFn<T>) =>
|
|
142
|
-
createDrainPipeline(drainOptions),
|
|
143
|
-
};
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
export const fastifyToolkit = createAdapterToolkit<FastifyRequestLike>({
|
|
147
|
-
adapterName: 'fastify',
|
|
148
|
-
enrich: enrichFromRequest,
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
export { parseError, createDrainPipeline, createStructuredError };
|
package/src/hono.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import type { Context } from 'hono';
|
|
2
|
-
import { createUseLogger, createAdapterToolkit } from './core';
|
|
3
|
-
|
|
4
|
-
export const useLogger = createUseLogger<Context>({
|
|
5
|
-
adapterName: 'hono',
|
|
6
|
-
enrich: (c) => ({
|
|
7
|
-
'http.request.method': c.req.method,
|
|
8
|
-
'url.full': c.req.url,
|
|
9
|
-
'http.route': c.req.path,
|
|
10
|
-
}),
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
export const honoToolkit = createAdapterToolkit<Context>({
|
|
14
|
-
adapterName: 'hono',
|
|
15
|
-
enrich: (c) => ({
|
|
16
|
-
'http.request.method': c.req.method,
|
|
17
|
-
'url.full': c.req.url,
|
|
18
|
-
'http.route': c.req.path,
|
|
19
|
-
}),
|
|
20
|
-
});
|
package/src/index.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
export * from './core';
|
|
2
|
-
export { createNextAdapter, withAutotel } from './next';
|
|
3
|
-
export { createNitroAdapter, withAutotelEventHandler } from './nitro';
|
|
4
|
-
export { createCloudflareAdapter, withAutotelFetch } from './cloudflare';
|
|
5
|
-
export {
|
|
6
|
-
createExpressAdapter,
|
|
7
|
-
withAutotel as withAutotelExpress,
|
|
8
|
-
} from './express';
|
|
9
|
-
export {
|
|
10
|
-
createFastifyAdapter,
|
|
11
|
-
withAutotel as withAutotelFastify,
|
|
12
|
-
} from './fastify';
|
|
13
|
-
export { honoToolkit } from './hono';
|
|
14
|
-
export { tanstackToolkit } from './tanstack';
|
package/src/next.test.ts
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
-
import { useLogger, withAutotel } from './next';
|
|
3
|
-
|
|
4
|
-
describe('next adapter', () => {
|
|
5
|
-
it('throws clear error when useLogger is called outside traced context', () => {
|
|
6
|
-
expect(() => useLogger({ method: 'GET', url: '/api/orders' })).toThrow(
|
|
7
|
-
'[autotel-adapters/next] No active trace context.',
|
|
8
|
-
);
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
it('provides request-scoped logger inside withAutotel()', async () => {
|
|
12
|
-
const handler = withAutotel(async (request: { url: string }) => {
|
|
13
|
-
const log = useLogger(request);
|
|
14
|
-
log.set({ feature: 'checkout' });
|
|
15
|
-
return 'ok';
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
await expect(handler({ url: 'https://example.com/orders' })).resolves.toBe(
|
|
19
|
-
'ok',
|
|
20
|
-
);
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it('auto-emits one wide event by default', async () => {
|
|
24
|
-
const onEmit = vi.fn();
|
|
25
|
-
const handler = withAutotel(
|
|
26
|
-
async (request: { url: string }) => {
|
|
27
|
-
useLogger(request).set({ feature: 'checkout' });
|
|
28
|
-
return 'ok';
|
|
29
|
-
},
|
|
30
|
-
{ requestLoggerOptions: { onEmit } },
|
|
31
|
-
);
|
|
32
|
-
|
|
33
|
-
await handler({ url: 'https://example.com/orders' });
|
|
34
|
-
expect(onEmit).toHaveBeenCalledTimes(1);
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it('does not emit when autoEmit is false', async () => {
|
|
38
|
-
const onEmit = vi.fn();
|
|
39
|
-
const handler = withAutotel(async () => 'ok', {
|
|
40
|
-
autoEmit: false,
|
|
41
|
-
requestLoggerOptions: { onEmit },
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
await handler();
|
|
45
|
-
expect(onEmit).not.toHaveBeenCalled();
|
|
46
|
-
});
|
|
47
|
-
});
|
package/src/next.ts
DELETED
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
2
|
-
import {
|
|
3
|
-
createDrainPipeline,
|
|
4
|
-
getRequestLogger,
|
|
5
|
-
parseError,
|
|
6
|
-
trace,
|
|
7
|
-
createStructuredError,
|
|
8
|
-
type RequestLogger,
|
|
9
|
-
type RequestLoggerOptions,
|
|
10
|
-
type ParsedError,
|
|
11
|
-
type DrainPipelineOptions,
|
|
12
|
-
type PipelineDrainFn,
|
|
13
|
-
type StructuredError,
|
|
14
|
-
type StructuredErrorInput,
|
|
15
|
-
} from 'autotel';
|
|
16
|
-
import { createAdapterToolkit, createUseLogger, getHeader } from './core';
|
|
17
|
-
|
|
18
|
-
export interface NextRequestLike {
|
|
19
|
-
method?: string;
|
|
20
|
-
url?: string;
|
|
21
|
-
headers?:
|
|
22
|
-
| { get(name: string): string | null }
|
|
23
|
-
| Record<string, string | undefined>;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export interface NextWithAutotelOptions {
|
|
27
|
-
spanName?: string | ((request?: NextRequestLike) => string);
|
|
28
|
-
requestLoggerOptions?: RequestLoggerOptions;
|
|
29
|
-
enrich?: (request?: NextRequestLike) => Record<string, unknown> | undefined;
|
|
30
|
-
/** Emit one wide event automatically when the handler settles. Default `true`. */
|
|
31
|
-
autoEmit?: boolean;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const nextLoggerStorage = new AsyncLocalStorage<RequestLogger>();
|
|
35
|
-
|
|
36
|
-
function enrichFromRequest(
|
|
37
|
-
request?: NextRequestLike,
|
|
38
|
-
): Record<string, unknown> | undefined {
|
|
39
|
-
if (!request) return undefined;
|
|
40
|
-
|
|
41
|
-
let route = '/';
|
|
42
|
-
if (request.url) {
|
|
43
|
-
try {
|
|
44
|
-
route = new URL(request.url).pathname;
|
|
45
|
-
} catch {
|
|
46
|
-
route = request.url;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
const requestId = getHeader(request.headers, 'x-request-id');
|
|
50
|
-
|
|
51
|
-
return {
|
|
52
|
-
...(request.method ? { 'http.request.method': request.method } : {}),
|
|
53
|
-
...(request.url ? { 'url.full': request.url } : {}),
|
|
54
|
-
...(route ? { 'http.route': route } : {}),
|
|
55
|
-
...(requestId ? { 'http.request.header.x-request-id': requestId } : {}),
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const baseUseLogger = createUseLogger<NextRequestLike>({
|
|
60
|
-
adapterName: 'next',
|
|
61
|
-
enrich: enrichFromRequest,
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
export function useLogger(
|
|
65
|
-
request?: NextRequestLike,
|
|
66
|
-
requestLoggerOptions?: RequestLoggerOptions,
|
|
67
|
-
): RequestLogger {
|
|
68
|
-
const logger = nextLoggerStorage.getStore();
|
|
69
|
-
if (logger) return logger;
|
|
70
|
-
return baseUseLogger(request, requestLoggerOptions);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
export function withAutotel<TArgs extends unknown[], TReturn>(
|
|
74
|
-
handler: (...args: TArgs) => TReturn | Promise<TReturn>,
|
|
75
|
-
options?: NextWithAutotelOptions,
|
|
76
|
-
): (...args: TArgs) => Promise<TReturn> {
|
|
77
|
-
return async (...args: TArgs): Promise<TReturn> => {
|
|
78
|
-
const request = args[0] as NextRequestLike | undefined;
|
|
79
|
-
const spanName =
|
|
80
|
-
typeof options?.spanName === 'function'
|
|
81
|
-
? options.spanName(request)
|
|
82
|
-
: (options?.spanName ?? 'next.request');
|
|
83
|
-
|
|
84
|
-
const wrapped = trace(
|
|
85
|
-
{ name: spanName },
|
|
86
|
-
(ctx) => async (...innerArgs: TArgs) => {
|
|
87
|
-
const innerRequest = innerArgs[0] as NextRequestLike | undefined;
|
|
88
|
-
const log = getRequestLogger(ctx, options?.requestLoggerOptions);
|
|
89
|
-
const auto = enrichFromRequest(innerRequest);
|
|
90
|
-
if (auto && Object.keys(auto).length > 0) {
|
|
91
|
-
log.set(auto);
|
|
92
|
-
}
|
|
93
|
-
const custom = options?.enrich?.(innerRequest);
|
|
94
|
-
if (custom && Object.keys(custom).length > 0) {
|
|
95
|
-
log.set(custom);
|
|
96
|
-
}
|
|
97
|
-
try {
|
|
98
|
-
return await nextLoggerStorage.run(log, async () =>
|
|
99
|
-
handler(...innerArgs),
|
|
100
|
-
);
|
|
101
|
-
} finally {
|
|
102
|
-
if (options?.autoEmit !== false) {
|
|
103
|
-
log.emitNow();
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
},
|
|
107
|
-
);
|
|
108
|
-
return await wrapped(...args);
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
export function createNextAdapter(options?: NextWithAutotelOptions) {
|
|
113
|
-
return {
|
|
114
|
-
withAutotel: <TArgs extends unknown[], TReturn>(
|
|
115
|
-
handler: (...args: TArgs) => TReturn | Promise<TReturn>,
|
|
116
|
-
) => withAutotel(handler, options),
|
|
117
|
-
useLogger: (
|
|
118
|
-
request?: NextRequestLike,
|
|
119
|
-
requestLoggerOptions?: RequestLoggerOptions,
|
|
120
|
-
): RequestLogger => useLogger(request, requestLoggerOptions),
|
|
121
|
-
parseError: (error: unknown): ParsedError => parseError(error),
|
|
122
|
-
createStructuredError: (
|
|
123
|
-
input: StructuredErrorInput,
|
|
124
|
-
): StructuredError => createStructuredError(input),
|
|
125
|
-
createDrainPipeline: <T = unknown>(
|
|
126
|
-
drainOptions?: DrainPipelineOptions<T>,
|
|
127
|
-
): ((batchDrain: (batch: T[]) => void | Promise<void>) => PipelineDrainFn<T>) =>
|
|
128
|
-
createDrainPipeline(drainOptions),
|
|
129
|
-
};
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
export const nextToolkit = createAdapterToolkit<NextRequestLike>({
|
|
133
|
-
adapterName: 'next',
|
|
134
|
-
enrich: enrichFromRequest,
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
export { parseError, createDrainPipeline, createStructuredError };
|
package/src/nitro.test.ts
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
-
import { useLogger, withAutotelEventHandler } from './nitro';
|
|
3
|
-
|
|
4
|
-
describe('nitro adapter', () => {
|
|
5
|
-
it('throws clear error when useLogger is called outside traced context', () => {
|
|
6
|
-
expect(() =>
|
|
7
|
-
useLogger({ method: 'GET', path: '/api/orders', context: {} }),
|
|
8
|
-
).toThrow('[autotel-adapters/nitro] No active trace context.');
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
it('provides request-scoped logger inside withAutotelEventHandler()', async () => {
|
|
12
|
-
const handler = withAutotelEventHandler(
|
|
13
|
-
async (event: { path: string; context: Record<string, unknown> }) => {
|
|
14
|
-
const log = useLogger(event, 'api-service');
|
|
15
|
-
log.set({ route: event.path });
|
|
16
|
-
return { ok: true };
|
|
17
|
-
},
|
|
18
|
-
);
|
|
19
|
-
|
|
20
|
-
await expect(
|
|
21
|
-
handler({ path: '/orders', context: {} }),
|
|
22
|
-
).resolves.toMatchObject({ ok: true });
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
it('auto-emits one wide event by default', async () => {
|
|
26
|
-
const onEmit = vi.fn();
|
|
27
|
-
const handler = withAutotelEventHandler(
|
|
28
|
-
async (event: { path: string; context: Record<string, unknown> }) => {
|
|
29
|
-
useLogger(event).set({ route: event.path });
|
|
30
|
-
return { ok: true };
|
|
31
|
-
},
|
|
32
|
-
{ requestLoggerOptions: { onEmit } },
|
|
33
|
-
);
|
|
34
|
-
|
|
35
|
-
await handler({ path: '/orders', context: {} });
|
|
36
|
-
expect(onEmit).toHaveBeenCalledTimes(1);
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it('does not emit when autoEmit is false', async () => {
|
|
40
|
-
const onEmit = vi.fn();
|
|
41
|
-
const handler = withAutotelEventHandler(async () => ({ ok: true }), {
|
|
42
|
-
autoEmit: false,
|
|
43
|
-
requestLoggerOptions: { onEmit },
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
await handler({ path: '/x', context: {} });
|
|
47
|
-
expect(onEmit).not.toHaveBeenCalled();
|
|
48
|
-
});
|
|
49
|
-
});
|