@geekmidas/telescope 10.0.0-alpha.80 → 10.0.0-alpha.82
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 +25 -2
- package/dist/instrumentation/hono.cjs.map +1 -1
- package/dist/instrumentation/hono.d.cts +19 -0
- package/dist/instrumentation/hono.d.cts.map +1 -1
- package/dist/instrumentation/hono.d.mts +19 -0
- package/dist/instrumentation/hono.d.mts.map +1 -1
- package/dist/instrumentation/hono.mjs +26 -3
- package/dist/instrumentation/hono.mjs.map +1 -1
- package/dist/instrumentation/http.cjs +27 -2
- package/dist/instrumentation/http.cjs.map +1 -1
- package/dist/instrumentation/http.d.cts +18 -2
- package/dist/instrumentation/http.d.cts.map +1 -1
- package/dist/instrumentation/http.d.mts +18 -2
- package/dist/instrumentation/http.d.mts.map +1 -1
- package/dist/instrumentation/http.mjs +28 -4
- package/dist/instrumentation/http.mjs.map +1 -1
- package/dist/instrumentation/index.cjs +8 -0
- package/dist/instrumentation/index.d.cts +4 -2
- package/dist/instrumentation/index.d.mts +4 -2
- package/dist/instrumentation/index.mjs +4 -2
- package/dist/instrumentation/middleware.cjs +4 -2
- package/dist/instrumentation/middleware.cjs.map +1 -1
- package/dist/instrumentation/middleware.d.cts +13 -0
- package/dist/instrumentation/middleware.d.cts.map +1 -1
- package/dist/instrumentation/middleware.d.mts +13 -0
- package/dist/instrumentation/middleware.d.mts.map +1 -1
- package/dist/instrumentation/middleware.mjs +5 -3
- package/dist/instrumentation/middleware.mjs.map +1 -1
- package/dist/instrumentation/sampler.cjs +54 -0
- package/dist/instrumentation/sampler.cjs.map +1 -0
- package/dist/instrumentation/sampler.d.cts +38 -0
- package/dist/instrumentation/sampler.d.cts.map +1 -0
- package/dist/instrumentation/sampler.d.mts +38 -0
- package/dist/instrumentation/sampler.d.mts.map +1 -0
- package/dist/instrumentation/sampler.mjs +52 -0
- package/dist/instrumentation/sampler.mjs.map +1 -0
- package/dist/instrumentation/setup.cjs +6 -4
- package/dist/instrumentation/setup.cjs.map +1 -1
- package/dist/instrumentation/setup.d.cts +5 -3
- package/dist/instrumentation/setup.d.cts.map +1 -1
- package/dist/instrumentation/setup.d.mts +5 -3
- package/dist/instrumentation/setup.d.mts.map +1 -1
- package/dist/instrumentation/setup.mjs +7 -5
- package/dist/instrumentation/setup.mjs.map +1 -1
- package/dist/instrumentation/trust.cjs +101 -0
- package/dist/instrumentation/trust.cjs.map +1 -0
- package/dist/instrumentation/trust.d.cts +57 -0
- package/dist/instrumentation/trust.d.cts.map +1 -0
- package/dist/instrumentation/trust.d.mts +57 -0
- package/dist/instrumentation/trust.d.mts.map +1 -0
- package/dist/instrumentation/trust.mjs +98 -0
- package/dist/instrumentation/trust.mjs.map +1 -0
- package/package.json +4 -3
|
@@ -2,9 +2,11 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
|
2
2
|
const require_instrumentation_core = require("./core.cjs");
|
|
3
3
|
const require_instrumentation_tracing = require("./tracing.cjs");
|
|
4
4
|
const require_instrumentation_http = require("./http.cjs");
|
|
5
|
+
const require_instrumentation_trust = require("./trust.cjs");
|
|
5
6
|
const require_instrumentation_hono = require("./hono.cjs");
|
|
6
7
|
const require_instrumentation_middleware = require("./middleware.cjs");
|
|
7
8
|
const require_instrumentation_otel = require("./otel.cjs");
|
|
9
|
+
const require_instrumentation_sampler = require("./sampler.cjs");
|
|
8
10
|
const require_instrumentation_setup = require("./setup.cjs");
|
|
9
11
|
exports.InvalidSampleRatio = require_instrumentation_setup.InvalidSampleRatio;
|
|
10
12
|
exports.NoopSpanProcessor = require_instrumentation_core.NoopSpanProcessor;
|
|
@@ -28,9 +30,13 @@ exports.getSpanId = require_instrumentation_tracing.getSpanId;
|
|
|
28
30
|
exports.getTraceContextFromHono = require_instrumentation_hono.getTraceContextFromHono;
|
|
29
31
|
exports.getTraceId = require_instrumentation_tracing.getTraceId;
|
|
30
32
|
exports.honoTelemetryMiddleware = require_instrumentation_hono.honoTelemetryMiddleware;
|
|
33
|
+
exports.incomingTraceContext = require_instrumentation_http.incomingTraceContext;
|
|
31
34
|
exports.injectTraceContext = require_instrumentation_http.injectTraceContext;
|
|
35
|
+
exports.isInternalCaller = require_instrumentation_trust.isInternalCaller;
|
|
36
|
+
exports.isPrivateAddress = require_instrumentation_trust.isPrivateAddress;
|
|
32
37
|
exports.isTelemetryInitialized = require_instrumentation_core.isTelemetryInitialized;
|
|
33
38
|
exports.isTracingEnabled = require_instrumentation_http.isTracingEnabled;
|
|
39
|
+
exports.isTrustedOrigin = require_instrumentation_trust.isTrustedOrigin;
|
|
34
40
|
exports.recordException = require_instrumentation_tracing.recordException;
|
|
35
41
|
exports.setGlobalLogProcessor = require_instrumentation_core.setGlobalLogProcessor;
|
|
36
42
|
exports.setGlobalSpanProcessor = require_instrumentation_core.setGlobalSpanProcessor;
|
|
@@ -39,6 +45,8 @@ exports.setupTelemetry = require_instrumentation_setup.setupTelemetry;
|
|
|
39
45
|
exports.shutdownTelemetry = require_instrumentation_setup.shutdownTelemetry;
|
|
40
46
|
exports.telemetryMiddleware = require_instrumentation_middleware.telemetryMiddleware;
|
|
41
47
|
exports.toOtelAttributes = require_instrumentation_http.toOtelAttributes;
|
|
48
|
+
exports.traceSampler = require_instrumentation_sampler.traceSampler;
|
|
49
|
+
exports.traceSamplerFromEnv = require_instrumentation_sampler.traceSamplerFromEnv;
|
|
42
50
|
exports.withChildSpan = require_instrumentation_http.withChildSpan;
|
|
43
51
|
exports.withEventContext = require_instrumentation_middleware.withEventContext;
|
|
44
52
|
exports.withHonoSpanContext = require_instrumentation_hono.withHonoSpanContext;
|
|
@@ -2,8 +2,10 @@ import { SpanProcessorOptions, SpanProcessorStrategy } from "../adapters/types.c
|
|
|
2
2
|
import { NoopSpanProcessor, createSpanProcessor, enableTelemetryDebug, flushTelemetry, getRecommendedStrategy, isTelemetryInitialized, setGlobalLogProcessor, setGlobalSpanProcessor, withTelemetryFlush } from "./core.cjs";
|
|
3
3
|
import { addSpanEvent, createSpan, getActiveSpan, getSpanId, getTraceId, recordException, setSpanAttributes, withSpan, withSpanSync } from "./tracing.cjs";
|
|
4
4
|
import { HonoTelemetryMiddlewareOptions, getSpanFromContext, getTraceContextFromHono, honoTelemetryMiddleware, withHonoSpanContext } from "./hono.cjs";
|
|
5
|
-
import { EndpointAttributes, HttpRequestAttributes, HttpResponseAttributes, HttpSpanAttributes, UserAttributes, createChildSpan, createHttpServerSpan, endHttpSpan, extractTraceContext, getConstructsTracer, injectTraceContext, isTracingEnabled, toOtelAttributes, withChildSpan, withHttpSpan } from "./http.cjs";
|
|
5
|
+
import { EndpointAttributes, HttpRequestAttributes, HttpResponseAttributes, HttpSpanAttributes, IncomingTraceContext, UserAttributes, createChildSpan, createHttpServerSpan, endHttpSpan, extractTraceContext, getConstructsTracer, incomingTraceContext, injectTraceContext, isTracingEnabled, toOtelAttributes, withChildSpan, withHttpSpan } from "./http.cjs";
|
|
6
6
|
import { TelemetryMiddlewareOptions, getContextFromEvent, getSpanFromEvent, telemetryMiddleware, withEventContext } from "./middleware.cjs";
|
|
7
7
|
import { OTelTelemetry, OTelTelemetryOptions } from "./otel.cjs";
|
|
8
|
+
import { traceSampler, traceSamplerFromEnv } from "./sampler.cjs";
|
|
8
9
|
import { InvalidSampleRatio, TelemetryOptions, setupTelemetry, shutdownTelemetry } from "./setup.cjs";
|
|
9
|
-
|
|
10
|
+
import { IncomingRequestFacts, isInternalCaller, isPrivateAddress, isTrustedOrigin } from "./trust.cjs";
|
|
11
|
+
export { type EndpointAttributes, type HonoTelemetryMiddlewareOptions, type HttpRequestAttributes, type HttpResponseAttributes, type HttpSpanAttributes, type IncomingRequestFacts, type IncomingTraceContext, InvalidSampleRatio, NoopSpanProcessor, OTelTelemetry, type OTelTelemetryOptions, type SpanProcessorOptions, type SpanProcessorStrategy, type TelemetryMiddlewareOptions, type TelemetryOptions, type UserAttributes, addSpanEvent, createChildSpan, createHttpServerSpan, createSpan, createSpanProcessor, enableTelemetryDebug, endHttpSpan, extractTraceContext, flushTelemetry, getActiveSpan, getConstructsTracer, getContextFromEvent, getRecommendedStrategy, getSpanFromContext, getSpanFromEvent, getSpanId, getTraceContextFromHono, getTraceId, honoTelemetryMiddleware, incomingTraceContext, injectTraceContext, isInternalCaller, isPrivateAddress, isTelemetryInitialized, isTracingEnabled, isTrustedOrigin, recordException, setGlobalLogProcessor, setGlobalSpanProcessor, setSpanAttributes, setupTelemetry, shutdownTelemetry, telemetryMiddleware, toOtelAttributes, traceSampler, traceSamplerFromEnv, withChildSpan, withEventContext, withHonoSpanContext, withHttpSpan, withSpan, withSpanSync, withTelemetryFlush };
|
|
@@ -2,8 +2,10 @@ import { SpanProcessorOptions, SpanProcessorStrategy } from "../adapters/types.m
|
|
|
2
2
|
import { NoopSpanProcessor, createSpanProcessor, enableTelemetryDebug, flushTelemetry, getRecommendedStrategy, isTelemetryInitialized, setGlobalLogProcessor, setGlobalSpanProcessor, withTelemetryFlush } from "./core.mjs";
|
|
3
3
|
import { addSpanEvent, createSpan, getActiveSpan, getSpanId, getTraceId, recordException, setSpanAttributes, withSpan, withSpanSync } from "./tracing.mjs";
|
|
4
4
|
import { HonoTelemetryMiddlewareOptions, getSpanFromContext, getTraceContextFromHono, honoTelemetryMiddleware, withHonoSpanContext } from "./hono.mjs";
|
|
5
|
-
import { EndpointAttributes, HttpRequestAttributes, HttpResponseAttributes, HttpSpanAttributes, UserAttributes, createChildSpan, createHttpServerSpan, endHttpSpan, extractTraceContext, getConstructsTracer, injectTraceContext, isTracingEnabled, toOtelAttributes, withChildSpan, withHttpSpan } from "./http.mjs";
|
|
5
|
+
import { EndpointAttributes, HttpRequestAttributes, HttpResponseAttributes, HttpSpanAttributes, IncomingTraceContext, UserAttributes, createChildSpan, createHttpServerSpan, endHttpSpan, extractTraceContext, getConstructsTracer, incomingTraceContext, injectTraceContext, isTracingEnabled, toOtelAttributes, withChildSpan, withHttpSpan } from "./http.mjs";
|
|
6
6
|
import { TelemetryMiddlewareOptions, getContextFromEvent, getSpanFromEvent, telemetryMiddleware, withEventContext } from "./middleware.mjs";
|
|
7
7
|
import { OTelTelemetry, OTelTelemetryOptions } from "./otel.mjs";
|
|
8
|
+
import { traceSampler, traceSamplerFromEnv } from "./sampler.mjs";
|
|
8
9
|
import { InvalidSampleRatio, TelemetryOptions, setupTelemetry, shutdownTelemetry } from "./setup.mjs";
|
|
9
|
-
|
|
10
|
+
import { IncomingRequestFacts, isInternalCaller, isPrivateAddress, isTrustedOrigin } from "./trust.mjs";
|
|
11
|
+
export { type EndpointAttributes, type HonoTelemetryMiddlewareOptions, type HttpRequestAttributes, type HttpResponseAttributes, type HttpSpanAttributes, type IncomingRequestFacts, type IncomingTraceContext, InvalidSampleRatio, NoopSpanProcessor, OTelTelemetry, type OTelTelemetryOptions, type SpanProcessorOptions, type SpanProcessorStrategy, type TelemetryMiddlewareOptions, type TelemetryOptions, type UserAttributes, addSpanEvent, createChildSpan, createHttpServerSpan, createSpan, createSpanProcessor, enableTelemetryDebug, endHttpSpan, extractTraceContext, flushTelemetry, getActiveSpan, getConstructsTracer, getContextFromEvent, getRecommendedStrategy, getSpanFromContext, getSpanFromEvent, getSpanId, getTraceContextFromHono, getTraceId, honoTelemetryMiddleware, incomingTraceContext, injectTraceContext, isInternalCaller, isPrivateAddress, isTelemetryInitialized, isTracingEnabled, isTrustedOrigin, recordException, setGlobalLogProcessor, setGlobalSpanProcessor, setSpanAttributes, setupTelemetry, shutdownTelemetry, telemetryMiddleware, toOtelAttributes, traceSampler, traceSamplerFromEnv, withChildSpan, withEventContext, withHonoSpanContext, withHttpSpan, withSpan, withSpanSync, withTelemetryFlush };
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { NoopSpanProcessor, createSpanProcessor, enableTelemetryDebug, flushTelemetry, getRecommendedStrategy, isTelemetryInitialized, setGlobalLogProcessor, setGlobalSpanProcessor, withTelemetryFlush } from "./core.mjs";
|
|
2
2
|
import { addSpanEvent, createSpan, getActiveSpan, getSpanId, getTraceId, recordException, setSpanAttributes, withSpan, withSpanSync } from "./tracing.mjs";
|
|
3
|
-
import { createChildSpan, createHttpServerSpan, endHttpSpan, extractTraceContext, getConstructsTracer, injectTraceContext, isTracingEnabled, toOtelAttributes, withChildSpan, withHttpSpan } from "./http.mjs";
|
|
3
|
+
import { createChildSpan, createHttpServerSpan, endHttpSpan, extractTraceContext, getConstructsTracer, incomingTraceContext, injectTraceContext, isTracingEnabled, toOtelAttributes, withChildSpan, withHttpSpan } from "./http.mjs";
|
|
4
|
+
import { isInternalCaller, isPrivateAddress, isTrustedOrigin } from "./trust.mjs";
|
|
4
5
|
import { getSpanFromContext, getTraceContextFromHono, honoTelemetryMiddleware, withHonoSpanContext } from "./hono.mjs";
|
|
5
6
|
import { getContextFromEvent, getSpanFromEvent, telemetryMiddleware, withEventContext } from "./middleware.mjs";
|
|
6
7
|
import { OTelTelemetry } from "./otel.mjs";
|
|
8
|
+
import { traceSampler, traceSamplerFromEnv } from "./sampler.mjs";
|
|
7
9
|
import { InvalidSampleRatio, setupTelemetry, shutdownTelemetry } from "./setup.mjs";
|
|
8
|
-
export { InvalidSampleRatio, NoopSpanProcessor, OTelTelemetry, addSpanEvent, createChildSpan, createHttpServerSpan, createSpan, createSpanProcessor, enableTelemetryDebug, endHttpSpan, extractTraceContext, flushTelemetry, getActiveSpan, getConstructsTracer, getContextFromEvent, getRecommendedStrategy, getSpanFromContext, getSpanFromEvent, getSpanId, getTraceContextFromHono, getTraceId, honoTelemetryMiddleware, injectTraceContext, isTelemetryInitialized, isTracingEnabled, recordException, setGlobalLogProcessor, setGlobalSpanProcessor, setSpanAttributes, setupTelemetry, shutdownTelemetry, telemetryMiddleware, toOtelAttributes, withChildSpan, withEventContext, withHonoSpanContext, withHttpSpan, withSpan, withSpanSync, withTelemetryFlush };
|
|
10
|
+
export { InvalidSampleRatio, NoopSpanProcessor, OTelTelemetry, addSpanEvent, createChildSpan, createHttpServerSpan, createSpan, createSpanProcessor, enableTelemetryDebug, endHttpSpan, extractTraceContext, flushTelemetry, getActiveSpan, getConstructsTracer, getContextFromEvent, getRecommendedStrategy, getSpanFromContext, getSpanFromEvent, getSpanId, getTraceContextFromHono, getTraceId, honoTelemetryMiddleware, incomingTraceContext, injectTraceContext, isInternalCaller, isPrivateAddress, isTelemetryInitialized, isTracingEnabled, isTrustedOrigin, recordException, setGlobalLogProcessor, setGlobalSpanProcessor, setSpanAttributes, setupTelemetry, shutdownTelemetry, telemetryMiddleware, toOtelAttributes, traceSampler, traceSamplerFromEnv, withChildSpan, withEventContext, withHonoSpanContext, withHttpSpan, withSpan, withSpanSync, withTelemetryFlush };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_instrumentation_http = require("./http.cjs");
|
|
3
|
+
const require_instrumentation_trust = require("./trust.cjs");
|
|
3
4
|
let _opentelemetry_api = require("@opentelemetry/api");
|
|
4
5
|
//#region src/instrumentation/middleware.ts
|
|
5
6
|
const SPAN_SYMBOL = Symbol("telemetry.span");
|
|
@@ -35,9 +36,10 @@ function telemetryMiddleware(options = {}) {
|
|
|
35
36
|
const event = request.event;
|
|
36
37
|
const lambdaContext = request.context;
|
|
37
38
|
const headers = normalizeHeaders(event.headers || {});
|
|
38
|
-
const
|
|
39
|
+
const trusted = require_instrumentation_trust.isTrustedOrigin(typeof headers.origin === "string" ? headers.origin : void 0, options.trustedOrigins) || Boolean(options.trustRequest?.(event));
|
|
40
|
+
const { parent: parentContext, links } = require_instrumentation_http.incomingTraceContext(headers, trusted);
|
|
39
41
|
const attrs = buildSpanAttributes(event, lambdaContext, options);
|
|
40
|
-
const span = require_instrumentation_http.createHttpServerSpan(attrs, parentContext);
|
|
42
|
+
const span = require_instrumentation_http.createHttpServerSpan(attrs, parentContext, links);
|
|
41
43
|
event[SPAN_SYMBOL] = span;
|
|
42
44
|
event[CONTEXT_SYMBOL] = _opentelemetry_api.trace.setSpan(parentContext, span);
|
|
43
45
|
span.setAttribute("faas.invocation_id", lambdaContext.awsRequestId);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"middleware.cjs","names":["extractTraceContext","createHttpServerSpan","trace","context"],"sources":["../../src/instrumentation/middleware.ts"],"sourcesContent":["/**\n * OpenTelemetry instrumentation middleware for @geekmidas/constructs\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 { MiddlewareObj } from '@middy/core';\nimport { context, type Span, trace } from '@opentelemetry/api';\nimport type { Context as LambdaContext } from 'aws-lambda';\nimport {\n\tcreateHttpServerSpan,\n\tendHttpSpan,\n\textractTraceContext,\n\ttype HttpSpanAttributes,\n} from './http';\n\n/**\n * Options for the telemetry middleware\n */\nexport interface TelemetryMiddlewareOptions {\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 endpoint name from event\n\t */\n\tgetEndpointName?: (event: any) => string | undefined;\n\n\t/**\n\t * Custom function to extract operation ID from event\n\t */\n\tgetOperationId?: (event: any) => string | undefined;\n\n\t/**\n\t * Custom function to extract user ID from event\n\t */\n\tgetUserId?: (event: any) => string | undefined;\n\n\t/**\n\t * Whether to skip tracing for this request\n\t */\n\tshouldSkip?: (event: any) => boolean;\n}\n\n// Symbol for storing span on event\nconst SPAN_SYMBOL = Symbol('telemetry.span');\nconst CONTEXT_SYMBOL = Symbol('telemetry.context');\n\n/**\n * Get the current span from the Lambda event\n */\nexport function getSpanFromEvent(event: any): Span | undefined {\n\treturn event?.[SPAN_SYMBOL];\n}\n\n/**\n * Get the current trace context from the Lambda event\n */\nexport function getContextFromEvent(event: any): any {\n\treturn event?.[CONTEXT_SYMBOL];\n}\n\n/**\n * Create a telemetry middleware for Lambda handlers\n *\n * @example\n * ```typescript\n * import { telemetryMiddleware } from '@geekmidas/telescope/instrumentation';\n *\n * const adaptor = new AmazonApiGatewayV2Endpoint(endpoint, {\n * telemetry: { middleware: telemetryMiddleware() },\n * });\n * ```\n */\nexport function telemetryMiddleware(\n\toptions: TelemetryMiddlewareOptions = {},\n): MiddlewareObj<any, any, Error, LambdaContext> {\n\treturn {\n\t\tbefore: async (request) => {\n\t\t\t// Skip if custom skip function returns true\n\t\t\tif (options.shouldSkip?.(request.event)) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst event = request.event;\n\t\t\tconst lambdaContext = request.context;\n\n\t\t\t// Extract trace context from headers\n\t\t\tconst headers = normalizeHeaders(event.headers || {});\n\t\t\tconst parentContext = extractTraceContext(headers);\n\n\t\t\t// Build span attributes from event\n\t\t\tconst attrs = buildSpanAttributes(event, lambdaContext, options);\n\n\t\t\t// Create the span\n\t\t\tconst span = createHttpServerSpan(attrs, parentContext);\n\n\t\t\t// Store span and context on event for access in handler\n\t\t\tevent[SPAN_SYMBOL] = span;\n\t\t\tevent[CONTEXT_SYMBOL] = trace.setSpan(parentContext, span);\n\n\t\t\t// Add Lambda-specific attributes\n\t\t\tspan.setAttribute('faas.invocation_id', lambdaContext.awsRequestId);\n\t\t\tspan.setAttribute('faas.name', lambdaContext.functionName);\n\n\t\t\tif (options.recordBody && event.body) {\n\t\t\t\tconst bodyStr =\n\t\t\t\t\ttypeof event.body === 'string'\n\t\t\t\t\t\t? event.body\n\t\t\t\t\t\t: JSON.stringify(event.body);\n\t\t\t\t// Truncate large bodies\n\t\t\t\tspan.setAttribute(\n\t\t\t\t\t'http.request.body',\n\t\t\t\t\tbodyStr.length > 4096 ? `${bodyStr.slice(0, 4096)}...` : bodyStr,\n\t\t\t\t);\n\t\t\t}\n\t\t},\n\n\t\tafter: async (request) => {\n\t\t\tconst span = getSpanFromEvent(request.event);\n\t\t\tif (!span) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst response = request.response;\n\t\t\tconst statusCode =\n\t\t\t\ttypeof response?.statusCode === 'number' ? response.statusCode : 200;\n\n\t\t\tif (options.recordResponseBody && response?.body) {\n\t\t\t\tconst bodyStr =\n\t\t\t\t\ttypeof response.body === 'string'\n\t\t\t\t\t\t? response.body\n\t\t\t\t\t\t: JSON.stringify(response.body);\n\t\t\t\tspan.setAttribute(\n\t\t\t\t\t'http.response.body',\n\t\t\t\t\tbodyStr.length > 4096 ? `${bodyStr.slice(0, 4096)}...` : bodyStr,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tendHttpSpan(span, { statusCode });\n\t\t},\n\n\t\tonError: async (request) => {\n\t\t\tconst span = getSpanFromEvent(request.event);\n\t\t\tif (!span) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst error = request.error;\n\t\t\tconst response = request.response;\n\n\t\t\t// Determine status code from response or error\n\t\t\tlet statusCode = 500;\n\t\t\tif (response?.statusCode) {\n\t\t\t\tstatusCode = response.statusCode;\n\t\t\t} else if (error && 'statusCode' in error) {\n\t\t\t\tstatusCode = (error as any).statusCode;\n\t\t\t}\n\n\t\t\tendHttpSpan(\n\t\t\t\tspan,\n\t\t\t\t{ statusCode },\n\t\t\t\terror instanceof Error ? error : new Error(String(error)),\n\t\t\t);\n\t\t},\n\t};\n}\n\n/**\n * Build span attributes from Lambda event\n */\nfunction buildSpanAttributes(\n\tevent: any,\n\tlambdaContext: LambdaContext,\n\toptions: TelemetryMiddlewareOptions,\n): HttpSpanAttributes {\n\t// Detect event type (API Gateway v1, v2, ALB, or direct invoke)\n\tconst isV2 =\n\t\t'requestContext' in event && 'http' in (event.requestContext || {});\n\tconst isV1 =\n\t\t'requestContext' in event &&\n\t\t'httpMethod' in event &&\n\t\t!('http' in (event.requestContext || {}));\n\n\tlet method = 'INVOKE';\n\tlet path = '/';\n\tlet route: string | undefined;\n\tlet host: string | undefined;\n\tlet scheme: 'http' | 'https' | undefined;\n\tlet userAgent: string | undefined;\n\tlet clientIp: string | undefined;\n\tlet requestId: string | undefined;\n\n\tif (isV2) {\n\t\t// API Gateway v2 (HTTP API)\n\t\tconst http = event.requestContext.http;\n\t\tmethod = http.method;\n\t\tpath = event.rawPath || http.path;\n\t\troute = event.routeKey?.replace(/^[A-Z]+\\s+/, ''); // Remove HTTP method prefix\n\t\thost = event.headers?.host;\n\t\tscheme = 'https';\n\t\tuserAgent = http.userAgent;\n\t\tclientIp = http.sourceIp;\n\t\trequestId = event.requestContext.requestId;\n\t} else if (isV1) {\n\t\t// API Gateway v1 (REST API)\n\t\tmethod = event.httpMethod;\n\t\tpath = event.path;\n\t\troute = event.resource;\n\t\thost = event.headers?.Host || event.headers?.host;\n\t\tscheme = 'https';\n\t\tuserAgent = event.headers?.['User-Agent'] || event.headers?.['user-agent'];\n\t\tclientIp = event.requestContext?.identity?.sourceIp;\n\t\trequestId = event.requestContext?.requestId;\n\t} else if ('httpMethod' in event) {\n\t\t// ALB event\n\t\tmethod = event.httpMethod;\n\t\tpath = event.path;\n\t\thost = event.headers?.host;\n\t\tuserAgent = event.headers?.['user-agent'] || event.headers?.['User-Agent'];\n\t\tclientIp = event.headers?.['x-forwarded-for']?.split(',')[0]?.trim();\n\t}\n\n\tconst attrs: HttpSpanAttributes = {\n\t\tmethod,\n\t\tpath,\n\t\troute,\n\t\thost,\n\t\tscheme,\n\t\tuserAgent,\n\t\tclientIp,\n\t\trequestId: requestId || lambdaContext.awsRequestId,\n\t};\n\n\t// Add endpoint metadata if extractors provided\n\tif (options.getEndpointName || options.getOperationId) {\n\t\tattrs.endpoint = {\n\t\t\tname: options.getEndpointName?.(event),\n\t\t\toperationId: options.getOperationId?.(event),\n\t\t};\n\t}\n\n\t// Add user metadata if extractor provided\n\tif (options.getUserId) {\n\t\tconst userId = options.getUserId(event);\n\t\tif (userId) {\n\t\t\tattrs.user = { userId };\n\t\t}\n\t}\n\n\treturn attrs;\n}\n\n/**\n * Normalize headers to lowercase keys\n */\nfunction normalizeHeaders(\n\theaders: Record<string, string | string[] | undefined>,\n): Record<string, string | string[] | undefined> {\n\tconst normalized: Record<string, string | string[] | undefined> = {};\n\tfor (const [key, value] of Object.entries(headers)) {\n\t\tnormalized[key.toLowerCase()] = value;\n\t}\n\treturn normalized;\n}\n\n/**\n * Run a function within the span context from a Lambda event\n *\n * @example\n * ```typescript\n * await withEventContext(event, async () => {\n * // Any spans created here will be children of the request span\n * await doSomething();\n * });\n * ```\n */\nexport async function withEventContext<T>(\n\tevent: any,\n\tfn: () => Promise<T>,\n): Promise<T> {\n\tconst ctx = getContextFromEvent(event);\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":"middleware.cjs","names":["isTrustedOrigin","incomingTraceContext","createHttpServerSpan","trace","context"],"sources":["../../src/instrumentation/middleware.ts"],"sourcesContent":["/**\n * OpenTelemetry instrumentation middleware for @geekmidas/constructs\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 { MiddlewareObj } from '@middy/core';\nimport { context, type Span, trace } from '@opentelemetry/api';\nimport type { Context as LambdaContext } from 'aws-lambda';\nimport {\n\tcreateHttpServerSpan,\n\tendHttpSpan,\n\ttype HttpSpanAttributes,\n\tincomingTraceContext,\n} from './http';\nimport { isTrustedOrigin } from './trust';\n\n/**\n * Options for the telemetry middleware\n */\nexport interface TelemetryMiddlewareOptions {\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 endpoint name from event\n\t */\n\tgetEndpointName?: (event: any) => string | undefined;\n\n\t/**\n\t * Custom function to extract operation ID from event\n\t */\n\tgetOperationId?: (event: any) => string | undefined;\n\n\t/**\n\t * Custom function to extract user ID from event\n\t */\n\tgetUserId?: (event: any) => string | undefined;\n\n\t/**\n\t * Whether to skip tracing for this request\n\t */\n\tshouldSkip?: (event: any) => boolean;\n\n\t/**\n\t * The origins whose `traceparent` is continued: the API's own sites, the\n\t * same list its CORS allows. Any other caller starts a new trace linked to\n\t * the one it claimed.\n\t */\n\ttrustedOrigins?: readonly string[];\n\n\t/**\n\t * Trust a request with no trusted origin anyway — a caller the function\n\t * can vouch for, such as one that passed IAM auth. A Lambda behind API\n\t * Gateway has no private network to tell an internal caller by, so none\n\t * is trusted unless this says so.\n\t */\n\ttrustRequest?: (event: any) => boolean;\n}\n\n// Symbol for storing span on event\nconst SPAN_SYMBOL = Symbol('telemetry.span');\nconst CONTEXT_SYMBOL = Symbol('telemetry.context');\n\n/**\n * Get the current span from the Lambda event\n */\nexport function getSpanFromEvent(event: any): Span | undefined {\n\treturn event?.[SPAN_SYMBOL];\n}\n\n/**\n * Get the current trace context from the Lambda event\n */\nexport function getContextFromEvent(event: any): any {\n\treturn event?.[CONTEXT_SYMBOL];\n}\n\n/**\n * Create a telemetry middleware for Lambda handlers\n *\n * @example\n * ```typescript\n * import { telemetryMiddleware } from '@geekmidas/telescope/instrumentation';\n *\n * const adaptor = new AmazonApiGatewayV2Endpoint(endpoint, {\n * telemetry: { middleware: telemetryMiddleware() },\n * });\n * ```\n */\nexport function telemetryMiddleware(\n\toptions: TelemetryMiddlewareOptions = {},\n): MiddlewareObj<any, any, Error, LambdaContext> {\n\treturn {\n\t\tbefore: async (request) => {\n\t\t\t// Skip if custom skip function returns true\n\t\t\tif (options.shouldSkip?.(request.event)) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst event = request.event;\n\t\t\tconst lambdaContext = request.context;\n\n\t\t\t// Extract trace context from headers\n\t\t\tconst headers = normalizeHeaders(event.headers || {});\n\t\t\tconst trusted =\n\t\t\t\tisTrustedOrigin(\n\t\t\t\t\ttypeof headers.origin === 'string' ? headers.origin : undefined,\n\t\t\t\t\toptions.trustedOrigins,\n\t\t\t\t) || Boolean(options.trustRequest?.(event));\n\t\t\tconst { parent: parentContext, links } = incomingTraceContext(\n\t\t\t\theaders,\n\t\t\t\ttrusted,\n\t\t\t);\n\n\t\t\t// Build span attributes from event\n\t\t\tconst attrs = buildSpanAttributes(event, lambdaContext, options);\n\n\t\t\t// Create the span\n\t\t\tconst span = createHttpServerSpan(attrs, parentContext, links);\n\n\t\t\t// Store span and context on event for access in handler\n\t\t\tevent[SPAN_SYMBOL] = span;\n\t\t\tevent[CONTEXT_SYMBOL] = trace.setSpan(parentContext, span);\n\n\t\t\t// Add Lambda-specific attributes\n\t\t\tspan.setAttribute('faas.invocation_id', lambdaContext.awsRequestId);\n\t\t\tspan.setAttribute('faas.name', lambdaContext.functionName);\n\n\t\t\tif (options.recordBody && event.body) {\n\t\t\t\tconst bodyStr =\n\t\t\t\t\ttypeof event.body === 'string'\n\t\t\t\t\t\t? event.body\n\t\t\t\t\t\t: JSON.stringify(event.body);\n\t\t\t\t// Truncate large bodies\n\t\t\t\tspan.setAttribute(\n\t\t\t\t\t'http.request.body',\n\t\t\t\t\tbodyStr.length > 4096 ? `${bodyStr.slice(0, 4096)}...` : bodyStr,\n\t\t\t\t);\n\t\t\t}\n\t\t},\n\n\t\tafter: async (request) => {\n\t\t\tconst span = getSpanFromEvent(request.event);\n\t\t\tif (!span) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst response = request.response;\n\t\t\tconst statusCode =\n\t\t\t\ttypeof response?.statusCode === 'number' ? response.statusCode : 200;\n\n\t\t\tif (options.recordResponseBody && response?.body) {\n\t\t\t\tconst bodyStr =\n\t\t\t\t\ttypeof response.body === 'string'\n\t\t\t\t\t\t? response.body\n\t\t\t\t\t\t: JSON.stringify(response.body);\n\t\t\t\tspan.setAttribute(\n\t\t\t\t\t'http.response.body',\n\t\t\t\t\tbodyStr.length > 4096 ? `${bodyStr.slice(0, 4096)}...` : bodyStr,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tendHttpSpan(span, { statusCode });\n\t\t},\n\n\t\tonError: async (request) => {\n\t\t\tconst span = getSpanFromEvent(request.event);\n\t\t\tif (!span) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst error = request.error;\n\t\t\tconst response = request.response;\n\n\t\t\t// Determine status code from response or error\n\t\t\tlet statusCode = 500;\n\t\t\tif (response?.statusCode) {\n\t\t\t\tstatusCode = response.statusCode;\n\t\t\t} else if (error && 'statusCode' in error) {\n\t\t\t\tstatusCode = (error as any).statusCode;\n\t\t\t}\n\n\t\t\tendHttpSpan(\n\t\t\t\tspan,\n\t\t\t\t{ statusCode },\n\t\t\t\terror instanceof Error ? error : new Error(String(error)),\n\t\t\t);\n\t\t},\n\t};\n}\n\n/**\n * Build span attributes from Lambda event\n */\nfunction buildSpanAttributes(\n\tevent: any,\n\tlambdaContext: LambdaContext,\n\toptions: TelemetryMiddlewareOptions,\n): HttpSpanAttributes {\n\t// Detect event type (API Gateway v1, v2, ALB, or direct invoke)\n\tconst isV2 =\n\t\t'requestContext' in event && 'http' in (event.requestContext || {});\n\tconst isV1 =\n\t\t'requestContext' in event &&\n\t\t'httpMethod' in event &&\n\t\t!('http' in (event.requestContext || {}));\n\n\tlet method = 'INVOKE';\n\tlet path = '/';\n\tlet route: string | undefined;\n\tlet host: string | undefined;\n\tlet scheme: 'http' | 'https' | undefined;\n\tlet userAgent: string | undefined;\n\tlet clientIp: string | undefined;\n\tlet requestId: string | undefined;\n\n\tif (isV2) {\n\t\t// API Gateway v2 (HTTP API)\n\t\tconst http = event.requestContext.http;\n\t\tmethod = http.method;\n\t\tpath = event.rawPath || http.path;\n\t\troute = event.routeKey?.replace(/^[A-Z]+\\s+/, ''); // Remove HTTP method prefix\n\t\thost = event.headers?.host;\n\t\tscheme = 'https';\n\t\tuserAgent = http.userAgent;\n\t\tclientIp = http.sourceIp;\n\t\trequestId = event.requestContext.requestId;\n\t} else if (isV1) {\n\t\t// API Gateway v1 (REST API)\n\t\tmethod = event.httpMethod;\n\t\tpath = event.path;\n\t\troute = event.resource;\n\t\thost = event.headers?.Host || event.headers?.host;\n\t\tscheme = 'https';\n\t\tuserAgent = event.headers?.['User-Agent'] || event.headers?.['user-agent'];\n\t\tclientIp = event.requestContext?.identity?.sourceIp;\n\t\trequestId = event.requestContext?.requestId;\n\t} else if ('httpMethod' in event) {\n\t\t// ALB event\n\t\tmethod = event.httpMethod;\n\t\tpath = event.path;\n\t\thost = event.headers?.host;\n\t\tuserAgent = event.headers?.['user-agent'] || event.headers?.['User-Agent'];\n\t\tclientIp = event.headers?.['x-forwarded-for']?.split(',')[0]?.trim();\n\t}\n\n\tconst attrs: HttpSpanAttributes = {\n\t\tmethod,\n\t\tpath,\n\t\troute,\n\t\thost,\n\t\tscheme,\n\t\tuserAgent,\n\t\tclientIp,\n\t\trequestId: requestId || lambdaContext.awsRequestId,\n\t};\n\n\t// Add endpoint metadata if extractors provided\n\tif (options.getEndpointName || options.getOperationId) {\n\t\tattrs.endpoint = {\n\t\t\tname: options.getEndpointName?.(event),\n\t\t\toperationId: options.getOperationId?.(event),\n\t\t};\n\t}\n\n\t// Add user metadata if extractor provided\n\tif (options.getUserId) {\n\t\tconst userId = options.getUserId(event);\n\t\tif (userId) {\n\t\t\tattrs.user = { userId };\n\t\t}\n\t}\n\n\treturn attrs;\n}\n\n/**\n * Normalize headers to lowercase keys\n */\nfunction normalizeHeaders(\n\theaders: Record<string, string | string[] | undefined>,\n): Record<string, string | string[] | undefined> {\n\tconst normalized: Record<string, string | string[] | undefined> = {};\n\tfor (const [key, value] of Object.entries(headers)) {\n\t\tnormalized[key.toLowerCase()] = value;\n\t}\n\treturn normalized;\n}\n\n/**\n * Run a function within the span context from a Lambda event\n *\n * @example\n * ```typescript\n * await withEventContext(event, async () => {\n * // Any spans created here will be children of the request span\n * await doSomething();\n * });\n * ```\n */\nexport async function withEventContext<T>(\n\tevent: any,\n\tfn: () => Promise<T>,\n): Promise<T> {\n\tconst ctx = getContextFromEvent(event);\n\tif (!ctx) {\n\t\treturn fn();\n\t}\n\treturn context.with(ctx, fn);\n}\n"],"mappings":"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"}
|
|
@@ -32,6 +32,19 @@ export interface TelemetryMiddlewareOptions {
|
|
|
32
32
|
* Whether to skip tracing for this request
|
|
33
33
|
*/
|
|
34
34
|
shouldSkip?: (event: any) => boolean;
|
|
35
|
+
/**
|
|
36
|
+
* The origins whose `traceparent` is continued: the API's own sites, the
|
|
37
|
+
* same list its CORS allows. Any other caller starts a new trace linked to
|
|
38
|
+
* the one it claimed.
|
|
39
|
+
*/
|
|
40
|
+
trustedOrigins?: readonly string[];
|
|
41
|
+
/**
|
|
42
|
+
* Trust a request with no trusted origin anyway — a caller the function
|
|
43
|
+
* can vouch for, such as one that passed IAM auth. A Lambda behind API
|
|
44
|
+
* Gateway has no private network to tell an internal caller by, so none
|
|
45
|
+
* is trusted unless this says so.
|
|
46
|
+
*/
|
|
47
|
+
trustRequest?: (event: any) => boolean;
|
|
35
48
|
}
|
|
36
49
|
/**
|
|
37
50
|
* Get the current span from the Lambda event
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"middleware.d.cts","names":["LambdaContext"],"sources":["../../src/instrumentation/middleware.ts"],"mappings":";;;;;;;
|
|
1
|
+
{"version":3,"file":"middleware.d.cts","names":["LambdaContext"],"sources":["../../src/instrumentation/middleware.ts"],"mappings":";;;;;;;iBAqBiB;;;;;EAKhB;;;;;EAMA;;;;EAKA,mBAAmB;;;;EAKnB,kBAAkB;;;;EAKlB,aAAa;;;;EAKb,cAAc;;;;;;EAOd;;;;;;;EAQA,gBAAgB;;;;;wBAUD,iBAAiB,aAAa;;;;wBAO9B,oBAAoB;;;;;;;;;;;;;wBAgBpB,oBACf,UAAS,6BACP,wBAAwB,OAAOA;;;;;;;;;;;;wBAiNZ,iBAAiB,GACtC,YACA,UAAU,QAAQ,KAChB,QAAQ"}
|
|
@@ -32,6 +32,19 @@ export interface TelemetryMiddlewareOptions {
|
|
|
32
32
|
* Whether to skip tracing for this request
|
|
33
33
|
*/
|
|
34
34
|
shouldSkip?: (event: any) => boolean;
|
|
35
|
+
/**
|
|
36
|
+
* The origins whose `traceparent` is continued: the API's own sites, the
|
|
37
|
+
* same list its CORS allows. Any other caller starts a new trace linked to
|
|
38
|
+
* the one it claimed.
|
|
39
|
+
*/
|
|
40
|
+
trustedOrigins?: readonly string[];
|
|
41
|
+
/**
|
|
42
|
+
* Trust a request with no trusted origin anyway — a caller the function
|
|
43
|
+
* can vouch for, such as one that passed IAM auth. A Lambda behind API
|
|
44
|
+
* Gateway has no private network to tell an internal caller by, so none
|
|
45
|
+
* is trusted unless this says so.
|
|
46
|
+
*/
|
|
47
|
+
trustRequest?: (event: any) => boolean;
|
|
35
48
|
}
|
|
36
49
|
/**
|
|
37
50
|
* Get the current span from the Lambda event
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"middleware.d.mts","names":["LambdaContext"],"sources":["../../src/instrumentation/middleware.ts"],"mappings":";;;;;;;
|
|
1
|
+
{"version":3,"file":"middleware.d.mts","names":["LambdaContext"],"sources":["../../src/instrumentation/middleware.ts"],"mappings":";;;;;;;iBAqBiB;;;;;EAKhB;;;;;EAMA;;;;EAKA,mBAAmB;;;;EAKnB,kBAAkB;;;;EAKlB,aAAa;;;;EAKb,cAAc;;;;;;EAOd;;;;;;;EAQA,gBAAgB;;;;;wBAUD,iBAAiB,aAAa;;;;wBAO9B,oBAAoB;;;;;;;;;;;;;wBAgBpB,oBACf,UAAS,6BACP,wBAAwB,OAAOA;;;;;;;;;;;;wBAiNZ,iBAAiB,GACtC,YACA,UAAU,QAAQ,KAChB,QAAQ"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { createHttpServerSpan, endHttpSpan,
|
|
1
|
+
import { createHttpServerSpan, endHttpSpan, incomingTraceContext } from "./http.mjs";
|
|
2
|
+
import { isTrustedOrigin } from "./trust.mjs";
|
|
2
3
|
import { context, trace } from "@opentelemetry/api";
|
|
3
4
|
//#region src/instrumentation/middleware.ts
|
|
4
5
|
const SPAN_SYMBOL = Symbol("telemetry.span");
|
|
@@ -34,9 +35,10 @@ function telemetryMiddleware(options = {}) {
|
|
|
34
35
|
const event = request.event;
|
|
35
36
|
const lambdaContext = request.context;
|
|
36
37
|
const headers = normalizeHeaders(event.headers || {});
|
|
37
|
-
const
|
|
38
|
+
const trusted = isTrustedOrigin(typeof headers.origin === "string" ? headers.origin : void 0, options.trustedOrigins) || Boolean(options.trustRequest?.(event));
|
|
39
|
+
const { parent: parentContext, links } = incomingTraceContext(headers, trusted);
|
|
38
40
|
const attrs = buildSpanAttributes(event, lambdaContext, options);
|
|
39
|
-
const span = createHttpServerSpan(attrs, parentContext);
|
|
41
|
+
const span = createHttpServerSpan(attrs, parentContext, links);
|
|
40
42
|
event[SPAN_SYMBOL] = span;
|
|
41
43
|
event[CONTEXT_SYMBOL] = trace.setSpan(parentContext, span);
|
|
42
44
|
span.setAttribute("faas.invocation_id", lambdaContext.awsRequestId);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"middleware.mjs","names":[],"sources":["../../src/instrumentation/middleware.ts"],"sourcesContent":["/**\n * OpenTelemetry instrumentation middleware for @geekmidas/constructs\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 { MiddlewareObj } from '@middy/core';\nimport { context, type Span, trace } from '@opentelemetry/api';\nimport type { Context as LambdaContext } from 'aws-lambda';\nimport {\n\tcreateHttpServerSpan,\n\tendHttpSpan,\n\textractTraceContext,\n\ttype HttpSpanAttributes,\n} from './http';\n\n/**\n * Options for the telemetry middleware\n */\nexport interface TelemetryMiddlewareOptions {\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 endpoint name from event\n\t */\n\tgetEndpointName?: (event: any) => string | undefined;\n\n\t/**\n\t * Custom function to extract operation ID from event\n\t */\n\tgetOperationId?: (event: any) => string | undefined;\n\n\t/**\n\t * Custom function to extract user ID from event\n\t */\n\tgetUserId?: (event: any) => string | undefined;\n\n\t/**\n\t * Whether to skip tracing for this request\n\t */\n\tshouldSkip?: (event: any) => boolean;\n}\n\n// Symbol for storing span on event\nconst SPAN_SYMBOL = Symbol('telemetry.span');\nconst CONTEXT_SYMBOL = Symbol('telemetry.context');\n\n/**\n * Get the current span from the Lambda event\n */\nexport function getSpanFromEvent(event: any): Span | undefined {\n\treturn event?.[SPAN_SYMBOL];\n}\n\n/**\n * Get the current trace context from the Lambda event\n */\nexport function getContextFromEvent(event: any): any {\n\treturn event?.[CONTEXT_SYMBOL];\n}\n\n/**\n * Create a telemetry middleware for Lambda handlers\n *\n * @example\n * ```typescript\n * import { telemetryMiddleware } from '@geekmidas/telescope/instrumentation';\n *\n * const adaptor = new AmazonApiGatewayV2Endpoint(endpoint, {\n * telemetry: { middleware: telemetryMiddleware() },\n * });\n * ```\n */\nexport function telemetryMiddleware(\n\toptions: TelemetryMiddlewareOptions = {},\n): MiddlewareObj<any, any, Error, LambdaContext> {\n\treturn {\n\t\tbefore: async (request) => {\n\t\t\t// Skip if custom skip function returns true\n\t\t\tif (options.shouldSkip?.(request.event)) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst event = request.event;\n\t\t\tconst lambdaContext = request.context;\n\n\t\t\t// Extract trace context from headers\n\t\t\tconst headers = normalizeHeaders(event.headers || {});\n\t\t\tconst parentContext = extractTraceContext(headers);\n\n\t\t\t// Build span attributes from event\n\t\t\tconst attrs = buildSpanAttributes(event, lambdaContext, options);\n\n\t\t\t// Create the span\n\t\t\tconst span = createHttpServerSpan(attrs, parentContext);\n\n\t\t\t// Store span and context on event for access in handler\n\t\t\tevent[SPAN_SYMBOL] = span;\n\t\t\tevent[CONTEXT_SYMBOL] = trace.setSpan(parentContext, span);\n\n\t\t\t// Add Lambda-specific attributes\n\t\t\tspan.setAttribute('faas.invocation_id', lambdaContext.awsRequestId);\n\t\t\tspan.setAttribute('faas.name', lambdaContext.functionName);\n\n\t\t\tif (options.recordBody && event.body) {\n\t\t\t\tconst bodyStr =\n\t\t\t\t\ttypeof event.body === 'string'\n\t\t\t\t\t\t? event.body\n\t\t\t\t\t\t: JSON.stringify(event.body);\n\t\t\t\t// Truncate large bodies\n\t\t\t\tspan.setAttribute(\n\t\t\t\t\t'http.request.body',\n\t\t\t\t\tbodyStr.length > 4096 ? `${bodyStr.slice(0, 4096)}...` : bodyStr,\n\t\t\t\t);\n\t\t\t}\n\t\t},\n\n\t\tafter: async (request) => {\n\t\t\tconst span = getSpanFromEvent(request.event);\n\t\t\tif (!span) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst response = request.response;\n\t\t\tconst statusCode =\n\t\t\t\ttypeof response?.statusCode === 'number' ? response.statusCode : 200;\n\n\t\t\tif (options.recordResponseBody && response?.body) {\n\t\t\t\tconst bodyStr =\n\t\t\t\t\ttypeof response.body === 'string'\n\t\t\t\t\t\t? response.body\n\t\t\t\t\t\t: JSON.stringify(response.body);\n\t\t\t\tspan.setAttribute(\n\t\t\t\t\t'http.response.body',\n\t\t\t\t\tbodyStr.length > 4096 ? `${bodyStr.slice(0, 4096)}...` : bodyStr,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tendHttpSpan(span, { statusCode });\n\t\t},\n\n\t\tonError: async (request) => {\n\t\t\tconst span = getSpanFromEvent(request.event);\n\t\t\tif (!span) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst error = request.error;\n\t\t\tconst response = request.response;\n\n\t\t\t// Determine status code from response or error\n\t\t\tlet statusCode = 500;\n\t\t\tif (response?.statusCode) {\n\t\t\t\tstatusCode = response.statusCode;\n\t\t\t} else if (error && 'statusCode' in error) {\n\t\t\t\tstatusCode = (error as any).statusCode;\n\t\t\t}\n\n\t\t\tendHttpSpan(\n\t\t\t\tspan,\n\t\t\t\t{ statusCode },\n\t\t\t\terror instanceof Error ? error : new Error(String(error)),\n\t\t\t);\n\t\t},\n\t};\n}\n\n/**\n * Build span attributes from Lambda event\n */\nfunction buildSpanAttributes(\n\tevent: any,\n\tlambdaContext: LambdaContext,\n\toptions: TelemetryMiddlewareOptions,\n): HttpSpanAttributes {\n\t// Detect event type (API Gateway v1, v2, ALB, or direct invoke)\n\tconst isV2 =\n\t\t'requestContext' in event && 'http' in (event.requestContext || {});\n\tconst isV1 =\n\t\t'requestContext' in event &&\n\t\t'httpMethod' in event &&\n\t\t!('http' in (event.requestContext || {}));\n\n\tlet method = 'INVOKE';\n\tlet path = '/';\n\tlet route: string | undefined;\n\tlet host: string | undefined;\n\tlet scheme: 'http' | 'https' | undefined;\n\tlet userAgent: string | undefined;\n\tlet clientIp: string | undefined;\n\tlet requestId: string | undefined;\n\n\tif (isV2) {\n\t\t// API Gateway v2 (HTTP API)\n\t\tconst http = event.requestContext.http;\n\t\tmethod = http.method;\n\t\tpath = event.rawPath || http.path;\n\t\troute = event.routeKey?.replace(/^[A-Z]+\\s+/, ''); // Remove HTTP method prefix\n\t\thost = event.headers?.host;\n\t\tscheme = 'https';\n\t\tuserAgent = http.userAgent;\n\t\tclientIp = http.sourceIp;\n\t\trequestId = event.requestContext.requestId;\n\t} else if (isV1) {\n\t\t// API Gateway v1 (REST API)\n\t\tmethod = event.httpMethod;\n\t\tpath = event.path;\n\t\troute = event.resource;\n\t\thost = event.headers?.Host || event.headers?.host;\n\t\tscheme = 'https';\n\t\tuserAgent = event.headers?.['User-Agent'] || event.headers?.['user-agent'];\n\t\tclientIp = event.requestContext?.identity?.sourceIp;\n\t\trequestId = event.requestContext?.requestId;\n\t} else if ('httpMethod' in event) {\n\t\t// ALB event\n\t\tmethod = event.httpMethod;\n\t\tpath = event.path;\n\t\thost = event.headers?.host;\n\t\tuserAgent = event.headers?.['user-agent'] || event.headers?.['User-Agent'];\n\t\tclientIp = event.headers?.['x-forwarded-for']?.split(',')[0]?.trim();\n\t}\n\n\tconst attrs: HttpSpanAttributes = {\n\t\tmethod,\n\t\tpath,\n\t\troute,\n\t\thost,\n\t\tscheme,\n\t\tuserAgent,\n\t\tclientIp,\n\t\trequestId: requestId || lambdaContext.awsRequestId,\n\t};\n\n\t// Add endpoint metadata if extractors provided\n\tif (options.getEndpointName || options.getOperationId) {\n\t\tattrs.endpoint = {\n\t\t\tname: options.getEndpointName?.(event),\n\t\t\toperationId: options.getOperationId?.(event),\n\t\t};\n\t}\n\n\t// Add user metadata if extractor provided\n\tif (options.getUserId) {\n\t\tconst userId = options.getUserId(event);\n\t\tif (userId) {\n\t\t\tattrs.user = { userId };\n\t\t}\n\t}\n\n\treturn attrs;\n}\n\n/**\n * Normalize headers to lowercase keys\n */\nfunction normalizeHeaders(\n\theaders: Record<string, string | string[] | undefined>,\n): Record<string, string | string[] | undefined> {\n\tconst normalized: Record<string, string | string[] | undefined> = {};\n\tfor (const [key, value] of Object.entries(headers)) {\n\t\tnormalized[key.toLowerCase()] = value;\n\t}\n\treturn normalized;\n}\n\n/**\n * Run a function within the span context from a Lambda event\n *\n * @example\n * ```typescript\n * await withEventContext(event, async () => {\n * // Any spans created here will be children of the request span\n * await doSomething();\n * });\n * ```\n */\nexport async function withEventContext<T>(\n\tevent: any,\n\tfn: () => Promise<T>,\n): Promise<T> {\n\tconst ctx = getContextFromEvent(event);\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":"middleware.mjs","names":[],"sources":["../../src/instrumentation/middleware.ts"],"sourcesContent":["/**\n * OpenTelemetry instrumentation middleware for @geekmidas/constructs\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 { MiddlewareObj } from '@middy/core';\nimport { context, type Span, trace } from '@opentelemetry/api';\nimport type { Context as LambdaContext } from 'aws-lambda';\nimport {\n\tcreateHttpServerSpan,\n\tendHttpSpan,\n\ttype HttpSpanAttributes,\n\tincomingTraceContext,\n} from './http';\nimport { isTrustedOrigin } from './trust';\n\n/**\n * Options for the telemetry middleware\n */\nexport interface TelemetryMiddlewareOptions {\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 endpoint name from event\n\t */\n\tgetEndpointName?: (event: any) => string | undefined;\n\n\t/**\n\t * Custom function to extract operation ID from event\n\t */\n\tgetOperationId?: (event: any) => string | undefined;\n\n\t/**\n\t * Custom function to extract user ID from event\n\t */\n\tgetUserId?: (event: any) => string | undefined;\n\n\t/**\n\t * Whether to skip tracing for this request\n\t */\n\tshouldSkip?: (event: any) => boolean;\n\n\t/**\n\t * The origins whose `traceparent` is continued: the API's own sites, the\n\t * same list its CORS allows. Any other caller starts a new trace linked to\n\t * the one it claimed.\n\t */\n\ttrustedOrigins?: readonly string[];\n\n\t/**\n\t * Trust a request with no trusted origin anyway — a caller the function\n\t * can vouch for, such as one that passed IAM auth. A Lambda behind API\n\t * Gateway has no private network to tell an internal caller by, so none\n\t * is trusted unless this says so.\n\t */\n\ttrustRequest?: (event: any) => boolean;\n}\n\n// Symbol for storing span on event\nconst SPAN_SYMBOL = Symbol('telemetry.span');\nconst CONTEXT_SYMBOL = Symbol('telemetry.context');\n\n/**\n * Get the current span from the Lambda event\n */\nexport function getSpanFromEvent(event: any): Span | undefined {\n\treturn event?.[SPAN_SYMBOL];\n}\n\n/**\n * Get the current trace context from the Lambda event\n */\nexport function getContextFromEvent(event: any): any {\n\treturn event?.[CONTEXT_SYMBOL];\n}\n\n/**\n * Create a telemetry middleware for Lambda handlers\n *\n * @example\n * ```typescript\n * import { telemetryMiddleware } from '@geekmidas/telescope/instrumentation';\n *\n * const adaptor = new AmazonApiGatewayV2Endpoint(endpoint, {\n * telemetry: { middleware: telemetryMiddleware() },\n * });\n * ```\n */\nexport function telemetryMiddleware(\n\toptions: TelemetryMiddlewareOptions = {},\n): MiddlewareObj<any, any, Error, LambdaContext> {\n\treturn {\n\t\tbefore: async (request) => {\n\t\t\t// Skip if custom skip function returns true\n\t\t\tif (options.shouldSkip?.(request.event)) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst event = request.event;\n\t\t\tconst lambdaContext = request.context;\n\n\t\t\t// Extract trace context from headers\n\t\t\tconst headers = normalizeHeaders(event.headers || {});\n\t\t\tconst trusted =\n\t\t\t\tisTrustedOrigin(\n\t\t\t\t\ttypeof headers.origin === 'string' ? headers.origin : undefined,\n\t\t\t\t\toptions.trustedOrigins,\n\t\t\t\t) || Boolean(options.trustRequest?.(event));\n\t\t\tconst { parent: parentContext, links } = incomingTraceContext(\n\t\t\t\theaders,\n\t\t\t\ttrusted,\n\t\t\t);\n\n\t\t\t// Build span attributes from event\n\t\t\tconst attrs = buildSpanAttributes(event, lambdaContext, options);\n\n\t\t\t// Create the span\n\t\t\tconst span = createHttpServerSpan(attrs, parentContext, links);\n\n\t\t\t// Store span and context on event for access in handler\n\t\t\tevent[SPAN_SYMBOL] = span;\n\t\t\tevent[CONTEXT_SYMBOL] = trace.setSpan(parentContext, span);\n\n\t\t\t// Add Lambda-specific attributes\n\t\t\tspan.setAttribute('faas.invocation_id', lambdaContext.awsRequestId);\n\t\t\tspan.setAttribute('faas.name', lambdaContext.functionName);\n\n\t\t\tif (options.recordBody && event.body) {\n\t\t\t\tconst bodyStr =\n\t\t\t\t\ttypeof event.body === 'string'\n\t\t\t\t\t\t? event.body\n\t\t\t\t\t\t: JSON.stringify(event.body);\n\t\t\t\t// Truncate large bodies\n\t\t\t\tspan.setAttribute(\n\t\t\t\t\t'http.request.body',\n\t\t\t\t\tbodyStr.length > 4096 ? `${bodyStr.slice(0, 4096)}...` : bodyStr,\n\t\t\t\t);\n\t\t\t}\n\t\t},\n\n\t\tafter: async (request) => {\n\t\t\tconst span = getSpanFromEvent(request.event);\n\t\t\tif (!span) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst response = request.response;\n\t\t\tconst statusCode =\n\t\t\t\ttypeof response?.statusCode === 'number' ? response.statusCode : 200;\n\n\t\t\tif (options.recordResponseBody && response?.body) {\n\t\t\t\tconst bodyStr =\n\t\t\t\t\ttypeof response.body === 'string'\n\t\t\t\t\t\t? response.body\n\t\t\t\t\t\t: JSON.stringify(response.body);\n\t\t\t\tspan.setAttribute(\n\t\t\t\t\t'http.response.body',\n\t\t\t\t\tbodyStr.length > 4096 ? `${bodyStr.slice(0, 4096)}...` : bodyStr,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tendHttpSpan(span, { statusCode });\n\t\t},\n\n\t\tonError: async (request) => {\n\t\t\tconst span = getSpanFromEvent(request.event);\n\t\t\tif (!span) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst error = request.error;\n\t\t\tconst response = request.response;\n\n\t\t\t// Determine status code from response or error\n\t\t\tlet statusCode = 500;\n\t\t\tif (response?.statusCode) {\n\t\t\t\tstatusCode = response.statusCode;\n\t\t\t} else if (error && 'statusCode' in error) {\n\t\t\t\tstatusCode = (error as any).statusCode;\n\t\t\t}\n\n\t\t\tendHttpSpan(\n\t\t\t\tspan,\n\t\t\t\t{ statusCode },\n\t\t\t\terror instanceof Error ? error : new Error(String(error)),\n\t\t\t);\n\t\t},\n\t};\n}\n\n/**\n * Build span attributes from Lambda event\n */\nfunction buildSpanAttributes(\n\tevent: any,\n\tlambdaContext: LambdaContext,\n\toptions: TelemetryMiddlewareOptions,\n): HttpSpanAttributes {\n\t// Detect event type (API Gateway v1, v2, ALB, or direct invoke)\n\tconst isV2 =\n\t\t'requestContext' in event && 'http' in (event.requestContext || {});\n\tconst isV1 =\n\t\t'requestContext' in event &&\n\t\t'httpMethod' in event &&\n\t\t!('http' in (event.requestContext || {}));\n\n\tlet method = 'INVOKE';\n\tlet path = '/';\n\tlet route: string | undefined;\n\tlet host: string | undefined;\n\tlet scheme: 'http' | 'https' | undefined;\n\tlet userAgent: string | undefined;\n\tlet clientIp: string | undefined;\n\tlet requestId: string | undefined;\n\n\tif (isV2) {\n\t\t// API Gateway v2 (HTTP API)\n\t\tconst http = event.requestContext.http;\n\t\tmethod = http.method;\n\t\tpath = event.rawPath || http.path;\n\t\troute = event.routeKey?.replace(/^[A-Z]+\\s+/, ''); // Remove HTTP method prefix\n\t\thost = event.headers?.host;\n\t\tscheme = 'https';\n\t\tuserAgent = http.userAgent;\n\t\tclientIp = http.sourceIp;\n\t\trequestId = event.requestContext.requestId;\n\t} else if (isV1) {\n\t\t// API Gateway v1 (REST API)\n\t\tmethod = event.httpMethod;\n\t\tpath = event.path;\n\t\troute = event.resource;\n\t\thost = event.headers?.Host || event.headers?.host;\n\t\tscheme = 'https';\n\t\tuserAgent = event.headers?.['User-Agent'] || event.headers?.['user-agent'];\n\t\tclientIp = event.requestContext?.identity?.sourceIp;\n\t\trequestId = event.requestContext?.requestId;\n\t} else if ('httpMethod' in event) {\n\t\t// ALB event\n\t\tmethod = event.httpMethod;\n\t\tpath = event.path;\n\t\thost = event.headers?.host;\n\t\tuserAgent = event.headers?.['user-agent'] || event.headers?.['User-Agent'];\n\t\tclientIp = event.headers?.['x-forwarded-for']?.split(',')[0]?.trim();\n\t}\n\n\tconst attrs: HttpSpanAttributes = {\n\t\tmethod,\n\t\tpath,\n\t\troute,\n\t\thost,\n\t\tscheme,\n\t\tuserAgent,\n\t\tclientIp,\n\t\trequestId: requestId || lambdaContext.awsRequestId,\n\t};\n\n\t// Add endpoint metadata if extractors provided\n\tif (options.getEndpointName || options.getOperationId) {\n\t\tattrs.endpoint = {\n\t\t\tname: options.getEndpointName?.(event),\n\t\t\toperationId: options.getOperationId?.(event),\n\t\t};\n\t}\n\n\t// Add user metadata if extractor provided\n\tif (options.getUserId) {\n\t\tconst userId = options.getUserId(event);\n\t\tif (userId) {\n\t\t\tattrs.user = { userId };\n\t\t}\n\t}\n\n\treturn attrs;\n}\n\n/**\n * Normalize headers to lowercase keys\n */\nfunction normalizeHeaders(\n\theaders: Record<string, string | string[] | undefined>,\n): Record<string, string | string[] | undefined> {\n\tconst normalized: Record<string, string | string[] | undefined> = {};\n\tfor (const [key, value] of Object.entries(headers)) {\n\t\tnormalized[key.toLowerCase()] = value;\n\t}\n\treturn normalized;\n}\n\n/**\n * Run a function within the span context from a Lambda event\n *\n * @example\n * ```typescript\n * await withEventContext(event, async () => {\n * // Any spans created here will be children of the request span\n * await doSomething();\n * });\n * ```\n */\nexport async function withEventContext<T>(\n\tevent: any,\n\tfn: () => Promise<T>,\n): Promise<T> {\n\tconst ctx = getContextFromEvent(event);\n\tif (!ctx) {\n\t\treturn fn();\n\t}\n\treturn context.with(ctx, fn);\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
let _opentelemetry_sdk_trace_base = require("@opentelemetry/sdk-trace-base");
|
|
3
|
+
//#region src/instrumentation/sampler.ts
|
|
4
|
+
/**
|
|
5
|
+
* The sampler a service uses at `ratio`: parent-based, with the ratio as a cap
|
|
6
|
+
* on what a remote caller asks for.
|
|
7
|
+
*
|
|
8
|
+
* `parentbased_traceidratio` follows a remote parent's sampled flag as it is.
|
|
9
|
+
* Between services that is how a trace stays whole — but the flag on a
|
|
10
|
+
* request from a browser is the caller's to set, and following it would let
|
|
11
|
+
* any page force 100% tracing on a stage that keeps 10%. So:
|
|
12
|
+
*
|
|
13
|
+
* | parent | decision |
|
|
14
|
+
* |---------------------------|---------------------------------------|
|
|
15
|
+
* | none (a new trace) | the ratio, from the trace id |
|
|
16
|
+
* | remote, sampled | the ratio again: the flag is a request |
|
|
17
|
+
* | remote, not sampled | not sampled: a caller may only ask for less |
|
|
18
|
+
* | local (this process) | the parent's decision |
|
|
19
|
+
*
|
|
20
|
+
* The cap costs a whole trace nothing. `TraceIdRatioBasedSampler` decides from
|
|
21
|
+
* the trace id alone, so every service at the same rate — and the generated
|
|
22
|
+
* API client, which applies the same rule to the page view's trace id — makes
|
|
23
|
+
* the same decision for the same trace: a trace sampled where it started is
|
|
24
|
+
* sampled at every hop.
|
|
25
|
+
*/
|
|
26
|
+
function traceSampler(ratio) {
|
|
27
|
+
const capped = new _opentelemetry_sdk_trace_base.TraceIdRatioBasedSampler(ratio);
|
|
28
|
+
return new _opentelemetry_sdk_trace_base.ParentBasedSampler({
|
|
29
|
+
root: capped,
|
|
30
|
+
remoteParentSampled: capped
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* {@link traceSampler} at the rate the standard variables name, when they ask
|
|
35
|
+
* for `parentbased_traceidratio` — which is what a deploy sets for a stage's
|
|
36
|
+
* sample rate. `OTEL_TRACES_SAMPLER_ARG` is the rate, 1 when absent.
|
|
37
|
+
*
|
|
38
|
+
* Undefined otherwise, and for an argument that is not a rate from 0 to 1:
|
|
39
|
+
* the SDK then reads the variables itself, as it would without this, and
|
|
40
|
+
* reports a bad argument the way it always has — a sampling setting is not a
|
|
41
|
+
* reason for a server not to start.
|
|
42
|
+
*/
|
|
43
|
+
function traceSamplerFromEnv(env = process.env) {
|
|
44
|
+
if (env.OTEL_TRACES_SAMPLER?.trim() !== "parentbased_traceidratio") return;
|
|
45
|
+
const arg = env.OTEL_TRACES_SAMPLER_ARG?.trim();
|
|
46
|
+
const ratio = arg ? Number(arg) : 1;
|
|
47
|
+
if (!Number.isFinite(ratio) || ratio < 0 || ratio > 1) return void 0;
|
|
48
|
+
return traceSampler(ratio);
|
|
49
|
+
}
|
|
50
|
+
//#endregion
|
|
51
|
+
exports.traceSampler = traceSampler;
|
|
52
|
+
exports.traceSamplerFromEnv = traceSamplerFromEnv;
|
|
53
|
+
|
|
54
|
+
//# sourceMappingURL=sampler.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sampler.cjs","names":["TraceIdRatioBasedSampler","ParentBasedSampler"],"sources":["../../src/instrumentation/sampler.ts"],"sourcesContent":["import {\n\tParentBasedSampler,\n\ttype Sampler,\n\tTraceIdRatioBasedSampler,\n} from '@opentelemetry/sdk-trace-base';\n\n/**\n * The sampler a service uses at `ratio`: parent-based, with the ratio as a cap\n * on what a remote caller asks for.\n *\n * `parentbased_traceidratio` follows a remote parent's sampled flag as it is.\n * Between services that is how a trace stays whole — but the flag on a\n * request from a browser is the caller's to set, and following it would let\n * any page force 100% tracing on a stage that keeps 10%. So:\n *\n * | parent | decision |\n * |---------------------------|---------------------------------------|\n * | none (a new trace) | the ratio, from the trace id |\n * | remote, sampled | the ratio again: the flag is a request |\n * | remote, not sampled | not sampled: a caller may only ask for less |\n * | local (this process) | the parent's decision |\n *\n * The cap costs a whole trace nothing. `TraceIdRatioBasedSampler` decides from\n * the trace id alone, so every service at the same rate — and the generated\n * API client, which applies the same rule to the page view's trace id — makes\n * the same decision for the same trace: a trace sampled where it started is\n * sampled at every hop.\n */\nexport function traceSampler(ratio: number): Sampler {\n\tconst capped = new TraceIdRatioBasedSampler(ratio);\n\treturn new ParentBasedSampler({\n\t\troot: capped,\n\t\tremoteParentSampled: capped,\n\t});\n}\n\n/**\n * {@link traceSampler} at the rate the standard variables name, when they ask\n * for `parentbased_traceidratio` — which is what a deploy sets for a stage's\n * sample rate. `OTEL_TRACES_SAMPLER_ARG` is the rate, 1 when absent.\n *\n * Undefined otherwise, and for an argument that is not a rate from 0 to 1:\n * the SDK then reads the variables itself, as it would without this, and\n * reports a bad argument the way it always has — a sampling setting is not a\n * reason for a server not to start.\n */\nexport function traceSamplerFromEnv(\n\tenv: Record<string, string | undefined> = process.env,\n): Sampler | undefined {\n\tif (env.OTEL_TRACES_SAMPLER?.trim() !== 'parentbased_traceidratio') {\n\t\treturn undefined;\n\t}\n\tconst arg = env.OTEL_TRACES_SAMPLER_ARG?.trim();\n\tconst ratio = arg ? Number(arg) : 1;\n\tif (!Number.isFinite(ratio) || ratio < 0 || ratio > 1) return undefined;\n\treturn traceSampler(ratio);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AA4BA,SAAgB,aAAa,OAAwB;CACpD,MAAM,SAAS,IAAIA,8BAAAA,yBAAyB,KAAK;CACjD,OAAO,IAAIC,8BAAAA,mBAAmB;EAC7B,MAAM;EACN,qBAAqB;CACtB,CAAC;AACF;;;;;;;;;;;AAYA,SAAgB,oBACf,MAA0C,QAAQ,KAC5B;CACtB,IAAI,IAAI,qBAAqB,KAAK,MAAM,4BACvC;CAED,MAAM,MAAM,IAAI,yBAAyB,KAAK;CAC9C,MAAM,QAAQ,MAAM,OAAO,GAAG,IAAI;CAClC,IAAI,CAAC,OAAO,SAAS,KAAK,KAAK,QAAQ,KAAK,QAAQ,GAAG,OAAO,KAAA;CAC9D,OAAO,aAAa,KAAK;AAC1B"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Sampler } from "@opentelemetry/sdk-trace-base";
|
|
2
|
+
//#region src/instrumentation/sampler.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* The sampler a service uses at `ratio`: parent-based, with the ratio as a cap
|
|
5
|
+
* on what a remote caller asks for.
|
|
6
|
+
*
|
|
7
|
+
* `parentbased_traceidratio` follows a remote parent's sampled flag as it is.
|
|
8
|
+
* Between services that is how a trace stays whole — but the flag on a
|
|
9
|
+
* request from a browser is the caller's to set, and following it would let
|
|
10
|
+
* any page force 100% tracing on a stage that keeps 10%. So:
|
|
11
|
+
*
|
|
12
|
+
* | parent | decision |
|
|
13
|
+
* |---------------------------|---------------------------------------|
|
|
14
|
+
* | none (a new trace) | the ratio, from the trace id |
|
|
15
|
+
* | remote, sampled | the ratio again: the flag is a request |
|
|
16
|
+
* | remote, not sampled | not sampled: a caller may only ask for less |
|
|
17
|
+
* | local (this process) | the parent's decision |
|
|
18
|
+
*
|
|
19
|
+
* The cap costs a whole trace nothing. `TraceIdRatioBasedSampler` decides from
|
|
20
|
+
* the trace id alone, so every service at the same rate — and the generated
|
|
21
|
+
* API client, which applies the same rule to the page view's trace id — makes
|
|
22
|
+
* the same decision for the same trace: a trace sampled where it started is
|
|
23
|
+
* sampled at every hop.
|
|
24
|
+
*/
|
|
25
|
+
export declare function traceSampler(ratio: number): Sampler;
|
|
26
|
+
/**
|
|
27
|
+
* {@link traceSampler} at the rate the standard variables name, when they ask
|
|
28
|
+
* for `parentbased_traceidratio` — which is what a deploy sets for a stage's
|
|
29
|
+
* sample rate. `OTEL_TRACES_SAMPLER_ARG` is the rate, 1 when absent.
|
|
30
|
+
*
|
|
31
|
+
* Undefined otherwise, and for an argument that is not a rate from 0 to 1:
|
|
32
|
+
* the SDK then reads the variables itself, as it would without this, and
|
|
33
|
+
* reports a bad argument the way it always has — a sampling setting is not a
|
|
34
|
+
* reason for a server not to start.
|
|
35
|
+
*/
|
|
36
|
+
export declare function traceSamplerFromEnv(env?: Record<string, string | undefined>): Sampler | undefined;
|
|
37
|
+
//#endregion
|
|
38
|
+
//# sourceMappingURL=sampler.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sampler.d.cts","names":[],"sources":["../../src/instrumentation/sampler.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;wBA4BgB,aAAa,gBAAgB;;;;;;;;;;;wBAkB7B,oBACf,MAAK,qCACH"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Sampler } from "@opentelemetry/sdk-trace-base";
|
|
2
|
+
//#region src/instrumentation/sampler.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* The sampler a service uses at `ratio`: parent-based, with the ratio as a cap
|
|
5
|
+
* on what a remote caller asks for.
|
|
6
|
+
*
|
|
7
|
+
* `parentbased_traceidratio` follows a remote parent's sampled flag as it is.
|
|
8
|
+
* Between services that is how a trace stays whole — but the flag on a
|
|
9
|
+
* request from a browser is the caller's to set, and following it would let
|
|
10
|
+
* any page force 100% tracing on a stage that keeps 10%. So:
|
|
11
|
+
*
|
|
12
|
+
* | parent | decision |
|
|
13
|
+
* |---------------------------|---------------------------------------|
|
|
14
|
+
* | none (a new trace) | the ratio, from the trace id |
|
|
15
|
+
* | remote, sampled | the ratio again: the flag is a request |
|
|
16
|
+
* | remote, not sampled | not sampled: a caller may only ask for less |
|
|
17
|
+
* | local (this process) | the parent's decision |
|
|
18
|
+
*
|
|
19
|
+
* The cap costs a whole trace nothing. `TraceIdRatioBasedSampler` decides from
|
|
20
|
+
* the trace id alone, so every service at the same rate — and the generated
|
|
21
|
+
* API client, which applies the same rule to the page view's trace id — makes
|
|
22
|
+
* the same decision for the same trace: a trace sampled where it started is
|
|
23
|
+
* sampled at every hop.
|
|
24
|
+
*/
|
|
25
|
+
export declare function traceSampler(ratio: number): Sampler;
|
|
26
|
+
/**
|
|
27
|
+
* {@link traceSampler} at the rate the standard variables name, when they ask
|
|
28
|
+
* for `parentbased_traceidratio` — which is what a deploy sets for a stage's
|
|
29
|
+
* sample rate. `OTEL_TRACES_SAMPLER_ARG` is the rate, 1 when absent.
|
|
30
|
+
*
|
|
31
|
+
* Undefined otherwise, and for an argument that is not a rate from 0 to 1:
|
|
32
|
+
* the SDK then reads the variables itself, as it would without this, and
|
|
33
|
+
* reports a bad argument the way it always has — a sampling setting is not a
|
|
34
|
+
* reason for a server not to start.
|
|
35
|
+
*/
|
|
36
|
+
export declare function traceSamplerFromEnv(env?: Record<string, string | undefined>): Sampler | undefined;
|
|
37
|
+
//#endregion
|
|
38
|
+
//# sourceMappingURL=sampler.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sampler.d.mts","names":[],"sources":["../../src/instrumentation/sampler.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;wBA4BgB,aAAa,gBAAgB;;;;;;;;;;;wBAkB7B,oBACf,MAAK,qCACH"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { ParentBasedSampler, TraceIdRatioBasedSampler } from "@opentelemetry/sdk-trace-base";
|
|
2
|
+
//#region src/instrumentation/sampler.ts
|
|
3
|
+
/**
|
|
4
|
+
* The sampler a service uses at `ratio`: parent-based, with the ratio as a cap
|
|
5
|
+
* on what a remote caller asks for.
|
|
6
|
+
*
|
|
7
|
+
* `parentbased_traceidratio` follows a remote parent's sampled flag as it is.
|
|
8
|
+
* Between services that is how a trace stays whole — but the flag on a
|
|
9
|
+
* request from a browser is the caller's to set, and following it would let
|
|
10
|
+
* any page force 100% tracing on a stage that keeps 10%. So:
|
|
11
|
+
*
|
|
12
|
+
* | parent | decision |
|
|
13
|
+
* |---------------------------|---------------------------------------|
|
|
14
|
+
* | none (a new trace) | the ratio, from the trace id |
|
|
15
|
+
* | remote, sampled | the ratio again: the flag is a request |
|
|
16
|
+
* | remote, not sampled | not sampled: a caller may only ask for less |
|
|
17
|
+
* | local (this process) | the parent's decision |
|
|
18
|
+
*
|
|
19
|
+
* The cap costs a whole trace nothing. `TraceIdRatioBasedSampler` decides from
|
|
20
|
+
* the trace id alone, so every service at the same rate — and the generated
|
|
21
|
+
* API client, which applies the same rule to the page view's trace id — makes
|
|
22
|
+
* the same decision for the same trace: a trace sampled where it started is
|
|
23
|
+
* sampled at every hop.
|
|
24
|
+
*/
|
|
25
|
+
function traceSampler(ratio) {
|
|
26
|
+
const capped = new TraceIdRatioBasedSampler(ratio);
|
|
27
|
+
return new ParentBasedSampler({
|
|
28
|
+
root: capped,
|
|
29
|
+
remoteParentSampled: capped
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* {@link traceSampler} at the rate the standard variables name, when they ask
|
|
34
|
+
* for `parentbased_traceidratio` — which is what a deploy sets for a stage's
|
|
35
|
+
* sample rate. `OTEL_TRACES_SAMPLER_ARG` is the rate, 1 when absent.
|
|
36
|
+
*
|
|
37
|
+
* Undefined otherwise, and for an argument that is not a rate from 0 to 1:
|
|
38
|
+
* the SDK then reads the variables itself, as it would without this, and
|
|
39
|
+
* reports a bad argument the way it always has — a sampling setting is not a
|
|
40
|
+
* reason for a server not to start.
|
|
41
|
+
*/
|
|
42
|
+
function traceSamplerFromEnv(env = process.env) {
|
|
43
|
+
if (env.OTEL_TRACES_SAMPLER?.trim() !== "parentbased_traceidratio") return;
|
|
44
|
+
const arg = env.OTEL_TRACES_SAMPLER_ARG?.trim();
|
|
45
|
+
const ratio = arg ? Number(arg) : 1;
|
|
46
|
+
if (!Number.isFinite(ratio) || ratio < 0 || ratio > 1) return void 0;
|
|
47
|
+
return traceSampler(ratio);
|
|
48
|
+
}
|
|
49
|
+
//#endregion
|
|
50
|
+
export { traceSampler, traceSamplerFromEnv };
|
|
51
|
+
|
|
52
|
+
//# sourceMappingURL=sampler.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sampler.mjs","names":[],"sources":["../../src/instrumentation/sampler.ts"],"sourcesContent":["import {\n\tParentBasedSampler,\n\ttype Sampler,\n\tTraceIdRatioBasedSampler,\n} from '@opentelemetry/sdk-trace-base';\n\n/**\n * The sampler a service uses at `ratio`: parent-based, with the ratio as a cap\n * on what a remote caller asks for.\n *\n * `parentbased_traceidratio` follows a remote parent's sampled flag as it is.\n * Between services that is how a trace stays whole — but the flag on a\n * request from a browser is the caller's to set, and following it would let\n * any page force 100% tracing on a stage that keeps 10%. So:\n *\n * | parent | decision |\n * |---------------------------|---------------------------------------|\n * | none (a new trace) | the ratio, from the trace id |\n * | remote, sampled | the ratio again: the flag is a request |\n * | remote, not sampled | not sampled: a caller may only ask for less |\n * | local (this process) | the parent's decision |\n *\n * The cap costs a whole trace nothing. `TraceIdRatioBasedSampler` decides from\n * the trace id alone, so every service at the same rate — and the generated\n * API client, which applies the same rule to the page view's trace id — makes\n * the same decision for the same trace: a trace sampled where it started is\n * sampled at every hop.\n */\nexport function traceSampler(ratio: number): Sampler {\n\tconst capped = new TraceIdRatioBasedSampler(ratio);\n\treturn new ParentBasedSampler({\n\t\troot: capped,\n\t\tremoteParentSampled: capped,\n\t});\n}\n\n/**\n * {@link traceSampler} at the rate the standard variables name, when they ask\n * for `parentbased_traceidratio` — which is what a deploy sets for a stage's\n * sample rate. `OTEL_TRACES_SAMPLER_ARG` is the rate, 1 when absent.\n *\n * Undefined otherwise, and for an argument that is not a rate from 0 to 1:\n * the SDK then reads the variables itself, as it would without this, and\n * reports a bad argument the way it always has — a sampling setting is not a\n * reason for a server not to start.\n */\nexport function traceSamplerFromEnv(\n\tenv: Record<string, string | undefined> = process.env,\n): Sampler | undefined {\n\tif (env.OTEL_TRACES_SAMPLER?.trim() !== 'parentbased_traceidratio') {\n\t\treturn undefined;\n\t}\n\tconst arg = env.OTEL_TRACES_SAMPLER_ARG?.trim();\n\tconst ratio = arg ? Number(arg) : 1;\n\tif (!Number.isFinite(ratio) || ratio < 0 || ratio > 1) return undefined;\n\treturn traceSampler(ratio);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AA4BA,SAAgB,aAAa,OAAwB;CACpD,MAAM,SAAS,IAAI,yBAAyB,KAAK;CACjD,OAAO,IAAI,mBAAmB;EAC7B,MAAM;EACN,qBAAqB;CACtB,CAAC;AACF;;;;;;;;;;;AAYA,SAAgB,oBACf,MAA0C,QAAQ,KAC5B;CACtB,IAAI,IAAI,qBAAqB,KAAK,MAAM,4BACvC;CAED,MAAM,MAAM,IAAI,yBAAyB,KAAK;CAC9C,MAAM,QAAQ,MAAM,OAAO,GAAG,IAAI;CAClC,IAAI,CAAC,OAAO,SAAS,KAAK,KAAK,QAAQ,KAAK,QAAQ,GAAG,OAAO,KAAA;CAC9D,OAAO,aAAa,KAAK;AAC1B"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_instrumentation_core = require("./core.cjs");
|
|
3
|
+
const require_instrumentation_sampler = require("./sampler.cjs");
|
|
3
4
|
let _opentelemetry_api = require("@opentelemetry/api");
|
|
4
5
|
let _opentelemetry_auto_instrumentations_node = require("@opentelemetry/auto-instrumentations-node");
|
|
5
6
|
let _opentelemetry_exporter_logs_otlp_http = require("@opentelemetry/exporter-logs-otlp-http");
|
|
@@ -27,13 +28,14 @@ function otlpEndpointFromEnv(signal) {
|
|
|
27
28
|
return Boolean(process.env[`OTEL_EXPORTER_OTLP_${signal}_ENDPOINT`] || process.env.OTEL_EXPORTER_OTLP_ENDPOINT);
|
|
28
29
|
}
|
|
29
30
|
/**
|
|
30
|
-
* The sampler for an explicit ratio, or
|
|
31
|
-
* `
|
|
31
|
+
* The sampler for an explicit ratio, or for the stage's rate in
|
|
32
|
+
* `OTEL_TRACES_SAMPLER_ARG` — or undefined to let the SDK read any other
|
|
33
|
+
* `OTEL_TRACES_SAMPLER` itself.
|
|
32
34
|
*/
|
|
33
35
|
function samplerFor(sampleRatio) {
|
|
34
|
-
if (sampleRatio === void 0) return
|
|
36
|
+
if (sampleRatio === void 0) return require_instrumentation_sampler.traceSamplerFromEnv();
|
|
35
37
|
if (!Number.isFinite(sampleRatio) || sampleRatio < 0 || sampleRatio > 1) throw new InvalidSampleRatio(sampleRatio);
|
|
36
|
-
return
|
|
38
|
+
return require_instrumentation_sampler.traceSampler(sampleRatio);
|
|
37
39
|
}
|
|
38
40
|
/**
|
|
39
41
|
* Set up OpenTelemetry instrumentation.
|