@geekmidas/telescope 9.0.2 → 10.0.0-alpha.1
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 +14 -4
- package/dist/{http-D5kn3_Mt.d.mts → http-BiNtg85L.d.mts} +3 -3
- package/dist/{http-D5kn3_Mt.d.mts.map → http-BiNtg85L.d.mts.map} +1 -1
- package/dist/instrumentation/http.d.mts +1 -1
- package/dist/instrumentation/index.cjs +2 -2
- package/dist/instrumentation/index.d.cts +2 -2
- package/dist/instrumentation/index.d.mts +3 -3
- package/dist/instrumentation/index.mjs +2 -2
- package/dist/instrumentation/middleware.cjs +1 -1
- package/dist/instrumentation/middleware.d.cts +1 -1
- package/dist/instrumentation/middleware.d.mts +1 -1
- package/dist/instrumentation/middleware.mjs +1 -1
- package/dist/instrumentation/otel.cjs +1 -1
- package/dist/instrumentation/otel.d.cts +1 -1
- package/dist/instrumentation/otel.d.mts +1 -1
- package/dist/instrumentation/otel.mjs +1 -1
- package/dist/logger/console.d.cts.map +1 -1
- package/dist/logger/pino.d.mts +2 -2
- package/dist/{middleware-DqPQpcup.cjs → middleware-Bcq6XXGG.cjs} +2 -2
- package/dist/middleware-Bcq6XXGG.cjs.map +1 -0
- package/dist/{middleware-4-GrQlOT.mjs → middleware-D1XEHvyy.mjs} +2 -2
- package/dist/middleware-D1XEHvyy.mjs.map +1 -0
- package/dist/{middleware-CA84nsNN.d.cts → middleware-PeyS-9Ny.d.cts} +2 -2
- package/dist/{middleware-CA84nsNN.d.cts.map → middleware-PeyS-9Ny.d.cts.map} +1 -1
- package/dist/{middleware-DAceVpfw.d.mts → middleware-oHvo_ZgU.d.mts} +2 -2
- package/dist/{middleware-DAceVpfw.d.mts.map → middleware-oHvo_ZgU.d.mts.map} +1 -1
- package/dist/{otel-40udNP5g.mjs → otel-B9bNoeZ2.mjs} +3 -3
- package/dist/otel-B9bNoeZ2.mjs.map +1 -0
- package/dist/{otel-SBVoVEHr.d.cts → otel-Bk89b1lX.d.cts} +3 -3
- package/dist/{otel-1bJPb2Z7.d.mts.map → otel-Bk89b1lX.d.cts.map} +1 -1
- package/dist/{otel-COrpz_GZ.cjs → otel-BzjOtVxZ.cjs} +3 -3
- package/dist/otel-BzjOtVxZ.cjs.map +1 -0
- package/dist/{otel-1bJPb2Z7.d.mts → otel-lPMsM26e.d.mts} +3 -3
- package/dist/{otel-SBVoVEHr.d.cts.map → otel-lPMsM26e.d.mts.map} +1 -1
- package/dist/storage/kysely.cjs +48 -38
- package/dist/storage/kysely.cjs.map +1 -1
- package/dist/storage/kysely.d.cts +29 -9
- package/dist/storage/kysely.d.cts.map +1 -1
- package/dist/storage/kysely.d.mts +29 -9
- package/dist/storage/kysely.d.mts.map +1 -1
- package/dist/storage/kysely.mjs +48 -38
- package/dist/storage/kysely.mjs.map +1 -1
- package/dist/tracing-PnECEbEu.d.mts.map +1 -1
- package/package.json +6 -3
- package/CHANGELOG.md +0 -72
- package/dist/middleware-4-GrQlOT.mjs.map +0 -1
- package/dist/middleware-DqPQpcup.cjs.map +0 -1
- package/dist/otel-40udNP5g.mjs.map +0 -1
- package/dist/otel-COrpz_GZ.cjs.map +0 -1
- package/src/Telescope.ts +0 -561
- package/src/__tests__/Telescope.spec.ts +0 -438
- package/src/__tests__/pino-transport.spec.ts +0 -115
- package/src/__tests__/redact.spec.ts +0 -336
- package/src/adapters/__tests__/lambda.spec.ts +0 -318
- package/src/adapters/__tests__/middy.spec.ts +0 -741
- package/src/adapters/index.ts +0 -22
- package/src/adapters/lambda.ts +0 -432
- package/src/adapters/middy.ts +0 -402
- package/src/adapters/types.ts +0 -181
- package/src/core.ts +0 -51
- package/src/index.ts +0 -34
- package/src/instrumentation/__tests__/core.spec.ts +0 -248
- package/src/instrumentation/__tests__/hono.spec.ts +0 -348
- package/src/instrumentation/__tests__/middleware.spec.ts +0 -528
- package/src/instrumentation/__tests__/otel.spec.ts +0 -291
- package/src/instrumentation/__tests__/tracing.spec.ts +0 -208
- package/src/instrumentation/core.ts +0 -228
- package/src/instrumentation/hono.ts +0 -270
- package/src/instrumentation/http.ts +0 -306
- package/src/instrumentation/index.ts +0 -90
- package/src/instrumentation/middleware.ts +0 -297
- package/src/instrumentation/otel.ts +0 -271
- package/src/instrumentation/setup.ts +0 -246
- package/src/instrumentation/tracing.ts +0 -145
- package/src/logger/__tests__/console.spec.ts +0 -366
- package/src/logger/__tests__/pino.spec.ts +0 -217
- package/src/logger/console.ts +0 -230
- package/src/logger/pino.ts +0 -201
- package/src/metrics/MetricsAggregator.ts +0 -555
- package/src/metrics/__tests__/MetricsAggregator.spec.ts +0 -298
- package/src/metrics/index.ts +0 -2
- package/src/metrics/storage/InMemoryMetricsStorage.ts +0 -380
- package/src/metrics/storage/MetricsStorage.ts +0 -74
- package/src/metrics/storage/index.ts +0 -3
- package/src/metrics/storage/types.ts +0 -243
- package/src/otlp/__tests__/receiver.spec.ts +0 -367
- package/src/otlp/__tests__/transformer.spec.ts +0 -455
- package/src/otlp/hono.ts +0 -120
- package/src/otlp/index.ts +0 -22
- package/src/otlp/receiver.ts +0 -162
- package/src/otlp/transformer.ts +0 -367
- package/src/otlp/types.ts +0 -364
- package/src/redact.ts +0 -116
- package/src/server/__tests__/hono.spec.ts +0 -814
- package/src/server/hono.ts +0 -442
- package/src/storage/__tests__/kysely.spec.ts +0 -715
- package/src/storage/__tests__/memory.spec.ts +0 -694
- package/src/storage/kysely.ts +0 -599
- package/src/storage/memory.ts +0 -204
- package/src/types.ts +0 -447
- package/src/ui-assets.ts +0 -40
- package/tsconfig.json +0 -9
- package/ui/CHANGELOG.md +0 -7
- package/ui/index.html +0 -12
- package/ui/node_modules/.bin/tsc +0 -21
- package/ui/node_modules/.bin/tsserver +0 -21
- package/ui/node_modules/.bin/vite +0 -21
- package/ui/package.json +0 -24
- package/ui/src/App.tsx +0 -595
- package/ui/src/api.ts +0 -88
- package/ui/src/components/ExceptionDetail.tsx +0 -100
- package/ui/src/components/FilterBar.tsx +0 -162
- package/ui/src/components/LogDetail.tsx +0 -91
- package/ui/src/components/RequestDetail.tsx +0 -143
- package/ui/src/main.tsx +0 -10
- package/ui/src/styles.css +0 -11
- package/ui/src/types.ts +0 -63
- package/ui/src/vite-env.d.ts +0 -1
- package/ui/src/vite-plugin-gkm-config.ts +0 -54
- package/ui/tsconfig.json +0 -20
- package/ui/tsconfig.tsbuildinfo +0 -1
- package/ui/vite.config.ts +0 -13
|
@@ -1,306 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
type Attributes,
|
|
3
|
-
type Context,
|
|
4
|
-
context,
|
|
5
|
-
propagation,
|
|
6
|
-
type Span,
|
|
7
|
-
SpanKind,
|
|
8
|
-
SpanStatusCode,
|
|
9
|
-
trace,
|
|
10
|
-
} from '@opentelemetry/api';
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* HTTP request attributes following OpenTelemetry semantic conventions
|
|
14
|
-
*/
|
|
15
|
-
export interface HttpRequestAttributes {
|
|
16
|
-
method: string;
|
|
17
|
-
url?: string;
|
|
18
|
-
path?: string;
|
|
19
|
-
route?: string;
|
|
20
|
-
host?: string;
|
|
21
|
-
scheme?: 'http' | 'https';
|
|
22
|
-
userAgent?: string;
|
|
23
|
-
clientIp?: string;
|
|
24
|
-
requestId?: string;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* HTTP response attributes
|
|
29
|
-
*/
|
|
30
|
-
export interface HttpResponseAttributes {
|
|
31
|
-
statusCode: number;
|
|
32
|
-
responseSize?: number;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Endpoint-specific attributes
|
|
37
|
-
*/
|
|
38
|
-
export interface EndpointAttributes {
|
|
39
|
-
name?: string;
|
|
40
|
-
operationId?: string;
|
|
41
|
-
tags?: string[];
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* User/session attributes
|
|
46
|
-
*/
|
|
47
|
-
export interface UserAttributes {
|
|
48
|
-
userId?: string;
|
|
49
|
-
sessionId?: string;
|
|
50
|
-
roles?: string[];
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Full span attributes for HTTP requests
|
|
55
|
-
*/
|
|
56
|
-
export interface HttpSpanAttributes extends HttpRequestAttributes {
|
|
57
|
-
response?: HttpResponseAttributes;
|
|
58
|
-
endpoint?: EndpointAttributes;
|
|
59
|
-
user?: UserAttributes;
|
|
60
|
-
custom?: Attributes;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const TRACER_NAME = '@geekmidas/constructs';
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Get or create a tracer for constructs instrumentation
|
|
67
|
-
*/
|
|
68
|
-
export function getConstructsTracer() {
|
|
69
|
-
return trace.getTracer(TRACER_NAME);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Convert HTTP attributes to OpenTelemetry semantic conventions
|
|
74
|
-
*/
|
|
75
|
-
export function toOtelAttributes(attrs: HttpSpanAttributes): Attributes {
|
|
76
|
-
const otelAttrs: Attributes = {};
|
|
77
|
-
|
|
78
|
-
// HTTP request attributes (OpenTelemetry semantic conventions)
|
|
79
|
-
if (attrs.method) otelAttrs['http.request.method'] = attrs.method;
|
|
80
|
-
if (attrs.url) otelAttrs['url.full'] = attrs.url;
|
|
81
|
-
if (attrs.path) otelAttrs['url.path'] = attrs.path;
|
|
82
|
-
if (attrs.route) otelAttrs['http.route'] = attrs.route;
|
|
83
|
-
if (attrs.host) otelAttrs['server.address'] = attrs.host;
|
|
84
|
-
if (attrs.scheme) otelAttrs['url.scheme'] = attrs.scheme;
|
|
85
|
-
if (attrs.userAgent) otelAttrs['user_agent.original'] = attrs.userAgent;
|
|
86
|
-
if (attrs.clientIp) otelAttrs['client.address'] = attrs.clientIp;
|
|
87
|
-
if (attrs.requestId) otelAttrs['http.request.id'] = attrs.requestId;
|
|
88
|
-
|
|
89
|
-
// HTTP response attributes
|
|
90
|
-
if (attrs.response?.statusCode) {
|
|
91
|
-
otelAttrs['http.response.status_code'] = attrs.response.statusCode;
|
|
92
|
-
}
|
|
93
|
-
if (attrs.response?.responseSize) {
|
|
94
|
-
otelAttrs['http.response.body.size'] = attrs.response.responseSize;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Endpoint attributes
|
|
98
|
-
if (attrs.endpoint?.name) otelAttrs['endpoint.name'] = attrs.endpoint.name;
|
|
99
|
-
if (attrs.endpoint?.operationId) {
|
|
100
|
-
otelAttrs['endpoint.operation_id'] = attrs.endpoint.operationId;
|
|
101
|
-
}
|
|
102
|
-
if (attrs.endpoint?.tags?.length) {
|
|
103
|
-
otelAttrs['endpoint.tags'] = attrs.endpoint.tags;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// User attributes
|
|
107
|
-
if (attrs.user?.userId) otelAttrs['enduser.id'] = attrs.user.userId;
|
|
108
|
-
if (attrs.user?.sessionId) otelAttrs['session.id'] = attrs.user.sessionId;
|
|
109
|
-
if (attrs.user?.roles?.length)
|
|
110
|
-
otelAttrs['enduser.role'] = attrs.user.roles.join(',');
|
|
111
|
-
|
|
112
|
-
// Custom attributes
|
|
113
|
-
if (attrs.custom) {
|
|
114
|
-
Object.assign(otelAttrs, attrs.custom);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
return otelAttrs;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Extract trace context from HTTP headers
|
|
122
|
-
*/
|
|
123
|
-
export function extractTraceContext(
|
|
124
|
-
headers: Record<string, string | string[] | undefined>,
|
|
125
|
-
): Context {
|
|
126
|
-
// Normalize headers to Record<string, string>
|
|
127
|
-
const normalizedHeaders: Record<string, string> = {};
|
|
128
|
-
for (const [key, value] of Object.entries(headers)) {
|
|
129
|
-
if (value) {
|
|
130
|
-
const stringValue = Array.isArray(value) ? value[0] : value;
|
|
131
|
-
if (stringValue) {
|
|
132
|
-
normalizedHeaders[key.toLowerCase()] = stringValue;
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
return propagation.extract(context.active(), normalizedHeaders);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Inject trace context into HTTP headers
|
|
142
|
-
*/
|
|
143
|
-
export function injectTraceContext(
|
|
144
|
-
headers: Record<string, string>,
|
|
145
|
-
ctx?: Context,
|
|
146
|
-
): Record<string, string> {
|
|
147
|
-
const result = { ...headers };
|
|
148
|
-
propagation.inject(ctx ?? context.active(), result);
|
|
149
|
-
return result;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* Create an HTTP server span for an incoming request
|
|
154
|
-
*/
|
|
155
|
-
export function createHttpServerSpan(
|
|
156
|
-
attrs: HttpSpanAttributes,
|
|
157
|
-
parentContext?: Context,
|
|
158
|
-
): Span {
|
|
159
|
-
const tracer = getConstructsTracer();
|
|
160
|
-
const spanName = attrs.route
|
|
161
|
-
? `${attrs.method} ${attrs.route}`
|
|
162
|
-
: `${attrs.method} ${attrs.path || '/'}`;
|
|
163
|
-
|
|
164
|
-
const ctx = parentContext ?? context.active();
|
|
165
|
-
|
|
166
|
-
return tracer.startSpan(
|
|
167
|
-
spanName,
|
|
168
|
-
{
|
|
169
|
-
kind: SpanKind.SERVER,
|
|
170
|
-
attributes: toOtelAttributes(attrs),
|
|
171
|
-
},
|
|
172
|
-
ctx,
|
|
173
|
-
);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* End an HTTP span with response data
|
|
178
|
-
*/
|
|
179
|
-
export function endHttpSpan(
|
|
180
|
-
span: Span,
|
|
181
|
-
response: HttpResponseAttributes,
|
|
182
|
-
error?: Error,
|
|
183
|
-
): void {
|
|
184
|
-
span.setAttribute('http.response.status_code', response.statusCode);
|
|
185
|
-
|
|
186
|
-
if (response.responseSize) {
|
|
187
|
-
span.setAttribute('http.response.body.size', response.responseSize);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
if (error) {
|
|
191
|
-
span.recordException(error);
|
|
192
|
-
span.setStatus({
|
|
193
|
-
code: SpanStatusCode.ERROR,
|
|
194
|
-
message: error.message,
|
|
195
|
-
});
|
|
196
|
-
} else if (response.statusCode >= 400) {
|
|
197
|
-
span.setStatus({
|
|
198
|
-
code: SpanStatusCode.ERROR,
|
|
199
|
-
message: `HTTP ${response.statusCode}`,
|
|
200
|
-
});
|
|
201
|
-
} else {
|
|
202
|
-
span.setStatus({ code: SpanStatusCode.OK });
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
span.end();
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
/**
|
|
209
|
-
* Execute a function within an HTTP server span context
|
|
210
|
-
*
|
|
211
|
-
* @example
|
|
212
|
-
* ```typescript
|
|
213
|
-
* const result = await withHttpSpan(
|
|
214
|
-
* { method: 'GET', path: '/users', route: '/users' },
|
|
215
|
-
* async (span) => {
|
|
216
|
-
* const users = await getUsers();
|
|
217
|
-
* return { statusCode: 200, body: users };
|
|
218
|
-
* }
|
|
219
|
-
* );
|
|
220
|
-
* ```
|
|
221
|
-
*/
|
|
222
|
-
export async function withHttpSpan<T>(
|
|
223
|
-
attrs: HttpSpanAttributes,
|
|
224
|
-
fn: (span: Span) => Promise<T & { statusCode?: number }>,
|
|
225
|
-
parentContext?: Context,
|
|
226
|
-
): Promise<T> {
|
|
227
|
-
const span = createHttpServerSpan(attrs, parentContext);
|
|
228
|
-
|
|
229
|
-
try {
|
|
230
|
-
const result = await context.with(
|
|
231
|
-
trace.setSpan(parentContext ?? context.active(), span),
|
|
232
|
-
() => fn(span),
|
|
233
|
-
);
|
|
234
|
-
|
|
235
|
-
const statusCode = (result as { statusCode?: number }).statusCode ?? 200;
|
|
236
|
-
endHttpSpan(span, { statusCode });
|
|
237
|
-
|
|
238
|
-
return result;
|
|
239
|
-
} catch (error) {
|
|
240
|
-
endHttpSpan(
|
|
241
|
-
span,
|
|
242
|
-
{ statusCode: 500 },
|
|
243
|
-
error instanceof Error ? error : new Error(String(error)),
|
|
244
|
-
);
|
|
245
|
-
throw error;
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
/**
|
|
250
|
-
* Create a child span for internal operations (middleware, handlers, etc.)
|
|
251
|
-
*/
|
|
252
|
-
export function createChildSpan(name: string, attributes?: Attributes): Span {
|
|
253
|
-
const tracer = getConstructsTracer();
|
|
254
|
-
return tracer.startSpan(name, {
|
|
255
|
-
kind: SpanKind.INTERNAL,
|
|
256
|
-
attributes,
|
|
257
|
-
});
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
/**
|
|
261
|
-
* Execute a function within a child span
|
|
262
|
-
*/
|
|
263
|
-
export async function withChildSpan<T>(
|
|
264
|
-
name: string,
|
|
265
|
-
fn: (span: Span) => Promise<T>,
|
|
266
|
-
attributes?: Attributes,
|
|
267
|
-
): Promise<T> {
|
|
268
|
-
const span = createChildSpan(name, attributes);
|
|
269
|
-
|
|
270
|
-
try {
|
|
271
|
-
const result = await context.with(
|
|
272
|
-
trace.setSpan(context.active(), span),
|
|
273
|
-
() => fn(span),
|
|
274
|
-
);
|
|
275
|
-
span.setStatus({ code: SpanStatusCode.OK });
|
|
276
|
-
return result;
|
|
277
|
-
} catch (error) {
|
|
278
|
-
span.setStatus({
|
|
279
|
-
code: SpanStatusCode.ERROR,
|
|
280
|
-
message: error instanceof Error ? error.message : 'Unknown error',
|
|
281
|
-
});
|
|
282
|
-
span.recordException(
|
|
283
|
-
error instanceof Error ? error : new Error(String(error)),
|
|
284
|
-
);
|
|
285
|
-
throw error;
|
|
286
|
-
} finally {
|
|
287
|
-
span.end();
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
/**
|
|
292
|
-
* Check if tracing is enabled (OpenTelemetry API is available and configured)
|
|
293
|
-
*/
|
|
294
|
-
export function isTracingEnabled(): boolean {
|
|
295
|
-
try {
|
|
296
|
-
const tracer = trace.getTracer(TRACER_NAME);
|
|
297
|
-
// Check if we have a real tracer (not a no-op)
|
|
298
|
-
const span = tracer.startSpan('test');
|
|
299
|
-
const isReal =
|
|
300
|
-
span.spanContext().traceId !== '00000000000000000000000000000000';
|
|
301
|
-
span.end();
|
|
302
|
-
return isReal;
|
|
303
|
-
} catch {
|
|
304
|
-
return false;
|
|
305
|
-
}
|
|
306
|
-
}
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* OpenTelemetry Instrumentation for @geekmidas/constructs
|
|
3
|
-
*
|
|
4
|
-
* This module provides automatic instrumentation for constructs endpoints,
|
|
5
|
-
* functions, crons, and subscribers using the OpenTelemetry SDK.
|
|
6
|
-
*
|
|
7
|
-
* @example
|
|
8
|
-
* ```typescript
|
|
9
|
-
* import { setupTelemetry } from '@geekmidas/telescope/instrumentation';
|
|
10
|
-
*
|
|
11
|
-
* // Call before importing your app code
|
|
12
|
-
* setupTelemetry({
|
|
13
|
-
* serviceName: 'my-api',
|
|
14
|
-
* // Dev: send to Telescope
|
|
15
|
-
* endpoint: 'http://localhost:3000/__telescope/v1',
|
|
16
|
-
* // Enable Pino log correlation
|
|
17
|
-
* instrumentPino: true,
|
|
18
|
-
* });
|
|
19
|
-
* ```
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
export type {
|
|
23
|
-
SpanProcessorOptions,
|
|
24
|
-
SpanProcessorStrategy,
|
|
25
|
-
} from '../adapters/types';
|
|
26
|
-
export {
|
|
27
|
-
createSpanProcessor,
|
|
28
|
-
enableTelemetryDebug,
|
|
29
|
-
flushTelemetry,
|
|
30
|
-
getRecommendedStrategy,
|
|
31
|
-
isTelemetryInitialized,
|
|
32
|
-
NoopSpanProcessor,
|
|
33
|
-
setGlobalLogProcessor,
|
|
34
|
-
setGlobalSpanProcessor,
|
|
35
|
-
withTelemetryFlush,
|
|
36
|
-
} from './core';
|
|
37
|
-
export type { HonoTelemetryMiddlewareOptions } from './hono';
|
|
38
|
-
// Middleware for Hono auto-instrumentation
|
|
39
|
-
export {
|
|
40
|
-
getSpanFromContext,
|
|
41
|
-
getTraceContextFromHono,
|
|
42
|
-
honoTelemetryMiddleware,
|
|
43
|
-
withHonoSpanContext,
|
|
44
|
-
} from './hono';
|
|
45
|
-
export type {
|
|
46
|
-
EndpointAttributes,
|
|
47
|
-
HttpRequestAttributes,
|
|
48
|
-
HttpResponseAttributes,
|
|
49
|
-
HttpSpanAttributes,
|
|
50
|
-
UserAttributes,
|
|
51
|
-
} from './http';
|
|
52
|
-
|
|
53
|
-
// HTTP instrumentation utilities for constructs
|
|
54
|
-
export {
|
|
55
|
-
createChildSpan,
|
|
56
|
-
createHttpServerSpan,
|
|
57
|
-
endHttpSpan,
|
|
58
|
-
extractTraceContext,
|
|
59
|
-
getConstructsTracer,
|
|
60
|
-
injectTraceContext,
|
|
61
|
-
isTracingEnabled,
|
|
62
|
-
toOtelAttributes,
|
|
63
|
-
withChildSpan,
|
|
64
|
-
withHttpSpan,
|
|
65
|
-
} from './http';
|
|
66
|
-
export type { TelemetryMiddlewareOptions } from './middleware';
|
|
67
|
-
|
|
68
|
-
// Middleware for Lambda auto-instrumentation
|
|
69
|
-
export {
|
|
70
|
-
getContextFromEvent,
|
|
71
|
-
getSpanFromEvent,
|
|
72
|
-
telemetryMiddleware,
|
|
73
|
-
withEventContext,
|
|
74
|
-
} from './middleware';
|
|
75
|
-
export type { OTelTelemetryOptions } from './otel';
|
|
76
|
-
// OTelTelemetry - Telemetry interface implementation using OpenTelemetry
|
|
77
|
-
export { OTelTelemetry } from './otel';
|
|
78
|
-
export type { TelemetryOptions } from './setup';
|
|
79
|
-
export { setupTelemetry, shutdownTelemetry } from './setup';
|
|
80
|
-
export {
|
|
81
|
-
addSpanEvent,
|
|
82
|
-
createSpan,
|
|
83
|
-
getActiveSpan,
|
|
84
|
-
getSpanId,
|
|
85
|
-
getTraceId,
|
|
86
|
-
recordException,
|
|
87
|
-
setSpanAttributes,
|
|
88
|
-
withSpan,
|
|
89
|
-
withSpanSync,
|
|
90
|
-
} from './tracing';
|
|
@@ -1,297 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* OpenTelemetry instrumentation middleware for @geekmidas/constructs
|
|
3
|
-
*
|
|
4
|
-
* This middleware automatically creates spans for HTTP requests,
|
|
5
|
-
* extracts trace context from incoming headers, and records
|
|
6
|
-
* request/response metadata as span attributes.
|
|
7
|
-
*/
|
|
8
|
-
import type { MiddlewareObj } from '@middy/core';
|
|
9
|
-
import { context, type Span, trace } from '@opentelemetry/api';
|
|
10
|
-
import type { Context as LambdaContext } from 'aws-lambda';
|
|
11
|
-
import {
|
|
12
|
-
createHttpServerSpan,
|
|
13
|
-
endHttpSpan,
|
|
14
|
-
extractTraceContext,
|
|
15
|
-
type HttpSpanAttributes,
|
|
16
|
-
} from './http';
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Options for the telemetry middleware
|
|
20
|
-
*/
|
|
21
|
-
export interface TelemetryMiddlewareOptions {
|
|
22
|
-
/**
|
|
23
|
-
* Whether to record request body as span attribute
|
|
24
|
-
* @default false
|
|
25
|
-
*/
|
|
26
|
-
recordBody?: boolean;
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Whether to record response body as span attribute
|
|
30
|
-
* @default false
|
|
31
|
-
*/
|
|
32
|
-
recordResponseBody?: boolean;
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Custom function to extract endpoint name from event
|
|
36
|
-
*/
|
|
37
|
-
getEndpointName?: (event: any) => string | undefined;
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Custom function to extract operation ID from event
|
|
41
|
-
*/
|
|
42
|
-
getOperationId?: (event: any) => string | undefined;
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Custom function to extract user ID from event
|
|
46
|
-
*/
|
|
47
|
-
getUserId?: (event: any) => string | undefined;
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Whether to skip tracing for this request
|
|
51
|
-
*/
|
|
52
|
-
shouldSkip?: (event: any) => boolean;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// Symbol for storing span on event
|
|
56
|
-
const SPAN_SYMBOL = Symbol('telemetry.span');
|
|
57
|
-
const CONTEXT_SYMBOL = Symbol('telemetry.context');
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Get the current span from the Lambda event
|
|
61
|
-
*/
|
|
62
|
-
export function getSpanFromEvent(event: any): Span | undefined {
|
|
63
|
-
return event?.[SPAN_SYMBOL];
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Get the current trace context from the Lambda event
|
|
68
|
-
*/
|
|
69
|
-
export function getContextFromEvent(event: any): any {
|
|
70
|
-
return event?.[CONTEXT_SYMBOL];
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Create a telemetry middleware for Lambda handlers
|
|
75
|
-
*
|
|
76
|
-
* @example
|
|
77
|
-
* ```typescript
|
|
78
|
-
* import { telemetryMiddleware } from '@geekmidas/telescope/instrumentation';
|
|
79
|
-
*
|
|
80
|
-
* const adaptor = new AmazonApiGatewayV2Endpoint(envParser, endpoint, {
|
|
81
|
-
* telemetry: { middleware: telemetryMiddleware() },
|
|
82
|
-
* });
|
|
83
|
-
* ```
|
|
84
|
-
*/
|
|
85
|
-
export function telemetryMiddleware(
|
|
86
|
-
options: TelemetryMiddlewareOptions = {},
|
|
87
|
-
): MiddlewareObj<any, any, Error, LambdaContext> {
|
|
88
|
-
return {
|
|
89
|
-
before: async (request) => {
|
|
90
|
-
// Skip if custom skip function returns true
|
|
91
|
-
if (options.shouldSkip?.(request.event)) {
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const event = request.event;
|
|
96
|
-
const lambdaContext = request.context;
|
|
97
|
-
|
|
98
|
-
// Extract trace context from headers
|
|
99
|
-
const headers = normalizeHeaders(event.headers || {});
|
|
100
|
-
const parentContext = extractTraceContext(headers);
|
|
101
|
-
|
|
102
|
-
// Build span attributes from event
|
|
103
|
-
const attrs = buildSpanAttributes(event, lambdaContext, options);
|
|
104
|
-
|
|
105
|
-
// Create the span
|
|
106
|
-
const span = createHttpServerSpan(attrs, parentContext);
|
|
107
|
-
|
|
108
|
-
// Store span and context on event for access in handler
|
|
109
|
-
event[SPAN_SYMBOL] = span;
|
|
110
|
-
event[CONTEXT_SYMBOL] = trace.setSpan(parentContext, span);
|
|
111
|
-
|
|
112
|
-
// Add Lambda-specific attributes
|
|
113
|
-
span.setAttribute('faas.invocation_id', lambdaContext.awsRequestId);
|
|
114
|
-
span.setAttribute('faas.name', lambdaContext.functionName);
|
|
115
|
-
|
|
116
|
-
if (options.recordBody && event.body) {
|
|
117
|
-
const bodyStr =
|
|
118
|
-
typeof event.body === 'string'
|
|
119
|
-
? event.body
|
|
120
|
-
: JSON.stringify(event.body);
|
|
121
|
-
// Truncate large bodies
|
|
122
|
-
span.setAttribute(
|
|
123
|
-
'http.request.body',
|
|
124
|
-
bodyStr.length > 4096 ? `${bodyStr.slice(0, 4096)}...` : bodyStr,
|
|
125
|
-
);
|
|
126
|
-
}
|
|
127
|
-
},
|
|
128
|
-
|
|
129
|
-
after: async (request) => {
|
|
130
|
-
const span = getSpanFromEvent(request.event);
|
|
131
|
-
if (!span) {
|
|
132
|
-
return;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
const response = request.response;
|
|
136
|
-
const statusCode =
|
|
137
|
-
typeof response?.statusCode === 'number' ? response.statusCode : 200;
|
|
138
|
-
|
|
139
|
-
if (options.recordResponseBody && response?.body) {
|
|
140
|
-
const bodyStr =
|
|
141
|
-
typeof response.body === 'string'
|
|
142
|
-
? response.body
|
|
143
|
-
: JSON.stringify(response.body);
|
|
144
|
-
span.setAttribute(
|
|
145
|
-
'http.response.body',
|
|
146
|
-
bodyStr.length > 4096 ? `${bodyStr.slice(0, 4096)}...` : bodyStr,
|
|
147
|
-
);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
endHttpSpan(span, { statusCode });
|
|
151
|
-
},
|
|
152
|
-
|
|
153
|
-
onError: async (request) => {
|
|
154
|
-
const span = getSpanFromEvent(request.event);
|
|
155
|
-
if (!span) {
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
const error = request.error;
|
|
160
|
-
const response = request.response;
|
|
161
|
-
|
|
162
|
-
// Determine status code from response or error
|
|
163
|
-
let statusCode = 500;
|
|
164
|
-
if (response?.statusCode) {
|
|
165
|
-
statusCode = response.statusCode;
|
|
166
|
-
} else if (error && 'statusCode' in error) {
|
|
167
|
-
statusCode = (error as any).statusCode;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
endHttpSpan(
|
|
171
|
-
span,
|
|
172
|
-
{ statusCode },
|
|
173
|
-
error instanceof Error ? error : new Error(String(error)),
|
|
174
|
-
);
|
|
175
|
-
},
|
|
176
|
-
};
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* Build span attributes from Lambda event
|
|
181
|
-
*/
|
|
182
|
-
function buildSpanAttributes(
|
|
183
|
-
event: any,
|
|
184
|
-
lambdaContext: LambdaContext,
|
|
185
|
-
options: TelemetryMiddlewareOptions,
|
|
186
|
-
): HttpSpanAttributes {
|
|
187
|
-
// Detect event type (API Gateway v1, v2, ALB, or direct invoke)
|
|
188
|
-
const isV2 =
|
|
189
|
-
'requestContext' in event && 'http' in (event.requestContext || {});
|
|
190
|
-
const isV1 =
|
|
191
|
-
'requestContext' in event &&
|
|
192
|
-
'httpMethod' in event &&
|
|
193
|
-
!('http' in (event.requestContext || {}));
|
|
194
|
-
|
|
195
|
-
let method = 'INVOKE';
|
|
196
|
-
let path = '/';
|
|
197
|
-
let route: string | undefined;
|
|
198
|
-
let host: string | undefined;
|
|
199
|
-
let scheme: 'http' | 'https' | undefined;
|
|
200
|
-
let userAgent: string | undefined;
|
|
201
|
-
let clientIp: string | undefined;
|
|
202
|
-
let requestId: string | undefined;
|
|
203
|
-
|
|
204
|
-
if (isV2) {
|
|
205
|
-
// API Gateway v2 (HTTP API)
|
|
206
|
-
const http = event.requestContext.http;
|
|
207
|
-
method = http.method;
|
|
208
|
-
path = event.rawPath || http.path;
|
|
209
|
-
route = event.routeKey?.replace(/^[A-Z]+\s+/, ''); // Remove HTTP method prefix
|
|
210
|
-
host = event.headers?.host;
|
|
211
|
-
scheme = 'https';
|
|
212
|
-
userAgent = http.userAgent;
|
|
213
|
-
clientIp = http.sourceIp;
|
|
214
|
-
requestId = event.requestContext.requestId;
|
|
215
|
-
} else if (isV1) {
|
|
216
|
-
// API Gateway v1 (REST API)
|
|
217
|
-
method = event.httpMethod;
|
|
218
|
-
path = event.path;
|
|
219
|
-
route = event.resource;
|
|
220
|
-
host = event.headers?.Host || event.headers?.host;
|
|
221
|
-
scheme = 'https';
|
|
222
|
-
userAgent = event.headers?.['User-Agent'] || event.headers?.['user-agent'];
|
|
223
|
-
clientIp = event.requestContext?.identity?.sourceIp;
|
|
224
|
-
requestId = event.requestContext?.requestId;
|
|
225
|
-
} else if ('httpMethod' in event) {
|
|
226
|
-
// ALB event
|
|
227
|
-
method = event.httpMethod;
|
|
228
|
-
path = event.path;
|
|
229
|
-
host = event.headers?.host;
|
|
230
|
-
userAgent = event.headers?.['user-agent'] || event.headers?.['User-Agent'];
|
|
231
|
-
clientIp = event.headers?.['x-forwarded-for']?.split(',')[0]?.trim();
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
const attrs: HttpSpanAttributes = {
|
|
235
|
-
method,
|
|
236
|
-
path,
|
|
237
|
-
route,
|
|
238
|
-
host,
|
|
239
|
-
scheme,
|
|
240
|
-
userAgent,
|
|
241
|
-
clientIp,
|
|
242
|
-
requestId: requestId || lambdaContext.awsRequestId,
|
|
243
|
-
};
|
|
244
|
-
|
|
245
|
-
// Add endpoint metadata if extractors provided
|
|
246
|
-
if (options.getEndpointName || options.getOperationId) {
|
|
247
|
-
attrs.endpoint = {
|
|
248
|
-
name: options.getEndpointName?.(event),
|
|
249
|
-
operationId: options.getOperationId?.(event),
|
|
250
|
-
};
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
// Add user metadata if extractor provided
|
|
254
|
-
if (options.getUserId) {
|
|
255
|
-
const userId = options.getUserId(event);
|
|
256
|
-
if (userId) {
|
|
257
|
-
attrs.user = { userId };
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
return attrs;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
/**
|
|
265
|
-
* Normalize headers to lowercase keys
|
|
266
|
-
*/
|
|
267
|
-
function normalizeHeaders(
|
|
268
|
-
headers: Record<string, string | string[] | undefined>,
|
|
269
|
-
): Record<string, string | string[] | undefined> {
|
|
270
|
-
const normalized: Record<string, string | string[] | undefined> = {};
|
|
271
|
-
for (const [key, value] of Object.entries(headers)) {
|
|
272
|
-
normalized[key.toLowerCase()] = value;
|
|
273
|
-
}
|
|
274
|
-
return normalized;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
/**
|
|
278
|
-
* Run a function within the span context from a Lambda event
|
|
279
|
-
*
|
|
280
|
-
* @example
|
|
281
|
-
* ```typescript
|
|
282
|
-
* await withEventContext(event, async () => {
|
|
283
|
-
* // Any spans created here will be children of the request span
|
|
284
|
-
* await doSomething();
|
|
285
|
-
* });
|
|
286
|
-
* ```
|
|
287
|
-
*/
|
|
288
|
-
export async function withEventContext<T>(
|
|
289
|
-
event: any,
|
|
290
|
-
fn: () => Promise<T>,
|
|
291
|
-
): Promise<T> {
|
|
292
|
-
const ctx = getContextFromEvent(event);
|
|
293
|
-
if (!ctx) {
|
|
294
|
-
return fn();
|
|
295
|
-
}
|
|
296
|
-
return context.with(ctx, fn);
|
|
297
|
-
}
|