@geekmidas/telescope 10.0.0-alpha.69 → 10.0.0-alpha.70
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/dist/instrumentation/hono.cjs +22 -5
- package/dist/instrumentation/hono.cjs.map +1 -1
- package/dist/instrumentation/hono.d.cts.map +1 -1
- package/dist/instrumentation/hono.d.mts.map +1 -1
- package/dist/instrumentation/hono.mjs +22 -5
- package/dist/instrumentation/hono.mjs.map +1 -1
- package/dist/instrumentation/http.cjs +2 -2
- package/dist/instrumentation/http.cjs.map +1 -1
- package/dist/instrumentation/http.d.cts.map +1 -1
- package/dist/instrumentation/http.d.mts.map +1 -1
- package/dist/instrumentation/http.mjs +2 -2
- package/dist/instrumentation/http.mjs.map +1 -1
- package/dist/instrumentation/setup.cjs +8 -5
- package/dist/instrumentation/setup.cjs.map +1 -1
- package/dist/instrumentation/setup.d.cts +17 -1
- package/dist/instrumentation/setup.d.cts.map +1 -1
- package/dist/instrumentation/setup.d.mts +17 -1
- package/dist/instrumentation/setup.d.mts.map +1 -1
- package/dist/instrumentation/setup.mjs +8 -5
- package/dist/instrumentation/setup.mjs.map +1 -1
- package/package.json +3 -3
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_instrumentation_http = require("./http.cjs");
|
|
3
3
|
let _opentelemetry_api = require("@opentelemetry/api");
|
|
4
|
+
let hono_route = require("hono/route");
|
|
4
5
|
//#region src/instrumentation/hono.ts
|
|
5
6
|
const SPAN_KEY = "telemetry.span";
|
|
6
7
|
const CONTEXT_KEY = "telemetry.context";
|
|
@@ -49,31 +50,47 @@ function honoTelemetryMiddleware(options = {}) {
|
|
|
49
50
|
await _opentelemetry_api.context.with(spanContext, async () => {
|
|
50
51
|
await next();
|
|
51
52
|
});
|
|
53
|
+
nameAfterRoute(c, span);
|
|
52
54
|
const statusCode = c.res.status;
|
|
53
55
|
if (options.recordResponseBody) try {
|
|
54
56
|
const body = await c.res.clone().text();
|
|
55
57
|
if (body) span.setAttribute("http.response.body", body.length > 4096 ? `${body.slice(0, 4096)}...` : body);
|
|
56
58
|
} catch {}
|
|
57
|
-
require_instrumentation_http.endHttpSpan(span, { statusCode });
|
|
59
|
+
require_instrumentation_http.endHttpSpan(span, { statusCode }, c.error);
|
|
58
60
|
} catch (error) {
|
|
61
|
+
nameAfterRoute(c, span);
|
|
59
62
|
require_instrumentation_http.endHttpSpan(span, { statusCode: 500 }, error instanceof Error ? error : new Error(String(error)));
|
|
60
63
|
throw error;
|
|
61
64
|
}
|
|
62
65
|
};
|
|
63
66
|
}
|
|
64
67
|
/**
|
|
68
|
+
* The route the request matched, once the handler has run — `/users/:id`,
|
|
69
|
+
* never `/users/42` — or undefined when only middleware ran (a 404).
|
|
70
|
+
*
|
|
71
|
+
* Read after `next()`: before it, the route is this middleware's own `*`.
|
|
72
|
+
*/
|
|
73
|
+
function matchedRoute(c) {
|
|
74
|
+
const path = (0, hono_route.routePath)(c);
|
|
75
|
+
return path && !path.includes("*") ? path : void 0;
|
|
76
|
+
}
|
|
77
|
+
/** `GET /users/:id`, or the bare method when no route matched. */
|
|
78
|
+
function nameAfterRoute(c, span) {
|
|
79
|
+
const route = matchedRoute(c);
|
|
80
|
+
if (route) {
|
|
81
|
+
span.setAttribute("http.route", route);
|
|
82
|
+
span.updateName(`${c.req.method} ${route}`);
|
|
83
|
+
} else span.updateName(c.req.method);
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
65
86
|
* Build span attributes from Hono context
|
|
66
87
|
*/
|
|
67
88
|
function buildHonoSpanAttributes(c, options) {
|
|
68
89
|
const req = c.req;
|
|
69
90
|
const url = new URL(req.url);
|
|
70
|
-
const routePath = c.req.routePath;
|
|
71
|
-
const route = routePath && routePath !== "*" && !routePath.includes("/*") ? routePath : url.pathname;
|
|
72
91
|
const attrs = {
|
|
73
92
|
method: req.method,
|
|
74
|
-
url: req.url,
|
|
75
93
|
path: url.pathname,
|
|
76
|
-
route,
|
|
77
94
|
host: url.host,
|
|
78
95
|
scheme: url.protocol.replace(":", ""),
|
|
79
96
|
userAgent: req.header("user-agent"),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hono.cjs","names":["extractTraceContext","createHttpServerSpan","trace","context"],"sources":["../../src/instrumentation/hono.ts"],"sourcesContent":["import { context, type Span, trace } from '@opentelemetry/api';\n/**\n * OpenTelemetry instrumentation middleware for Hono\n *\n * This middleware automatically creates spans for HTTP requests,\n * extracts trace context from incoming headers, and records\n * request/response metadata as span attributes.\n */\nimport type { Context, MiddlewareHandler } from 'hono';\nimport {\n\tcreateHttpServerSpan,\n\tendHttpSpan,\n\textractTraceContext,\n\ttype HttpSpanAttributes,\n} from './http';\n\n/**\n * Options for the Hono telemetry middleware\n */\nexport interface HonoTelemetryMiddlewareOptions {\n\t/**\n\t * Whether to record request body as span attribute\n\t * @default false\n\t */\n\trecordBody?: boolean;\n\n\t/**\n\t * Whether to record response body as span attribute\n\t * @default false\n\t */\n\trecordResponseBody?: boolean;\n\n\t/**\n\t * Custom function to extract user ID from context\n\t */\n\tgetUserId?: (c: Context) => string | undefined;\n\n\t/**\n\t * Paths to skip tracing (supports wildcards)\n\t */\n\tignorePaths?: string[];\n\n\t/**\n\t * Whether to skip tracing for this request\n\t */\n\tshouldSkip?: (c: Context) => boolean;\n}\n\n// Key for storing span on context\nconst SPAN_KEY = 'telemetry.span';\nconst CONTEXT_KEY = 'telemetry.context';\n\n/**\n * Get the current span from Hono context\n */\nexport function getSpanFromContext(c: Context): Span | undefined {\n\treturn c.get(SPAN_KEY);\n}\n\n/**\n * Get the current trace context from Hono context\n */\nexport function getTraceContextFromHono(c: Context): any {\n\treturn c.get(CONTEXT_KEY);\n}\n\n/**\n * Create a telemetry middleware for Hono applications\n *\n * @example\n * ```typescript\n * import { honoTelemetryMiddleware } from '@geekmidas/telescope/instrumentation';\n *\n * const app = new Hono();\n * app.use('*', honoTelemetryMiddleware());\n * ```\n */\nexport function honoTelemetryMiddleware(\n\toptions: HonoTelemetryMiddlewareOptions = {},\n): MiddlewareHandler {\n\treturn async (c, next) => {\n\t\t// Check if path should be ignored\n\t\tif (\n\t\t\toptions.ignorePaths &&\n\t\t\tshouldIgnorePath(c.req.path, options.ignorePaths)\n\t\t) {\n\t\t\treturn next();\n\t\t}\n\n\t\t// Skip if custom skip function returns true\n\t\tif (options.shouldSkip?.(c)) {\n\t\t\treturn next();\n\t\t}\n\n\t\t// Extract trace context from headers\n\t\tconst headers: Record<string, string | string[] | undefined> = {};\n\t\tc.req.raw.headers.forEach((value, key) => {\n\t\t\theaders[key] = value;\n\t\t});\n\t\tconst parentContext = extractTraceContext(headers);\n\n\t\t// Build span attributes from request\n\t\tconst attrs = buildHonoSpanAttributes(c, options);\n\n\t\t// Create the span\n\t\tconst span = createHttpServerSpan(attrs, parentContext);\n\t\tconst spanContext = trace.setSpan(parentContext, span);\n\n\t\t// Store span and context on Hono context\n\t\tc.set(SPAN_KEY, span);\n\t\tc.set(CONTEXT_KEY, spanContext);\n\n\t\t// Record body if requested\n\t\tif (options.recordBody) {\n\t\t\ttry {\n\t\t\t\tconst body = await c.req.text();\n\t\t\t\tif (body) {\n\t\t\t\t\tspan.setAttribute(\n\t\t\t\t\t\t'http.request.body',\n\t\t\t\t\t\tbody.length > 4096 ? `${body.slice(0, 4096)}...` : body,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Body may have already been consumed\n\t\t\t}\n\t\t}\n\n\t\ttry {\n\t\t\t// Execute the rest of the middleware chain within span context\n\t\t\tawait context.with(spanContext, async () => {\n\t\t\t\tawait next();\n\t\t\t});\n\n\t\t\t// Record response\n\t\t\tconst statusCode = c.res.status;\n\n\t\t\tif (options.recordResponseBody) {\n\t\t\t\ttry {\n\t\t\t\t\tconst body = await c.res.clone().text();\n\t\t\t\t\tif (body) {\n\t\t\t\t\t\tspan.setAttribute(\n\t\t\t\t\t\t\t'http.response.body',\n\t\t\t\t\t\t\tbody.length > 4096 ? `${body.slice(0, 4096)}...` : body,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t} catch {\n\t\t\t\t\t// Response body may not be available\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tendHttpSpan(span, { statusCode });\n\t\t} catch (error) {\n\t\t\tendHttpSpan(\n\t\t\t\tspan,\n\t\t\t\t{ statusCode: 500 },\n\t\t\t\terror instanceof Error ? error : new Error(String(error)),\n\t\t\t);\n\t\t\tthrow error;\n\t\t}\n\t};\n}\n\n/**\n * Build span attributes from Hono context\n */\nfunction buildHonoSpanAttributes(\n\tc: Context,\n\toptions: HonoTelemetryMiddlewareOptions,\n): HttpSpanAttributes {\n\tconst req = c.req;\n\tconst url = new URL(req.url);\n\n\t// Use actual path for route unless it's a specific route pattern\n\tconst routePath = c.req.routePath;\n\tconst route =\n\t\troutePath && routePath !== '*' && !routePath.includes('/*')\n\t\t\t? routePath\n\t\t\t: url.pathname;\n\n\tconst attrs: HttpSpanAttributes = {\n\t\tmethod: req.method,\n\t\turl: req.url,\n\t\tpath: url.pathname,\n\t\troute,\n\t\thost: url.host,\n\t\tscheme: url.protocol.replace(':', '') as 'http' | 'https',\n\t\tuserAgent: req.header('user-agent'),\n\t\tclientIp: getClientIp(c),\n\t\trequestId: req.header('x-request-id') || req.header('x-amzn-requestid'),\n\t};\n\n\t// Add user metadata if extractor provided\n\tif (options.getUserId) {\n\t\tconst userId = options.getUserId(c);\n\t\tif (userId) {\n\t\t\tattrs.user = { userId };\n\t\t}\n\t}\n\n\treturn attrs;\n}\n\n/**\n * Extract client IP from Hono context\n */\nfunction getClientIp(c: Context): string | undefined {\n\t// Try various headers in order of preference\n\tconst xForwardedFor = c.req.header('x-forwarded-for');\n\tif (xForwardedFor) {\n\t\tconst first = xForwardedFor.split(',')[0];\n\t\treturn first?.trim();\n\t}\n\n\tconst xRealIp = c.req.header('x-real-ip');\n\tif (xRealIp) {\n\t\treturn xRealIp;\n\t}\n\n\t// Try CF-Connecting-IP (Cloudflare)\n\tconst cfConnectingIp = c.req.header('cf-connecting-ip');\n\tif (cfConnectingIp) {\n\t\treturn cfConnectingIp;\n\t}\n\n\treturn undefined;\n}\n\n/**\n * Check if path should be ignored\n */\nfunction shouldIgnorePath(path: string, ignorePaths: string[]): boolean {\n\tfor (const pattern of ignorePaths) {\n\t\tif (pattern.endsWith('*')) {\n\t\t\t// Wildcard match\n\t\t\tconst prefix = pattern.slice(0, -1);\n\t\t\tif (path.startsWith(prefix)) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t} else if (path === pattern) {\n\t\t\t// Exact match\n\t\t\treturn true;\n\t\t}\n\t}\n\treturn false;\n}\n\n/**\n * Run a function within the span context from Hono context\n *\n * @example\n * ```typescript\n * app.get('/api/users', async (c) => {\n * return withHonoSpanContext(c, async () => {\n * // Any spans created here will be children of the request span\n * const users = await fetchUsers();\n * return c.json(users);\n * });\n * });\n * ```\n */\nexport async function withHonoSpanContext<T>(\n\tc: Context,\n\tfn: () => Promise<T>,\n): Promise<T> {\n\tconst ctx = getTraceContextFromHono(c);\n\tif (!ctx) {\n\t\treturn fn();\n\t}\n\treturn context.with(ctx, fn);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"hono.cjs","names":["extractTraceContext","createHttpServerSpan","trace","context","routePath"],"sources":["../../src/instrumentation/hono.ts"],"sourcesContent":["import { context, type Span, trace } from '@opentelemetry/api';\n/**\n * OpenTelemetry instrumentation middleware for Hono\n *\n * This middleware automatically creates spans for HTTP requests,\n * extracts trace context from incoming headers, and records\n * request/response metadata as span attributes.\n */\nimport type { Context, MiddlewareHandler } from 'hono';\nimport { routePath } from 'hono/route';\nimport {\n\tcreateHttpServerSpan,\n\tendHttpSpan,\n\textractTraceContext,\n\ttype HttpSpanAttributes,\n} from './http';\n\n/**\n * Options for the Hono telemetry middleware\n */\nexport interface HonoTelemetryMiddlewareOptions {\n\t/**\n\t * Whether to record request body as span attribute\n\t * @default false\n\t */\n\trecordBody?: boolean;\n\n\t/**\n\t * Whether to record response body as span attribute\n\t * @default false\n\t */\n\trecordResponseBody?: boolean;\n\n\t/**\n\t * Custom function to extract user ID from context\n\t */\n\tgetUserId?: (c: Context) => string | undefined;\n\n\t/**\n\t * Paths to skip tracing (supports wildcards)\n\t */\n\tignorePaths?: string[];\n\n\t/**\n\t * Whether to skip tracing for this request\n\t */\n\tshouldSkip?: (c: Context) => boolean;\n}\n\n// Key for storing span on context\nconst SPAN_KEY = 'telemetry.span';\nconst CONTEXT_KEY = 'telemetry.context';\n\n/**\n * Get the current span from Hono context\n */\nexport function getSpanFromContext(c: Context): Span | undefined {\n\treturn c.get(SPAN_KEY);\n}\n\n/**\n * Get the current trace context from Hono context\n */\nexport function getTraceContextFromHono(c: Context): any {\n\treturn c.get(CONTEXT_KEY);\n}\n\n/**\n * Create a telemetry middleware for Hono applications\n *\n * @example\n * ```typescript\n * import { honoTelemetryMiddleware } from '@geekmidas/telescope/instrumentation';\n *\n * const app = new Hono();\n * app.use('*', honoTelemetryMiddleware());\n * ```\n */\nexport function honoTelemetryMiddleware(\n\toptions: HonoTelemetryMiddlewareOptions = {},\n): MiddlewareHandler {\n\treturn async (c, next) => {\n\t\t// Check if path should be ignored\n\t\tif (\n\t\t\toptions.ignorePaths &&\n\t\t\tshouldIgnorePath(c.req.path, options.ignorePaths)\n\t\t) {\n\t\t\treturn next();\n\t\t}\n\n\t\t// Skip if custom skip function returns true\n\t\tif (options.shouldSkip?.(c)) {\n\t\t\treturn next();\n\t\t}\n\n\t\t// Extract trace context from headers\n\t\tconst headers: Record<string, string | string[] | undefined> = {};\n\t\tc.req.raw.headers.forEach((value, key) => {\n\t\t\theaders[key] = value;\n\t\t});\n\t\tconst parentContext = extractTraceContext(headers);\n\n\t\t// Build span attributes from request\n\t\tconst attrs = buildHonoSpanAttributes(c, options);\n\n\t\t// Create the span\n\t\tconst span = createHttpServerSpan(attrs, parentContext);\n\t\tconst spanContext = trace.setSpan(parentContext, span);\n\n\t\t// Store span and context on Hono context\n\t\tc.set(SPAN_KEY, span);\n\t\tc.set(CONTEXT_KEY, spanContext);\n\n\t\t// Record body if requested\n\t\tif (options.recordBody) {\n\t\t\ttry {\n\t\t\t\tconst body = await c.req.text();\n\t\t\t\tif (body) {\n\t\t\t\t\tspan.setAttribute(\n\t\t\t\t\t\t'http.request.body',\n\t\t\t\t\t\tbody.length > 4096 ? `${body.slice(0, 4096)}...` : body,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Body may have already been consumed\n\t\t\t}\n\t\t}\n\n\t\ttry {\n\t\t\t// Execute the rest of the middleware chain within span context, so\n\t\t\t// the handler's logs and outbound spans nest under this one.\n\t\t\tawait context.with(spanContext, async () => {\n\t\t\t\tawait next();\n\t\t\t});\n\n\t\t\tnameAfterRoute(c, span);\n\n\t\t\t// Record response\n\t\t\tconst statusCode = c.res.status;\n\n\t\t\tif (options.recordResponseBody) {\n\t\t\t\ttry {\n\t\t\t\t\tconst body = await c.res.clone().text();\n\t\t\t\t\tif (body) {\n\t\t\t\t\t\tspan.setAttribute(\n\t\t\t\t\t\t\t'http.response.body',\n\t\t\t\t\t\t\tbody.length > 4096 ? `${body.slice(0, 4096)}...` : body,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t} catch {\n\t\t\t\t\t// Response body may not be available\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Hono hands a thrown error to `onError` and sets `c.error`\n\t\t\t// rather than rethrowing, so that is where it is found.\n\t\t\tendHttpSpan(span, { statusCode }, c.error);\n\t\t} catch (error) {\n\t\t\tnameAfterRoute(c, span);\n\t\t\tendHttpSpan(\n\t\t\t\tspan,\n\t\t\t\t{ statusCode: 500 },\n\t\t\t\terror instanceof Error ? error : new Error(String(error)),\n\t\t\t);\n\t\t\tthrow error;\n\t\t}\n\t};\n}\n\n/**\n * The route the request matched, once the handler has run — `/users/:id`,\n * never `/users/42` — or undefined when only middleware ran (a 404).\n *\n * Read after `next()`: before it, the route is this middleware's own `*`.\n */\nfunction matchedRoute(c: Context): string | undefined {\n\tconst path = routePath(c);\n\treturn path && !path.includes('*') ? path : undefined;\n}\n\n/** `GET /users/:id`, or the bare method when no route matched. */\nfunction nameAfterRoute(c: Context, span: Span): void {\n\tconst route = matchedRoute(c);\n\tif (route) {\n\t\tspan.setAttribute('http.route', route);\n\t\tspan.updateName(`${c.req.method} ${route}`);\n\t} else {\n\t\tspan.updateName(c.req.method);\n\t}\n}\n\n/**\n * Build span attributes from Hono context\n */\nfunction buildHonoSpanAttributes(\n\tc: Context,\n\toptions: HonoTelemetryMiddlewareOptions,\n): HttpSpanAttributes {\n\tconst req = c.req;\n\tconst url = new URL(req.url);\n\n\t// No `url.full`: a query string can carry a token, and the span would\n\t// keep it. The route is named once the handler has run.\n\tconst attrs: HttpSpanAttributes = {\n\t\tmethod: req.method,\n\t\tpath: url.pathname,\n\t\thost: url.host,\n\t\tscheme: url.protocol.replace(':', '') as 'http' | 'https',\n\t\tuserAgent: req.header('user-agent'),\n\t\tclientIp: getClientIp(c),\n\t\trequestId: req.header('x-request-id') || req.header('x-amzn-requestid'),\n\t};\n\n\t// Add user metadata if extractor provided\n\tif (options.getUserId) {\n\t\tconst userId = options.getUserId(c);\n\t\tif (userId) {\n\t\t\tattrs.user = { userId };\n\t\t}\n\t}\n\n\treturn attrs;\n}\n\n/**\n * Extract client IP from Hono context\n */\nfunction getClientIp(c: Context): string | undefined {\n\t// Try various headers in order of preference\n\tconst xForwardedFor = c.req.header('x-forwarded-for');\n\tif (xForwardedFor) {\n\t\tconst first = xForwardedFor.split(',')[0];\n\t\treturn first?.trim();\n\t}\n\n\tconst xRealIp = c.req.header('x-real-ip');\n\tif (xRealIp) {\n\t\treturn xRealIp;\n\t}\n\n\t// Try CF-Connecting-IP (Cloudflare)\n\tconst cfConnectingIp = c.req.header('cf-connecting-ip');\n\tif (cfConnectingIp) {\n\t\treturn cfConnectingIp;\n\t}\n\n\treturn undefined;\n}\n\n/**\n * Check if path should be ignored\n */\nfunction shouldIgnorePath(path: string, ignorePaths: string[]): boolean {\n\tfor (const pattern of ignorePaths) {\n\t\tif (pattern.endsWith('*')) {\n\t\t\t// Wildcard match\n\t\t\tconst prefix = pattern.slice(0, -1);\n\t\t\tif (path.startsWith(prefix)) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t} else if (path === pattern) {\n\t\t\t// Exact match\n\t\t\treturn true;\n\t\t}\n\t}\n\treturn false;\n}\n\n/**\n * Run a function within the span context from Hono context\n *\n * @example\n * ```typescript\n * app.get('/api/users', async (c) => {\n * return withHonoSpanContext(c, async () => {\n * // Any spans created here will be children of the request span\n * const users = await fetchUsers();\n * return c.json(users);\n * });\n * });\n * ```\n */\nexport async function withHonoSpanContext<T>(\n\tc: Context,\n\tfn: () => Promise<T>,\n): Promise<T> {\n\tconst ctx = getTraceContextFromHono(c);\n\tif (!ctx) {\n\t\treturn fn();\n\t}\n\treturn context.with(ctx, fn);\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hono.d.cts","names":[],"sources":["../../src/instrumentation/hono.ts"],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"hono.d.cts","names":[],"sources":["../../src/instrumentation/hono.ts"],"mappings":";;;;;;iBAoBiB;;;;;EAKhB;;;;;EAMA;;;;EAKA,aAAa,GAAG;;;;EAKhB;;;;EAKA,cAAc,GAAG;;;;;wBAUF,mBAAmB,GAAG,UAAU;;;;wBAOhC,wBAAwB,GAAG;;;;;;;;;;;;wBAe3B,wBACf,UAAS,iCACP;;;;;;;;;;;;;;;wBA0MmB,oBAAoB,GACzC,GAAG,SACH,UAAU,QAAQ,KAChB,QAAQ"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hono.d.mts","names":["Context"],"sources":["../../src/instrumentation/hono.ts"],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"hono.d.mts","names":["Context"],"sources":["../../src/instrumentation/hono.ts"],"mappings":";;;;;;iBAoBiB;;;;;EAKhB;;;;;EAMA;;;;EAKA,aAAa,GAAGA;;;;EAKhB;;;;EAKA,cAAc,GAAGA;;;;;wBAUF,mBAAmB,GAAGA,YAAU;;;;wBAOhC,wBAAwB,GAAGA;;;;;;;;;;;;wBAe3B,wBACf,UAAS,iCACP;;;;;;;;;;;;;;;wBA0MmB,oBAAoB,GACzC,GAAGA,WACH,UAAU,QAAQ,KAChB,QAAQ"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createHttpServerSpan, endHttpSpan, extractTraceContext } from "./http.mjs";
|
|
2
2
|
import { context, trace } from "@opentelemetry/api";
|
|
3
|
+
import { routePath } from "hono/route";
|
|
3
4
|
//#region src/instrumentation/hono.ts
|
|
4
5
|
const SPAN_KEY = "telemetry.span";
|
|
5
6
|
const CONTEXT_KEY = "telemetry.context";
|
|
@@ -48,31 +49,47 @@ function honoTelemetryMiddleware(options = {}) {
|
|
|
48
49
|
await context.with(spanContext, async () => {
|
|
49
50
|
await next();
|
|
50
51
|
});
|
|
52
|
+
nameAfterRoute(c, span);
|
|
51
53
|
const statusCode = c.res.status;
|
|
52
54
|
if (options.recordResponseBody) try {
|
|
53
55
|
const body = await c.res.clone().text();
|
|
54
56
|
if (body) span.setAttribute("http.response.body", body.length > 4096 ? `${body.slice(0, 4096)}...` : body);
|
|
55
57
|
} catch {}
|
|
56
|
-
endHttpSpan(span, { statusCode });
|
|
58
|
+
endHttpSpan(span, { statusCode }, c.error);
|
|
57
59
|
} catch (error) {
|
|
60
|
+
nameAfterRoute(c, span);
|
|
58
61
|
endHttpSpan(span, { statusCode: 500 }, error instanceof Error ? error : new Error(String(error)));
|
|
59
62
|
throw error;
|
|
60
63
|
}
|
|
61
64
|
};
|
|
62
65
|
}
|
|
63
66
|
/**
|
|
67
|
+
* The route the request matched, once the handler has run — `/users/:id`,
|
|
68
|
+
* never `/users/42` — or undefined when only middleware ran (a 404).
|
|
69
|
+
*
|
|
70
|
+
* Read after `next()`: before it, the route is this middleware's own `*`.
|
|
71
|
+
*/
|
|
72
|
+
function matchedRoute(c) {
|
|
73
|
+
const path = routePath(c);
|
|
74
|
+
return path && !path.includes("*") ? path : void 0;
|
|
75
|
+
}
|
|
76
|
+
/** `GET /users/:id`, or the bare method when no route matched. */
|
|
77
|
+
function nameAfterRoute(c, span) {
|
|
78
|
+
const route = matchedRoute(c);
|
|
79
|
+
if (route) {
|
|
80
|
+
span.setAttribute("http.route", route);
|
|
81
|
+
span.updateName(`${c.req.method} ${route}`);
|
|
82
|
+
} else span.updateName(c.req.method);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
64
85
|
* Build span attributes from Hono context
|
|
65
86
|
*/
|
|
66
87
|
function buildHonoSpanAttributes(c, options) {
|
|
67
88
|
const req = c.req;
|
|
68
89
|
const url = new URL(req.url);
|
|
69
|
-
const routePath = c.req.routePath;
|
|
70
|
-
const route = routePath && routePath !== "*" && !routePath.includes("/*") ? routePath : url.pathname;
|
|
71
90
|
const attrs = {
|
|
72
91
|
method: req.method,
|
|
73
|
-
url: req.url,
|
|
74
92
|
path: url.pathname,
|
|
75
|
-
route,
|
|
76
93
|
host: url.host,
|
|
77
94
|
scheme: url.protocol.replace(":", ""),
|
|
78
95
|
userAgent: req.header("user-agent"),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hono.mjs","names":[],"sources":["../../src/instrumentation/hono.ts"],"sourcesContent":["import { context, type Span, trace } from '@opentelemetry/api';\n/**\n * OpenTelemetry instrumentation middleware for Hono\n *\n * This middleware automatically creates spans for HTTP requests,\n * extracts trace context from incoming headers, and records\n * request/response metadata as span attributes.\n */\nimport type { Context, MiddlewareHandler } from 'hono';\nimport {\n\tcreateHttpServerSpan,\n\tendHttpSpan,\n\textractTraceContext,\n\ttype HttpSpanAttributes,\n} from './http';\n\n/**\n * Options for the Hono telemetry middleware\n */\nexport interface HonoTelemetryMiddlewareOptions {\n\t/**\n\t * Whether to record request body as span attribute\n\t * @default false\n\t */\n\trecordBody?: boolean;\n\n\t/**\n\t * Whether to record response body as span attribute\n\t * @default false\n\t */\n\trecordResponseBody?: boolean;\n\n\t/**\n\t * Custom function to extract user ID from context\n\t */\n\tgetUserId?: (c: Context) => string | undefined;\n\n\t/**\n\t * Paths to skip tracing (supports wildcards)\n\t */\n\tignorePaths?: string[];\n\n\t/**\n\t * Whether to skip tracing for this request\n\t */\n\tshouldSkip?: (c: Context) => boolean;\n}\n\n// Key for storing span on context\nconst SPAN_KEY = 'telemetry.span';\nconst CONTEXT_KEY = 'telemetry.context';\n\n/**\n * Get the current span from Hono context\n */\nexport function getSpanFromContext(c: Context): Span | undefined {\n\treturn c.get(SPAN_KEY);\n}\n\n/**\n * Get the current trace context from Hono context\n */\nexport function getTraceContextFromHono(c: Context): any {\n\treturn c.get(CONTEXT_KEY);\n}\n\n/**\n * Create a telemetry middleware for Hono applications\n *\n * @example\n * ```typescript\n * import { honoTelemetryMiddleware } from '@geekmidas/telescope/instrumentation';\n *\n * const app = new Hono();\n * app.use('*', honoTelemetryMiddleware());\n * ```\n */\nexport function honoTelemetryMiddleware(\n\toptions: HonoTelemetryMiddlewareOptions = {},\n): MiddlewareHandler {\n\treturn async (c, next) => {\n\t\t// Check if path should be ignored\n\t\tif (\n\t\t\toptions.ignorePaths &&\n\t\t\tshouldIgnorePath(c.req.path, options.ignorePaths)\n\t\t) {\n\t\t\treturn next();\n\t\t}\n\n\t\t// Skip if custom skip function returns true\n\t\tif (options.shouldSkip?.(c)) {\n\t\t\treturn next();\n\t\t}\n\n\t\t// Extract trace context from headers\n\t\tconst headers: Record<string, string | string[] | undefined> = {};\n\t\tc.req.raw.headers.forEach((value, key) => {\n\t\t\theaders[key] = value;\n\t\t});\n\t\tconst parentContext = extractTraceContext(headers);\n\n\t\t// Build span attributes from request\n\t\tconst attrs = buildHonoSpanAttributes(c, options);\n\n\t\t// Create the span\n\t\tconst span = createHttpServerSpan(attrs, parentContext);\n\t\tconst spanContext = trace.setSpan(parentContext, span);\n\n\t\t// Store span and context on Hono context\n\t\tc.set(SPAN_KEY, span);\n\t\tc.set(CONTEXT_KEY, spanContext);\n\n\t\t// Record body if requested\n\t\tif (options.recordBody) {\n\t\t\ttry {\n\t\t\t\tconst body = await c.req.text();\n\t\t\t\tif (body) {\n\t\t\t\t\tspan.setAttribute(\n\t\t\t\t\t\t'http.request.body',\n\t\t\t\t\t\tbody.length > 4096 ? `${body.slice(0, 4096)}...` : body,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Body may have already been consumed\n\t\t\t}\n\t\t}\n\n\t\ttry {\n\t\t\t// Execute the rest of the middleware chain within span context\n\t\t\tawait context.with(spanContext, async () => {\n\t\t\t\tawait next();\n\t\t\t});\n\n\t\t\t// Record response\n\t\t\tconst statusCode = c.res.status;\n\n\t\t\tif (options.recordResponseBody) {\n\t\t\t\ttry {\n\t\t\t\t\tconst body = await c.res.clone().text();\n\t\t\t\t\tif (body) {\n\t\t\t\t\t\tspan.setAttribute(\n\t\t\t\t\t\t\t'http.response.body',\n\t\t\t\t\t\t\tbody.length > 4096 ? `${body.slice(0, 4096)}...` : body,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t} catch {\n\t\t\t\t\t// Response body may not be available\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tendHttpSpan(span, { statusCode });\n\t\t} catch (error) {\n\t\t\tendHttpSpan(\n\t\t\t\tspan,\n\t\t\t\t{ statusCode: 500 },\n\t\t\t\terror instanceof Error ? error : new Error(String(error)),\n\t\t\t);\n\t\t\tthrow error;\n\t\t}\n\t};\n}\n\n/**\n * Build span attributes from Hono context\n */\nfunction buildHonoSpanAttributes(\n\tc: Context,\n\toptions: HonoTelemetryMiddlewareOptions,\n): HttpSpanAttributes {\n\tconst req = c.req;\n\tconst url = new URL(req.url);\n\n\t// Use actual path for route unless it's a specific route pattern\n\tconst routePath = c.req.routePath;\n\tconst route =\n\t\troutePath && routePath !== '*' && !routePath.includes('/*')\n\t\t\t? routePath\n\t\t\t: url.pathname;\n\n\tconst attrs: HttpSpanAttributes = {\n\t\tmethod: req.method,\n\t\turl: req.url,\n\t\tpath: url.pathname,\n\t\troute,\n\t\thost: url.host,\n\t\tscheme: url.protocol.replace(':', '') as 'http' | 'https',\n\t\tuserAgent: req.header('user-agent'),\n\t\tclientIp: getClientIp(c),\n\t\trequestId: req.header('x-request-id') || req.header('x-amzn-requestid'),\n\t};\n\n\t// Add user metadata if extractor provided\n\tif (options.getUserId) {\n\t\tconst userId = options.getUserId(c);\n\t\tif (userId) {\n\t\t\tattrs.user = { userId };\n\t\t}\n\t}\n\n\treturn attrs;\n}\n\n/**\n * Extract client IP from Hono context\n */\nfunction getClientIp(c: Context): string | undefined {\n\t// Try various headers in order of preference\n\tconst xForwardedFor = c.req.header('x-forwarded-for');\n\tif (xForwardedFor) {\n\t\tconst first = xForwardedFor.split(',')[0];\n\t\treturn first?.trim();\n\t}\n\n\tconst xRealIp = c.req.header('x-real-ip');\n\tif (xRealIp) {\n\t\treturn xRealIp;\n\t}\n\n\t// Try CF-Connecting-IP (Cloudflare)\n\tconst cfConnectingIp = c.req.header('cf-connecting-ip');\n\tif (cfConnectingIp) {\n\t\treturn cfConnectingIp;\n\t}\n\n\treturn undefined;\n}\n\n/**\n * Check if path should be ignored\n */\nfunction shouldIgnorePath(path: string, ignorePaths: string[]): boolean {\n\tfor (const pattern of ignorePaths) {\n\t\tif (pattern.endsWith('*')) {\n\t\t\t// Wildcard match\n\t\t\tconst prefix = pattern.slice(0, -1);\n\t\t\tif (path.startsWith(prefix)) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t} else if (path === pattern) {\n\t\t\t// Exact match\n\t\t\treturn true;\n\t\t}\n\t}\n\treturn false;\n}\n\n/**\n * Run a function within the span context from Hono context\n *\n * @example\n * ```typescript\n * app.get('/api/users', async (c) => {\n * return withHonoSpanContext(c, async () => {\n * // Any spans created here will be children of the request span\n * const users = await fetchUsers();\n * return c.json(users);\n * });\n * });\n * ```\n */\nexport async function withHonoSpanContext<T>(\n\tc: Context,\n\tfn: () => Promise<T>,\n): Promise<T> {\n\tconst ctx = getTraceContextFromHono(c);\n\tif (!ctx) {\n\t\treturn fn();\n\t}\n\treturn context.with(ctx, fn);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"hono.mjs","names":[],"sources":["../../src/instrumentation/hono.ts"],"sourcesContent":["import { context, type Span, trace } from '@opentelemetry/api';\n/**\n * OpenTelemetry instrumentation middleware for Hono\n *\n * This middleware automatically creates spans for HTTP requests,\n * extracts trace context from incoming headers, and records\n * request/response metadata as span attributes.\n */\nimport type { Context, MiddlewareHandler } from 'hono';\nimport { routePath } from 'hono/route';\nimport {\n\tcreateHttpServerSpan,\n\tendHttpSpan,\n\textractTraceContext,\n\ttype HttpSpanAttributes,\n} from './http';\n\n/**\n * Options for the Hono telemetry middleware\n */\nexport interface HonoTelemetryMiddlewareOptions {\n\t/**\n\t * Whether to record request body as span attribute\n\t * @default false\n\t */\n\trecordBody?: boolean;\n\n\t/**\n\t * Whether to record response body as span attribute\n\t * @default false\n\t */\n\trecordResponseBody?: boolean;\n\n\t/**\n\t * Custom function to extract user ID from context\n\t */\n\tgetUserId?: (c: Context) => string | undefined;\n\n\t/**\n\t * Paths to skip tracing (supports wildcards)\n\t */\n\tignorePaths?: string[];\n\n\t/**\n\t * Whether to skip tracing for this request\n\t */\n\tshouldSkip?: (c: Context) => boolean;\n}\n\n// Key for storing span on context\nconst SPAN_KEY = 'telemetry.span';\nconst CONTEXT_KEY = 'telemetry.context';\n\n/**\n * Get the current span from Hono context\n */\nexport function getSpanFromContext(c: Context): Span | undefined {\n\treturn c.get(SPAN_KEY);\n}\n\n/**\n * Get the current trace context from Hono context\n */\nexport function getTraceContextFromHono(c: Context): any {\n\treturn c.get(CONTEXT_KEY);\n}\n\n/**\n * Create a telemetry middleware for Hono applications\n *\n * @example\n * ```typescript\n * import { honoTelemetryMiddleware } from '@geekmidas/telescope/instrumentation';\n *\n * const app = new Hono();\n * app.use('*', honoTelemetryMiddleware());\n * ```\n */\nexport function honoTelemetryMiddleware(\n\toptions: HonoTelemetryMiddlewareOptions = {},\n): MiddlewareHandler {\n\treturn async (c, next) => {\n\t\t// Check if path should be ignored\n\t\tif (\n\t\t\toptions.ignorePaths &&\n\t\t\tshouldIgnorePath(c.req.path, options.ignorePaths)\n\t\t) {\n\t\t\treturn next();\n\t\t}\n\n\t\t// Skip if custom skip function returns true\n\t\tif (options.shouldSkip?.(c)) {\n\t\t\treturn next();\n\t\t}\n\n\t\t// Extract trace context from headers\n\t\tconst headers: Record<string, string | string[] | undefined> = {};\n\t\tc.req.raw.headers.forEach((value, key) => {\n\t\t\theaders[key] = value;\n\t\t});\n\t\tconst parentContext = extractTraceContext(headers);\n\n\t\t// Build span attributes from request\n\t\tconst attrs = buildHonoSpanAttributes(c, options);\n\n\t\t// Create the span\n\t\tconst span = createHttpServerSpan(attrs, parentContext);\n\t\tconst spanContext = trace.setSpan(parentContext, span);\n\n\t\t// Store span and context on Hono context\n\t\tc.set(SPAN_KEY, span);\n\t\tc.set(CONTEXT_KEY, spanContext);\n\n\t\t// Record body if requested\n\t\tif (options.recordBody) {\n\t\t\ttry {\n\t\t\t\tconst body = await c.req.text();\n\t\t\t\tif (body) {\n\t\t\t\t\tspan.setAttribute(\n\t\t\t\t\t\t'http.request.body',\n\t\t\t\t\t\tbody.length > 4096 ? `${body.slice(0, 4096)}...` : body,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Body may have already been consumed\n\t\t\t}\n\t\t}\n\n\t\ttry {\n\t\t\t// Execute the rest of the middleware chain within span context, so\n\t\t\t// the handler's logs and outbound spans nest under this one.\n\t\t\tawait context.with(spanContext, async () => {\n\t\t\t\tawait next();\n\t\t\t});\n\n\t\t\tnameAfterRoute(c, span);\n\n\t\t\t// Record response\n\t\t\tconst statusCode = c.res.status;\n\n\t\t\tif (options.recordResponseBody) {\n\t\t\t\ttry {\n\t\t\t\t\tconst body = await c.res.clone().text();\n\t\t\t\t\tif (body) {\n\t\t\t\t\t\tspan.setAttribute(\n\t\t\t\t\t\t\t'http.response.body',\n\t\t\t\t\t\t\tbody.length > 4096 ? `${body.slice(0, 4096)}...` : body,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t} catch {\n\t\t\t\t\t// Response body may not be available\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Hono hands a thrown error to `onError` and sets `c.error`\n\t\t\t// rather than rethrowing, so that is where it is found.\n\t\t\tendHttpSpan(span, { statusCode }, c.error);\n\t\t} catch (error) {\n\t\t\tnameAfterRoute(c, span);\n\t\t\tendHttpSpan(\n\t\t\t\tspan,\n\t\t\t\t{ statusCode: 500 },\n\t\t\t\terror instanceof Error ? error : new Error(String(error)),\n\t\t\t);\n\t\t\tthrow error;\n\t\t}\n\t};\n}\n\n/**\n * The route the request matched, once the handler has run — `/users/:id`,\n * never `/users/42` — or undefined when only middleware ran (a 404).\n *\n * Read after `next()`: before it, the route is this middleware's own `*`.\n */\nfunction matchedRoute(c: Context): string | undefined {\n\tconst path = routePath(c);\n\treturn path && !path.includes('*') ? path : undefined;\n}\n\n/** `GET /users/:id`, or the bare method when no route matched. */\nfunction nameAfterRoute(c: Context, span: Span): void {\n\tconst route = matchedRoute(c);\n\tif (route) {\n\t\tspan.setAttribute('http.route', route);\n\t\tspan.updateName(`${c.req.method} ${route}`);\n\t} else {\n\t\tspan.updateName(c.req.method);\n\t}\n}\n\n/**\n * Build span attributes from Hono context\n */\nfunction buildHonoSpanAttributes(\n\tc: Context,\n\toptions: HonoTelemetryMiddlewareOptions,\n): HttpSpanAttributes {\n\tconst req = c.req;\n\tconst url = new URL(req.url);\n\n\t// No `url.full`: a query string can carry a token, and the span would\n\t// keep it. The route is named once the handler has run.\n\tconst attrs: HttpSpanAttributes = {\n\t\tmethod: req.method,\n\t\tpath: url.pathname,\n\t\thost: url.host,\n\t\tscheme: url.protocol.replace(':', '') as 'http' | 'https',\n\t\tuserAgent: req.header('user-agent'),\n\t\tclientIp: getClientIp(c),\n\t\trequestId: req.header('x-request-id') || req.header('x-amzn-requestid'),\n\t};\n\n\t// Add user metadata if extractor provided\n\tif (options.getUserId) {\n\t\tconst userId = options.getUserId(c);\n\t\tif (userId) {\n\t\t\tattrs.user = { userId };\n\t\t}\n\t}\n\n\treturn attrs;\n}\n\n/**\n * Extract client IP from Hono context\n */\nfunction getClientIp(c: Context): string | undefined {\n\t// Try various headers in order of preference\n\tconst xForwardedFor = c.req.header('x-forwarded-for');\n\tif (xForwardedFor) {\n\t\tconst first = xForwardedFor.split(',')[0];\n\t\treturn first?.trim();\n\t}\n\n\tconst xRealIp = c.req.header('x-real-ip');\n\tif (xRealIp) {\n\t\treturn xRealIp;\n\t}\n\n\t// Try CF-Connecting-IP (Cloudflare)\n\tconst cfConnectingIp = c.req.header('cf-connecting-ip');\n\tif (cfConnectingIp) {\n\t\treturn cfConnectingIp;\n\t}\n\n\treturn undefined;\n}\n\n/**\n * Check if path should be ignored\n */\nfunction shouldIgnorePath(path: string, ignorePaths: string[]): boolean {\n\tfor (const pattern of ignorePaths) {\n\t\tif (pattern.endsWith('*')) {\n\t\t\t// Wildcard match\n\t\t\tconst prefix = pattern.slice(0, -1);\n\t\t\tif (path.startsWith(prefix)) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t} else if (path === pattern) {\n\t\t\t// Exact match\n\t\t\treturn true;\n\t\t}\n\t}\n\treturn false;\n}\n\n/**\n * Run a function within the span context from Hono context\n *\n * @example\n * ```typescript\n * app.get('/api/users', async (c) => {\n * return withHonoSpanContext(c, async () => {\n * // Any spans created here will be children of the request span\n * const users = await fetchUsers();\n * return c.json(users);\n * });\n * });\n * ```\n */\nexport async function withHonoSpanContext<T>(\n\tc: Context,\n\tfn: () => Promise<T>,\n): Promise<T> {\n\tconst ctx = getTraceContextFromHono(c);\n\tif (!ctx) {\n\t\treturn fn();\n\t}\n\treturn context.with(ctx, fn);\n}\n"],"mappings":"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"}
|
|
@@ -76,11 +76,11 @@ function endHttpSpan(span, response, error) {
|
|
|
76
76
|
code: _opentelemetry_api.SpanStatusCode.ERROR,
|
|
77
77
|
message: error.message
|
|
78
78
|
});
|
|
79
|
-
} else if (response.statusCode >=
|
|
79
|
+
} else if (response.statusCode >= 500) span.setStatus({
|
|
80
80
|
code: _opentelemetry_api.SpanStatusCode.ERROR,
|
|
81
81
|
message: `HTTP ${response.statusCode}`
|
|
82
82
|
});
|
|
83
|
-
else span.setStatus({ code: _opentelemetry_api.SpanStatusCode.OK });
|
|
83
|
+
else if (response.statusCode < 400) span.setStatus({ code: _opentelemetry_api.SpanStatusCode.OK });
|
|
84
84
|
span.end();
|
|
85
85
|
}
|
|
86
86
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.cjs","names":["trace","propagation","context","SpanKind","SpanStatusCode"],"sources":["../../src/instrumentation/http.ts"],"sourcesContent":["import {\n\ttype Attributes,\n\ttype Context,\n\tcontext,\n\tpropagation,\n\ttype Span,\n\tSpanKind,\n\tSpanStatusCode,\n\ttrace,\n} from '@opentelemetry/api';\n\n/**\n * HTTP request attributes following OpenTelemetry semantic conventions\n */\nexport interface HttpRequestAttributes {\n\tmethod: string;\n\turl?: string;\n\tpath?: string;\n\troute?: string;\n\thost?: string;\n\tscheme?: 'http' | 'https';\n\tuserAgent?: string;\n\tclientIp?: string;\n\trequestId?: string;\n}\n\n/**\n * HTTP response attributes\n */\nexport interface HttpResponseAttributes {\n\tstatusCode: number;\n\tresponseSize?: number;\n}\n\n/**\n * Endpoint-specific attributes\n */\nexport interface EndpointAttributes {\n\tname?: string;\n\toperationId?: string;\n\ttags?: string[];\n}\n\n/**\n * User/session attributes\n */\nexport interface UserAttributes {\n\tuserId?: string;\n\tsessionId?: string;\n\troles?: string[];\n}\n\n/**\n * Full span attributes for HTTP requests\n */\nexport interface HttpSpanAttributes extends HttpRequestAttributes {\n\tresponse?: HttpResponseAttributes;\n\tendpoint?: EndpointAttributes;\n\tuser?: UserAttributes;\n\tcustom?: Attributes;\n}\n\nconst TRACER_NAME = '@geekmidas/constructs';\n\n/**\n * Get or create a tracer for constructs instrumentation\n */\nexport function getConstructsTracer() {\n\treturn trace.getTracer(TRACER_NAME);\n}\n\n/**\n * Convert HTTP attributes to OpenTelemetry semantic conventions\n */\nexport function toOtelAttributes(attrs: HttpSpanAttributes): Attributes {\n\tconst otelAttrs: Attributes = {};\n\n\t// HTTP request attributes (OpenTelemetry semantic conventions)\n\tif (attrs.method) otelAttrs['http.request.method'] = attrs.method;\n\tif (attrs.url) otelAttrs['url.full'] = attrs.url;\n\tif (attrs.path) otelAttrs['url.path'] = attrs.path;\n\tif (attrs.route) otelAttrs['http.route'] = attrs.route;\n\tif (attrs.host) otelAttrs['server.address'] = attrs.host;\n\tif (attrs.scheme) otelAttrs['url.scheme'] = attrs.scheme;\n\tif (attrs.userAgent) otelAttrs['user_agent.original'] = attrs.userAgent;\n\tif (attrs.clientIp) otelAttrs['client.address'] = attrs.clientIp;\n\tif (attrs.requestId) otelAttrs['http.request.id'] = attrs.requestId;\n\n\t// HTTP response attributes\n\tif (attrs.response?.statusCode) {\n\t\totelAttrs['http.response.status_code'] = attrs.response.statusCode;\n\t}\n\tif (attrs.response?.responseSize) {\n\t\totelAttrs['http.response.body.size'] = attrs.response.responseSize;\n\t}\n\n\t// Endpoint attributes\n\tif (attrs.endpoint?.name) otelAttrs['endpoint.name'] = attrs.endpoint.name;\n\tif (attrs.endpoint?.operationId) {\n\t\totelAttrs['endpoint.operation_id'] = attrs.endpoint.operationId;\n\t}\n\tif (attrs.endpoint?.tags?.length) {\n\t\totelAttrs['endpoint.tags'] = attrs.endpoint.tags;\n\t}\n\n\t// User attributes\n\tif (attrs.user?.userId) otelAttrs['enduser.id'] = attrs.user.userId;\n\tif (attrs.user?.sessionId) otelAttrs['session.id'] = attrs.user.sessionId;\n\tif (attrs.user?.roles?.length)\n\t\totelAttrs['enduser.role'] = attrs.user.roles.join(',');\n\n\t// Custom attributes\n\tif (attrs.custom) {\n\t\tObject.assign(otelAttrs, attrs.custom);\n\t}\n\n\treturn otelAttrs;\n}\n\n/**\n * Extract trace context from HTTP headers\n */\nexport function extractTraceContext(\n\theaders: Record<string, string | string[] | undefined>,\n): Context {\n\t// Normalize headers to Record<string, string>\n\tconst normalizedHeaders: Record<string, string> = {};\n\tfor (const [key, value] of Object.entries(headers)) {\n\t\tif (value) {\n\t\t\tconst stringValue = Array.isArray(value) ? value[0] : value;\n\t\t\tif (stringValue) {\n\t\t\t\tnormalizedHeaders[key.toLowerCase()] = stringValue;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn propagation.extract(context.active(), normalizedHeaders);\n}\n\n/**\n * Inject trace context into HTTP headers\n */\nexport function injectTraceContext(\n\theaders: Record<string, string>,\n\tctx?: Context,\n): Record<string, string> {\n\tconst result = { ...headers };\n\tpropagation.inject(ctx ?? context.active(), result);\n\treturn result;\n}\n\n/**\n * Create an HTTP server span for an incoming request\n */\nexport function createHttpServerSpan(\n\tattrs: HttpSpanAttributes,\n\tparentContext?: Context,\n): Span {\n\tconst tracer = getConstructsTracer();\n\tconst spanName = attrs.route\n\t\t? `${attrs.method} ${attrs.route}`\n\t\t: `${attrs.method} ${attrs.path || '/'}`;\n\n\tconst ctx = parentContext ?? context.active();\n\n\treturn tracer.startSpan(\n\t\tspanName,\n\t\t{\n\t\t\tkind: SpanKind.SERVER,\n\t\t\tattributes: toOtelAttributes(attrs),\n\t\t},\n\t\tctx,\n\t);\n}\n\n/**\n * End an HTTP span with response data\n */\nexport function endHttpSpan(\n\tspan: Span,\n\tresponse: HttpResponseAttributes,\n\terror?: Error,\n): void {\n\tspan.setAttribute('http.response.status_code', response.statusCode);\n\n\tif (response.responseSize) {\n\t\tspan.setAttribute('http.response.body.size', response.responseSize);\n\t}\n\n\tif (error) {\n\t\tspan.recordException(error);\n\t\tspan.setStatus({\n\t\t\tcode: SpanStatusCode.ERROR,\n\t\t\tmessage: error.message,\n\t\t});\n\t} else if (response.statusCode >= 400) {\n\t\tspan.setStatus({\n\t\t\tcode: SpanStatusCode.ERROR,\n\t\t\tmessage: `HTTP ${response.statusCode}`,\n\t\t});\n\t} else {\n\t\tspan.setStatus({ code: SpanStatusCode.OK });\n\t}\n\n\tspan.end();\n}\n\n/**\n * Execute a function within an HTTP server span context\n *\n * @example\n * ```typescript\n * const result = await withHttpSpan(\n * { method: 'GET', path: '/users', route: '/users' },\n * async (span) => {\n * const users = await getUsers();\n * return { statusCode: 200, body: users };\n * }\n * );\n * ```\n */\nexport async function withHttpSpan<T>(\n\tattrs: HttpSpanAttributes,\n\tfn: (span: Span) => Promise<T & { statusCode?: number }>,\n\tparentContext?: Context,\n): Promise<T> {\n\tconst span = createHttpServerSpan(attrs, parentContext);\n\n\ttry {\n\t\tconst result = await context.with(\n\t\t\ttrace.setSpan(parentContext ?? context.active(), span),\n\t\t\t() => fn(span),\n\t\t);\n\n\t\tconst statusCode = (result as { statusCode?: number }).statusCode ?? 200;\n\t\tendHttpSpan(span, { statusCode });\n\n\t\treturn result;\n\t} catch (error) {\n\t\tendHttpSpan(\n\t\t\tspan,\n\t\t\t{ statusCode: 500 },\n\t\t\terror instanceof Error ? error : new Error(String(error)),\n\t\t);\n\t\tthrow error;\n\t}\n}\n\n/**\n * Create a child span for internal operations (middleware, handlers, etc.)\n */\nexport function createChildSpan(name: string, attributes?: Attributes): Span {\n\tconst tracer = getConstructsTracer();\n\treturn tracer.startSpan(name, {\n\t\tkind: SpanKind.INTERNAL,\n\t\tattributes,\n\t});\n}\n\n/**\n * Execute a function within a child span\n */\nexport async function withChildSpan<T>(\n\tname: string,\n\tfn: (span: Span) => Promise<T>,\n\tattributes?: Attributes,\n): Promise<T> {\n\tconst span = createChildSpan(name, attributes);\n\n\ttry {\n\t\tconst result = await context.with(\n\t\t\ttrace.setSpan(context.active(), span),\n\t\t\t() => fn(span),\n\t\t);\n\t\tspan.setStatus({ code: SpanStatusCode.OK });\n\t\treturn result;\n\t} catch (error) {\n\t\tspan.setStatus({\n\t\t\tcode: SpanStatusCode.ERROR,\n\t\t\tmessage: error instanceof Error ? error.message : 'Unknown error',\n\t\t});\n\t\tspan.recordException(\n\t\t\terror instanceof Error ? error : new Error(String(error)),\n\t\t);\n\t\tthrow error;\n\t} finally {\n\t\tspan.end();\n\t}\n}\n\n/**\n * Check if tracing is enabled (OpenTelemetry API is available and configured)\n */\nexport function isTracingEnabled(): boolean {\n\ttry {\n\t\tconst tracer = trace.getTracer(TRACER_NAME);\n\t\t// Check if we have a real tracer (not a no-op)\n\t\tconst span = tracer.startSpan('test');\n\t\tconst isReal =\n\t\t\tspan.spanContext().traceId !== '00000000000000000000000000000000';\n\t\tspan.end();\n\t\treturn isReal;\n\t} catch {\n\t\treturn false;\n\t}\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"http.cjs","names":["trace","propagation","context","SpanKind","SpanStatusCode"],"sources":["../../src/instrumentation/http.ts"],"sourcesContent":["import {\n\ttype Attributes,\n\ttype Context,\n\tcontext,\n\tpropagation,\n\ttype Span,\n\tSpanKind,\n\tSpanStatusCode,\n\ttrace,\n} from '@opentelemetry/api';\n\n/**\n * HTTP request attributes following OpenTelemetry semantic conventions\n */\nexport interface HttpRequestAttributes {\n\tmethod: string;\n\turl?: string;\n\tpath?: string;\n\troute?: string;\n\thost?: string;\n\tscheme?: 'http' | 'https';\n\tuserAgent?: string;\n\tclientIp?: string;\n\trequestId?: string;\n}\n\n/**\n * HTTP response attributes\n */\nexport interface HttpResponseAttributes {\n\tstatusCode: number;\n\tresponseSize?: number;\n}\n\n/**\n * Endpoint-specific attributes\n */\nexport interface EndpointAttributes {\n\tname?: string;\n\toperationId?: string;\n\ttags?: string[];\n}\n\n/**\n * User/session attributes\n */\nexport interface UserAttributes {\n\tuserId?: string;\n\tsessionId?: string;\n\troles?: string[];\n}\n\n/**\n * Full span attributes for HTTP requests\n */\nexport interface HttpSpanAttributes extends HttpRequestAttributes {\n\tresponse?: HttpResponseAttributes;\n\tendpoint?: EndpointAttributes;\n\tuser?: UserAttributes;\n\tcustom?: Attributes;\n}\n\nconst TRACER_NAME = '@geekmidas/constructs';\n\n/**\n * Get or create a tracer for constructs instrumentation\n */\nexport function getConstructsTracer() {\n\treturn trace.getTracer(TRACER_NAME);\n}\n\n/**\n * Convert HTTP attributes to OpenTelemetry semantic conventions\n */\nexport function toOtelAttributes(attrs: HttpSpanAttributes): Attributes {\n\tconst otelAttrs: Attributes = {};\n\n\t// HTTP request attributes (OpenTelemetry semantic conventions)\n\tif (attrs.method) otelAttrs['http.request.method'] = attrs.method;\n\tif (attrs.url) otelAttrs['url.full'] = attrs.url;\n\tif (attrs.path) otelAttrs['url.path'] = attrs.path;\n\tif (attrs.route) otelAttrs['http.route'] = attrs.route;\n\tif (attrs.host) otelAttrs['server.address'] = attrs.host;\n\tif (attrs.scheme) otelAttrs['url.scheme'] = attrs.scheme;\n\tif (attrs.userAgent) otelAttrs['user_agent.original'] = attrs.userAgent;\n\tif (attrs.clientIp) otelAttrs['client.address'] = attrs.clientIp;\n\tif (attrs.requestId) otelAttrs['http.request.id'] = attrs.requestId;\n\n\t// HTTP response attributes\n\tif (attrs.response?.statusCode) {\n\t\totelAttrs['http.response.status_code'] = attrs.response.statusCode;\n\t}\n\tif (attrs.response?.responseSize) {\n\t\totelAttrs['http.response.body.size'] = attrs.response.responseSize;\n\t}\n\n\t// Endpoint attributes\n\tif (attrs.endpoint?.name) otelAttrs['endpoint.name'] = attrs.endpoint.name;\n\tif (attrs.endpoint?.operationId) {\n\t\totelAttrs['endpoint.operation_id'] = attrs.endpoint.operationId;\n\t}\n\tif (attrs.endpoint?.tags?.length) {\n\t\totelAttrs['endpoint.tags'] = attrs.endpoint.tags;\n\t}\n\n\t// User attributes\n\tif (attrs.user?.userId) otelAttrs['enduser.id'] = attrs.user.userId;\n\tif (attrs.user?.sessionId) otelAttrs['session.id'] = attrs.user.sessionId;\n\tif (attrs.user?.roles?.length)\n\t\totelAttrs['enduser.role'] = attrs.user.roles.join(',');\n\n\t// Custom attributes\n\tif (attrs.custom) {\n\t\tObject.assign(otelAttrs, attrs.custom);\n\t}\n\n\treturn otelAttrs;\n}\n\n/**\n * Extract trace context from HTTP headers\n */\nexport function extractTraceContext(\n\theaders: Record<string, string | string[] | undefined>,\n): Context {\n\t// Normalize headers to Record<string, string>\n\tconst normalizedHeaders: Record<string, string> = {};\n\tfor (const [key, value] of Object.entries(headers)) {\n\t\tif (value) {\n\t\t\tconst stringValue = Array.isArray(value) ? value[0] : value;\n\t\t\tif (stringValue) {\n\t\t\t\tnormalizedHeaders[key.toLowerCase()] = stringValue;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn propagation.extract(context.active(), normalizedHeaders);\n}\n\n/**\n * Inject trace context into HTTP headers\n */\nexport function injectTraceContext(\n\theaders: Record<string, string>,\n\tctx?: Context,\n): Record<string, string> {\n\tconst result = { ...headers };\n\tpropagation.inject(ctx ?? context.active(), result);\n\treturn result;\n}\n\n/**\n * Create an HTTP server span for an incoming request\n */\nexport function createHttpServerSpan(\n\tattrs: HttpSpanAttributes,\n\tparentContext?: Context,\n): Span {\n\tconst tracer = getConstructsTracer();\n\tconst spanName = attrs.route\n\t\t? `${attrs.method} ${attrs.route}`\n\t\t: `${attrs.method} ${attrs.path || '/'}`;\n\n\tconst ctx = parentContext ?? context.active();\n\n\treturn tracer.startSpan(\n\t\tspanName,\n\t\t{\n\t\t\tkind: SpanKind.SERVER,\n\t\t\tattributes: toOtelAttributes(attrs),\n\t\t},\n\t\tctx,\n\t);\n}\n\n/**\n * End an HTTP span with response data\n */\nexport function endHttpSpan(\n\tspan: Span,\n\tresponse: HttpResponseAttributes,\n\terror?: Error,\n): void {\n\tspan.setAttribute('http.response.status_code', response.statusCode);\n\n\tif (response.responseSize) {\n\t\tspan.setAttribute('http.response.body.size', response.responseSize);\n\t}\n\n\tif (error) {\n\t\tspan.recordException(error);\n\t\tspan.setStatus({\n\t\t\tcode: SpanStatusCode.ERROR,\n\t\t\tmessage: error.message,\n\t\t});\n\t} else if (response.statusCode >= 500) {\n\t\tspan.setStatus({\n\t\t\tcode: SpanStatusCode.ERROR,\n\t\t\tmessage: `HTTP ${response.statusCode}`,\n\t\t});\n\t} else if (response.statusCode < 400) {\n\t\tspan.setStatus({ code: SpanStatusCode.OK });\n\t}\n\t// A 4xx leaves the status unset: on a server span it is the client's\n\t// mistake, not the server's failure (HTTP semantic conventions).\n\n\tspan.end();\n}\n\n/**\n * Execute a function within an HTTP server span context\n *\n * @example\n * ```typescript\n * const result = await withHttpSpan(\n * { method: 'GET', path: '/users', route: '/users' },\n * async (span) => {\n * const users = await getUsers();\n * return { statusCode: 200, body: users };\n * }\n * );\n * ```\n */\nexport async function withHttpSpan<T>(\n\tattrs: HttpSpanAttributes,\n\tfn: (span: Span) => Promise<T & { statusCode?: number }>,\n\tparentContext?: Context,\n): Promise<T> {\n\tconst span = createHttpServerSpan(attrs, parentContext);\n\n\ttry {\n\t\tconst result = await context.with(\n\t\t\ttrace.setSpan(parentContext ?? context.active(), span),\n\t\t\t() => fn(span),\n\t\t);\n\n\t\tconst statusCode = (result as { statusCode?: number }).statusCode ?? 200;\n\t\tendHttpSpan(span, { statusCode });\n\n\t\treturn result;\n\t} catch (error) {\n\t\tendHttpSpan(\n\t\t\tspan,\n\t\t\t{ statusCode: 500 },\n\t\t\terror instanceof Error ? error : new Error(String(error)),\n\t\t);\n\t\tthrow error;\n\t}\n}\n\n/**\n * Create a child span for internal operations (middleware, handlers, etc.)\n */\nexport function createChildSpan(name: string, attributes?: Attributes): Span {\n\tconst tracer = getConstructsTracer();\n\treturn tracer.startSpan(name, {\n\t\tkind: SpanKind.INTERNAL,\n\t\tattributes,\n\t});\n}\n\n/**\n * Execute a function within a child span\n */\nexport async function withChildSpan<T>(\n\tname: string,\n\tfn: (span: Span) => Promise<T>,\n\tattributes?: Attributes,\n): Promise<T> {\n\tconst span = createChildSpan(name, attributes);\n\n\ttry {\n\t\tconst result = await context.with(\n\t\t\ttrace.setSpan(context.active(), span),\n\t\t\t() => fn(span),\n\t\t);\n\t\tspan.setStatus({ code: SpanStatusCode.OK });\n\t\treturn result;\n\t} catch (error) {\n\t\tspan.setStatus({\n\t\t\tcode: SpanStatusCode.ERROR,\n\t\t\tmessage: error instanceof Error ? error.message : 'Unknown error',\n\t\t});\n\t\tspan.recordException(\n\t\t\terror instanceof Error ? error : new Error(String(error)),\n\t\t);\n\t\tthrow error;\n\t} finally {\n\t\tspan.end();\n\t}\n}\n\n/**\n * Check if tracing is enabled (OpenTelemetry API is available and configured)\n */\nexport function isTracingEnabled(): boolean {\n\ttry {\n\t\tconst tracer = trace.getTracer(TRACER_NAME);\n\t\t// Check if we have a real tracer (not a no-op)\n\t\tconst span = tracer.startSpan('test');\n\t\tconst isReal =\n\t\t\tspan.spanContext().traceId !== '00000000000000000000000000000000';\n\t\tspan.end();\n\t\treturn isReal;\n\t} catch {\n\t\treturn false;\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.d.cts","names":[],"sources":["../../src/instrumentation/http.ts"],"mappings":";;;;;iBAciB;EAChB;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;;;iBAMgB;EAChB;EACA;;;;;iBAMgB;EAChB;EACA;EACA;;;;;iBAMgB;EAChB;EACA;EACA;;;;;iBAMgB,2BAA2B;EAC3C,WAAW;EACX,WAAW;EACX,OAAO;EACP,SAAS;;;;;wBAQM,oDAAmB;;;;wBAOnB,iBAAiB,OAAO,qBAAqB;;;;wBAgD7C,oBACf,SAAS,gDACP;;;;wBAkBa,mBACf,SAAS,wBACT,MAAM,UACJ;;;;wBASa,qBACf,OAAO,oBACP,gBAAgB,UACd;;;;wBAqBa,YACf,MAAM,MACN,UAAU,wBACV,QAAQ;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"http.d.cts","names":[],"sources":["../../src/instrumentation/http.ts"],"mappings":";;;;;iBAciB;EAChB;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;;;iBAMgB;EAChB;EACA;;;;;iBAMgB;EAChB;EACA;EACA;;;;;iBAMgB;EAChB;EACA;EACA;;;;;iBAMgB,2BAA2B;EAC3C,WAAW;EACX,WAAW;EACX,OAAO;EACP,SAAS;;;;;wBAQM,oDAAmB;;;;wBAOnB,iBAAiB,OAAO,qBAAqB;;;;wBAgD7C,oBACf,SAAS,gDACP;;;;wBAkBa,mBACf,SAAS,wBACT,MAAM,UACJ;;;;wBASa,qBACf,OAAO,oBACP,gBAAgB,UACd;;;;wBAqBa,YACf,MAAM,MACN,UAAU,wBACV,QAAQ;;;;;;;;;;;;;;;wBA0Ca,aAAa,GAClC,OAAO,oBACP,KAAK,MAAM,SAAS,QAAQ;EAAM;IAClC,gBAAgB,UACd,QAAQ;;;;wBA0BK,gBAAgB,cAAc,aAAa,aAAa;;;;wBAWlD,cAAc,GACnC,cACA,KAAK,MAAM,SAAS,QAAQ,IAC5B,aAAa,aACX,QAAQ;;;;wBA2BK"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.d.mts","names":[],"sources":["../../src/instrumentation/http.ts"],"mappings":";;;;;iBAciB;EAChB;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;;;iBAMgB;EAChB;EACA;;;;;iBAMgB;EAChB;EACA;EACA;;;;;iBAMgB;EAChB;EACA;EACA;;;;;iBAMgB,2BAA2B;EAC3C,WAAW;EACX,WAAW;EACX,OAAO;EACP,SAAS;;;;;wBAQM,oDAAmB;;;;wBAOnB,iBAAiB,OAAO,qBAAqB;;;;wBAgD7C,oBACf,SAAS,gDACP;;;;wBAkBa,mBACf,SAAS,wBACT,MAAM,UACJ;;;;wBASa,qBACf,OAAO,oBACP,gBAAgB,UACd;;;;wBAqBa,YACf,MAAM,MACN,UAAU,wBACV,QAAQ;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"http.d.mts","names":[],"sources":["../../src/instrumentation/http.ts"],"mappings":";;;;;iBAciB;EAChB;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;;;iBAMgB;EAChB;EACA;;;;;iBAMgB;EAChB;EACA;EACA;;;;;iBAMgB;EAChB;EACA;EACA;;;;;iBAMgB,2BAA2B;EAC3C,WAAW;EACX,WAAW;EACX,OAAO;EACP,SAAS;;;;;wBAQM,oDAAmB;;;;wBAOnB,iBAAiB,OAAO,qBAAqB;;;;wBAgD7C,oBACf,SAAS,gDACP;;;;wBAkBa,mBACf,SAAS,wBACT,MAAM,UACJ;;;;wBASa,qBACf,OAAO,oBACP,gBAAgB,UACd;;;;wBAqBa,YACf,MAAM,MACN,UAAU,wBACV,QAAQ;;;;;;;;;;;;;;;wBA0Ca,aAAa,GAClC,OAAO,oBACP,KAAK,MAAM,SAAS,QAAQ;EAAM;IAClC,gBAAgB,UACd,QAAQ;;;;wBA0BK,gBAAgB,cAAc,aAAa,aAAa;;;;wBAWlD,cAAc,GACnC,cACA,KAAK,MAAM,SAAS,QAAQ,IAC5B,aAAa,aACX,QAAQ;;;;wBA2BK"}
|
|
@@ -75,11 +75,11 @@ function endHttpSpan(span, response, error) {
|
|
|
75
75
|
code: SpanStatusCode.ERROR,
|
|
76
76
|
message: error.message
|
|
77
77
|
});
|
|
78
|
-
} else if (response.statusCode >=
|
|
78
|
+
} else if (response.statusCode >= 500) span.setStatus({
|
|
79
79
|
code: SpanStatusCode.ERROR,
|
|
80
80
|
message: `HTTP ${response.statusCode}`
|
|
81
81
|
});
|
|
82
|
-
else span.setStatus({ code: SpanStatusCode.OK });
|
|
82
|
+
else if (response.statusCode < 400) span.setStatus({ code: SpanStatusCode.OK });
|
|
83
83
|
span.end();
|
|
84
84
|
}
|
|
85
85
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.mjs","names":[],"sources":["../../src/instrumentation/http.ts"],"sourcesContent":["import {\n\ttype Attributes,\n\ttype Context,\n\tcontext,\n\tpropagation,\n\ttype Span,\n\tSpanKind,\n\tSpanStatusCode,\n\ttrace,\n} from '@opentelemetry/api';\n\n/**\n * HTTP request attributes following OpenTelemetry semantic conventions\n */\nexport interface HttpRequestAttributes {\n\tmethod: string;\n\turl?: string;\n\tpath?: string;\n\troute?: string;\n\thost?: string;\n\tscheme?: 'http' | 'https';\n\tuserAgent?: string;\n\tclientIp?: string;\n\trequestId?: string;\n}\n\n/**\n * HTTP response attributes\n */\nexport interface HttpResponseAttributes {\n\tstatusCode: number;\n\tresponseSize?: number;\n}\n\n/**\n * Endpoint-specific attributes\n */\nexport interface EndpointAttributes {\n\tname?: string;\n\toperationId?: string;\n\ttags?: string[];\n}\n\n/**\n * User/session attributes\n */\nexport interface UserAttributes {\n\tuserId?: string;\n\tsessionId?: string;\n\troles?: string[];\n}\n\n/**\n * Full span attributes for HTTP requests\n */\nexport interface HttpSpanAttributes extends HttpRequestAttributes {\n\tresponse?: HttpResponseAttributes;\n\tendpoint?: EndpointAttributes;\n\tuser?: UserAttributes;\n\tcustom?: Attributes;\n}\n\nconst TRACER_NAME = '@geekmidas/constructs';\n\n/**\n * Get or create a tracer for constructs instrumentation\n */\nexport function getConstructsTracer() {\n\treturn trace.getTracer(TRACER_NAME);\n}\n\n/**\n * Convert HTTP attributes to OpenTelemetry semantic conventions\n */\nexport function toOtelAttributes(attrs: HttpSpanAttributes): Attributes {\n\tconst otelAttrs: Attributes = {};\n\n\t// HTTP request attributes (OpenTelemetry semantic conventions)\n\tif (attrs.method) otelAttrs['http.request.method'] = attrs.method;\n\tif (attrs.url) otelAttrs['url.full'] = attrs.url;\n\tif (attrs.path) otelAttrs['url.path'] = attrs.path;\n\tif (attrs.route) otelAttrs['http.route'] = attrs.route;\n\tif (attrs.host) otelAttrs['server.address'] = attrs.host;\n\tif (attrs.scheme) otelAttrs['url.scheme'] = attrs.scheme;\n\tif (attrs.userAgent) otelAttrs['user_agent.original'] = attrs.userAgent;\n\tif (attrs.clientIp) otelAttrs['client.address'] = attrs.clientIp;\n\tif (attrs.requestId) otelAttrs['http.request.id'] = attrs.requestId;\n\n\t// HTTP response attributes\n\tif (attrs.response?.statusCode) {\n\t\totelAttrs['http.response.status_code'] = attrs.response.statusCode;\n\t}\n\tif (attrs.response?.responseSize) {\n\t\totelAttrs['http.response.body.size'] = attrs.response.responseSize;\n\t}\n\n\t// Endpoint attributes\n\tif (attrs.endpoint?.name) otelAttrs['endpoint.name'] = attrs.endpoint.name;\n\tif (attrs.endpoint?.operationId) {\n\t\totelAttrs['endpoint.operation_id'] = attrs.endpoint.operationId;\n\t}\n\tif (attrs.endpoint?.tags?.length) {\n\t\totelAttrs['endpoint.tags'] = attrs.endpoint.tags;\n\t}\n\n\t// User attributes\n\tif (attrs.user?.userId) otelAttrs['enduser.id'] = attrs.user.userId;\n\tif (attrs.user?.sessionId) otelAttrs['session.id'] = attrs.user.sessionId;\n\tif (attrs.user?.roles?.length)\n\t\totelAttrs['enduser.role'] = attrs.user.roles.join(',');\n\n\t// Custom attributes\n\tif (attrs.custom) {\n\t\tObject.assign(otelAttrs, attrs.custom);\n\t}\n\n\treturn otelAttrs;\n}\n\n/**\n * Extract trace context from HTTP headers\n */\nexport function extractTraceContext(\n\theaders: Record<string, string | string[] | undefined>,\n): Context {\n\t// Normalize headers to Record<string, string>\n\tconst normalizedHeaders: Record<string, string> = {};\n\tfor (const [key, value] of Object.entries(headers)) {\n\t\tif (value) {\n\t\t\tconst stringValue = Array.isArray(value) ? value[0] : value;\n\t\t\tif (stringValue) {\n\t\t\t\tnormalizedHeaders[key.toLowerCase()] = stringValue;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn propagation.extract(context.active(), normalizedHeaders);\n}\n\n/**\n * Inject trace context into HTTP headers\n */\nexport function injectTraceContext(\n\theaders: Record<string, string>,\n\tctx?: Context,\n): Record<string, string> {\n\tconst result = { ...headers };\n\tpropagation.inject(ctx ?? context.active(), result);\n\treturn result;\n}\n\n/**\n * Create an HTTP server span for an incoming request\n */\nexport function createHttpServerSpan(\n\tattrs: HttpSpanAttributes,\n\tparentContext?: Context,\n): Span {\n\tconst tracer = getConstructsTracer();\n\tconst spanName = attrs.route\n\t\t? `${attrs.method} ${attrs.route}`\n\t\t: `${attrs.method} ${attrs.path || '/'}`;\n\n\tconst ctx = parentContext ?? context.active();\n\n\treturn tracer.startSpan(\n\t\tspanName,\n\t\t{\n\t\t\tkind: SpanKind.SERVER,\n\t\t\tattributes: toOtelAttributes(attrs),\n\t\t},\n\t\tctx,\n\t);\n}\n\n/**\n * End an HTTP span with response data\n */\nexport function endHttpSpan(\n\tspan: Span,\n\tresponse: HttpResponseAttributes,\n\terror?: Error,\n): void {\n\tspan.setAttribute('http.response.status_code', response.statusCode);\n\n\tif (response.responseSize) {\n\t\tspan.setAttribute('http.response.body.size', response.responseSize);\n\t}\n\n\tif (error) {\n\t\tspan.recordException(error);\n\t\tspan.setStatus({\n\t\t\tcode: SpanStatusCode.ERROR,\n\t\t\tmessage: error.message,\n\t\t});\n\t} else if (response.statusCode >= 400) {\n\t\tspan.setStatus({\n\t\t\tcode: SpanStatusCode.ERROR,\n\t\t\tmessage: `HTTP ${response.statusCode}`,\n\t\t});\n\t} else {\n\t\tspan.setStatus({ code: SpanStatusCode.OK });\n\t}\n\n\tspan.end();\n}\n\n/**\n * Execute a function within an HTTP server span context\n *\n * @example\n * ```typescript\n * const result = await withHttpSpan(\n * { method: 'GET', path: '/users', route: '/users' },\n * async (span) => {\n * const users = await getUsers();\n * return { statusCode: 200, body: users };\n * }\n * );\n * ```\n */\nexport async function withHttpSpan<T>(\n\tattrs: HttpSpanAttributes,\n\tfn: (span: Span) => Promise<T & { statusCode?: number }>,\n\tparentContext?: Context,\n): Promise<T> {\n\tconst span = createHttpServerSpan(attrs, parentContext);\n\n\ttry {\n\t\tconst result = await context.with(\n\t\t\ttrace.setSpan(parentContext ?? context.active(), span),\n\t\t\t() => fn(span),\n\t\t);\n\n\t\tconst statusCode = (result as { statusCode?: number }).statusCode ?? 200;\n\t\tendHttpSpan(span, { statusCode });\n\n\t\treturn result;\n\t} catch (error) {\n\t\tendHttpSpan(\n\t\t\tspan,\n\t\t\t{ statusCode: 500 },\n\t\t\terror instanceof Error ? error : new Error(String(error)),\n\t\t);\n\t\tthrow error;\n\t}\n}\n\n/**\n * Create a child span for internal operations (middleware, handlers, etc.)\n */\nexport function createChildSpan(name: string, attributes?: Attributes): Span {\n\tconst tracer = getConstructsTracer();\n\treturn tracer.startSpan(name, {\n\t\tkind: SpanKind.INTERNAL,\n\t\tattributes,\n\t});\n}\n\n/**\n * Execute a function within a child span\n */\nexport async function withChildSpan<T>(\n\tname: string,\n\tfn: (span: Span) => Promise<T>,\n\tattributes?: Attributes,\n): Promise<T> {\n\tconst span = createChildSpan(name, attributes);\n\n\ttry {\n\t\tconst result = await context.with(\n\t\t\ttrace.setSpan(context.active(), span),\n\t\t\t() => fn(span),\n\t\t);\n\t\tspan.setStatus({ code: SpanStatusCode.OK });\n\t\treturn result;\n\t} catch (error) {\n\t\tspan.setStatus({\n\t\t\tcode: SpanStatusCode.ERROR,\n\t\t\tmessage: error instanceof Error ? error.message : 'Unknown error',\n\t\t});\n\t\tspan.recordException(\n\t\t\terror instanceof Error ? error : new Error(String(error)),\n\t\t);\n\t\tthrow error;\n\t} finally {\n\t\tspan.end();\n\t}\n}\n\n/**\n * Check if tracing is enabled (OpenTelemetry API is available and configured)\n */\nexport function isTracingEnabled(): boolean {\n\ttry {\n\t\tconst tracer = trace.getTracer(TRACER_NAME);\n\t\t// Check if we have a real tracer (not a no-op)\n\t\tconst span = tracer.startSpan('test');\n\t\tconst isReal =\n\t\t\tspan.spanContext().traceId !== '00000000000000000000000000000000';\n\t\tspan.end();\n\t\treturn isReal;\n\t} catch {\n\t\treturn false;\n\t}\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"http.mjs","names":[],"sources":["../../src/instrumentation/http.ts"],"sourcesContent":["import {\n\ttype Attributes,\n\ttype Context,\n\tcontext,\n\tpropagation,\n\ttype Span,\n\tSpanKind,\n\tSpanStatusCode,\n\ttrace,\n} from '@opentelemetry/api';\n\n/**\n * HTTP request attributes following OpenTelemetry semantic conventions\n */\nexport interface HttpRequestAttributes {\n\tmethod: string;\n\turl?: string;\n\tpath?: string;\n\troute?: string;\n\thost?: string;\n\tscheme?: 'http' | 'https';\n\tuserAgent?: string;\n\tclientIp?: string;\n\trequestId?: string;\n}\n\n/**\n * HTTP response attributes\n */\nexport interface HttpResponseAttributes {\n\tstatusCode: number;\n\tresponseSize?: number;\n}\n\n/**\n * Endpoint-specific attributes\n */\nexport interface EndpointAttributes {\n\tname?: string;\n\toperationId?: string;\n\ttags?: string[];\n}\n\n/**\n * User/session attributes\n */\nexport interface UserAttributes {\n\tuserId?: string;\n\tsessionId?: string;\n\troles?: string[];\n}\n\n/**\n * Full span attributes for HTTP requests\n */\nexport interface HttpSpanAttributes extends HttpRequestAttributes {\n\tresponse?: HttpResponseAttributes;\n\tendpoint?: EndpointAttributes;\n\tuser?: UserAttributes;\n\tcustom?: Attributes;\n}\n\nconst TRACER_NAME = '@geekmidas/constructs';\n\n/**\n * Get or create a tracer for constructs instrumentation\n */\nexport function getConstructsTracer() {\n\treturn trace.getTracer(TRACER_NAME);\n}\n\n/**\n * Convert HTTP attributes to OpenTelemetry semantic conventions\n */\nexport function toOtelAttributes(attrs: HttpSpanAttributes): Attributes {\n\tconst otelAttrs: Attributes = {};\n\n\t// HTTP request attributes (OpenTelemetry semantic conventions)\n\tif (attrs.method) otelAttrs['http.request.method'] = attrs.method;\n\tif (attrs.url) otelAttrs['url.full'] = attrs.url;\n\tif (attrs.path) otelAttrs['url.path'] = attrs.path;\n\tif (attrs.route) otelAttrs['http.route'] = attrs.route;\n\tif (attrs.host) otelAttrs['server.address'] = attrs.host;\n\tif (attrs.scheme) otelAttrs['url.scheme'] = attrs.scheme;\n\tif (attrs.userAgent) otelAttrs['user_agent.original'] = attrs.userAgent;\n\tif (attrs.clientIp) otelAttrs['client.address'] = attrs.clientIp;\n\tif (attrs.requestId) otelAttrs['http.request.id'] = attrs.requestId;\n\n\t// HTTP response attributes\n\tif (attrs.response?.statusCode) {\n\t\totelAttrs['http.response.status_code'] = attrs.response.statusCode;\n\t}\n\tif (attrs.response?.responseSize) {\n\t\totelAttrs['http.response.body.size'] = attrs.response.responseSize;\n\t}\n\n\t// Endpoint attributes\n\tif (attrs.endpoint?.name) otelAttrs['endpoint.name'] = attrs.endpoint.name;\n\tif (attrs.endpoint?.operationId) {\n\t\totelAttrs['endpoint.operation_id'] = attrs.endpoint.operationId;\n\t}\n\tif (attrs.endpoint?.tags?.length) {\n\t\totelAttrs['endpoint.tags'] = attrs.endpoint.tags;\n\t}\n\n\t// User attributes\n\tif (attrs.user?.userId) otelAttrs['enduser.id'] = attrs.user.userId;\n\tif (attrs.user?.sessionId) otelAttrs['session.id'] = attrs.user.sessionId;\n\tif (attrs.user?.roles?.length)\n\t\totelAttrs['enduser.role'] = attrs.user.roles.join(',');\n\n\t// Custom attributes\n\tif (attrs.custom) {\n\t\tObject.assign(otelAttrs, attrs.custom);\n\t}\n\n\treturn otelAttrs;\n}\n\n/**\n * Extract trace context from HTTP headers\n */\nexport function extractTraceContext(\n\theaders: Record<string, string | string[] | undefined>,\n): Context {\n\t// Normalize headers to Record<string, string>\n\tconst normalizedHeaders: Record<string, string> = {};\n\tfor (const [key, value] of Object.entries(headers)) {\n\t\tif (value) {\n\t\t\tconst stringValue = Array.isArray(value) ? value[0] : value;\n\t\t\tif (stringValue) {\n\t\t\t\tnormalizedHeaders[key.toLowerCase()] = stringValue;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn propagation.extract(context.active(), normalizedHeaders);\n}\n\n/**\n * Inject trace context into HTTP headers\n */\nexport function injectTraceContext(\n\theaders: Record<string, string>,\n\tctx?: Context,\n): Record<string, string> {\n\tconst result = { ...headers };\n\tpropagation.inject(ctx ?? context.active(), result);\n\treturn result;\n}\n\n/**\n * Create an HTTP server span for an incoming request\n */\nexport function createHttpServerSpan(\n\tattrs: HttpSpanAttributes,\n\tparentContext?: Context,\n): Span {\n\tconst tracer = getConstructsTracer();\n\tconst spanName = attrs.route\n\t\t? `${attrs.method} ${attrs.route}`\n\t\t: `${attrs.method} ${attrs.path || '/'}`;\n\n\tconst ctx = parentContext ?? context.active();\n\n\treturn tracer.startSpan(\n\t\tspanName,\n\t\t{\n\t\t\tkind: SpanKind.SERVER,\n\t\t\tattributes: toOtelAttributes(attrs),\n\t\t},\n\t\tctx,\n\t);\n}\n\n/**\n * End an HTTP span with response data\n */\nexport function endHttpSpan(\n\tspan: Span,\n\tresponse: HttpResponseAttributes,\n\terror?: Error,\n): void {\n\tspan.setAttribute('http.response.status_code', response.statusCode);\n\n\tif (response.responseSize) {\n\t\tspan.setAttribute('http.response.body.size', response.responseSize);\n\t}\n\n\tif (error) {\n\t\tspan.recordException(error);\n\t\tspan.setStatus({\n\t\t\tcode: SpanStatusCode.ERROR,\n\t\t\tmessage: error.message,\n\t\t});\n\t} else if (response.statusCode >= 500) {\n\t\tspan.setStatus({\n\t\t\tcode: SpanStatusCode.ERROR,\n\t\t\tmessage: `HTTP ${response.statusCode}`,\n\t\t});\n\t} else if (response.statusCode < 400) {\n\t\tspan.setStatus({ code: SpanStatusCode.OK });\n\t}\n\t// A 4xx leaves the status unset: on a server span it is the client's\n\t// mistake, not the server's failure (HTTP semantic conventions).\n\n\tspan.end();\n}\n\n/**\n * Execute a function within an HTTP server span context\n *\n * @example\n * ```typescript\n * const result = await withHttpSpan(\n * { method: 'GET', path: '/users', route: '/users' },\n * async (span) => {\n * const users = await getUsers();\n * return { statusCode: 200, body: users };\n * }\n * );\n * ```\n */\nexport async function withHttpSpan<T>(\n\tattrs: HttpSpanAttributes,\n\tfn: (span: Span) => Promise<T & { statusCode?: number }>,\n\tparentContext?: Context,\n): Promise<T> {\n\tconst span = createHttpServerSpan(attrs, parentContext);\n\n\ttry {\n\t\tconst result = await context.with(\n\t\t\ttrace.setSpan(parentContext ?? context.active(), span),\n\t\t\t() => fn(span),\n\t\t);\n\n\t\tconst statusCode = (result as { statusCode?: number }).statusCode ?? 200;\n\t\tendHttpSpan(span, { statusCode });\n\n\t\treturn result;\n\t} catch (error) {\n\t\tendHttpSpan(\n\t\t\tspan,\n\t\t\t{ statusCode: 500 },\n\t\t\terror instanceof Error ? error : new Error(String(error)),\n\t\t);\n\t\tthrow error;\n\t}\n}\n\n/**\n * Create a child span for internal operations (middleware, handlers, etc.)\n */\nexport function createChildSpan(name: string, attributes?: Attributes): Span {\n\tconst tracer = getConstructsTracer();\n\treturn tracer.startSpan(name, {\n\t\tkind: SpanKind.INTERNAL,\n\t\tattributes,\n\t});\n}\n\n/**\n * Execute a function within a child span\n */\nexport async function withChildSpan<T>(\n\tname: string,\n\tfn: (span: Span) => Promise<T>,\n\tattributes?: Attributes,\n): Promise<T> {\n\tconst span = createChildSpan(name, attributes);\n\n\ttry {\n\t\tconst result = await context.with(\n\t\t\ttrace.setSpan(context.active(), span),\n\t\t\t() => fn(span),\n\t\t);\n\t\tspan.setStatus({ code: SpanStatusCode.OK });\n\t\treturn result;\n\t} catch (error) {\n\t\tspan.setStatus({\n\t\t\tcode: SpanStatusCode.ERROR,\n\t\t\tmessage: error instanceof Error ? error.message : 'Unknown error',\n\t\t});\n\t\tspan.recordException(\n\t\t\terror instanceof Error ? error : new Error(String(error)),\n\t\t);\n\t\tthrow error;\n\t} finally {\n\t\tspan.end();\n\t}\n}\n\n/**\n * Check if tracing is enabled (OpenTelemetry API is available and configured)\n */\nexport function isTracingEnabled(): boolean {\n\ttry {\n\t\tconst tracer = trace.getTracer(TRACER_NAME);\n\t\t// Check if we have a real tracer (not a no-op)\n\t\tconst span = tracer.startSpan('test');\n\t\tconst isReal =\n\t\t\tspan.spanContext().traceId !== '00000000000000000000000000000000';\n\t\tspan.end();\n\t\treturn isReal;\n\t} catch {\n\t\treturn false;\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -56,7 +56,7 @@ function samplerFor(sampleRatio) {
|
|
|
56
56
|
*/
|
|
57
57
|
function setupTelemetry(options) {
|
|
58
58
|
if (sdk) return;
|
|
59
|
-
const { serviceName, serviceVersion = "1.0.0", serviceNamespace, deploymentEnvironment, endpoint, sampleRatio, handleSignals = true, instrumentPino = true, autoInstrument = true, debug = false, resourceAttributes = {}, headers, spanProcessorStrategy, environment = "server" } = options;
|
|
59
|
+
const { serviceName, serviceVersion = "1.0.0", serviceNamespace, deploymentEnvironment, endpoint, sampleRatio, handleSignals = true, instrumentPino = true, incomingHttpSpans = true, autoInstrument = true, debug = false, resourceAttributes = {}, headers, spanProcessorStrategy, environment = "server" } = options;
|
|
60
60
|
const sampler = samplerFor(sampleRatio);
|
|
61
61
|
if (debug) _opentelemetry_api.diag.setLogger(new _opentelemetry_api.DiagConsoleLogger(), _opentelemetry_api.DiagLogLevel.DEBUG);
|
|
62
62
|
const resource = (0, _opentelemetry_resources.resourceFromAttributes)({
|
|
@@ -77,10 +77,13 @@ function setupTelemetry(options) {
|
|
|
77
77
|
} }));
|
|
78
78
|
if (autoInstrument) instrumentations.push((0, _opentelemetry_auto_instrumentations_node.getNodeAutoInstrumentations)({
|
|
79
79
|
"@opentelemetry/instrumentation-fs": { enabled: false },
|
|
80
|
-
"@opentelemetry/instrumentation-http": {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
80
|
+
"@opentelemetry/instrumentation-http": {
|
|
81
|
+
disableIncomingRequestInstrumentation: !incomingHttpSpans,
|
|
82
|
+
ignoreIncomingRequestHook: (request) => {
|
|
83
|
+
const path = request.url || "";
|
|
84
|
+
return path.includes("/__health") || path.includes("/__telescope") || path.includes("/favicon");
|
|
85
|
+
}
|
|
86
|
+
}
|
|
84
87
|
}));
|
|
85
88
|
let traceExporter;
|
|
86
89
|
if (endpoint) traceExporter = new _opentelemetry_exporter_trace_otlp_http.OTLPTraceExporter({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"setup.cjs","names":["ParentBasedSampler","TraceIdRatioBasedSampler","DiagConsoleLogger","DiagLogLevel","resourceFromAttributes","ATTR_SERVICE_NAME","ATTR_SERVICE_VERSION","PinoInstrumentation","getNodeAutoInstrumentations","OTLPTraceExporter","ConsoleSpanExporter","getRecommendedStrategy","createSpanProcessor","OTLPLogExporter","BatchLogRecordProcessor","NodeSDK"],"sources":["../../src/instrumentation/setup.ts"],"sourcesContent":["import { DiagConsoleLogger, DiagLogLevel, diag } from '@opentelemetry/api';\nimport { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';\nimport { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-http';\nimport { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';\nimport { PinoInstrumentation } from '@opentelemetry/instrumentation-pino';\nimport { resourceFromAttributes } from '@opentelemetry/resources';\nimport { BatchLogRecordProcessor } from '@opentelemetry/sdk-logs';\nimport { NodeSDK } from '@opentelemetry/sdk-node';\nimport {\n\tConsoleSpanExporter,\n\tParentBasedSampler,\n\ttype Sampler,\n\ttype SpanExporter,\n\tTraceIdRatioBasedSampler,\n} from '@opentelemetry/sdk-trace-node';\nimport {\n\tATTR_SERVICE_NAME,\n\tATTR_SERVICE_VERSION,\n} from '@opentelemetry/semantic-conventions';\nimport type { SpanProcessorStrategy } from '../adapters/types';\nimport {\n\tcreateSpanProcessor,\n\tgetRecommendedStrategy,\n\tsetGlobalLogProcessor,\n\tsetGlobalSpanProcessor,\n} from './core';\n\n/**\n * Options for configuring telemetry\n */\nexport interface TelemetryOptions {\n\t/**\n\t * Service name for resource identification\n\t */\n\tserviceName: string;\n\n\t/**\n\t * Service version\n\t */\n\tserviceVersion?: string;\n\n\t/**\n\t * The `service.namespace` resource attribute — the product an app is part\n\t * of, so several services from one workspace group together.\n\t */\n\tserviceNamespace?: string;\n\n\t/**\n\t * The stage this process runs as (`production`, `staging`, …). Sent as\n\t * `deployment.environment.name`, and as the older `deployment.environment`\n\t * many backends still filter on.\n\t */\n\tdeploymentEnvironment?: string;\n\n\t/**\n\t * OTLP endpoint URL (e.g., 'http://localhost:3000/__telescope/v1').\n\t * Traces go to `<endpoint>/traces` and logs to `<endpoint>/logs`.\n\t *\n\t * If not provided, the standard `OTEL_EXPORTER_OTLP_ENDPOINT` (and its\n\t * per-signal `_TRACES_`/`_LOGS_` variants and `OTEL_EXPORTER_OTLP_HEADERS`)\n\t * configure the exporters, with the spec's `/v1/traces` and `/v1/logs`\n\t * paths. With neither, traces are written to the console.\n\t */\n\tendpoint?: string;\n\n\t/**\n\t * The fraction of new traces to sample, from 0 to 1. A span whose parent\n\t * was sampled (or not) follows its parent, so a trace is never cut in half.\n\t *\n\t * When left out, the standard `OTEL_TRACES_SAMPLER` and\n\t * `OTEL_TRACES_SAMPLER_ARG` decide (e.g. `parentbased_traceidratio` and\n\t * `0.1`); with neither, every trace is sampled.\n\t */\n\tsampleRatio?: number;\n\n\t/**\n\t * Shut the SDK down and exit on `SIGTERM`.\n\t *\n\t * Turn it off when the process has its own graceful shutdown — exiting as\n\t * soon as telemetry is flushed would cut in-flight requests short.\n\t * @default true\n\t */\n\thandleSignals?: boolean;\n\n\t/**\n\t * Whether to instrument Pino for log correlation\n\t * @default true\n\t */\n\tinstrumentPino?: boolean;\n\n\t/**\n\t * Whether to enable auto-instrumentation for common libraries\n\t * (http, fetch, express, etc.)\n\t * @default true\n\t */\n\tautoInstrument?: boolean;\n\n\t/**\n\t * Enable debug logging for OTel SDK\n\t * @default false\n\t */\n\tdebug?: boolean;\n\n\t/**\n\t * Additional resource attributes\n\t */\n\tresourceAttributes?: Record<string, string>;\n\n\t/**\n\t * Headers to send with OTLP requests\n\t */\n\theaders?: Record<string, string>;\n\n\t/**\n\t * Span processor strategy.\n\t * - 'batch': Efficient batching for long-running servers (default)\n\t * - 'simple': Immediate export for serverless environments (Lambda, Edge)\n\t *\n\t * If not specified, automatically selected based on environment detection.\n\t */\n\tspanProcessorStrategy?: SpanProcessorStrategy;\n\n\t/**\n\t * Environment type for automatic configuration\n\t * @default 'server'\n\t */\n\tenvironment?: 'server' | 'lambda' | 'edge' | 'custom';\n}\n\nlet sdk: NodeSDK | null = null;\nlet onSigterm: (() => void) | null = null;\n\n/** A `sampleRatio` outside 0–1, which no sampler can honour. */\nexport class InvalidSampleRatio extends Error {\n\tconstructor(readonly sampleRatio: number) {\n\t\tsuper(\n\t\t\t`sampleRatio must be a number from 0 (no traces) to 1 (every trace), got ${sampleRatio}. Pass a fraction such as 0.1 to keep one trace in ten.`,\n\t\t);\n\t\tthis.name = 'InvalidSampleRatio';\n\t}\n}\n\n/** Whether the standard OTLP environment variables name a collector. */\nfunction otlpEndpointFromEnv(signal: 'TRACES' | 'LOGS'): boolean {\n\treturn Boolean(\n\t\tprocess.env[`OTEL_EXPORTER_OTLP_${signal}_ENDPOINT`] ||\n\t\t\tprocess.env.OTEL_EXPORTER_OTLP_ENDPOINT,\n\t);\n}\n\n/**\n * The sampler for an explicit ratio, or undefined to let the SDK read\n * `OTEL_TRACES_SAMPLER` / `OTEL_TRACES_SAMPLER_ARG`.\n */\nfunction samplerFor(sampleRatio: number | undefined): Sampler | undefined {\n\tif (sampleRatio === undefined) return undefined;\n\tif (!Number.isFinite(sampleRatio) || sampleRatio < 0 || sampleRatio > 1) {\n\t\tthrow new InvalidSampleRatio(sampleRatio);\n\t}\n\treturn new ParentBasedSampler({\n\t\troot: new TraceIdRatioBasedSampler(sampleRatio),\n\t});\n}\n\n/**\n * Set up OpenTelemetry instrumentation.\n * Call this BEFORE importing your application code.\n *\n * @example\n * ```typescript\n * // instrumentation.ts (create this file)\n * import { setupTelemetry } from '@geekmidas/telescope/instrumentation';\n *\n * setupTelemetry({\n * serviceName: 'my-api',\n * endpoint: 'http://localhost:3000/__telescope/v1',\n * });\n *\n * // Then in your entry point:\n * // import './instrumentation';\n * // import { app } from './app';\n * ```\n */\nexport function setupTelemetry(options: TelemetryOptions): void {\n\tif (sdk) {\n\t\treturn;\n\t}\n\n\tconst {\n\t\tserviceName,\n\t\tserviceVersion = '1.0.0',\n\t\tserviceNamespace,\n\t\tdeploymentEnvironment,\n\t\tendpoint,\n\t\tsampleRatio,\n\t\thandleSignals = true,\n\t\tinstrumentPino = true,\n\t\tautoInstrument = true,\n\t\tdebug = false,\n\t\tresourceAttributes = {},\n\t\theaders,\n\t\tspanProcessorStrategy,\n\t\tenvironment = 'server',\n\t} = options;\n\n\t// Before anything starts, so a bad ratio leaves nothing half set up.\n\tconst sampler = samplerFor(sampleRatio);\n\n\t// Enable debug logging if requested\n\tif (debug) {\n\t\tdiag.setLogger(new DiagConsoleLogger(), DiagLogLevel.DEBUG);\n\t}\n\n\t// Create resource\n\t// `new Resource(...)` went in resources 2.x — a resource is now made from\n\t// attributes by a function, so a detector and a literal produce the same\n\t// thing rather than two shapes that had to be merged.\n\t//\n\t// The SDK merges what `OTEL_RESOURCE_ATTRIBUTES` and `OTEL_SERVICE_NAME`\n\t// say over this, so an operator can still rename or relabel a service.\n\tconst resource = resourceFromAttributes({\n\t\t[ATTR_SERVICE_NAME]: serviceName,\n\t\t[ATTR_SERVICE_VERSION]: serviceVersion,\n\t\t...(serviceNamespace && { 'service.namespace': serviceNamespace }),\n\t\t...(deploymentEnvironment && {\n\t\t\t'deployment.environment.name': deploymentEnvironment,\n\t\t\t'deployment.environment': deploymentEnvironment,\n\t\t}),\n\t\t...resourceAttributes,\n\t});\n\n\t// Build instrumentations list\n\tconst instrumentations = [];\n\n\t// Add Pino instrumentation for log correlation\n\tif (instrumentPino) {\n\t\tinstrumentations.push(\n\t\t\tnew PinoInstrumentation({\n\t\t\t\t// Inject trace context into log records\n\t\t\t\tlogHook: (span, record) => {\n\t\t\t\t\trecord.trace_id = span.spanContext().traceId;\n\t\t\t\t\trecord.span_id = span.spanContext().spanId;\n\t\t\t\t\trecord.trace_flags = span.spanContext().traceFlags;\n\t\t\t\t},\n\t\t\t}),\n\t\t);\n\t}\n\n\t// Add auto-instrumentations for common libraries\n\tif (autoInstrument) {\n\t\tinstrumentations.push(\n\t\t\tgetNodeAutoInstrumentations({\n\t\t\t\t// Disable file system instrumentation (too noisy)\n\t\t\t\t'@opentelemetry/instrumentation-fs': { enabled: false },\n\t\t\t\t// Configure HTTP instrumentation\n\t\t\t\t'@opentelemetry/instrumentation-http': {\n\t\t\t\t\tignoreIncomingRequestHook: (request) => {\n\t\t\t\t\t\t// Ignore health checks and internal routes\n\t\t\t\t\t\tconst path = request.url || '';\n\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\tpath.includes('/__health') ||\n\t\t\t\t\t\t\tpath.includes('/__telescope') ||\n\t\t\t\t\t\t\tpath.includes('/favicon')\n\t\t\t\t\t\t);\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}),\n\t\t);\n\t}\n\n\t// Create exporters\n\tlet traceExporter: SpanExporter;\n\tif (endpoint) {\n\t\ttraceExporter = new OTLPTraceExporter({\n\t\t\turl: `${endpoint}/traces`,\n\t\t\theaders: headers ?? {},\n\t\t});\n\t} else if (otlpEndpointFromEnv('TRACES')) {\n\t\t// No URL: the exporter reads the OTLP variables itself, the way any\n\t\t// other OpenTelemetry SDK would.\n\t\ttraceExporter = new OTLPTraceExporter(headers ? { headers } : {});\n\t} else {\n\t\t// Fall back to console exporter for debugging\n\t\ttraceExporter = new ConsoleSpanExporter();\n\t}\n\n\t// Determine span processor strategy\n\tconst strategy = spanProcessorStrategy ?? getRecommendedStrategy(environment);\n\tconst spanProcessor = createSpanProcessor(traceExporter, { strategy });\n\n\t// Register globally for flush operations\n\tsetGlobalSpanProcessor(spanProcessor);\n\n\t// Set up log exporter if a collector is named\n\tlet logProcessor: BatchLogRecordProcessor | undefined;\n\tif (endpoint || otlpEndpointFromEnv('LOGS')) {\n\t\tconst logExporter = endpoint\n\t\t\t? new OTLPLogExporter({\n\t\t\t\t\turl: `${endpoint}/logs`,\n\t\t\t\t\theaders: headers ?? {},\n\t\t\t\t})\n\t\t\t: new OTLPLogExporter(headers ? { headers } : {});\n\n\t\t// sdk-logs 2.x: processors are constructor-only and the exporter is\n\t\t// named rather than positional.\n\t\tlogProcessor = new BatchLogRecordProcessor({ exporter: logExporter });\n\n\t\t// Register for flush operations\n\t\tsetGlobalLogProcessor(logProcessor);\n\t}\n\n\t// Create and configure SDK. The SDK owns the logger provider, so the log\n\t// processor is handed to it rather than to a provider of our own — one\n\t// nothing registered would never receive a record.\n\tsdk = new NodeSDK({\n\t\tresource,\n\t\ttraceExporter,\n\t\tspanProcessors: [spanProcessor],\n\t\t...(logProcessor && { logRecordProcessors: [logProcessor] }),\n\t\t...(sampler && { sampler }),\n\t\tinstrumentations,\n\t});\n\n\t// Start the SDK\n\tsdk.start();\n\n\tif (handleSignals) {\n\t\t// Graceful shutdown\n\t\tonSigterm = () => {\n\t\t\tsdk\n\t\t\t\t?.shutdown()\n\t\t\t\t.then(() => {})\n\t\t\t\t.catch((_error) => {})\n\t\t\t\t.finally(() => process.exit(0));\n\t\t};\n\t\tprocess.on('SIGTERM', onSigterm);\n\t}\n}\n\n/**\n * Shut down telemetry (for testing or graceful shutdown)\n */\nexport async function shutdownTelemetry(): Promise<void> {\n\tif (onSigterm) {\n\t\tprocess.off('SIGTERM', onSigterm);\n\t\tonSigterm = null;\n\t}\n\tif (sdk) {\n\t\tawait sdk.shutdown();\n\t\tsdk = null;\n\t}\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"setup.cjs","names":["ParentBasedSampler","TraceIdRatioBasedSampler","DiagConsoleLogger","DiagLogLevel","resourceFromAttributes","ATTR_SERVICE_NAME","ATTR_SERVICE_VERSION","PinoInstrumentation","getNodeAutoInstrumentations","OTLPTraceExporter","ConsoleSpanExporter","getRecommendedStrategy","createSpanProcessor","OTLPLogExporter","BatchLogRecordProcessor","NodeSDK"],"sources":["../../src/instrumentation/setup.ts"],"sourcesContent":["import { DiagConsoleLogger, DiagLogLevel, diag } from '@opentelemetry/api';\nimport { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';\nimport { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-http';\nimport { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';\nimport { PinoInstrumentation } from '@opentelemetry/instrumentation-pino';\nimport { resourceFromAttributes } from '@opentelemetry/resources';\nimport { BatchLogRecordProcessor } from '@opentelemetry/sdk-logs';\nimport { NodeSDK } from '@opentelemetry/sdk-node';\nimport {\n\tConsoleSpanExporter,\n\tParentBasedSampler,\n\ttype Sampler,\n\ttype SpanExporter,\n\tTraceIdRatioBasedSampler,\n} from '@opentelemetry/sdk-trace-node';\nimport {\n\tATTR_SERVICE_NAME,\n\tATTR_SERVICE_VERSION,\n} from '@opentelemetry/semantic-conventions';\nimport type { SpanProcessorStrategy } from '../adapters/types';\nimport {\n\tcreateSpanProcessor,\n\tgetRecommendedStrategy,\n\tsetGlobalLogProcessor,\n\tsetGlobalSpanProcessor,\n} from './core';\n\n/**\n * Options for configuring telemetry\n */\nexport interface TelemetryOptions {\n\t/**\n\t * Service name for resource identification\n\t */\n\tserviceName: string;\n\n\t/**\n\t * Service version\n\t */\n\tserviceVersion?: string;\n\n\t/**\n\t * The `service.namespace` resource attribute — the product an app is part\n\t * of, so several services from one workspace group together.\n\t */\n\tserviceNamespace?: string;\n\n\t/**\n\t * The stage this process runs as (`production`, `staging`, …). Sent as\n\t * `deployment.environment.name`, and as the older `deployment.environment`\n\t * many backends still filter on.\n\t */\n\tdeploymentEnvironment?: string;\n\n\t/**\n\t * OTLP endpoint URL (e.g., 'http://localhost:3000/__telescope/v1').\n\t * Traces go to `<endpoint>/traces` and logs to `<endpoint>/logs`.\n\t *\n\t * If not provided, the standard `OTEL_EXPORTER_OTLP_ENDPOINT` (and its\n\t * per-signal `_TRACES_`/`_LOGS_` variants and `OTEL_EXPORTER_OTLP_HEADERS`)\n\t * configure the exporters, with the spec's `/v1/traces` and `/v1/logs`\n\t * paths. With neither, traces are written to the console.\n\t */\n\tendpoint?: string;\n\n\t/**\n\t * The fraction of new traces to sample, from 0 to 1. A span whose parent\n\t * was sampled (or not) follows its parent, so a trace is never cut in half.\n\t *\n\t * When left out, the standard `OTEL_TRACES_SAMPLER` and\n\t * `OTEL_TRACES_SAMPLER_ARG` decide (e.g. `parentbased_traceidratio` and\n\t * `0.1`); with neither, every trace is sampled.\n\t */\n\tsampleRatio?: number;\n\n\t/**\n\t * Shut the SDK down and exit on `SIGTERM`.\n\t *\n\t * Turn it off when the process has its own graceful shutdown — exiting as\n\t * soon as telemetry is flushed would cut in-flight requests short.\n\t * @default true\n\t */\n\thandleSignals?: boolean;\n\n\t/**\n\t * Whether to instrument Pino for log correlation.\n\t *\n\t * It hooks module loading, so it sees nothing inside a bundle. A logger\n\t * from `@geekmidas/logger`'s `createLogger` sends its records through the\n\t * logs API itself; turn this off for one, or each record is sent twice\n\t * where pino can be hooked.\n\t * @default true\n\t */\n\tinstrumentPino?: boolean;\n\n\t/**\n\t * Whether the http auto-instrumentation opens a span for each incoming\n\t * request.\n\t *\n\t * Turn it off when the server opens its own — `honoTelemetryMiddleware`,\n\t * as a `gkm build` server does — or each request is traced twice where\n\t * `node:http` can be hooked, and not at all in a bundle, where it cannot.\n\t * Outgoing requests are traced either way.\n\t * @default true\n\t */\n\tincomingHttpSpans?: boolean;\n\n\t/**\n\t * Whether to enable auto-instrumentation for common libraries\n\t * (http, fetch, express, etc.)\n\t * @default true\n\t */\n\tautoInstrument?: boolean;\n\n\t/**\n\t * Enable debug logging for OTel SDK\n\t * @default false\n\t */\n\tdebug?: boolean;\n\n\t/**\n\t * Additional resource attributes\n\t */\n\tresourceAttributes?: Record<string, string>;\n\n\t/**\n\t * Headers to send with OTLP requests\n\t */\n\theaders?: Record<string, string>;\n\n\t/**\n\t * Span processor strategy.\n\t * - 'batch': Efficient batching for long-running servers (default)\n\t * - 'simple': Immediate export for serverless environments (Lambda, Edge)\n\t *\n\t * If not specified, automatically selected based on environment detection.\n\t */\n\tspanProcessorStrategy?: SpanProcessorStrategy;\n\n\t/**\n\t * Environment type for automatic configuration\n\t * @default 'server'\n\t */\n\tenvironment?: 'server' | 'lambda' | 'edge' | 'custom';\n}\n\nlet sdk: NodeSDK | null = null;\nlet onSigterm: (() => void) | null = null;\n\n/** A `sampleRatio` outside 0–1, which no sampler can honour. */\nexport class InvalidSampleRatio extends Error {\n\tconstructor(readonly sampleRatio: number) {\n\t\tsuper(\n\t\t\t`sampleRatio must be a number from 0 (no traces) to 1 (every trace), got ${sampleRatio}. Pass a fraction such as 0.1 to keep one trace in ten.`,\n\t\t);\n\t\tthis.name = 'InvalidSampleRatio';\n\t}\n}\n\n/** Whether the standard OTLP environment variables name a collector. */\nfunction otlpEndpointFromEnv(signal: 'TRACES' | 'LOGS'): boolean {\n\treturn Boolean(\n\t\tprocess.env[`OTEL_EXPORTER_OTLP_${signal}_ENDPOINT`] ||\n\t\t\tprocess.env.OTEL_EXPORTER_OTLP_ENDPOINT,\n\t);\n}\n\n/**\n * The sampler for an explicit ratio, or undefined to let the SDK read\n * `OTEL_TRACES_SAMPLER` / `OTEL_TRACES_SAMPLER_ARG`.\n */\nfunction samplerFor(sampleRatio: number | undefined): Sampler | undefined {\n\tif (sampleRatio === undefined) return undefined;\n\tif (!Number.isFinite(sampleRatio) || sampleRatio < 0 || sampleRatio > 1) {\n\t\tthrow new InvalidSampleRatio(sampleRatio);\n\t}\n\treturn new ParentBasedSampler({\n\t\troot: new TraceIdRatioBasedSampler(sampleRatio),\n\t});\n}\n\n/**\n * Set up OpenTelemetry instrumentation.\n * Call this BEFORE importing your application code.\n *\n * @example\n * ```typescript\n * // instrumentation.ts (create this file)\n * import { setupTelemetry } from '@geekmidas/telescope/instrumentation';\n *\n * setupTelemetry({\n * serviceName: 'my-api',\n * endpoint: 'http://localhost:3000/__telescope/v1',\n * });\n *\n * // Then in your entry point:\n * // import './instrumentation';\n * // import { app } from './app';\n * ```\n */\nexport function setupTelemetry(options: TelemetryOptions): void {\n\tif (sdk) {\n\t\treturn;\n\t}\n\n\tconst {\n\t\tserviceName,\n\t\tserviceVersion = '1.0.0',\n\t\tserviceNamespace,\n\t\tdeploymentEnvironment,\n\t\tendpoint,\n\t\tsampleRatio,\n\t\thandleSignals = true,\n\t\tinstrumentPino = true,\n\t\tincomingHttpSpans = true,\n\t\tautoInstrument = true,\n\t\tdebug = false,\n\t\tresourceAttributes = {},\n\t\theaders,\n\t\tspanProcessorStrategy,\n\t\tenvironment = 'server',\n\t} = options;\n\n\t// Before anything starts, so a bad ratio leaves nothing half set up.\n\tconst sampler = samplerFor(sampleRatio);\n\n\t// Enable debug logging if requested\n\tif (debug) {\n\t\tdiag.setLogger(new DiagConsoleLogger(), DiagLogLevel.DEBUG);\n\t}\n\n\t// Create resource\n\t// `new Resource(...)` went in resources 2.x — a resource is now made from\n\t// attributes by a function, so a detector and a literal produce the same\n\t// thing rather than two shapes that had to be merged.\n\t//\n\t// The SDK merges what `OTEL_RESOURCE_ATTRIBUTES` and `OTEL_SERVICE_NAME`\n\t// say over this, so an operator can still rename or relabel a service.\n\tconst resource = resourceFromAttributes({\n\t\t[ATTR_SERVICE_NAME]: serviceName,\n\t\t[ATTR_SERVICE_VERSION]: serviceVersion,\n\t\t...(serviceNamespace && { 'service.namespace': serviceNamespace }),\n\t\t...(deploymentEnvironment && {\n\t\t\t'deployment.environment.name': deploymentEnvironment,\n\t\t\t'deployment.environment': deploymentEnvironment,\n\t\t}),\n\t\t...resourceAttributes,\n\t});\n\n\t// Build instrumentations list\n\tconst instrumentations = [];\n\n\t// Add Pino instrumentation for log correlation\n\tif (instrumentPino) {\n\t\tinstrumentations.push(\n\t\t\tnew PinoInstrumentation({\n\t\t\t\t// Inject trace context into log records\n\t\t\t\tlogHook: (span, record) => {\n\t\t\t\t\trecord.trace_id = span.spanContext().traceId;\n\t\t\t\t\trecord.span_id = span.spanContext().spanId;\n\t\t\t\t\trecord.trace_flags = span.spanContext().traceFlags;\n\t\t\t\t},\n\t\t\t}),\n\t\t);\n\t}\n\n\t// Add auto-instrumentations for common libraries\n\tif (autoInstrument) {\n\t\tinstrumentations.push(\n\t\t\tgetNodeAutoInstrumentations({\n\t\t\t\t// Disable file system instrumentation (too noisy)\n\t\t\t\t'@opentelemetry/instrumentation-fs': { enabled: false },\n\t\t\t\t// Configure HTTP instrumentation\n\t\t\t\t'@opentelemetry/instrumentation-http': {\n\t\t\t\t\tdisableIncomingRequestInstrumentation: !incomingHttpSpans,\n\t\t\t\t\tignoreIncomingRequestHook: (request) => {\n\t\t\t\t\t\t// Ignore health checks and internal routes\n\t\t\t\t\t\tconst path = request.url || '';\n\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\tpath.includes('/__health') ||\n\t\t\t\t\t\t\tpath.includes('/__telescope') ||\n\t\t\t\t\t\t\tpath.includes('/favicon')\n\t\t\t\t\t\t);\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}),\n\t\t);\n\t}\n\n\t// Create exporters\n\tlet traceExporter: SpanExporter;\n\tif (endpoint) {\n\t\ttraceExporter = new OTLPTraceExporter({\n\t\t\turl: `${endpoint}/traces`,\n\t\t\theaders: headers ?? {},\n\t\t});\n\t} else if (otlpEndpointFromEnv('TRACES')) {\n\t\t// No URL: the exporter reads the OTLP variables itself, the way any\n\t\t// other OpenTelemetry SDK would.\n\t\ttraceExporter = new OTLPTraceExporter(headers ? { headers } : {});\n\t} else {\n\t\t// Fall back to console exporter for debugging\n\t\ttraceExporter = new ConsoleSpanExporter();\n\t}\n\n\t// Determine span processor strategy\n\tconst strategy = spanProcessorStrategy ?? getRecommendedStrategy(environment);\n\tconst spanProcessor = createSpanProcessor(traceExporter, { strategy });\n\n\t// Register globally for flush operations\n\tsetGlobalSpanProcessor(spanProcessor);\n\n\t// Set up log exporter if a collector is named\n\tlet logProcessor: BatchLogRecordProcessor | undefined;\n\tif (endpoint || otlpEndpointFromEnv('LOGS')) {\n\t\tconst logExporter = endpoint\n\t\t\t? new OTLPLogExporter({\n\t\t\t\t\turl: `${endpoint}/logs`,\n\t\t\t\t\theaders: headers ?? {},\n\t\t\t\t})\n\t\t\t: new OTLPLogExporter(headers ? { headers } : {});\n\n\t\t// sdk-logs 2.x: processors are constructor-only and the exporter is\n\t\t// named rather than positional.\n\t\tlogProcessor = new BatchLogRecordProcessor({ exporter: logExporter });\n\n\t\t// Register for flush operations\n\t\tsetGlobalLogProcessor(logProcessor);\n\t}\n\n\t// Create and configure SDK. The SDK owns the logger provider, so the log\n\t// processor is handed to it rather than to a provider of our own — one\n\t// nothing registered would never receive a record.\n\tsdk = new NodeSDK({\n\t\tresource,\n\t\ttraceExporter,\n\t\tspanProcessors: [spanProcessor],\n\t\t...(logProcessor && { logRecordProcessors: [logProcessor] }),\n\t\t...(sampler && { sampler }),\n\t\tinstrumentations,\n\t});\n\n\t// Start the SDK\n\tsdk.start();\n\n\tif (handleSignals) {\n\t\t// Graceful shutdown\n\t\tonSigterm = () => {\n\t\t\tsdk\n\t\t\t\t?.shutdown()\n\t\t\t\t.then(() => {})\n\t\t\t\t.catch((_error) => {})\n\t\t\t\t.finally(() => process.exit(0));\n\t\t};\n\t\tprocess.on('SIGTERM', onSigterm);\n\t}\n}\n\n/**\n * Shut down telemetry (for testing or graceful shutdown)\n */\nexport async function shutdownTelemetry(): Promise<void> {\n\tif (onSigterm) {\n\t\tprocess.off('SIGTERM', onSigterm);\n\t\tonSigterm = null;\n\t}\n\tif (sdk) {\n\t\tawait sdk.shutdown();\n\t\tsdk = null;\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;AAkJA,IAAI,MAAsB;AAC1B,IAAI,YAAiC;;AAGrC,IAAa,qBAAb,cAAwC,MAAM;CACxB;CAArB,YAAY,aAA8B;EACzC,MACC,2EAA2E,YAAY,wDACxF;EAHoB,KAAA,cAAA;EAIpB,KAAK,OAAO;CACb;AACD;;AAGA,SAAS,oBAAoB,QAAoC;CAChE,OAAO,QACN,QAAQ,IAAI,sBAAsB,OAAO,eACxC,QAAQ,IAAI,2BACd;AACD;;;;;AAMA,SAAS,WAAW,aAAsD;CACzE,IAAI,gBAAgB,KAAA,GAAW,OAAO,KAAA;CACtC,IAAI,CAAC,OAAO,SAAS,WAAW,KAAK,cAAc,KAAK,cAAc,GACrE,MAAM,IAAI,mBAAmB,WAAW;CAEzC,OAAO,IAAIA,8BAAAA,mBAAmB,EAC7B,MAAM,IAAIC,8BAAAA,yBAAyB,WAAW,EAC/C,CAAC;AACF;;;;;;;;;;;;;;;;;;;;AAqBA,SAAgB,eAAe,SAAiC;CAC/D,IAAI,KACH;CAGD,MAAM,EACL,aACA,iBAAiB,SACjB,kBACA,uBACA,UACA,aACA,gBAAgB,MAChB,iBAAiB,MACjB,oBAAoB,MACpB,iBAAiB,MACjB,QAAQ,OACR,qBAAqB,CAAC,GACtB,SACA,uBACA,cAAc,aACX;CAGJ,MAAM,UAAU,WAAW,WAAW;CAGtC,IAAI,OACH,mBAAA,KAAK,UAAU,IAAIC,mBAAAA,kBAAkB,GAAGC,mBAAAA,aAAa,KAAK;CAU3D,MAAM,YAAA,GAAWC,yBAAAA,uBAAAA,CAAuB;GACtCC,oCAAAA,oBAAoB;GACpBC,oCAAAA,uBAAuB;EACxB,GAAI,oBAAoB,EAAE,qBAAqB,iBAAiB;EAChE,GAAI,yBAAyB;GAC5B,+BAA+B;GAC/B,0BAA0B;EAC3B;EACA,GAAG;CACJ,CAAC;CAGD,MAAM,mBAAmB,CAAC;CAG1B,IAAI,gBACH,iBAAiB,KAChB,IAAIC,oCAAAA,oBAAoB,EAEvB,UAAU,MAAM,WAAW;EAC1B,OAAO,WAAW,KAAK,YAAY,CAAC,CAAC;EACrC,OAAO,UAAU,KAAK,YAAY,CAAC,CAAC;EACpC,OAAO,cAAc,KAAK,YAAY,CAAC,CAAC;CACzC,EACD,CAAC,CACF;CAID,IAAI,gBACH,iBAAiB,MAAA,GAChBC,0CAAAA,4BAAAA,CAA4B;EAE3B,qCAAqC,EAAE,SAAS,MAAM;EAEtD,uCAAuC;GACtC,uCAAuC,CAAC;GACxC,4BAA4B,YAAY;IAEvC,MAAM,OAAO,QAAQ,OAAO;IAC5B,OACC,KAAK,SAAS,WAAW,KACzB,KAAK,SAAS,cAAc,KAC5B,KAAK,SAAS,UAAU;GAE1B;EACD;CACD,CAAC,CACF;CAID,IAAI;CACJ,IAAI,UACH,gBAAgB,IAAIC,wCAAAA,kBAAkB;EACrC,KAAK,GAAG,SAAS;EACjB,SAAS,WAAW,CAAC;CACtB,CAAC;MACK,IAAI,oBAAoB,QAAQ,GAGtC,gBAAgB,IAAIA,wCAAAA,kBAAkB,UAAU,EAAE,QAAQ,IAAI,CAAC,CAAC;MAGhE,gBAAgB,IAAIC,8BAAAA,oBAAoB;CAIzC,MAAM,WAAW,yBAAyBC,6BAAAA,uBAAuB,WAAW;CAC5E,MAAM,gBAAgBC,6BAAAA,oBAAoB,eAAe,EAAE,SAAS,CAAC;CAGrE,6BAAA,uBAAuB,aAAa;CAGpC,IAAI;CACJ,IAAI,YAAY,oBAAoB,MAAM,GAAG;EAC5C,MAAM,cAAc,WACjB,IAAIC,uCAAAA,gBAAgB;GACpB,KAAK,GAAG,SAAS;GACjB,SAAS,WAAW,CAAC;EACtB,CAAC,IACA,IAAIA,uCAAAA,gBAAgB,UAAU,EAAE,QAAQ,IAAI,CAAC,CAAC;EAIjD,eAAe,IAAIC,wBAAAA,wBAAwB,EAAE,UAAU,YAAY,CAAC;EAGpE,6BAAA,sBAAsB,YAAY;CACnC;CAKA,MAAM,IAAIC,wBAAAA,QAAQ;EACjB;EACA;EACA,gBAAgB,CAAC,aAAa;EAC9B,GAAI,gBAAgB,EAAE,qBAAqB,CAAC,YAAY,EAAE;EAC1D,GAAI,WAAW,EAAE,QAAQ;EACzB;CACD,CAAC;CAGD,IAAI,MAAM;CAEV,IAAI,eAAe;EAElB,kBAAkB;GACjB,KACG,SAAS,CAAC,CACX,WAAW,CAAC,CAAC,CAAC,CACd,OAAO,WAAW,CAAC,CAAC,CAAC,CACrB,cAAc,QAAQ,KAAK,CAAC,CAAC;EAChC;EACA,QAAQ,GAAG,WAAW,SAAS;CAChC;AACD;;;;AAKA,eAAsB,oBAAmC;CACxD,IAAI,WAAW;EACd,QAAQ,IAAI,WAAW,SAAS;EAChC,YAAY;CACb;CACA,IAAI,KAAK;EACR,MAAM,IAAI,SAAS;EACnB,MAAM;CACP;AACD"}
|
|
@@ -51,10 +51,26 @@ export interface TelemetryOptions {
|
|
|
51
51
|
*/
|
|
52
52
|
handleSignals?: boolean;
|
|
53
53
|
/**
|
|
54
|
-
* Whether to instrument Pino for log correlation
|
|
54
|
+
* Whether to instrument Pino for log correlation.
|
|
55
|
+
*
|
|
56
|
+
* It hooks module loading, so it sees nothing inside a bundle. A logger
|
|
57
|
+
* from `@geekmidas/logger`'s `createLogger` sends its records through the
|
|
58
|
+
* logs API itself; turn this off for one, or each record is sent twice
|
|
59
|
+
* where pino can be hooked.
|
|
55
60
|
* @default true
|
|
56
61
|
*/
|
|
57
62
|
instrumentPino?: boolean;
|
|
63
|
+
/**
|
|
64
|
+
* Whether the http auto-instrumentation opens a span for each incoming
|
|
65
|
+
* request.
|
|
66
|
+
*
|
|
67
|
+
* Turn it off when the server opens its own — `honoTelemetryMiddleware`,
|
|
68
|
+
* as a `gkm build` server does — or each request is traced twice where
|
|
69
|
+
* `node:http` can be hooked, and not at all in a bundle, where it cannot.
|
|
70
|
+
* Outgoing requests are traced either way.
|
|
71
|
+
* @default true
|
|
72
|
+
*/
|
|
73
|
+
incomingHttpSpans?: boolean;
|
|
58
74
|
/**
|
|
59
75
|
* Whether to enable auto-instrumentation for common libraries
|
|
60
76
|
* (http, fetch, express, etc.)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"setup.d.cts","names":[],"sources":["../../src/instrumentation/setup.ts"],"mappings":";;;;;iBA8BiB;;;;EAIhB;;;;EAKA;;;;;EAMA;;;;;;EAOA;;;;;;;;;;EAWA;;;;;;;;;EAUA;;;;;;;;EASA
|
|
1
|
+
{"version":3,"file":"setup.d.cts","names":[],"sources":["../../src/instrumentation/setup.ts"],"mappings":";;;;;iBA8BiB;;;;EAIhB;;;;EAKA;;;;;EAMA;;;;;;EAOA;;;;;;;;;;EAWA;;;;;;;;;EAUA;;;;;;;;EASA;;;;;;;;;;EAWA;;;;;;;;;;;EAYA;;;;;;EAOA;;;;;EAMA;;;;EAKA,qBAAqB;;;;EAKrB,UAAU;;;;;;;;EASV,wBAAwB;;;;;EAMxB;;;qBAOY,2BAA2B;WAClB;EAArB,YAAqB;;;;;;;;;;;;;;;;;;;;;wBAiDN,eAAe,SAAS;;;;wBAiKlB,qBAAqB"}
|
|
@@ -51,10 +51,26 @@ export interface TelemetryOptions {
|
|
|
51
51
|
*/
|
|
52
52
|
handleSignals?: boolean;
|
|
53
53
|
/**
|
|
54
|
-
* Whether to instrument Pino for log correlation
|
|
54
|
+
* Whether to instrument Pino for log correlation.
|
|
55
|
+
*
|
|
56
|
+
* It hooks module loading, so it sees nothing inside a bundle. A logger
|
|
57
|
+
* from `@geekmidas/logger`'s `createLogger` sends its records through the
|
|
58
|
+
* logs API itself; turn this off for one, or each record is sent twice
|
|
59
|
+
* where pino can be hooked.
|
|
55
60
|
* @default true
|
|
56
61
|
*/
|
|
57
62
|
instrumentPino?: boolean;
|
|
63
|
+
/**
|
|
64
|
+
* Whether the http auto-instrumentation opens a span for each incoming
|
|
65
|
+
* request.
|
|
66
|
+
*
|
|
67
|
+
* Turn it off when the server opens its own — `honoTelemetryMiddleware`,
|
|
68
|
+
* as a `gkm build` server does — or each request is traced twice where
|
|
69
|
+
* `node:http` can be hooked, and not at all in a bundle, where it cannot.
|
|
70
|
+
* Outgoing requests are traced either way.
|
|
71
|
+
* @default true
|
|
72
|
+
*/
|
|
73
|
+
incomingHttpSpans?: boolean;
|
|
58
74
|
/**
|
|
59
75
|
* Whether to enable auto-instrumentation for common libraries
|
|
60
76
|
* (http, fetch, express, etc.)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"setup.d.mts","names":[],"sources":["../../src/instrumentation/setup.ts"],"mappings":";;;;;iBA8BiB;;;;EAIhB;;;;EAKA;;;;;EAMA;;;;;;EAOA;;;;;;;;;;EAWA;;;;;;;;;EAUA;;;;;;;;EASA
|
|
1
|
+
{"version":3,"file":"setup.d.mts","names":[],"sources":["../../src/instrumentation/setup.ts"],"mappings":";;;;;iBA8BiB;;;;EAIhB;;;;EAKA;;;;;EAMA;;;;;;EAOA;;;;;;;;;;EAWA;;;;;;;;;EAUA;;;;;;;;EASA;;;;;;;;;;EAWA;;;;;;;;;;;EAYA;;;;;;EAOA;;;;;EAMA;;;;EAKA,qBAAqB;;;;EAKrB,UAAU;;;;;;;;EASV,wBAAwB;;;;;EAMxB;;;qBAOY,2BAA2B;WAClB;EAArB,YAAqB;;;;;;;;;;;;;;;;;;;;;wBAiDN,eAAe,SAAS;;;;wBAiKlB,qBAAqB"}
|
|
@@ -55,7 +55,7 @@ function samplerFor(sampleRatio) {
|
|
|
55
55
|
*/
|
|
56
56
|
function setupTelemetry(options) {
|
|
57
57
|
if (sdk) return;
|
|
58
|
-
const { serviceName, serviceVersion = "1.0.0", serviceNamespace, deploymentEnvironment, endpoint, sampleRatio, handleSignals = true, instrumentPino = true, autoInstrument = true, debug = false, resourceAttributes = {}, headers, spanProcessorStrategy, environment = "server" } = options;
|
|
58
|
+
const { serviceName, serviceVersion = "1.0.0", serviceNamespace, deploymentEnvironment, endpoint, sampleRatio, handleSignals = true, instrumentPino = true, incomingHttpSpans = true, autoInstrument = true, debug = false, resourceAttributes = {}, headers, spanProcessorStrategy, environment = "server" } = options;
|
|
59
59
|
const sampler = samplerFor(sampleRatio);
|
|
60
60
|
if (debug) diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.DEBUG);
|
|
61
61
|
const resource = resourceFromAttributes({
|
|
@@ -76,10 +76,13 @@ function setupTelemetry(options) {
|
|
|
76
76
|
} }));
|
|
77
77
|
if (autoInstrument) instrumentations.push(getNodeAutoInstrumentations({
|
|
78
78
|
"@opentelemetry/instrumentation-fs": { enabled: false },
|
|
79
|
-
"@opentelemetry/instrumentation-http": {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
79
|
+
"@opentelemetry/instrumentation-http": {
|
|
80
|
+
disableIncomingRequestInstrumentation: !incomingHttpSpans,
|
|
81
|
+
ignoreIncomingRequestHook: (request) => {
|
|
82
|
+
const path = request.url || "";
|
|
83
|
+
return path.includes("/__health") || path.includes("/__telescope") || path.includes("/favicon");
|
|
84
|
+
}
|
|
85
|
+
}
|
|
83
86
|
}));
|
|
84
87
|
let traceExporter;
|
|
85
88
|
if (endpoint) traceExporter = new OTLPTraceExporter({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"setup.mjs","names":[],"sources":["../../src/instrumentation/setup.ts"],"sourcesContent":["import { DiagConsoleLogger, DiagLogLevel, diag } from '@opentelemetry/api';\nimport { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';\nimport { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-http';\nimport { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';\nimport { PinoInstrumentation } from '@opentelemetry/instrumentation-pino';\nimport { resourceFromAttributes } from '@opentelemetry/resources';\nimport { BatchLogRecordProcessor } from '@opentelemetry/sdk-logs';\nimport { NodeSDK } from '@opentelemetry/sdk-node';\nimport {\n\tConsoleSpanExporter,\n\tParentBasedSampler,\n\ttype Sampler,\n\ttype SpanExporter,\n\tTraceIdRatioBasedSampler,\n} from '@opentelemetry/sdk-trace-node';\nimport {\n\tATTR_SERVICE_NAME,\n\tATTR_SERVICE_VERSION,\n} from '@opentelemetry/semantic-conventions';\nimport type { SpanProcessorStrategy } from '../adapters/types';\nimport {\n\tcreateSpanProcessor,\n\tgetRecommendedStrategy,\n\tsetGlobalLogProcessor,\n\tsetGlobalSpanProcessor,\n} from './core';\n\n/**\n * Options for configuring telemetry\n */\nexport interface TelemetryOptions {\n\t/**\n\t * Service name for resource identification\n\t */\n\tserviceName: string;\n\n\t/**\n\t * Service version\n\t */\n\tserviceVersion?: string;\n\n\t/**\n\t * The `service.namespace` resource attribute — the product an app is part\n\t * of, so several services from one workspace group together.\n\t */\n\tserviceNamespace?: string;\n\n\t/**\n\t * The stage this process runs as (`production`, `staging`, …). Sent as\n\t * `deployment.environment.name`, and as the older `deployment.environment`\n\t * many backends still filter on.\n\t */\n\tdeploymentEnvironment?: string;\n\n\t/**\n\t * OTLP endpoint URL (e.g., 'http://localhost:3000/__telescope/v1').\n\t * Traces go to `<endpoint>/traces` and logs to `<endpoint>/logs`.\n\t *\n\t * If not provided, the standard `OTEL_EXPORTER_OTLP_ENDPOINT` (and its\n\t * per-signal `_TRACES_`/`_LOGS_` variants and `OTEL_EXPORTER_OTLP_HEADERS`)\n\t * configure the exporters, with the spec's `/v1/traces` and `/v1/logs`\n\t * paths. With neither, traces are written to the console.\n\t */\n\tendpoint?: string;\n\n\t/**\n\t * The fraction of new traces to sample, from 0 to 1. A span whose parent\n\t * was sampled (or not) follows its parent, so a trace is never cut in half.\n\t *\n\t * When left out, the standard `OTEL_TRACES_SAMPLER` and\n\t * `OTEL_TRACES_SAMPLER_ARG` decide (e.g. `parentbased_traceidratio` and\n\t * `0.1`); with neither, every trace is sampled.\n\t */\n\tsampleRatio?: number;\n\n\t/**\n\t * Shut the SDK down and exit on `SIGTERM`.\n\t *\n\t * Turn it off when the process has its own graceful shutdown — exiting as\n\t * soon as telemetry is flushed would cut in-flight requests short.\n\t * @default true\n\t */\n\thandleSignals?: boolean;\n\n\t/**\n\t * Whether to instrument Pino for log correlation\n\t * @default true\n\t */\n\tinstrumentPino?: boolean;\n\n\t/**\n\t * Whether to enable auto-instrumentation for common libraries\n\t * (http, fetch, express, etc.)\n\t * @default true\n\t */\n\tautoInstrument?: boolean;\n\n\t/**\n\t * Enable debug logging for OTel SDK\n\t * @default false\n\t */\n\tdebug?: boolean;\n\n\t/**\n\t * Additional resource attributes\n\t */\n\tresourceAttributes?: Record<string, string>;\n\n\t/**\n\t * Headers to send with OTLP requests\n\t */\n\theaders?: Record<string, string>;\n\n\t/**\n\t * Span processor strategy.\n\t * - 'batch': Efficient batching for long-running servers (default)\n\t * - 'simple': Immediate export for serverless environments (Lambda, Edge)\n\t *\n\t * If not specified, automatically selected based on environment detection.\n\t */\n\tspanProcessorStrategy?: SpanProcessorStrategy;\n\n\t/**\n\t * Environment type for automatic configuration\n\t * @default 'server'\n\t */\n\tenvironment?: 'server' | 'lambda' | 'edge' | 'custom';\n}\n\nlet sdk: NodeSDK | null = null;\nlet onSigterm: (() => void) | null = null;\n\n/** A `sampleRatio` outside 0–1, which no sampler can honour. */\nexport class InvalidSampleRatio extends Error {\n\tconstructor(readonly sampleRatio: number) {\n\t\tsuper(\n\t\t\t`sampleRatio must be a number from 0 (no traces) to 1 (every trace), got ${sampleRatio}. Pass a fraction such as 0.1 to keep one trace in ten.`,\n\t\t);\n\t\tthis.name = 'InvalidSampleRatio';\n\t}\n}\n\n/** Whether the standard OTLP environment variables name a collector. */\nfunction otlpEndpointFromEnv(signal: 'TRACES' | 'LOGS'): boolean {\n\treturn Boolean(\n\t\tprocess.env[`OTEL_EXPORTER_OTLP_${signal}_ENDPOINT`] ||\n\t\t\tprocess.env.OTEL_EXPORTER_OTLP_ENDPOINT,\n\t);\n}\n\n/**\n * The sampler for an explicit ratio, or undefined to let the SDK read\n * `OTEL_TRACES_SAMPLER` / `OTEL_TRACES_SAMPLER_ARG`.\n */\nfunction samplerFor(sampleRatio: number | undefined): Sampler | undefined {\n\tif (sampleRatio === undefined) return undefined;\n\tif (!Number.isFinite(sampleRatio) || sampleRatio < 0 || sampleRatio > 1) {\n\t\tthrow new InvalidSampleRatio(sampleRatio);\n\t}\n\treturn new ParentBasedSampler({\n\t\troot: new TraceIdRatioBasedSampler(sampleRatio),\n\t});\n}\n\n/**\n * Set up OpenTelemetry instrumentation.\n * Call this BEFORE importing your application code.\n *\n * @example\n * ```typescript\n * // instrumentation.ts (create this file)\n * import { setupTelemetry } from '@geekmidas/telescope/instrumentation';\n *\n * setupTelemetry({\n * serviceName: 'my-api',\n * endpoint: 'http://localhost:3000/__telescope/v1',\n * });\n *\n * // Then in your entry point:\n * // import './instrumentation';\n * // import { app } from './app';\n * ```\n */\nexport function setupTelemetry(options: TelemetryOptions): void {\n\tif (sdk) {\n\t\treturn;\n\t}\n\n\tconst {\n\t\tserviceName,\n\t\tserviceVersion = '1.0.0',\n\t\tserviceNamespace,\n\t\tdeploymentEnvironment,\n\t\tendpoint,\n\t\tsampleRatio,\n\t\thandleSignals = true,\n\t\tinstrumentPino = true,\n\t\tautoInstrument = true,\n\t\tdebug = false,\n\t\tresourceAttributes = {},\n\t\theaders,\n\t\tspanProcessorStrategy,\n\t\tenvironment = 'server',\n\t} = options;\n\n\t// Before anything starts, so a bad ratio leaves nothing half set up.\n\tconst sampler = samplerFor(sampleRatio);\n\n\t// Enable debug logging if requested\n\tif (debug) {\n\t\tdiag.setLogger(new DiagConsoleLogger(), DiagLogLevel.DEBUG);\n\t}\n\n\t// Create resource\n\t// `new Resource(...)` went in resources 2.x — a resource is now made from\n\t// attributes by a function, so a detector and a literal produce the same\n\t// thing rather than two shapes that had to be merged.\n\t//\n\t// The SDK merges what `OTEL_RESOURCE_ATTRIBUTES` and `OTEL_SERVICE_NAME`\n\t// say over this, so an operator can still rename or relabel a service.\n\tconst resource = resourceFromAttributes({\n\t\t[ATTR_SERVICE_NAME]: serviceName,\n\t\t[ATTR_SERVICE_VERSION]: serviceVersion,\n\t\t...(serviceNamespace && { 'service.namespace': serviceNamespace }),\n\t\t...(deploymentEnvironment && {\n\t\t\t'deployment.environment.name': deploymentEnvironment,\n\t\t\t'deployment.environment': deploymentEnvironment,\n\t\t}),\n\t\t...resourceAttributes,\n\t});\n\n\t// Build instrumentations list\n\tconst instrumentations = [];\n\n\t// Add Pino instrumentation for log correlation\n\tif (instrumentPino) {\n\t\tinstrumentations.push(\n\t\t\tnew PinoInstrumentation({\n\t\t\t\t// Inject trace context into log records\n\t\t\t\tlogHook: (span, record) => {\n\t\t\t\t\trecord.trace_id = span.spanContext().traceId;\n\t\t\t\t\trecord.span_id = span.spanContext().spanId;\n\t\t\t\t\trecord.trace_flags = span.spanContext().traceFlags;\n\t\t\t\t},\n\t\t\t}),\n\t\t);\n\t}\n\n\t// Add auto-instrumentations for common libraries\n\tif (autoInstrument) {\n\t\tinstrumentations.push(\n\t\t\tgetNodeAutoInstrumentations({\n\t\t\t\t// Disable file system instrumentation (too noisy)\n\t\t\t\t'@opentelemetry/instrumentation-fs': { enabled: false },\n\t\t\t\t// Configure HTTP instrumentation\n\t\t\t\t'@opentelemetry/instrumentation-http': {\n\t\t\t\t\tignoreIncomingRequestHook: (request) => {\n\t\t\t\t\t\t// Ignore health checks and internal routes\n\t\t\t\t\t\tconst path = request.url || '';\n\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\tpath.includes('/__health') ||\n\t\t\t\t\t\t\tpath.includes('/__telescope') ||\n\t\t\t\t\t\t\tpath.includes('/favicon')\n\t\t\t\t\t\t);\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}),\n\t\t);\n\t}\n\n\t// Create exporters\n\tlet traceExporter: SpanExporter;\n\tif (endpoint) {\n\t\ttraceExporter = new OTLPTraceExporter({\n\t\t\turl: `${endpoint}/traces`,\n\t\t\theaders: headers ?? {},\n\t\t});\n\t} else if (otlpEndpointFromEnv('TRACES')) {\n\t\t// No URL: the exporter reads the OTLP variables itself, the way any\n\t\t// other OpenTelemetry SDK would.\n\t\ttraceExporter = new OTLPTraceExporter(headers ? { headers } : {});\n\t} else {\n\t\t// Fall back to console exporter for debugging\n\t\ttraceExporter = new ConsoleSpanExporter();\n\t}\n\n\t// Determine span processor strategy\n\tconst strategy = spanProcessorStrategy ?? getRecommendedStrategy(environment);\n\tconst spanProcessor = createSpanProcessor(traceExporter, { strategy });\n\n\t// Register globally for flush operations\n\tsetGlobalSpanProcessor(spanProcessor);\n\n\t// Set up log exporter if a collector is named\n\tlet logProcessor: BatchLogRecordProcessor | undefined;\n\tif (endpoint || otlpEndpointFromEnv('LOGS')) {\n\t\tconst logExporter = endpoint\n\t\t\t? new OTLPLogExporter({\n\t\t\t\t\turl: `${endpoint}/logs`,\n\t\t\t\t\theaders: headers ?? {},\n\t\t\t\t})\n\t\t\t: new OTLPLogExporter(headers ? { headers } : {});\n\n\t\t// sdk-logs 2.x: processors are constructor-only and the exporter is\n\t\t// named rather than positional.\n\t\tlogProcessor = new BatchLogRecordProcessor({ exporter: logExporter });\n\n\t\t// Register for flush operations\n\t\tsetGlobalLogProcessor(logProcessor);\n\t}\n\n\t// Create and configure SDK. The SDK owns the logger provider, so the log\n\t// processor is handed to it rather than to a provider of our own — one\n\t// nothing registered would never receive a record.\n\tsdk = new NodeSDK({\n\t\tresource,\n\t\ttraceExporter,\n\t\tspanProcessors: [spanProcessor],\n\t\t...(logProcessor && { logRecordProcessors: [logProcessor] }),\n\t\t...(sampler && { sampler }),\n\t\tinstrumentations,\n\t});\n\n\t// Start the SDK\n\tsdk.start();\n\n\tif (handleSignals) {\n\t\t// Graceful shutdown\n\t\tonSigterm = () => {\n\t\t\tsdk\n\t\t\t\t?.shutdown()\n\t\t\t\t.then(() => {})\n\t\t\t\t.catch((_error) => {})\n\t\t\t\t.finally(() => process.exit(0));\n\t\t};\n\t\tprocess.on('SIGTERM', onSigterm);\n\t}\n}\n\n/**\n * Shut down telemetry (for testing or graceful shutdown)\n */\nexport async function shutdownTelemetry(): Promise<void> {\n\tif (onSigterm) {\n\t\tprocess.off('SIGTERM', onSigterm);\n\t\tonSigterm = null;\n\t}\n\tif (sdk) {\n\t\tawait sdk.shutdown();\n\t\tsdk = null;\n\t}\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"setup.mjs","names":[],"sources":["../../src/instrumentation/setup.ts"],"sourcesContent":["import { DiagConsoleLogger, DiagLogLevel, diag } from '@opentelemetry/api';\nimport { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';\nimport { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-http';\nimport { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';\nimport { PinoInstrumentation } from '@opentelemetry/instrumentation-pino';\nimport { resourceFromAttributes } from '@opentelemetry/resources';\nimport { BatchLogRecordProcessor } from '@opentelemetry/sdk-logs';\nimport { NodeSDK } from '@opentelemetry/sdk-node';\nimport {\n\tConsoleSpanExporter,\n\tParentBasedSampler,\n\ttype Sampler,\n\ttype SpanExporter,\n\tTraceIdRatioBasedSampler,\n} from '@opentelemetry/sdk-trace-node';\nimport {\n\tATTR_SERVICE_NAME,\n\tATTR_SERVICE_VERSION,\n} from '@opentelemetry/semantic-conventions';\nimport type { SpanProcessorStrategy } from '../adapters/types';\nimport {\n\tcreateSpanProcessor,\n\tgetRecommendedStrategy,\n\tsetGlobalLogProcessor,\n\tsetGlobalSpanProcessor,\n} from './core';\n\n/**\n * Options for configuring telemetry\n */\nexport interface TelemetryOptions {\n\t/**\n\t * Service name for resource identification\n\t */\n\tserviceName: string;\n\n\t/**\n\t * Service version\n\t */\n\tserviceVersion?: string;\n\n\t/**\n\t * The `service.namespace` resource attribute — the product an app is part\n\t * of, so several services from one workspace group together.\n\t */\n\tserviceNamespace?: string;\n\n\t/**\n\t * The stage this process runs as (`production`, `staging`, …). Sent as\n\t * `deployment.environment.name`, and as the older `deployment.environment`\n\t * many backends still filter on.\n\t */\n\tdeploymentEnvironment?: string;\n\n\t/**\n\t * OTLP endpoint URL (e.g., 'http://localhost:3000/__telescope/v1').\n\t * Traces go to `<endpoint>/traces` and logs to `<endpoint>/logs`.\n\t *\n\t * If not provided, the standard `OTEL_EXPORTER_OTLP_ENDPOINT` (and its\n\t * per-signal `_TRACES_`/`_LOGS_` variants and `OTEL_EXPORTER_OTLP_HEADERS`)\n\t * configure the exporters, with the spec's `/v1/traces` and `/v1/logs`\n\t * paths. With neither, traces are written to the console.\n\t */\n\tendpoint?: string;\n\n\t/**\n\t * The fraction of new traces to sample, from 0 to 1. A span whose parent\n\t * was sampled (or not) follows its parent, so a trace is never cut in half.\n\t *\n\t * When left out, the standard `OTEL_TRACES_SAMPLER` and\n\t * `OTEL_TRACES_SAMPLER_ARG` decide (e.g. `parentbased_traceidratio` and\n\t * `0.1`); with neither, every trace is sampled.\n\t */\n\tsampleRatio?: number;\n\n\t/**\n\t * Shut the SDK down and exit on `SIGTERM`.\n\t *\n\t * Turn it off when the process has its own graceful shutdown — exiting as\n\t * soon as telemetry is flushed would cut in-flight requests short.\n\t * @default true\n\t */\n\thandleSignals?: boolean;\n\n\t/**\n\t * Whether to instrument Pino for log correlation.\n\t *\n\t * It hooks module loading, so it sees nothing inside a bundle. A logger\n\t * from `@geekmidas/logger`'s `createLogger` sends its records through the\n\t * logs API itself; turn this off for one, or each record is sent twice\n\t * where pino can be hooked.\n\t * @default true\n\t */\n\tinstrumentPino?: boolean;\n\n\t/**\n\t * Whether the http auto-instrumentation opens a span for each incoming\n\t * request.\n\t *\n\t * Turn it off when the server opens its own — `honoTelemetryMiddleware`,\n\t * as a `gkm build` server does — or each request is traced twice where\n\t * `node:http` can be hooked, and not at all in a bundle, where it cannot.\n\t * Outgoing requests are traced either way.\n\t * @default true\n\t */\n\tincomingHttpSpans?: boolean;\n\n\t/**\n\t * Whether to enable auto-instrumentation for common libraries\n\t * (http, fetch, express, etc.)\n\t * @default true\n\t */\n\tautoInstrument?: boolean;\n\n\t/**\n\t * Enable debug logging for OTel SDK\n\t * @default false\n\t */\n\tdebug?: boolean;\n\n\t/**\n\t * Additional resource attributes\n\t */\n\tresourceAttributes?: Record<string, string>;\n\n\t/**\n\t * Headers to send with OTLP requests\n\t */\n\theaders?: Record<string, string>;\n\n\t/**\n\t * Span processor strategy.\n\t * - 'batch': Efficient batching for long-running servers (default)\n\t * - 'simple': Immediate export for serverless environments (Lambda, Edge)\n\t *\n\t * If not specified, automatically selected based on environment detection.\n\t */\n\tspanProcessorStrategy?: SpanProcessorStrategy;\n\n\t/**\n\t * Environment type for automatic configuration\n\t * @default 'server'\n\t */\n\tenvironment?: 'server' | 'lambda' | 'edge' | 'custom';\n}\n\nlet sdk: NodeSDK | null = null;\nlet onSigterm: (() => void) | null = null;\n\n/** A `sampleRatio` outside 0–1, which no sampler can honour. */\nexport class InvalidSampleRatio extends Error {\n\tconstructor(readonly sampleRatio: number) {\n\t\tsuper(\n\t\t\t`sampleRatio must be a number from 0 (no traces) to 1 (every trace), got ${sampleRatio}. Pass a fraction such as 0.1 to keep one trace in ten.`,\n\t\t);\n\t\tthis.name = 'InvalidSampleRatio';\n\t}\n}\n\n/** Whether the standard OTLP environment variables name a collector. */\nfunction otlpEndpointFromEnv(signal: 'TRACES' | 'LOGS'): boolean {\n\treturn Boolean(\n\t\tprocess.env[`OTEL_EXPORTER_OTLP_${signal}_ENDPOINT`] ||\n\t\t\tprocess.env.OTEL_EXPORTER_OTLP_ENDPOINT,\n\t);\n}\n\n/**\n * The sampler for an explicit ratio, or undefined to let the SDK read\n * `OTEL_TRACES_SAMPLER` / `OTEL_TRACES_SAMPLER_ARG`.\n */\nfunction samplerFor(sampleRatio: number | undefined): Sampler | undefined {\n\tif (sampleRatio === undefined) return undefined;\n\tif (!Number.isFinite(sampleRatio) || sampleRatio < 0 || sampleRatio > 1) {\n\t\tthrow new InvalidSampleRatio(sampleRatio);\n\t}\n\treturn new ParentBasedSampler({\n\t\troot: new TraceIdRatioBasedSampler(sampleRatio),\n\t});\n}\n\n/**\n * Set up OpenTelemetry instrumentation.\n * Call this BEFORE importing your application code.\n *\n * @example\n * ```typescript\n * // instrumentation.ts (create this file)\n * import { setupTelemetry } from '@geekmidas/telescope/instrumentation';\n *\n * setupTelemetry({\n * serviceName: 'my-api',\n * endpoint: 'http://localhost:3000/__telescope/v1',\n * });\n *\n * // Then in your entry point:\n * // import './instrumentation';\n * // import { app } from './app';\n * ```\n */\nexport function setupTelemetry(options: TelemetryOptions): void {\n\tif (sdk) {\n\t\treturn;\n\t}\n\n\tconst {\n\t\tserviceName,\n\t\tserviceVersion = '1.0.0',\n\t\tserviceNamespace,\n\t\tdeploymentEnvironment,\n\t\tendpoint,\n\t\tsampleRatio,\n\t\thandleSignals = true,\n\t\tinstrumentPino = true,\n\t\tincomingHttpSpans = true,\n\t\tautoInstrument = true,\n\t\tdebug = false,\n\t\tresourceAttributes = {},\n\t\theaders,\n\t\tspanProcessorStrategy,\n\t\tenvironment = 'server',\n\t} = options;\n\n\t// Before anything starts, so a bad ratio leaves nothing half set up.\n\tconst sampler = samplerFor(sampleRatio);\n\n\t// Enable debug logging if requested\n\tif (debug) {\n\t\tdiag.setLogger(new DiagConsoleLogger(), DiagLogLevel.DEBUG);\n\t}\n\n\t// Create resource\n\t// `new Resource(...)` went in resources 2.x — a resource is now made from\n\t// attributes by a function, so a detector and a literal produce the same\n\t// thing rather than two shapes that had to be merged.\n\t//\n\t// The SDK merges what `OTEL_RESOURCE_ATTRIBUTES` and `OTEL_SERVICE_NAME`\n\t// say over this, so an operator can still rename or relabel a service.\n\tconst resource = resourceFromAttributes({\n\t\t[ATTR_SERVICE_NAME]: serviceName,\n\t\t[ATTR_SERVICE_VERSION]: serviceVersion,\n\t\t...(serviceNamespace && { 'service.namespace': serviceNamespace }),\n\t\t...(deploymentEnvironment && {\n\t\t\t'deployment.environment.name': deploymentEnvironment,\n\t\t\t'deployment.environment': deploymentEnvironment,\n\t\t}),\n\t\t...resourceAttributes,\n\t});\n\n\t// Build instrumentations list\n\tconst instrumentations = [];\n\n\t// Add Pino instrumentation for log correlation\n\tif (instrumentPino) {\n\t\tinstrumentations.push(\n\t\t\tnew PinoInstrumentation({\n\t\t\t\t// Inject trace context into log records\n\t\t\t\tlogHook: (span, record) => {\n\t\t\t\t\trecord.trace_id = span.spanContext().traceId;\n\t\t\t\t\trecord.span_id = span.spanContext().spanId;\n\t\t\t\t\trecord.trace_flags = span.spanContext().traceFlags;\n\t\t\t\t},\n\t\t\t}),\n\t\t);\n\t}\n\n\t// Add auto-instrumentations for common libraries\n\tif (autoInstrument) {\n\t\tinstrumentations.push(\n\t\t\tgetNodeAutoInstrumentations({\n\t\t\t\t// Disable file system instrumentation (too noisy)\n\t\t\t\t'@opentelemetry/instrumentation-fs': { enabled: false },\n\t\t\t\t// Configure HTTP instrumentation\n\t\t\t\t'@opentelemetry/instrumentation-http': {\n\t\t\t\t\tdisableIncomingRequestInstrumentation: !incomingHttpSpans,\n\t\t\t\t\tignoreIncomingRequestHook: (request) => {\n\t\t\t\t\t\t// Ignore health checks and internal routes\n\t\t\t\t\t\tconst path = request.url || '';\n\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\tpath.includes('/__health') ||\n\t\t\t\t\t\t\tpath.includes('/__telescope') ||\n\t\t\t\t\t\t\tpath.includes('/favicon')\n\t\t\t\t\t\t);\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}),\n\t\t);\n\t}\n\n\t// Create exporters\n\tlet traceExporter: SpanExporter;\n\tif (endpoint) {\n\t\ttraceExporter = new OTLPTraceExporter({\n\t\t\turl: `${endpoint}/traces`,\n\t\t\theaders: headers ?? {},\n\t\t});\n\t} else if (otlpEndpointFromEnv('TRACES')) {\n\t\t// No URL: the exporter reads the OTLP variables itself, the way any\n\t\t// other OpenTelemetry SDK would.\n\t\ttraceExporter = new OTLPTraceExporter(headers ? { headers } : {});\n\t} else {\n\t\t// Fall back to console exporter for debugging\n\t\ttraceExporter = new ConsoleSpanExporter();\n\t}\n\n\t// Determine span processor strategy\n\tconst strategy = spanProcessorStrategy ?? getRecommendedStrategy(environment);\n\tconst spanProcessor = createSpanProcessor(traceExporter, { strategy });\n\n\t// Register globally for flush operations\n\tsetGlobalSpanProcessor(spanProcessor);\n\n\t// Set up log exporter if a collector is named\n\tlet logProcessor: BatchLogRecordProcessor | undefined;\n\tif (endpoint || otlpEndpointFromEnv('LOGS')) {\n\t\tconst logExporter = endpoint\n\t\t\t? new OTLPLogExporter({\n\t\t\t\t\turl: `${endpoint}/logs`,\n\t\t\t\t\theaders: headers ?? {},\n\t\t\t\t})\n\t\t\t: new OTLPLogExporter(headers ? { headers } : {});\n\n\t\t// sdk-logs 2.x: processors are constructor-only and the exporter is\n\t\t// named rather than positional.\n\t\tlogProcessor = new BatchLogRecordProcessor({ exporter: logExporter });\n\n\t\t// Register for flush operations\n\t\tsetGlobalLogProcessor(logProcessor);\n\t}\n\n\t// Create and configure SDK. The SDK owns the logger provider, so the log\n\t// processor is handed to it rather than to a provider of our own — one\n\t// nothing registered would never receive a record.\n\tsdk = new NodeSDK({\n\t\tresource,\n\t\ttraceExporter,\n\t\tspanProcessors: [spanProcessor],\n\t\t...(logProcessor && { logRecordProcessors: [logProcessor] }),\n\t\t...(sampler && { sampler }),\n\t\tinstrumentations,\n\t});\n\n\t// Start the SDK\n\tsdk.start();\n\n\tif (handleSignals) {\n\t\t// Graceful shutdown\n\t\tonSigterm = () => {\n\t\t\tsdk\n\t\t\t\t?.shutdown()\n\t\t\t\t.then(() => {})\n\t\t\t\t.catch((_error) => {})\n\t\t\t\t.finally(() => process.exit(0));\n\t\t};\n\t\tprocess.on('SIGTERM', onSigterm);\n\t}\n}\n\n/**\n * Shut down telemetry (for testing or graceful shutdown)\n */\nexport async function shutdownTelemetry(): Promise<void> {\n\tif (onSigterm) {\n\t\tprocess.off('SIGTERM', onSigterm);\n\t\tonSigterm = null;\n\t}\n\tif (sdk) {\n\t\tawait sdk.shutdown();\n\t\tsdk = null;\n\t}\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@geekmidas/telescope",
|
|
3
|
-
"version": "10.0.0-alpha.
|
|
3
|
+
"version": "10.0.0-alpha.70",
|
|
4
4
|
"description": "Laravel Telescope-style request, exception and log recording for web applications, exposed as a JSON API",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -159,7 +159,7 @@
|
|
|
159
159
|
"dependencies": {
|
|
160
160
|
"@pinojs/redact": "~0.4.0",
|
|
161
161
|
"nanoid": "~6.0.1",
|
|
162
|
-
"@geekmidas/logger": "^10.0.0-alpha.
|
|
162
|
+
"@geekmidas/logger": "^10.0.0-alpha.70"
|
|
163
163
|
},
|
|
164
164
|
"peerDependencies": {
|
|
165
165
|
"@middy/core": ">=7.9.2",
|
|
@@ -250,6 +250,6 @@
|
|
|
250
250
|
"pg": "~8.23.0",
|
|
251
251
|
"pino": "~10.3.1",
|
|
252
252
|
"pino-abstract-transport": "~3.0.0",
|
|
253
|
-
"@geekmidas/logger": "^10.0.0-alpha.
|
|
253
|
+
"@geekmidas/logger": "^10.0.0-alpha.70"
|
|
254
254
|
}
|
|
255
255
|
}
|