@geekmidas/telescope 10.0.0-alpha.157 → 10.0.0-alpha.158
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/instrumentation/core.cjs +28 -0
- package/dist/instrumentation/core.cjs.map +1 -1
- package/dist/instrumentation/core.d.cts +17 -0
- package/dist/instrumentation/core.d.cts.map +1 -1
- package/dist/instrumentation/core.d.mts +17 -0
- package/dist/instrumentation/core.d.mts.map +1 -1
- package/dist/instrumentation/core.mjs +27 -1
- package/dist/instrumentation/core.mjs.map +1 -1
- package/dist/instrumentation/hono.cjs +20 -1
- 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 +20 -1
- package/dist/instrumentation/hono.mjs.map +1 -1
- package/dist/instrumentation/index.cjs +5 -0
- package/dist/instrumentation/index.d.cts +3 -2
- package/dist/instrumentation/index.d.mts +3 -2
- package/dist/instrumentation/index.mjs +3 -2
- package/dist/instrumentation/metrics.cjs +87 -0
- package/dist/instrumentation/metrics.cjs.map +1 -0
- package/dist/instrumentation/metrics.d.cts +28 -0
- package/dist/instrumentation/metrics.d.cts.map +1 -0
- package/dist/instrumentation/metrics.d.mts +28 -0
- package/dist/instrumentation/metrics.d.mts.map +1 -0
- package/dist/instrumentation/metrics.mjs +85 -0
- package/dist/instrumentation/metrics.mjs.map +1 -0
- package/dist/instrumentation/middleware.cjs +25 -1
- package/dist/instrumentation/middleware.cjs.map +1 -1
- package/dist/instrumentation/middleware.d.cts.map +1 -1
- package/dist/instrumentation/middleware.d.mts.map +1 -1
- package/dist/instrumentation/middleware.mjs +25 -1
- package/dist/instrumentation/middleware.mjs.map +1 -1
- package/dist/instrumentation/otel.cjs +18 -1
- package/dist/instrumentation/otel.cjs.map +1 -1
- package/dist/instrumentation/otel.d.cts.map +1 -1
- package/dist/instrumentation/otel.d.mts.map +1 -1
- package/dist/instrumentation/otel.mjs +18 -1
- package/dist/instrumentation/otel.mjs.map +1 -1
- package/dist/instrumentation/setup.cjs +40 -1
- package/dist/instrumentation/setup.cjs.map +1 -1
- package/dist/instrumentation/setup.d.cts +17 -4
- package/dist/instrumentation/setup.d.cts.map +1 -1
- package/dist/instrumentation/setup.d.mts +17 -4
- package/dist/instrumentation/setup.d.mts.map +1 -1
- package/dist/instrumentation/setup.mjs +41 -2
- package/dist/instrumentation/setup.mjs.map +1 -1
- package/package.json +15 -5
package/README.md
CHANGED
|
@@ -726,7 +726,7 @@ Each of these entries imports an optional peer when it loads, so using it means
|
|
|
726
726
|
| --- | --- |
|
|
727
727
|
| `@geekmidas/telescope/core` | `@opentelemetry/api`, `@opentelemetry/sdk-trace-base` |
|
|
728
728
|
| `@geekmidas/telescope/hono` | `hono` |
|
|
729
|
-
| `@geekmidas/telescope/instrumentation` | `@opentelemetry/api`, `@opentelemetry/auto-instrumentations-node`, `@opentelemetry/exporter-logs-otlp-http`, `@opentelemetry/exporter-trace-otlp-http`, `@opentelemetry/instrumentation-pino`, `@opentelemetry/resources`, `@opentelemetry/sdk-logs`, `@opentelemetry/sdk-node`, `@opentelemetry/sdk-trace-base`, `@opentelemetry/sdk-trace-node`, `@opentelemetry/semantic-conventions`, `hono` |
|
|
729
|
+
| `@geekmidas/telescope/instrumentation` | `@opentelemetry/api`, `@opentelemetry/auto-instrumentations-node`, `@opentelemetry/exporter-logs-otlp-http`, `@opentelemetry/exporter-metrics-otlp-http`, `@opentelemetry/exporter-trace-otlp-http`, `@opentelemetry/instrumentation-pino`, `@opentelemetry/resources`, `@opentelemetry/sdk-logs`, `@opentelemetry/sdk-metrics`, `@opentelemetry/sdk-node`, `@opentelemetry/sdk-trace-base`, `@opentelemetry/sdk-trace-node`, `@opentelemetry/semantic-conventions`, `hono` |
|
|
730
730
|
| `@geekmidas/telescope/instrumentation/hono` | `@opentelemetry/api`, `hono` |
|
|
731
731
|
| `@geekmidas/telescope/lambda` | `@opentelemetry/api`, `@opentelemetry/sdk-trace-base` |
|
|
732
732
|
| `@geekmidas/telescope/logger/pino` | `pino-abstract-transport` |
|
|
@@ -2,9 +2,19 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
|
2
2
|
let _opentelemetry_api = require("@opentelemetry/api");
|
|
3
3
|
let _opentelemetry_sdk_trace_base = require("@opentelemetry/sdk-trace-base");
|
|
4
4
|
//#region src/instrumentation/core.ts
|
|
5
|
+
/** A metric flush that did not finish in time; the process carries on. */
|
|
6
|
+
var MetricFlushTimeout = class extends Error {
|
|
7
|
+
timeoutMs;
|
|
8
|
+
constructor(timeoutMs) {
|
|
9
|
+
super(`Metrics were not exported within ${timeoutMs}ms. Check that the collector is reachable, or pass flushTelemetry a longer timeout.`);
|
|
10
|
+
this.timeoutMs = timeoutMs;
|
|
11
|
+
this.name = "MetricFlushTimeout";
|
|
12
|
+
}
|
|
13
|
+
};
|
|
5
14
|
const state = {
|
|
6
15
|
spanProcessor: null,
|
|
7
16
|
logProcessor: null,
|
|
17
|
+
metricReader: null,
|
|
8
18
|
initialized: false
|
|
9
19
|
};
|
|
10
20
|
/**
|
|
@@ -44,6 +54,12 @@ function setGlobalLogProcessor(processor) {
|
|
|
44
54
|
state.logProcessor = processor;
|
|
45
55
|
}
|
|
46
56
|
/**
|
|
57
|
+
* Set the global metric reader for flush operations
|
|
58
|
+
*/
|
|
59
|
+
function setGlobalMetricReader(reader) {
|
|
60
|
+
state.metricReader = reader;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
47
63
|
* Check if telemetry is initialized
|
|
48
64
|
*/
|
|
49
65
|
function isTelemetryInitialized() {
|
|
@@ -75,6 +91,12 @@ async function flushTelemetry(timeoutMs = 3e4) {
|
|
|
75
91
|
if (state.logProcessor && typeof state.logProcessor.forceFlush === "function") promises.push(Promise.race([state.logProcessor.forceFlush(), new Promise((_, reject) => setTimeout(() => reject(/* @__PURE__ */ new Error("Log flush timeout")), timeoutMs))]).catch((error) => {
|
|
76
92
|
if (_opentelemetry_api.diag.debug) _opentelemetry_api.diag.debug(`Log flush error: ${error.message}`);
|
|
77
93
|
}));
|
|
94
|
+
if (state.metricReader) {
|
|
95
|
+
const reader = state.metricReader;
|
|
96
|
+
promises.push(Promise.race([reader.forceFlush(), new Promise((_, reject) => setTimeout(() => reject(new MetricFlushTimeout(timeoutMs)), timeoutMs))]).catch((error) => {
|
|
97
|
+
if (_opentelemetry_api.diag.debug) _opentelemetry_api.diag.debug(`Metric flush error: ${error.message}`);
|
|
98
|
+
}));
|
|
99
|
+
}
|
|
78
100
|
await Promise.all(promises);
|
|
79
101
|
}
|
|
80
102
|
/**
|
|
@@ -90,6 +112,10 @@ async function shutdownTelemetry() {
|
|
|
90
112
|
promises.push(state.logProcessor.shutdown());
|
|
91
113
|
state.logProcessor = null;
|
|
92
114
|
}
|
|
115
|
+
if (state.metricReader) {
|
|
116
|
+
promises.push(state.metricReader.shutdown());
|
|
117
|
+
state.metricReader = null;
|
|
118
|
+
}
|
|
93
119
|
state.initialized = false;
|
|
94
120
|
await Promise.all(promises);
|
|
95
121
|
}
|
|
@@ -134,6 +160,7 @@ var NoopSpanProcessor = class {
|
|
|
134
160
|
}
|
|
135
161
|
};
|
|
136
162
|
//#endregion
|
|
163
|
+
exports.MetricFlushTimeout = MetricFlushTimeout;
|
|
137
164
|
exports.NoopSpanProcessor = NoopSpanProcessor;
|
|
138
165
|
exports.createSpanProcessor = createSpanProcessor;
|
|
139
166
|
exports.enableTelemetryDebug = enableTelemetryDebug;
|
|
@@ -141,6 +168,7 @@ exports.flushTelemetry = flushTelemetry;
|
|
|
141
168
|
exports.getRecommendedStrategy = getRecommendedStrategy;
|
|
142
169
|
exports.isTelemetryInitialized = isTelemetryInitialized;
|
|
143
170
|
exports.setGlobalLogProcessor = setGlobalLogProcessor;
|
|
171
|
+
exports.setGlobalMetricReader = setGlobalMetricReader;
|
|
144
172
|
exports.setGlobalSpanProcessor = setGlobalSpanProcessor;
|
|
145
173
|
exports.shutdownTelemetry = shutdownTelemetry;
|
|
146
174
|
exports.withTelemetryFlush = withTelemetryFlush;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"core.cjs","names":["SimpleSpanProcessor","BatchSpanProcessor","diag","DiagConsoleLogger","DiagLogLevel"],"sources":["../../src/instrumentation/core.ts"],"sourcesContent":["import { DiagConsoleLogger, DiagLogLevel, diag } from '@opentelemetry/api';\nimport {\n\tBatchSpanProcessor,\n\ttype ReadableSpan,\n\tSimpleSpanProcessor,\n\ttype Span,\n\ttype SpanExporter,\n\ttype SpanProcessor,\n} from '@opentelemetry/sdk-trace-base';\nimport type {\n\tSpanProcessorOptions,\n\tSpanProcessorStrategy,\n} from '../adapters/types';\n\n/**\n * Global telemetry state for flush operations\n */\ninterface TelemetryState {\n\tspanProcessor: SpanProcessor | null;\n\tlogProcessor: unknown | null;\n\tinitialized: boolean;\n}\n\nconst state: TelemetryState = {\n\tspanProcessor: null,\n\tlogProcessor: null,\n\tinitialized: false,\n};\n\n/**\n * Create a span processor based on the strategy\n */\nexport function createSpanProcessor(\n\texporter: SpanExporter,\n\toptions: SpanProcessorOptions,\n): SpanProcessor {\n\tconst { strategy, ...config } = options;\n\n\tif (strategy === 'simple') {\n\t\treturn new SimpleSpanProcessor(exporter);\n\t}\n\n\treturn new BatchSpanProcessor(exporter, {\n\t\tmaxQueueSize: config.maxQueueSize ?? 2048,\n\t\tscheduledDelayMillis: config.scheduledDelayMillis ?? 5000,\n\t\texportTimeoutMillis: config.exportTimeoutMillis ?? 30000,\n\t\tmaxExportBatchSize: config.maxExportBatchSize ?? 512,\n\t});\n}\n\n/**\n * Get the recommended span processor strategy for an environment\n */\nexport function getRecommendedStrategy(\n\tenvironment: 'server' | 'lambda' | 'edge' | 'custom',\n): SpanProcessorStrategy {\n\tswitch (environment) {\n\t\tcase 'lambda':\n\t\tcase 'edge':\n\t\t\t// Use simple processor for serverless - immediate export\n\t\t\treturn 'simple';\n\t\tdefault:\n\t\t\t// Use batch processor for long-running servers - efficient batching\n\t\t\treturn 'batch';\n\t}\n}\n\n/**\n * Set the global span processor for flush operations\n */\nexport function setGlobalSpanProcessor(processor: SpanProcessor): void {\n\tstate.spanProcessor = processor;\n\tstate.initialized = true;\n}\n\n/**\n * Set the global log processor for flush operations\n */\nexport function setGlobalLogProcessor(processor: unknown): void {\n\tstate.logProcessor = processor;\n}\n\n/**\n * Check if telemetry is initialized\n */\nexport function isTelemetryInitialized(): boolean {\n\treturn state.initialized;\n}\n\n/**\n * Flush all pending telemetry data.\n * This is critical for serverless environments where the process may freeze.\n *\n * @example\n * ```typescript\n * // In a Lambda handler\n * export const handler = async (event, context) => {\n * try {\n * const result = await processRequest(event);\n * return result;\n * } finally {\n * // Flush before Lambda freezes\n * await flushTelemetry();\n * }\n * };\n * ```\n */\nexport async function flushTelemetry(timeoutMs = 30000): Promise<void> {\n\tconst promises: Promise<void>[] = [];\n\n\t// Flush span processor\n\tif (state.spanProcessor) {\n\t\tpromises.push(\n\t\t\tPromise.race([\n\t\t\t\tstate.spanProcessor.forceFlush(),\n\t\t\t\tnew Promise<void>((_, reject) =>\n\t\t\t\t\tsetTimeout(() => reject(new Error('Span flush timeout')), timeoutMs),\n\t\t\t\t),\n\t\t\t]).catch((error) => {\n\t\t\t\tif (diag.debug) {\n\t\t\t\t\tdiag.debug(`Span flush error: ${error.message}`);\n\t\t\t\t}\n\t\t\t}),\n\t\t);\n\t}\n\n\t// Flush log processor if it has forceFlush method\n\tif (\n\t\tstate.logProcessor &&\n\t\ttypeof (state.logProcessor as { forceFlush?: () => Promise<void> })\n\t\t\t.forceFlush === 'function'\n\t) {\n\t\tpromises.push(\n\t\t\tPromise.race([\n\t\t\t\t(\n\t\t\t\t\tstate.logProcessor as { forceFlush: () => Promise<void> }\n\t\t\t\t).forceFlush(),\n\t\t\t\tnew Promise<void>((_, reject) =>\n\t\t\t\t\tsetTimeout(() => reject(new Error('Log flush timeout')), timeoutMs),\n\t\t\t\t),\n\t\t\t]).catch((error) => {\n\t\t\t\tif (diag.debug) {\n\t\t\t\t\tdiag.debug(`Log flush error: ${error.message}`);\n\t\t\t\t}\n\t\t\t}),\n\t\t);\n\t}\n\n\tawait Promise.all(promises);\n}\n\n/**\n * Shutdown all telemetry processors\n */\nexport async function shutdownTelemetry(): Promise<void> {\n\tconst promises: Promise<void>[] = [];\n\n\tif (state.spanProcessor) {\n\t\tpromises.push(state.spanProcessor.shutdown());\n\t\tstate.spanProcessor = null;\n\t}\n\n\tif (\n\t\tstate.logProcessor &&\n\t\ttypeof (state.logProcessor as { shutdown?: () => Promise<void> })\n\t\t\t.shutdown === 'function'\n\t) {\n\t\tpromises.push(\n\t\t\t(state.logProcessor as { shutdown: () => Promise<void> }).shutdown(),\n\t\t);\n\t\tstate.logProcessor = null;\n\t}\n\n\tstate.initialized = false;\n\tawait Promise.all(promises);\n}\n\n/**\n * Enable debug logging for telemetry\n */\nexport function enableTelemetryDebug(): void {\n\tdiag.setLogger(new DiagConsoleLogger(), DiagLogLevel.DEBUG);\n}\n\n/**\n * Wraps a handler function to ensure telemetry is flushed before returning.\n * Useful for Lambda handlers.\n *\n * @example\n * ```typescript\n * export const handler = withTelemetryFlush(async (event, context) => {\n * // Your Lambda logic here\n * return { statusCode: 200, body: 'OK' };\n * });\n * ```\n */\nexport function withTelemetryFlush<TEvent, TResult>(\n\thandler: (event: TEvent, context: unknown) => Promise<TResult>,\n): (event: TEvent, context: unknown) => Promise<TResult> {\n\treturn async (event: TEvent, context: unknown): Promise<TResult> => {\n\t\ttry {\n\t\t\treturn await handler(event, context);\n\t\t} finally {\n\t\t\tawait flushTelemetry();\n\t\t}\n\t};\n}\n\n/**\n * Noop span processor for testing or when telemetry is disabled\n */\nexport class NoopSpanProcessor implements SpanProcessor {\n\tforceFlush(): Promise<void> {\n\t\treturn Promise.resolve();\n\t}\n\n\tonStart(_span: Span): void {\n\t\t// noop\n\t}\n\n\tonEnd(_span: ReadableSpan): void {\n\t\t// noop\n\t}\n\n\tshutdown(): Promise<void> {\n\t\treturn Promise.resolve();\n\t}\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"core.cjs","names":["SimpleSpanProcessor","BatchSpanProcessor","diag","DiagConsoleLogger","DiagLogLevel"],"sources":["../../src/instrumentation/core.ts"],"sourcesContent":["import { DiagConsoleLogger, DiagLogLevel, diag } from '@opentelemetry/api';\nimport {\n\tBatchSpanProcessor,\n\ttype ReadableSpan,\n\tSimpleSpanProcessor,\n\ttype Span,\n\ttype SpanExporter,\n\ttype SpanProcessor,\n} from '@opentelemetry/sdk-trace-base';\nimport type {\n\tSpanProcessorOptions,\n\tSpanProcessorStrategy,\n} from '../adapters/types';\n\n/**\n * Global telemetry state for flush operations\n */\ninterface TelemetryState {\n\tspanProcessor: SpanProcessor | null;\n\tlogProcessor: unknown | null;\n\tmetricReader: FlushableMetricReader | null;\n\tinitialized: boolean;\n}\n\n/**\n * What flushing needs of a metric reader — the shape, so this module loads\n * without `@opentelemetry/sdk-metrics`.\n */\nexport interface FlushableMetricReader {\n\tforceFlush(): Promise<void>;\n\tshutdown(): Promise<void>;\n}\n\n/** A metric flush that did not finish in time; the process carries on. */\nexport class MetricFlushTimeout extends Error {\n\tconstructor(readonly timeoutMs: number) {\n\t\tsuper(\n\t\t\t`Metrics were not exported within ${timeoutMs}ms. Check that the collector is reachable, or pass flushTelemetry a longer timeout.`,\n\t\t);\n\t\tthis.name = 'MetricFlushTimeout';\n\t}\n}\n\nconst state: TelemetryState = {\n\tspanProcessor: null,\n\tlogProcessor: null,\n\tmetricReader: null,\n\tinitialized: false,\n};\n\n/**\n * Create a span processor based on the strategy\n */\nexport function createSpanProcessor(\n\texporter: SpanExporter,\n\toptions: SpanProcessorOptions,\n): SpanProcessor {\n\tconst { strategy, ...config } = options;\n\n\tif (strategy === 'simple') {\n\t\treturn new SimpleSpanProcessor(exporter);\n\t}\n\n\treturn new BatchSpanProcessor(exporter, {\n\t\tmaxQueueSize: config.maxQueueSize ?? 2048,\n\t\tscheduledDelayMillis: config.scheduledDelayMillis ?? 5000,\n\t\texportTimeoutMillis: config.exportTimeoutMillis ?? 30000,\n\t\tmaxExportBatchSize: config.maxExportBatchSize ?? 512,\n\t});\n}\n\n/**\n * Get the recommended span processor strategy for an environment\n */\nexport function getRecommendedStrategy(\n\tenvironment: 'server' | 'lambda' | 'edge' | 'custom',\n): SpanProcessorStrategy {\n\tswitch (environment) {\n\t\tcase 'lambda':\n\t\tcase 'edge':\n\t\t\t// Use simple processor for serverless - immediate export\n\t\t\treturn 'simple';\n\t\tdefault:\n\t\t\t// Use batch processor for long-running servers - efficient batching\n\t\t\treturn 'batch';\n\t}\n}\n\n/**\n * Set the global span processor for flush operations\n */\nexport function setGlobalSpanProcessor(processor: SpanProcessor): void {\n\tstate.spanProcessor = processor;\n\tstate.initialized = true;\n}\n\n/**\n * Set the global log processor for flush operations\n */\nexport function setGlobalLogProcessor(processor: unknown): void {\n\tstate.logProcessor = processor;\n}\n\n/**\n * Set the global metric reader for flush operations\n */\nexport function setGlobalMetricReader(reader: FlushableMetricReader): void {\n\tstate.metricReader = reader;\n}\n\n/**\n * Check if telemetry is initialized\n */\nexport function isTelemetryInitialized(): boolean {\n\treturn state.initialized;\n}\n\n/**\n * Flush all pending telemetry data.\n * This is critical for serverless environments where the process may freeze.\n *\n * @example\n * ```typescript\n * // In a Lambda handler\n * export const handler = async (event, context) => {\n * try {\n * const result = await processRequest(event);\n * return result;\n * } finally {\n * // Flush before Lambda freezes\n * await flushTelemetry();\n * }\n * };\n * ```\n */\nexport async function flushTelemetry(timeoutMs = 30000): Promise<void> {\n\tconst promises: Promise<void>[] = [];\n\n\t// Flush span processor\n\tif (state.spanProcessor) {\n\t\tpromises.push(\n\t\t\tPromise.race([\n\t\t\t\tstate.spanProcessor.forceFlush(),\n\t\t\t\tnew Promise<void>((_, reject) =>\n\t\t\t\t\tsetTimeout(() => reject(new Error('Span flush timeout')), timeoutMs),\n\t\t\t\t),\n\t\t\t]).catch((error) => {\n\t\t\t\tif (diag.debug) {\n\t\t\t\t\tdiag.debug(`Span flush error: ${error.message}`);\n\t\t\t\t}\n\t\t\t}),\n\t\t);\n\t}\n\n\t// Flush log processor if it has forceFlush method\n\tif (\n\t\tstate.logProcessor &&\n\t\ttypeof (state.logProcessor as { forceFlush?: () => Promise<void> })\n\t\t\t.forceFlush === 'function'\n\t) {\n\t\tpromises.push(\n\t\t\tPromise.race([\n\t\t\t\t(\n\t\t\t\t\tstate.logProcessor as { forceFlush: () => Promise<void> }\n\t\t\t\t).forceFlush(),\n\t\t\t\tnew Promise<void>((_, reject) =>\n\t\t\t\t\tsetTimeout(() => reject(new Error('Log flush timeout')), timeoutMs),\n\t\t\t\t),\n\t\t\t]).catch((error) => {\n\t\t\t\tif (diag.debug) {\n\t\t\t\t\tdiag.debug(`Log flush error: ${error.message}`);\n\t\t\t\t}\n\t\t\t}),\n\t\t);\n\t}\n\n\t// Flush metrics — a Lambda freezes between invocations, so its periodic\n\t// export would otherwise wait for an invocation that may never come.\n\tif (state.metricReader) {\n\t\tconst reader = state.metricReader;\n\t\tpromises.push(\n\t\t\tPromise.race([\n\t\t\t\treader.forceFlush(),\n\t\t\t\tnew Promise<void>((_, reject) =>\n\t\t\t\t\tsetTimeout(\n\t\t\t\t\t\t() => reject(new MetricFlushTimeout(timeoutMs)),\n\t\t\t\t\t\ttimeoutMs,\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t\t]).catch((error) => {\n\t\t\t\tif (diag.debug) {\n\t\t\t\t\tdiag.debug(`Metric flush error: ${error.message}`);\n\t\t\t\t}\n\t\t\t}),\n\t\t);\n\t}\n\n\tawait Promise.all(promises);\n}\n\n/**\n * Shutdown all telemetry processors\n */\nexport async function shutdownTelemetry(): Promise<void> {\n\tconst promises: Promise<void>[] = [];\n\n\tif (state.spanProcessor) {\n\t\tpromises.push(state.spanProcessor.shutdown());\n\t\tstate.spanProcessor = null;\n\t}\n\n\tif (\n\t\tstate.logProcessor &&\n\t\ttypeof (state.logProcessor as { shutdown?: () => Promise<void> })\n\t\t\t.shutdown === 'function'\n\t) {\n\t\tpromises.push(\n\t\t\t(state.logProcessor as { shutdown: () => Promise<void> }).shutdown(),\n\t\t);\n\t\tstate.logProcessor = null;\n\t}\n\n\tif (state.metricReader) {\n\t\tpromises.push(state.metricReader.shutdown());\n\t\tstate.metricReader = null;\n\t}\n\n\tstate.initialized = false;\n\tawait Promise.all(promises);\n}\n\n/**\n * Enable debug logging for telemetry\n */\nexport function enableTelemetryDebug(): void {\n\tdiag.setLogger(new DiagConsoleLogger(), DiagLogLevel.DEBUG);\n}\n\n/**\n * Wraps a handler function to ensure telemetry is flushed before returning.\n * Useful for Lambda handlers.\n *\n * @example\n * ```typescript\n * export const handler = withTelemetryFlush(async (event, context) => {\n * // Your Lambda logic here\n * return { statusCode: 200, body: 'OK' };\n * });\n * ```\n */\nexport function withTelemetryFlush<TEvent, TResult>(\n\thandler: (event: TEvent, context: unknown) => Promise<TResult>,\n): (event: TEvent, context: unknown) => Promise<TResult> {\n\treturn async (event: TEvent, context: unknown): Promise<TResult> => {\n\t\ttry {\n\t\t\treturn await handler(event, context);\n\t\t} finally {\n\t\t\tawait flushTelemetry();\n\t\t}\n\t};\n}\n\n/**\n * Noop span processor for testing or when telemetry is disabled\n */\nexport class NoopSpanProcessor implements SpanProcessor {\n\tforceFlush(): Promise<void> {\n\t\treturn Promise.resolve();\n\t}\n\n\tonStart(_span: Span): void {\n\t\t// noop\n\t}\n\n\tonEnd(_span: ReadableSpan): void {\n\t\t// noop\n\t}\n\n\tshutdown(): Promise<void> {\n\t\treturn Promise.resolve();\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
import { SpanProcessorOptions, SpanProcessorStrategy } from "../adapters/types.cjs";
|
|
2
2
|
import { ReadableSpan, Span, SpanExporter, SpanProcessor } from "@opentelemetry/sdk-trace-base";
|
|
3
3
|
//#region src/instrumentation/core.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* What flushing needs of a metric reader — the shape, so this module loads
|
|
6
|
+
* without `@opentelemetry/sdk-metrics`.
|
|
7
|
+
*/
|
|
8
|
+
export interface FlushableMetricReader {
|
|
9
|
+
forceFlush(): Promise<void>;
|
|
10
|
+
shutdown(): Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
/** A metric flush that did not finish in time; the process carries on. */
|
|
13
|
+
export declare class MetricFlushTimeout extends Error {
|
|
14
|
+
readonly timeoutMs: number;
|
|
15
|
+
constructor(timeoutMs: number);
|
|
16
|
+
}
|
|
4
17
|
/**
|
|
5
18
|
* Create a span processor based on the strategy
|
|
6
19
|
*/
|
|
@@ -17,6 +30,10 @@ export declare function setGlobalSpanProcessor(processor: SpanProcessor): void;
|
|
|
17
30
|
* Set the global log processor for flush operations
|
|
18
31
|
*/
|
|
19
32
|
export declare function setGlobalLogProcessor(processor: unknown): void;
|
|
33
|
+
/**
|
|
34
|
+
* Set the global metric reader for flush operations
|
|
35
|
+
*/
|
|
36
|
+
export declare function setGlobalMetricReader(reader: FlushableMetricReader): void;
|
|
20
37
|
/**
|
|
21
38
|
* Check if telemetry is initialized
|
|
22
39
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"core.d.cts","names":[],"sources":["../../src/instrumentation/core.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"core.d.cts","names":[],"sources":["../../src/instrumentation/core.ts"],"mappings":";;;;;;;iBA4BiB;EAChB,cAAc;EACd,YAAY;;;qBAIA,2BAA2B;WAClB;EAArB,YAAqB;;;;;wBAkBN,oBACf,UAAU,cACV,SAAS,uBACP;;;;wBAkBa,uBACf,uDACE;;;;wBAea,uBAAuB,WAAW;;;;wBAQlC,sBAAsB;;;;wBAOtB,sBAAsB,QAAQ;;;;wBAO9B;;;;;;;;;;;;;;;;;;;wBAsBM,eAAe,qBAAoB;;;;wBAoEnC,qBAAqB;;;;wBA+B3B;;;;;;;;;;;;;wBAgBA,mBAAmB,QAAQ,SAC1C,UAAU,OAAO,QAAQ,qBAAqB,QAAQ,YACnD,OAAO,QAAQ,qBAAqB,QAAQ;;;;qBAanC,6BAA6B;EACzC,cAAc;EAId,QAAQ,OAAO;EAIf,MAAM,OAAO;EAIb,YAAY"}
|
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
import { SpanProcessorOptions, SpanProcessorStrategy } from "../adapters/types.mjs";
|
|
2
2
|
import { ReadableSpan, Span, SpanExporter, SpanProcessor } from "@opentelemetry/sdk-trace-base";
|
|
3
3
|
//#region src/instrumentation/core.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* What flushing needs of a metric reader — the shape, so this module loads
|
|
6
|
+
* without `@opentelemetry/sdk-metrics`.
|
|
7
|
+
*/
|
|
8
|
+
export interface FlushableMetricReader {
|
|
9
|
+
forceFlush(): Promise<void>;
|
|
10
|
+
shutdown(): Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
/** A metric flush that did not finish in time; the process carries on. */
|
|
13
|
+
export declare class MetricFlushTimeout extends Error {
|
|
14
|
+
readonly timeoutMs: number;
|
|
15
|
+
constructor(timeoutMs: number);
|
|
16
|
+
}
|
|
4
17
|
/**
|
|
5
18
|
* Create a span processor based on the strategy
|
|
6
19
|
*/
|
|
@@ -17,6 +30,10 @@ export declare function setGlobalSpanProcessor(processor: SpanProcessor): void;
|
|
|
17
30
|
* Set the global log processor for flush operations
|
|
18
31
|
*/
|
|
19
32
|
export declare function setGlobalLogProcessor(processor: unknown): void;
|
|
33
|
+
/**
|
|
34
|
+
* Set the global metric reader for flush operations
|
|
35
|
+
*/
|
|
36
|
+
export declare function setGlobalMetricReader(reader: FlushableMetricReader): void;
|
|
20
37
|
/**
|
|
21
38
|
* Check if telemetry is initialized
|
|
22
39
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"core.d.mts","names":[],"sources":["../../src/instrumentation/core.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"core.d.mts","names":[],"sources":["../../src/instrumentation/core.ts"],"mappings":";;;;;;;iBA4BiB;EAChB,cAAc;EACd,YAAY;;;qBAIA,2BAA2B;WAClB;EAArB,YAAqB;;;;;wBAkBN,oBACf,UAAU,cACV,SAAS,uBACP;;;;wBAkBa,uBACf,uDACE;;;;wBAea,uBAAuB,WAAW;;;;wBAQlC,sBAAsB;;;;wBAOtB,sBAAsB,QAAQ;;;;wBAO9B;;;;;;;;;;;;;;;;;;;wBAsBM,eAAe,qBAAoB;;;;wBAoEnC,qBAAqB;;;;wBA+B3B;;;;;;;;;;;;;wBAgBA,mBAAmB,QAAQ,SAC1C,UAAU,OAAO,QAAQ,qBAAqB,QAAQ,YACnD,OAAO,QAAQ,qBAAqB,QAAQ;;;;qBAanC,6BAA6B;EACzC,cAAc;EAId,QAAQ,OAAO;EAIf,MAAM,OAAO;EAIb,YAAY"}
|
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
import { DiagConsoleLogger, DiagLogLevel, diag } from "@opentelemetry/api";
|
|
2
2
|
import { BatchSpanProcessor, SimpleSpanProcessor } from "@opentelemetry/sdk-trace-base";
|
|
3
3
|
//#region src/instrumentation/core.ts
|
|
4
|
+
/** A metric flush that did not finish in time; the process carries on. */
|
|
5
|
+
var MetricFlushTimeout = class extends Error {
|
|
6
|
+
timeoutMs;
|
|
7
|
+
constructor(timeoutMs) {
|
|
8
|
+
super(`Metrics were not exported within ${timeoutMs}ms. Check that the collector is reachable, or pass flushTelemetry a longer timeout.`);
|
|
9
|
+
this.timeoutMs = timeoutMs;
|
|
10
|
+
this.name = "MetricFlushTimeout";
|
|
11
|
+
}
|
|
12
|
+
};
|
|
4
13
|
const state = {
|
|
5
14
|
spanProcessor: null,
|
|
6
15
|
logProcessor: null,
|
|
16
|
+
metricReader: null,
|
|
7
17
|
initialized: false
|
|
8
18
|
};
|
|
9
19
|
/**
|
|
@@ -43,6 +53,12 @@ function setGlobalLogProcessor(processor) {
|
|
|
43
53
|
state.logProcessor = processor;
|
|
44
54
|
}
|
|
45
55
|
/**
|
|
56
|
+
* Set the global metric reader for flush operations
|
|
57
|
+
*/
|
|
58
|
+
function setGlobalMetricReader(reader) {
|
|
59
|
+
state.metricReader = reader;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
46
62
|
* Check if telemetry is initialized
|
|
47
63
|
*/
|
|
48
64
|
function isTelemetryInitialized() {
|
|
@@ -74,6 +90,12 @@ async function flushTelemetry(timeoutMs = 3e4) {
|
|
|
74
90
|
if (state.logProcessor && typeof state.logProcessor.forceFlush === "function") promises.push(Promise.race([state.logProcessor.forceFlush(), new Promise((_, reject) => setTimeout(() => reject(/* @__PURE__ */ new Error("Log flush timeout")), timeoutMs))]).catch((error) => {
|
|
75
91
|
if (diag.debug) diag.debug(`Log flush error: ${error.message}`);
|
|
76
92
|
}));
|
|
93
|
+
if (state.metricReader) {
|
|
94
|
+
const reader = state.metricReader;
|
|
95
|
+
promises.push(Promise.race([reader.forceFlush(), new Promise((_, reject) => setTimeout(() => reject(new MetricFlushTimeout(timeoutMs)), timeoutMs))]).catch((error) => {
|
|
96
|
+
if (diag.debug) diag.debug(`Metric flush error: ${error.message}`);
|
|
97
|
+
}));
|
|
98
|
+
}
|
|
77
99
|
await Promise.all(promises);
|
|
78
100
|
}
|
|
79
101
|
/**
|
|
@@ -89,6 +111,10 @@ async function shutdownTelemetry() {
|
|
|
89
111
|
promises.push(state.logProcessor.shutdown());
|
|
90
112
|
state.logProcessor = null;
|
|
91
113
|
}
|
|
114
|
+
if (state.metricReader) {
|
|
115
|
+
promises.push(state.metricReader.shutdown());
|
|
116
|
+
state.metricReader = null;
|
|
117
|
+
}
|
|
92
118
|
state.initialized = false;
|
|
93
119
|
await Promise.all(promises);
|
|
94
120
|
}
|
|
@@ -133,6 +159,6 @@ var NoopSpanProcessor = class {
|
|
|
133
159
|
}
|
|
134
160
|
};
|
|
135
161
|
//#endregion
|
|
136
|
-
export { NoopSpanProcessor, createSpanProcessor, enableTelemetryDebug, flushTelemetry, getRecommendedStrategy, isTelemetryInitialized, setGlobalLogProcessor, setGlobalSpanProcessor, shutdownTelemetry, withTelemetryFlush };
|
|
162
|
+
export { MetricFlushTimeout, NoopSpanProcessor, createSpanProcessor, enableTelemetryDebug, flushTelemetry, getRecommendedStrategy, isTelemetryInitialized, setGlobalLogProcessor, setGlobalMetricReader, setGlobalSpanProcessor, shutdownTelemetry, withTelemetryFlush };
|
|
137
163
|
|
|
138
164
|
//# sourceMappingURL=core.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"core.mjs","names":[],"sources":["../../src/instrumentation/core.ts"],"sourcesContent":["import { DiagConsoleLogger, DiagLogLevel, diag } from '@opentelemetry/api';\nimport {\n\tBatchSpanProcessor,\n\ttype ReadableSpan,\n\tSimpleSpanProcessor,\n\ttype Span,\n\ttype SpanExporter,\n\ttype SpanProcessor,\n} from '@opentelemetry/sdk-trace-base';\nimport type {\n\tSpanProcessorOptions,\n\tSpanProcessorStrategy,\n} from '../adapters/types';\n\n/**\n * Global telemetry state for flush operations\n */\ninterface TelemetryState {\n\tspanProcessor: SpanProcessor | null;\n\tlogProcessor: unknown | null;\n\tinitialized: boolean;\n}\n\nconst state: TelemetryState = {\n\tspanProcessor: null,\n\tlogProcessor: null,\n\tinitialized: false,\n};\n\n/**\n * Create a span processor based on the strategy\n */\nexport function createSpanProcessor(\n\texporter: SpanExporter,\n\toptions: SpanProcessorOptions,\n): SpanProcessor {\n\tconst { strategy, ...config } = options;\n\n\tif (strategy === 'simple') {\n\t\treturn new SimpleSpanProcessor(exporter);\n\t}\n\n\treturn new BatchSpanProcessor(exporter, {\n\t\tmaxQueueSize: config.maxQueueSize ?? 2048,\n\t\tscheduledDelayMillis: config.scheduledDelayMillis ?? 5000,\n\t\texportTimeoutMillis: config.exportTimeoutMillis ?? 30000,\n\t\tmaxExportBatchSize: config.maxExportBatchSize ?? 512,\n\t});\n}\n\n/**\n * Get the recommended span processor strategy for an environment\n */\nexport function getRecommendedStrategy(\n\tenvironment: 'server' | 'lambda' | 'edge' | 'custom',\n): SpanProcessorStrategy {\n\tswitch (environment) {\n\t\tcase 'lambda':\n\t\tcase 'edge':\n\t\t\t// Use simple processor for serverless - immediate export\n\t\t\treturn 'simple';\n\t\tdefault:\n\t\t\t// Use batch processor for long-running servers - efficient batching\n\t\t\treturn 'batch';\n\t}\n}\n\n/**\n * Set the global span processor for flush operations\n */\nexport function setGlobalSpanProcessor(processor: SpanProcessor): void {\n\tstate.spanProcessor = processor;\n\tstate.initialized = true;\n}\n\n/**\n * Set the global log processor for flush operations\n */\nexport function setGlobalLogProcessor(processor: unknown): void {\n\tstate.logProcessor = processor;\n}\n\n/**\n * Check if telemetry is initialized\n */\nexport function isTelemetryInitialized(): boolean {\n\treturn state.initialized;\n}\n\n/**\n * Flush all pending telemetry data.\n * This is critical for serverless environments where the process may freeze.\n *\n * @example\n * ```typescript\n * // In a Lambda handler\n * export const handler = async (event, context) => {\n * try {\n * const result = await processRequest(event);\n * return result;\n * } finally {\n * // Flush before Lambda freezes\n * await flushTelemetry();\n * }\n * };\n * ```\n */\nexport async function flushTelemetry(timeoutMs = 30000): Promise<void> {\n\tconst promises: Promise<void>[] = [];\n\n\t// Flush span processor\n\tif (state.spanProcessor) {\n\t\tpromises.push(\n\t\t\tPromise.race([\n\t\t\t\tstate.spanProcessor.forceFlush(),\n\t\t\t\tnew Promise<void>((_, reject) =>\n\t\t\t\t\tsetTimeout(() => reject(new Error('Span flush timeout')), timeoutMs),\n\t\t\t\t),\n\t\t\t]).catch((error) => {\n\t\t\t\tif (diag.debug) {\n\t\t\t\t\tdiag.debug(`Span flush error: ${error.message}`);\n\t\t\t\t}\n\t\t\t}),\n\t\t);\n\t}\n\n\t// Flush log processor if it has forceFlush method\n\tif (\n\t\tstate.logProcessor &&\n\t\ttypeof (state.logProcessor as { forceFlush?: () => Promise<void> })\n\t\t\t.forceFlush === 'function'\n\t) {\n\t\tpromises.push(\n\t\t\tPromise.race([\n\t\t\t\t(\n\t\t\t\t\tstate.logProcessor as { forceFlush: () => Promise<void> }\n\t\t\t\t).forceFlush(),\n\t\t\t\tnew Promise<void>((_, reject) =>\n\t\t\t\t\tsetTimeout(() => reject(new Error('Log flush timeout')), timeoutMs),\n\t\t\t\t),\n\t\t\t]).catch((error) => {\n\t\t\t\tif (diag.debug) {\n\t\t\t\t\tdiag.debug(`Log flush error: ${error.message}`);\n\t\t\t\t}\n\t\t\t}),\n\t\t);\n\t}\n\n\tawait Promise.all(promises);\n}\n\n/**\n * Shutdown all telemetry processors\n */\nexport async function shutdownTelemetry(): Promise<void> {\n\tconst promises: Promise<void>[] = [];\n\n\tif (state.spanProcessor) {\n\t\tpromises.push(state.spanProcessor.shutdown());\n\t\tstate.spanProcessor = null;\n\t}\n\n\tif (\n\t\tstate.logProcessor &&\n\t\ttypeof (state.logProcessor as { shutdown?: () => Promise<void> })\n\t\t\t.shutdown === 'function'\n\t) {\n\t\tpromises.push(\n\t\t\t(state.logProcessor as { shutdown: () => Promise<void> }).shutdown(),\n\t\t);\n\t\tstate.logProcessor = null;\n\t}\n\n\tstate.initialized = false;\n\tawait Promise.all(promises);\n}\n\n/**\n * Enable debug logging for telemetry\n */\nexport function enableTelemetryDebug(): void {\n\tdiag.setLogger(new DiagConsoleLogger(), DiagLogLevel.DEBUG);\n}\n\n/**\n * Wraps a handler function to ensure telemetry is flushed before returning.\n * Useful for Lambda handlers.\n *\n * @example\n * ```typescript\n * export const handler = withTelemetryFlush(async (event, context) => {\n * // Your Lambda logic here\n * return { statusCode: 200, body: 'OK' };\n * });\n * ```\n */\nexport function withTelemetryFlush<TEvent, TResult>(\n\thandler: (event: TEvent, context: unknown) => Promise<TResult>,\n): (event: TEvent, context: unknown) => Promise<TResult> {\n\treturn async (event: TEvent, context: unknown): Promise<TResult> => {\n\t\ttry {\n\t\t\treturn await handler(event, context);\n\t\t} finally {\n\t\t\tawait flushTelemetry();\n\t\t}\n\t};\n}\n\n/**\n * Noop span processor for testing or when telemetry is disabled\n */\nexport class NoopSpanProcessor implements SpanProcessor {\n\tforceFlush(): Promise<void> {\n\t\treturn Promise.resolve();\n\t}\n\n\tonStart(_span: Span): void {\n\t\t// noop\n\t}\n\n\tonEnd(_span: ReadableSpan): void {\n\t\t// noop\n\t}\n\n\tshutdown(): Promise<void> {\n\t\treturn Promise.resolve();\n\t}\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"core.mjs","names":[],"sources":["../../src/instrumentation/core.ts"],"sourcesContent":["import { DiagConsoleLogger, DiagLogLevel, diag } from '@opentelemetry/api';\nimport {\n\tBatchSpanProcessor,\n\ttype ReadableSpan,\n\tSimpleSpanProcessor,\n\ttype Span,\n\ttype SpanExporter,\n\ttype SpanProcessor,\n} from '@opentelemetry/sdk-trace-base';\nimport type {\n\tSpanProcessorOptions,\n\tSpanProcessorStrategy,\n} from '../adapters/types';\n\n/**\n * Global telemetry state for flush operations\n */\ninterface TelemetryState {\n\tspanProcessor: SpanProcessor | null;\n\tlogProcessor: unknown | null;\n\tmetricReader: FlushableMetricReader | null;\n\tinitialized: boolean;\n}\n\n/**\n * What flushing needs of a metric reader — the shape, so this module loads\n * without `@opentelemetry/sdk-metrics`.\n */\nexport interface FlushableMetricReader {\n\tforceFlush(): Promise<void>;\n\tshutdown(): Promise<void>;\n}\n\n/** A metric flush that did not finish in time; the process carries on. */\nexport class MetricFlushTimeout extends Error {\n\tconstructor(readonly timeoutMs: number) {\n\t\tsuper(\n\t\t\t`Metrics were not exported within ${timeoutMs}ms. Check that the collector is reachable, or pass flushTelemetry a longer timeout.`,\n\t\t);\n\t\tthis.name = 'MetricFlushTimeout';\n\t}\n}\n\nconst state: TelemetryState = {\n\tspanProcessor: null,\n\tlogProcessor: null,\n\tmetricReader: null,\n\tinitialized: false,\n};\n\n/**\n * Create a span processor based on the strategy\n */\nexport function createSpanProcessor(\n\texporter: SpanExporter,\n\toptions: SpanProcessorOptions,\n): SpanProcessor {\n\tconst { strategy, ...config } = options;\n\n\tif (strategy === 'simple') {\n\t\treturn new SimpleSpanProcessor(exporter);\n\t}\n\n\treturn new BatchSpanProcessor(exporter, {\n\t\tmaxQueueSize: config.maxQueueSize ?? 2048,\n\t\tscheduledDelayMillis: config.scheduledDelayMillis ?? 5000,\n\t\texportTimeoutMillis: config.exportTimeoutMillis ?? 30000,\n\t\tmaxExportBatchSize: config.maxExportBatchSize ?? 512,\n\t});\n}\n\n/**\n * Get the recommended span processor strategy for an environment\n */\nexport function getRecommendedStrategy(\n\tenvironment: 'server' | 'lambda' | 'edge' | 'custom',\n): SpanProcessorStrategy {\n\tswitch (environment) {\n\t\tcase 'lambda':\n\t\tcase 'edge':\n\t\t\t// Use simple processor for serverless - immediate export\n\t\t\treturn 'simple';\n\t\tdefault:\n\t\t\t// Use batch processor for long-running servers - efficient batching\n\t\t\treturn 'batch';\n\t}\n}\n\n/**\n * Set the global span processor for flush operations\n */\nexport function setGlobalSpanProcessor(processor: SpanProcessor): void {\n\tstate.spanProcessor = processor;\n\tstate.initialized = true;\n}\n\n/**\n * Set the global log processor for flush operations\n */\nexport function setGlobalLogProcessor(processor: unknown): void {\n\tstate.logProcessor = processor;\n}\n\n/**\n * Set the global metric reader for flush operations\n */\nexport function setGlobalMetricReader(reader: FlushableMetricReader): void {\n\tstate.metricReader = reader;\n}\n\n/**\n * Check if telemetry is initialized\n */\nexport function isTelemetryInitialized(): boolean {\n\treturn state.initialized;\n}\n\n/**\n * Flush all pending telemetry data.\n * This is critical for serverless environments where the process may freeze.\n *\n * @example\n * ```typescript\n * // In a Lambda handler\n * export const handler = async (event, context) => {\n * try {\n * const result = await processRequest(event);\n * return result;\n * } finally {\n * // Flush before Lambda freezes\n * await flushTelemetry();\n * }\n * };\n * ```\n */\nexport async function flushTelemetry(timeoutMs = 30000): Promise<void> {\n\tconst promises: Promise<void>[] = [];\n\n\t// Flush span processor\n\tif (state.spanProcessor) {\n\t\tpromises.push(\n\t\t\tPromise.race([\n\t\t\t\tstate.spanProcessor.forceFlush(),\n\t\t\t\tnew Promise<void>((_, reject) =>\n\t\t\t\t\tsetTimeout(() => reject(new Error('Span flush timeout')), timeoutMs),\n\t\t\t\t),\n\t\t\t]).catch((error) => {\n\t\t\t\tif (diag.debug) {\n\t\t\t\t\tdiag.debug(`Span flush error: ${error.message}`);\n\t\t\t\t}\n\t\t\t}),\n\t\t);\n\t}\n\n\t// Flush log processor if it has forceFlush method\n\tif (\n\t\tstate.logProcessor &&\n\t\ttypeof (state.logProcessor as { forceFlush?: () => Promise<void> })\n\t\t\t.forceFlush === 'function'\n\t) {\n\t\tpromises.push(\n\t\t\tPromise.race([\n\t\t\t\t(\n\t\t\t\t\tstate.logProcessor as { forceFlush: () => Promise<void> }\n\t\t\t\t).forceFlush(),\n\t\t\t\tnew Promise<void>((_, reject) =>\n\t\t\t\t\tsetTimeout(() => reject(new Error('Log flush timeout')), timeoutMs),\n\t\t\t\t),\n\t\t\t]).catch((error) => {\n\t\t\t\tif (diag.debug) {\n\t\t\t\t\tdiag.debug(`Log flush error: ${error.message}`);\n\t\t\t\t}\n\t\t\t}),\n\t\t);\n\t}\n\n\t// Flush metrics — a Lambda freezes between invocations, so its periodic\n\t// export would otherwise wait for an invocation that may never come.\n\tif (state.metricReader) {\n\t\tconst reader = state.metricReader;\n\t\tpromises.push(\n\t\t\tPromise.race([\n\t\t\t\treader.forceFlush(),\n\t\t\t\tnew Promise<void>((_, reject) =>\n\t\t\t\t\tsetTimeout(\n\t\t\t\t\t\t() => reject(new MetricFlushTimeout(timeoutMs)),\n\t\t\t\t\t\ttimeoutMs,\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t\t]).catch((error) => {\n\t\t\t\tif (diag.debug) {\n\t\t\t\t\tdiag.debug(`Metric flush error: ${error.message}`);\n\t\t\t\t}\n\t\t\t}),\n\t\t);\n\t}\n\n\tawait Promise.all(promises);\n}\n\n/**\n * Shutdown all telemetry processors\n */\nexport async function shutdownTelemetry(): Promise<void> {\n\tconst promises: Promise<void>[] = [];\n\n\tif (state.spanProcessor) {\n\t\tpromises.push(state.spanProcessor.shutdown());\n\t\tstate.spanProcessor = null;\n\t}\n\n\tif (\n\t\tstate.logProcessor &&\n\t\ttypeof (state.logProcessor as { shutdown?: () => Promise<void> })\n\t\t\t.shutdown === 'function'\n\t) {\n\t\tpromises.push(\n\t\t\t(state.logProcessor as { shutdown: () => Promise<void> }).shutdown(),\n\t\t);\n\t\tstate.logProcessor = null;\n\t}\n\n\tif (state.metricReader) {\n\t\tpromises.push(state.metricReader.shutdown());\n\t\tstate.metricReader = null;\n\t}\n\n\tstate.initialized = false;\n\tawait Promise.all(promises);\n}\n\n/**\n * Enable debug logging for telemetry\n */\nexport function enableTelemetryDebug(): void {\n\tdiag.setLogger(new DiagConsoleLogger(), DiagLogLevel.DEBUG);\n}\n\n/**\n * Wraps a handler function to ensure telemetry is flushed before returning.\n * Useful for Lambda handlers.\n *\n * @example\n * ```typescript\n * export const handler = withTelemetryFlush(async (event, context) => {\n * // Your Lambda logic here\n * return { statusCode: 200, body: 'OK' };\n * });\n * ```\n */\nexport function withTelemetryFlush<TEvent, TResult>(\n\thandler: (event: TEvent, context: unknown) => Promise<TResult>,\n): (event: TEvent, context: unknown) => Promise<TResult> {\n\treturn async (event: TEvent, context: unknown): Promise<TResult> => {\n\t\ttry {\n\t\t\treturn await handler(event, context);\n\t\t} finally {\n\t\t\tawait flushTelemetry();\n\t\t}\n\t};\n}\n\n/**\n * Noop span processor for testing or when telemetry is disabled\n */\nexport class NoopSpanProcessor implements SpanProcessor {\n\tforceFlush(): Promise<void> {\n\t\treturn Promise.resolve();\n\t}\n\n\tonStart(_span: Span): void {\n\t\t// noop\n\t}\n\n\tonEnd(_span: ReadableSpan): void {\n\t\t// noop\n\t}\n\n\tshutdown(): Promise<void> {\n\t\treturn Promise.resolve();\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -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_metrics = require("./metrics.cjs");
|
|
3
4
|
const require_instrumentation_trust = require("./trust.cjs");
|
|
4
5
|
let _opentelemetry_api = require("@opentelemetry/api");
|
|
5
6
|
let hono_route = require("hono/route");
|
|
@@ -39,6 +40,7 @@ function honoTelemetryMiddleware(options = {}) {
|
|
|
39
40
|
});
|
|
40
41
|
const { parent: parentContext, links } = require_instrumentation_http.incomingTraceContext(headers, isTrustedCaller(c, options));
|
|
41
42
|
const attrs = buildHonoSpanAttributes(c, options);
|
|
43
|
+
const startedAt = performance.now();
|
|
42
44
|
const span = require_instrumentation_http.createHttpServerSpan(attrs, parentContext, links);
|
|
43
45
|
const spanContext = _opentelemetry_api.trace.setSpan(parentContext, span);
|
|
44
46
|
c.set(SPAN_KEY, span);
|
|
@@ -58,9 +60,26 @@ function honoTelemetryMiddleware(options = {}) {
|
|
|
58
60
|
if (body) span.setAttribute("http.response.body", body.length > 4096 ? `${body.slice(0, 4096)}...` : body);
|
|
59
61
|
} catch {}
|
|
60
62
|
require_instrumentation_http.endHttpSpan(span, { statusCode }, c.error);
|
|
63
|
+
require_instrumentation_metrics.recordHttpServerRequest({
|
|
64
|
+
method: c.req.method,
|
|
65
|
+
route: matchedRoute(c),
|
|
66
|
+
scheme: attrs.scheme,
|
|
67
|
+
statusCode,
|
|
68
|
+
durationMs: performance.now() - startedAt,
|
|
69
|
+
...c.error && { error: c.error }
|
|
70
|
+
});
|
|
61
71
|
} catch (error) {
|
|
62
72
|
nameAfterRoute(c, span);
|
|
63
|
-
|
|
73
|
+
const failure = error instanceof Error ? error : new Error(String(error));
|
|
74
|
+
require_instrumentation_http.endHttpSpan(span, { statusCode: 500 }, failure);
|
|
75
|
+
require_instrumentation_metrics.recordHttpServerRequest({
|
|
76
|
+
method: c.req.method,
|
|
77
|
+
route: matchedRoute(c),
|
|
78
|
+
scheme: attrs.scheme,
|
|
79
|
+
statusCode: 500,
|
|
80
|
+
durationMs: performance.now() - startedAt,
|
|
81
|
+
error: failure
|
|
82
|
+
});
|
|
64
83
|
throw error;
|
|
65
84
|
}
|
|
66
85
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hono.cjs","names":["incomingTraceContext","createHttpServerSpan","trace","context","isTrustedOrigin","isInternalCaller","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\ttype HttpSpanAttributes,\n\tincomingTraceContext,\n} from './http';\nimport { isInternalCaller, isTrustedOrigin } from './trust';\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\t/**\n\t * The origins whose `traceparent` is continued: the API's own sites, the\n\t * same list its CORS allows. A request from any other origin starts a new\n\t * trace linked to the one it claimed.\n\t *\n\t * A function is read on each request, for a server that learns its origins\n\t * only once the app has loaded its configuration.\n\t */\n\ttrustedOrigins?: readonly string[] | (() => readonly string[] | undefined);\n\n\t/**\n\t * Whether a request with no `Origin` from an internal caller continues its\n\t * `traceparent` — another service on the private network (see `./trust`):\n\t * no `Origin`, no proxy's forwarding header, and a loopback or private\n\t * peer address, read from `@hono/node-server`'s socket.\n\t *\n\t * `false` trusts no caller without an origin; a function decides itself.\n\t * @default true\n\t */\n\tinternalCallers?: boolean | ((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\t// Continued only from a trusted caller; anyone else gets a new trace\n\t\t// that links to the one they claimed.\n\t\tconst { parent: parentContext, links } = incomingTraceContext(\n\t\t\theaders,\n\t\t\tisTrustedCaller(c, options),\n\t\t);\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, links);\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 * Whether this request's `traceparent` is continued: from one of the API's\n * own origins, or from an internal caller.\n */\nfunction isTrustedCaller(\n\tc: Context,\n\toptions: HonoTelemetryMiddlewareOptions,\n): boolean {\n\tconst origin = c.req.header('origin');\n\tif (origin) {\n\t\tconst trusted =\n\t\t\ttypeof options.trustedOrigins === 'function'\n\t\t\t\t? options.trustedOrigins()\n\t\t\t\t: options.trustedOrigins;\n\t\treturn isTrustedOrigin(origin, trusted);\n\t}\n\n\tconst internal = options.internalCallers ?? true;\n\tif (typeof internal === 'function') return internal(c);\n\tif (!internal) return false;\n\treturn isInternalCaller({\n\t\theaders: c.req.raw.headers,\n\t\tremoteAddress: remoteAddressOf(c),\n\t});\n}\n\n/** The TCP peer's address under `@hono/node-server`, or undefined. */\nfunction remoteAddressOf(c: Context): string | undefined {\n\tconst env = c.env as\n\t\t| { incoming?: { socket?: { remoteAddress?: string } } }\n\t\t| undefined;\n\treturn env?.incoming?.socket?.remoteAddress;\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
|
+
{"version":3,"file":"hono.cjs","names":["incomingTraceContext","createHttpServerSpan","trace","context","isTrustedOrigin","isInternalCaller","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\ttype HttpSpanAttributes,\n\tincomingTraceContext,\n} from './http';\nimport { recordHttpServerRequest } from './metrics';\nimport { isInternalCaller, isTrustedOrigin } from './trust';\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\t/**\n\t * The origins whose `traceparent` is continued: the API's own sites, the\n\t * same list its CORS allows. A request from any other origin starts a new\n\t * trace linked to the one it claimed.\n\t *\n\t * A function is read on each request, for a server that learns its origins\n\t * only once the app has loaded its configuration.\n\t */\n\ttrustedOrigins?: readonly string[] | (() => readonly string[] | undefined);\n\n\t/**\n\t * Whether a request with no `Origin` from an internal caller continues its\n\t * `traceparent` — another service on the private network (see `./trust`):\n\t * no `Origin`, no proxy's forwarding header, and a loopback or private\n\t * peer address, read from `@hono/node-server`'s socket.\n\t *\n\t * `false` trusts no caller without an origin; a function decides itself.\n\t * @default true\n\t */\n\tinternalCallers?: boolean | ((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\t// Continued only from a trusted caller; anyone else gets a new trace\n\t\t// that links to the one they claimed.\n\t\tconst { parent: parentContext, links } = incomingTraceContext(\n\t\t\theaders,\n\t\t\tisTrustedCaller(c, options),\n\t\t);\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 startedAt = performance.now();\n\t\tconst span = createHttpServerSpan(attrs, parentContext, links);\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\trecordHttpServerRequest({\n\t\t\t\tmethod: c.req.method,\n\t\t\t\troute: matchedRoute(c),\n\t\t\t\tscheme: attrs.scheme,\n\t\t\t\tstatusCode,\n\t\t\t\tdurationMs: performance.now() - startedAt,\n\t\t\t\t...(c.error && { error: c.error }),\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tnameAfterRoute(c, span);\n\t\t\tconst failure = error instanceof Error ? error : new Error(String(error));\n\t\t\tendHttpSpan(span, { statusCode: 500 }, failure);\n\t\t\trecordHttpServerRequest({\n\t\t\t\tmethod: c.req.method,\n\t\t\t\troute: matchedRoute(c),\n\t\t\t\tscheme: attrs.scheme,\n\t\t\t\tstatusCode: 500,\n\t\t\t\tdurationMs: performance.now() - startedAt,\n\t\t\t\terror: failure,\n\t\t\t});\n\t\t\tthrow error;\n\t\t}\n\t};\n}\n\n/**\n * Whether this request's `traceparent` is continued: from one of the API's\n * own origins, or from an internal caller.\n */\nfunction isTrustedCaller(\n\tc: Context,\n\toptions: HonoTelemetryMiddlewareOptions,\n): boolean {\n\tconst origin = c.req.header('origin');\n\tif (origin) {\n\t\tconst trusted =\n\t\t\ttypeof options.trustedOrigins === 'function'\n\t\t\t\t? options.trustedOrigins()\n\t\t\t\t: options.trustedOrigins;\n\t\treturn isTrustedOrigin(origin, trusted);\n\t}\n\n\tconst internal = options.internalCallers ?? true;\n\tif (typeof internal === 'function') return internal(c);\n\tif (!internal) return false;\n\treturn isInternalCaller({\n\t\theaders: c.req.raw.headers,\n\t\tremoteAddress: remoteAddressOf(c),\n\t});\n}\n\n/** The TCP peer's address under `@hono/node-server`, or undefined. */\nfunction remoteAddressOf(c: Context): string | undefined {\n\tconst env = c.env as\n\t\t| { incoming?: { socket?: { remoteAddress?: string } } }\n\t\t| undefined;\n\treturn env?.incoming?.socket?.remoteAddress;\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":";;;;;;iBAsBiB;;;;;EAKhB;;;;;EAMA;;;;EAKA,aAAa,GAAG;;;;EAKhB;;;;EAKA,cAAc,GAAG;;;;;;;;;EAUjB;;;;;;;;;;EAWA,8BAA8B,GAAG;;;;;wBAUlB,mBAAmB,GAAG,UAAU;;;;wBAOhC,wBAAwB,GAAG;;;;;;;;;;;;wBAe3B,wBACf,UAAS,iCACP;;;;;;;;;;;;;;;wBA+PmB,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":";;;;;;iBAsBiB;;;;;EAKhB;;;;;EAMA;;;;EAKA,aAAa,GAAGA;;;;EAKhB;;;;EAKA,cAAc,GAAGA;;;;;;;;;EAUjB;;;;;;;;;;EAWA,8BAA8B,GAAGA;;;;;wBAUlB,mBAAmB,GAAGA,YAAU;;;;wBAOhC,wBAAwB,GAAGA;;;;;;;;;;;;wBAe3B,wBACf,UAAS,iCACP;;;;;;;;;;;;;;;wBA+PmB,oBAAoB,GACzC,GAAGA,WACH,UAAU,QAAQ,KAChB,QAAQ"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createHttpServerSpan, endHttpSpan, incomingTraceContext } from "./http.mjs";
|
|
2
|
+
import { recordHttpServerRequest } from "./metrics.mjs";
|
|
2
3
|
import { isInternalCaller, isTrustedOrigin } from "./trust.mjs";
|
|
3
4
|
import { context, trace } from "@opentelemetry/api";
|
|
4
5
|
import { routePath } from "hono/route";
|
|
@@ -38,6 +39,7 @@ function honoTelemetryMiddleware(options = {}) {
|
|
|
38
39
|
});
|
|
39
40
|
const { parent: parentContext, links } = incomingTraceContext(headers, isTrustedCaller(c, options));
|
|
40
41
|
const attrs = buildHonoSpanAttributes(c, options);
|
|
42
|
+
const startedAt = performance.now();
|
|
41
43
|
const span = createHttpServerSpan(attrs, parentContext, links);
|
|
42
44
|
const spanContext = trace.setSpan(parentContext, span);
|
|
43
45
|
c.set(SPAN_KEY, span);
|
|
@@ -57,9 +59,26 @@ function honoTelemetryMiddleware(options = {}) {
|
|
|
57
59
|
if (body) span.setAttribute("http.response.body", body.length > 4096 ? `${body.slice(0, 4096)}...` : body);
|
|
58
60
|
} catch {}
|
|
59
61
|
endHttpSpan(span, { statusCode }, c.error);
|
|
62
|
+
recordHttpServerRequest({
|
|
63
|
+
method: c.req.method,
|
|
64
|
+
route: matchedRoute(c),
|
|
65
|
+
scheme: attrs.scheme,
|
|
66
|
+
statusCode,
|
|
67
|
+
durationMs: performance.now() - startedAt,
|
|
68
|
+
...c.error && { error: c.error }
|
|
69
|
+
});
|
|
60
70
|
} catch (error) {
|
|
61
71
|
nameAfterRoute(c, span);
|
|
62
|
-
|
|
72
|
+
const failure = error instanceof Error ? error : new Error(String(error));
|
|
73
|
+
endHttpSpan(span, { statusCode: 500 }, failure);
|
|
74
|
+
recordHttpServerRequest({
|
|
75
|
+
method: c.req.method,
|
|
76
|
+
route: matchedRoute(c),
|
|
77
|
+
scheme: attrs.scheme,
|
|
78
|
+
statusCode: 500,
|
|
79
|
+
durationMs: performance.now() - startedAt,
|
|
80
|
+
error: failure
|
|
81
|
+
});
|
|
63
82
|
throw error;
|
|
64
83
|
}
|
|
65
84
|
};
|
|
@@ -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 { routePath } from 'hono/route';\nimport {\n\tcreateHttpServerSpan,\n\tendHttpSpan,\n\ttype HttpSpanAttributes,\n\tincomingTraceContext,\n} from './http';\nimport { isInternalCaller, isTrustedOrigin } from './trust';\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\t/**\n\t * The origins whose `traceparent` is continued: the API's own sites, the\n\t * same list its CORS allows. A request from any other origin starts a new\n\t * trace linked to the one it claimed.\n\t *\n\t * A function is read on each request, for a server that learns its origins\n\t * only once the app has loaded its configuration.\n\t */\n\ttrustedOrigins?: readonly string[] | (() => readonly string[] | undefined);\n\n\t/**\n\t * Whether a request with no `Origin` from an internal caller continues its\n\t * `traceparent` — another service on the private network (see `./trust`):\n\t * no `Origin`, no proxy's forwarding header, and a loopback or private\n\t * peer address, read from `@hono/node-server`'s socket.\n\t *\n\t * `false` trusts no caller without an origin; a function decides itself.\n\t * @default true\n\t */\n\tinternalCallers?: boolean | ((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\t// Continued only from a trusted caller; anyone else gets a new trace\n\t\t// that links to the one they claimed.\n\t\tconst { parent: parentContext, links } = incomingTraceContext(\n\t\t\theaders,\n\t\t\tisTrustedCaller(c, options),\n\t\t);\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, links);\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 * Whether this request's `traceparent` is continued: from one of the API's\n * own origins, or from an internal caller.\n */\nfunction isTrustedCaller(\n\tc: Context,\n\toptions: HonoTelemetryMiddlewareOptions,\n): boolean {\n\tconst origin = c.req.header('origin');\n\tif (origin) {\n\t\tconst trusted =\n\t\t\ttypeof options.trustedOrigins === 'function'\n\t\t\t\t? options.trustedOrigins()\n\t\t\t\t: options.trustedOrigins;\n\t\treturn isTrustedOrigin(origin, trusted);\n\t}\n\n\tconst internal = options.internalCallers ?? true;\n\tif (typeof internal === 'function') return internal(c);\n\tif (!internal) return false;\n\treturn isInternalCaller({\n\t\theaders: c.req.raw.headers,\n\t\tremoteAddress: remoteAddressOf(c),\n\t});\n}\n\n/** The TCP peer's address under `@hono/node-server`, or undefined. */\nfunction remoteAddressOf(c: Context): string | undefined {\n\tconst env = c.env as\n\t\t| { incoming?: { socket?: { remoteAddress?: string } } }\n\t\t| undefined;\n\treturn env?.incoming?.socket?.remoteAddress;\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
|
+
{"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\ttype HttpSpanAttributes,\n\tincomingTraceContext,\n} from './http';\nimport { recordHttpServerRequest } from './metrics';\nimport { isInternalCaller, isTrustedOrigin } from './trust';\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\t/**\n\t * The origins whose `traceparent` is continued: the API's own sites, the\n\t * same list its CORS allows. A request from any other origin starts a new\n\t * trace linked to the one it claimed.\n\t *\n\t * A function is read on each request, for a server that learns its origins\n\t * only once the app has loaded its configuration.\n\t */\n\ttrustedOrigins?: readonly string[] | (() => readonly string[] | undefined);\n\n\t/**\n\t * Whether a request with no `Origin` from an internal caller continues its\n\t * `traceparent` — another service on the private network (see `./trust`):\n\t * no `Origin`, no proxy's forwarding header, and a loopback or private\n\t * peer address, read from `@hono/node-server`'s socket.\n\t *\n\t * `false` trusts no caller without an origin; a function decides itself.\n\t * @default true\n\t */\n\tinternalCallers?: boolean | ((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\t// Continued only from a trusted caller; anyone else gets a new trace\n\t\t// that links to the one they claimed.\n\t\tconst { parent: parentContext, links } = incomingTraceContext(\n\t\t\theaders,\n\t\t\tisTrustedCaller(c, options),\n\t\t);\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 startedAt = performance.now();\n\t\tconst span = createHttpServerSpan(attrs, parentContext, links);\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\trecordHttpServerRequest({\n\t\t\t\tmethod: c.req.method,\n\t\t\t\troute: matchedRoute(c),\n\t\t\t\tscheme: attrs.scheme,\n\t\t\t\tstatusCode,\n\t\t\t\tdurationMs: performance.now() - startedAt,\n\t\t\t\t...(c.error && { error: c.error }),\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tnameAfterRoute(c, span);\n\t\t\tconst failure = error instanceof Error ? error : new Error(String(error));\n\t\t\tendHttpSpan(span, { statusCode: 500 }, failure);\n\t\t\trecordHttpServerRequest({\n\t\t\t\tmethod: c.req.method,\n\t\t\t\troute: matchedRoute(c),\n\t\t\t\tscheme: attrs.scheme,\n\t\t\t\tstatusCode: 500,\n\t\t\t\tdurationMs: performance.now() - startedAt,\n\t\t\t\terror: failure,\n\t\t\t});\n\t\t\tthrow error;\n\t\t}\n\t};\n}\n\n/**\n * Whether this request's `traceparent` is continued: from one of the API's\n * own origins, or from an internal caller.\n */\nfunction isTrustedCaller(\n\tc: Context,\n\toptions: HonoTelemetryMiddlewareOptions,\n): boolean {\n\tconst origin = c.req.header('origin');\n\tif (origin) {\n\t\tconst trusted =\n\t\t\ttypeof options.trustedOrigins === 'function'\n\t\t\t\t? options.trustedOrigins()\n\t\t\t\t: options.trustedOrigins;\n\t\treturn isTrustedOrigin(origin, trusted);\n\t}\n\n\tconst internal = options.internalCallers ?? true;\n\tif (typeof internal === 'function') return internal(c);\n\tif (!internal) return false;\n\treturn isInternalCaller({\n\t\theaders: c.req.raw.headers,\n\t\tremoteAddress: remoteAddressOf(c),\n\t});\n}\n\n/** The TCP peer's address under `@hono/node-server`, or undefined. */\nfunction remoteAddressOf(c: Context): string | undefined {\n\tconst env = c.env as\n\t\t| { incoming?: { socket?: { remoteAddress?: string } } }\n\t\t| undefined;\n\treturn env?.incoming?.socket?.remoteAddress;\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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"}
|
|
@@ -2,13 +2,16 @@ 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_metrics = require("./metrics.cjs");
|
|
5
6
|
const require_instrumentation_trust = require("./trust.cjs");
|
|
6
7
|
const require_instrumentation_hono = require("./hono.cjs");
|
|
7
8
|
const require_instrumentation_middleware = require("./middleware.cjs");
|
|
8
9
|
const require_instrumentation_otel = require("./otel.cjs");
|
|
9
10
|
const require_instrumentation_sampler = require("./sampler.cjs");
|
|
10
11
|
const require_instrumentation_setup = require("./setup.cjs");
|
|
12
|
+
exports.HTTP_SERVER_REQUEST_DURATION = require_instrumentation_metrics.HTTP_SERVER_REQUEST_DURATION;
|
|
11
13
|
exports.InvalidSampleRatio = require_instrumentation_setup.InvalidSampleRatio;
|
|
14
|
+
exports.MetricFlushTimeout = require_instrumentation_core.MetricFlushTimeout;
|
|
12
15
|
exports.NoopSpanProcessor = require_instrumentation_core.NoopSpanProcessor;
|
|
13
16
|
exports.OTelTelemetry = require_instrumentation_otel.OTelTelemetry;
|
|
14
17
|
exports.addSpanEvent = require_instrumentation_tracing.addSpanEvent;
|
|
@@ -38,7 +41,9 @@ exports.isTelemetryInitialized = require_instrumentation_core.isTelemetryInitial
|
|
|
38
41
|
exports.isTracingEnabled = require_instrumentation_http.isTracingEnabled;
|
|
39
42
|
exports.isTrustedOrigin = require_instrumentation_trust.isTrustedOrigin;
|
|
40
43
|
exports.recordException = require_instrumentation_tracing.recordException;
|
|
44
|
+
exports.recordHttpServerRequest = require_instrumentation_metrics.recordHttpServerRequest;
|
|
41
45
|
exports.setGlobalLogProcessor = require_instrumentation_core.setGlobalLogProcessor;
|
|
46
|
+
exports.setGlobalMetricReader = require_instrumentation_core.setGlobalMetricReader;
|
|
42
47
|
exports.setGlobalSpanProcessor = require_instrumentation_core.setGlobalSpanProcessor;
|
|
43
48
|
exports.setSpanAttributes = require_instrumentation_tracing.setSpanAttributes;
|
|
44
49
|
exports.setupTelemetry = require_instrumentation_setup.setupTelemetry;
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { SpanProcessorOptions, SpanProcessorStrategy } from "../adapters/types.cjs";
|
|
2
|
-
import { NoopSpanProcessor, createSpanProcessor, enableTelemetryDebug, flushTelemetry, getRecommendedStrategy, isTelemetryInitialized, setGlobalLogProcessor, setGlobalSpanProcessor, withTelemetryFlush } from "./core.cjs";
|
|
2
|
+
import { FlushableMetricReader, MetricFlushTimeout, NoopSpanProcessor, createSpanProcessor, enableTelemetryDebug, flushTelemetry, getRecommendedStrategy, isTelemetryInitialized, setGlobalLogProcessor, setGlobalMetricReader, 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
5
|
import { EndpointAttributes, HttpRequestAttributes, HttpResponseAttributes, HttpSpanAttributes, IncomingTraceContext, UserAttributes, createChildSpan, createHttpServerSpan, endHttpSpan, extractTraceContext, getConstructsTracer, incomingTraceContext, injectTraceContext, isTracingEnabled, toOtelAttributes, withChildSpan, withHttpSpan } from "./http.cjs";
|
|
6
|
+
import { HTTP_SERVER_REQUEST_DURATION, HttpServerRequestMetric, recordHttpServerRequest } from "./metrics.cjs";
|
|
6
7
|
import { TelemetryMiddlewareOptions, getContextFromEvent, getSpanFromEvent, telemetryMiddleware, withEventContext } from "./middleware.cjs";
|
|
7
8
|
import { OTelTelemetry, OTelTelemetryOptions } from "./otel.cjs";
|
|
8
9
|
import { traceSampler, traceSamplerFromEnv } from "./sampler.cjs";
|
|
9
10
|
import { InvalidSampleRatio, TelemetryOptions, setupTelemetry, shutdownTelemetry } from "./setup.cjs";
|
|
10
11
|
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 };
|
|
12
|
+
export { type EndpointAttributes, type FlushableMetricReader, HTTP_SERVER_REQUEST_DURATION, type HonoTelemetryMiddlewareOptions, type HttpRequestAttributes, type HttpResponseAttributes, type HttpServerRequestMetric, type HttpSpanAttributes, type IncomingRequestFacts, type IncomingTraceContext, InvalidSampleRatio, MetricFlushTimeout, 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, recordHttpServerRequest, setGlobalLogProcessor, setGlobalMetricReader, setGlobalSpanProcessor, setSpanAttributes, setupTelemetry, shutdownTelemetry, telemetryMiddleware, toOtelAttributes, traceSampler, traceSamplerFromEnv, withChildSpan, withEventContext, withHonoSpanContext, withHttpSpan, withSpan, withSpanSync, withTelemetryFlush };
|