@goopil/clusterkit-otlp-meter 1.1.2 → 1.2.0
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 +8 -0
- package/dist/index.cjs +16 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +10 -2
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +10 -2
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +16 -4
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -61,6 +61,7 @@ orchestrator.use(otlp).run(async () => {
|
|
|
61
61
|
| Option | Type | Default | Description |
|
|
62
62
|
|--------|------|---------|-------------|
|
|
63
63
|
| `endpoint` | `string` | `http://localhost:4318/v1/metrics` (HTTP) or `localhost:4317` (gRPC) | OTLP collector endpoint URL |
|
|
64
|
+
| `headers` | `Record<string, string>` | — | Custom headers attached to every export request (e.g. `Authorization` for authenticated collectors). Applies to `http` only — with `grpc` they are ignored (warning logged); use exporter `metadata` instead |
|
|
64
65
|
| `protocol` | `'http' \| 'grpc'` | `'http'` | OTLP transport protocol |
|
|
65
66
|
| `instrumentation` | `boolean` | `true` | Collect Node.js host/process metrics |
|
|
66
67
|
| `prefix` | `string` | `'clusterkit.'` | Metric name prefix |
|
|
@@ -94,6 +95,13 @@ httpRequests.add(1);
|
|
|
94
95
|
This works in both primary and worker processes — each process has its own provider
|
|
95
96
|
pushing to the same collector endpoint.
|
|
96
97
|
|
|
98
|
+
If the host application has already registered its own OpenTelemetry global meter
|
|
99
|
+
provider, the plugin does **not** replace it: it logs a warning and keeps using its own
|
|
100
|
+
provider for the `clusterkit.*` metrics.
|
|
101
|
+
|
|
102
|
+
When the plugin did register the global provider itself, `uninstall()` releases that
|
|
103
|
+
registration so the application no longer resolves meters from the shut-down provider.
|
|
104
|
+
|
|
97
105
|
## Metrics exposed
|
|
98
106
|
|
|
99
107
|
With the default prefix (`clusterkit.`):
|
package/dist/index.cjs
CHANGED
|
@@ -38,7 +38,7 @@ let _opentelemetry_sdk_metrics = require("@opentelemetry/sdk-metrics");
|
|
|
38
38
|
let _opentelemetry_semantic_conventions = require("@opentelemetry/semantic-conventions");
|
|
39
39
|
|
|
40
40
|
//#region package.json
|
|
41
|
-
var version = "1.
|
|
41
|
+
var version = "1.2.0";
|
|
42
42
|
|
|
43
43
|
//#endregion
|
|
44
44
|
//#region src/index.ts
|
|
@@ -52,7 +52,7 @@ function isMissingModuleError(err) {
|
|
|
52
52
|
return code === "ERR_MODULE_NOT_FOUND" || code === "MODULE_NOT_FOUND";
|
|
53
53
|
}
|
|
54
54
|
function createOtlpMeterPlugin(options = {}) {
|
|
55
|
-
const { protocol = "http", instrumentation = true, prefix = "clusterkit.", attributes = {}, exportIntervalMs = 6e4, serviceName = "clusterkit", endpoint } = options;
|
|
55
|
+
const { protocol = "http", instrumentation = true, prefix = "clusterkit.", attributes = {}, exportIntervalMs = 6e4, serviceName = "clusterkit", endpoint, headers } = options;
|
|
56
56
|
if (!Number.isFinite(exportIntervalMs) || exportIntervalMs < 1e3) throw new TypeError("otlp-meter plugin: exportIntervalMs must be a finite number >= 1000 (minimum 1s to avoid flooding the collector)");
|
|
57
57
|
const resolvedEndpoint = endpoint ?? (protocol === "grpc" ? DEFAULT_GRPC_ENDPOINT : DEFAULT_HTTP_ENDPOINT);
|
|
58
58
|
if (protocol === "http") try {
|
|
@@ -62,6 +62,7 @@ function createOtlpMeterPlugin(options = {}) {
|
|
|
62
62
|
}
|
|
63
63
|
let meterProvider;
|
|
64
64
|
let isShutdown = false;
|
|
65
|
+
let pluginSetGlobalProvider = false;
|
|
65
66
|
let pluginLog = null;
|
|
66
67
|
let primaryOrchestrator;
|
|
67
68
|
const primaryListeners = [];
|
|
@@ -73,8 +74,14 @@ function createOtlpMeterPlugin(options = {}) {
|
|
|
73
74
|
};
|
|
74
75
|
async function createExporter() {
|
|
75
76
|
const exporterModuleName = protocol === "grpc" ? "@opentelemetry/exporter-metrics-otlp-grpc" : "@opentelemetry/exporter-metrics-otlp-http";
|
|
77
|
+
if (protocol === "grpc" && headers && Object.keys(headers).length > 0) pluginLog?.warn("headers are not supported by the gRPC exporter — configure metadata via the exporter's own options or use protocol: 'http'");
|
|
76
78
|
try {
|
|
77
|
-
|
|
79
|
+
const mod = await import(exporterModuleName);
|
|
80
|
+
const config = protocol === "grpc" ? { url: resolvedEndpoint } : {
|
|
81
|
+
url: resolvedEndpoint,
|
|
82
|
+
headers
|
|
83
|
+
};
|
|
84
|
+
return new mod.OTLPMetricExporter(config);
|
|
78
85
|
} catch (err) {
|
|
79
86
|
if (isMissingModuleError(err)) throw new Error(`otlp-meter plugin: protocol '${protocol}' requires ${exporterModuleName} — install it or use protocol '${protocol === "grpc" ? "http" : "grpc"}'`);
|
|
80
87
|
throw err;
|
|
@@ -120,7 +127,8 @@ function createOtlpMeterPlugin(options = {}) {
|
|
|
120
127
|
resource,
|
|
121
128
|
readers: [metricReader]
|
|
122
129
|
});
|
|
123
|
-
_opentelemetry_api.metrics.setGlobalMeterProvider(meterProvider);
|
|
130
|
+
if (_opentelemetry_api.metrics.setGlobalMeterProvider(meterProvider)) pluginSetGlobalProvider = true;
|
|
131
|
+
else log?.warn("A global OpenTelemetry meter provider is already registered — leaving it untouched; clusterkit meters use the plugin's own provider");
|
|
124
132
|
const meter = meterProvider.getMeter("@goopil/clusterkit", PLUGIN_VERSION);
|
|
125
133
|
if (node_cluster.default.isPrimary) {
|
|
126
134
|
clearPrimaryListeners();
|
|
@@ -159,6 +167,10 @@ function createOtlpMeterPlugin(options = {}) {
|
|
|
159
167
|
},
|
|
160
168
|
async uninstall() {
|
|
161
169
|
clearPrimaryListeners();
|
|
170
|
+
if (pluginSetGlobalProvider) {
|
|
171
|
+
_opentelemetry_api.metrics.disable();
|
|
172
|
+
pluginSetGlobalProvider = false;
|
|
173
|
+
}
|
|
162
174
|
await shutdownProvider();
|
|
163
175
|
},
|
|
164
176
|
async shutdown() {
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["pkgJson.version","withLoggerPrefix","resourceFromAttributes","ATTR_SERVICE_NAME","ATTR_SERVICE_INSTANCE_ID","randomUUID","os","PeriodicExportingMetricReader","MeterProvider","cluster"],"sources":["../package.json","../src/index.ts"],"sourcesContent":["","import cluster from \"node:cluster\";\nimport { randomUUID } from \"node:crypto\";\nimport os from \"node:os\";\nimport { type Logger, type Orchestrator, type ResolvedConfig, withLoggerPrefix } from \"@goopil/clusterkit\";\nimport { metrics } from \"@opentelemetry/api\";\nimport { resourceFromAttributes } from \"@opentelemetry/resources\";\nimport { MeterProvider, PeriodicExportingMetricReader, type PushMetricExporter } from \"@opentelemetry/sdk-metrics\";\nimport { ATTR_SERVICE_INSTANCE_ID, ATTR_SERVICE_NAME } from \"@opentelemetry/semantic-conventions\";\nimport pkgJson from \"../package.json\" with { type: \"json\" };\nimport type { OtlpMeterPlugin, OtlpMeterPluginOptions } from \"./types.js\";\n\nexport type { OtlpMeterPlugin, OtlpMeterPluginOptions } from \"./types.js\";\n\ntype PrimaryEvent = \"worker:crash\" | \"worker:restart\" | \"circuit-breaker:tripped\";\n\nconst DEFAULT_HTTP_ENDPOINT = \"http://localhost:4318/v1/metrics\";\nconst DEFAULT_GRPC_ENDPOINT = \"localhost:4317\";\n\nconst ATTR_HOST_NAME = \"host.name\";\nconst ATTR_PROCESS_PID = \"process.pid\";\n\nconst PLUGIN_VERSION: string = pkgJson.version;\n\nfunction isMissingModuleError(err: unknown): boolean {\n const code = (err as NodeJS.ErrnoException | undefined)?.code;\n return code === \"ERR_MODULE_NOT_FOUND\" || code === \"MODULE_NOT_FOUND\";\n}\n\nexport function createOtlpMeterPlugin(options: OtlpMeterPluginOptions = {}): OtlpMeterPlugin {\n const {\n protocol = \"http\",\n instrumentation = true,\n prefix = \"clusterkit.\",\n attributes = {},\n exportIntervalMs = 60_000,\n serviceName = \"clusterkit\",\n endpoint,\n } = options;\n\n if (!Number.isFinite(exportIntervalMs) || exportIntervalMs < 1_000) {\n throw new TypeError(\n \"otlp-meter plugin: exportIntervalMs must be a finite number >= 1000 (minimum 1s to avoid flooding the collector)\",\n );\n }\n\n const resolvedEndpoint = endpoint ?? (protocol === \"grpc\" ? DEFAULT_GRPC_ENDPOINT : DEFAULT_HTTP_ENDPOINT);\n\n if (protocol === \"http\") {\n try {\n new URL(resolvedEndpoint);\n } catch {\n throw new TypeError(`otlp-meter plugin: invalid endpoint URL \"${resolvedEndpoint}\"`);\n }\n }\n\n let meterProvider: MeterProvider | undefined;\n let isShutdown = false;\n let pluginLog: Logger | null = null;\n let primaryOrchestrator: Orchestrator | undefined;\n const primaryListeners: Array<{ event: PrimaryEvent; listener: () => void }> = [];\n\n const clearPrimaryListeners = (): void => {\n if (!primaryOrchestrator) return;\n for (const { event, listener } of primaryListeners) {\n primaryOrchestrator.off(event, listener);\n }\n primaryListeners.length = 0;\n primaryOrchestrator = undefined;\n };\n\n async function createExporter(): Promise<PushMetricExporter> {\n const exporterModuleName =\n protocol === \"grpc\" ? \"@opentelemetry/exporter-metrics-otlp-grpc\" : \"@opentelemetry/exporter-metrics-otlp-http\";\n\n try {\n const mod = await import(exporterModuleName);\n return new mod.OTLPMetricExporter({ url: resolvedEndpoint }) as PushMetricExporter;\n } catch (err) {\n if (isMissingModuleError(err)) {\n throw new Error(\n `otlp-meter plugin: protocol '${protocol}' requires ${exporterModuleName} — install it or use protocol '${protocol === \"grpc\" ? \"http\" : \"grpc\"}'`,\n );\n }\n throw err;\n }\n }\n\n async function startHostMetrics(provider: MeterProvider): Promise<void> {\n try {\n const mod = await import(\"@opentelemetry/host-metrics\");\n const hostMetrics = new mod.HostMetrics({ meterProvider: provider });\n hostMetrics.start();\n } catch (err) {\n if (isMissingModuleError(err)) {\n pluginLog?.warn(\"host-metrics package not installed — skipping process metrics\");\n } else {\n throw err;\n }\n }\n }\n\n async function shutdownProvider(): Promise<void> {\n if (isShutdown || !meterProvider) return;\n isShutdown = true;\n await meterProvider.shutdown();\n meterProvider = undefined;\n }\n\n return {\n name: \"otlp-meter\",\n\n get meterProvider() {\n return meterProvider;\n },\n\n async install(orchestrator: Orchestrator, logger: Logger | null, _config: ResolvedConfig): Promise<void> {\n const log = withLoggerPrefix(logger, \"clusterkit:otlp-meter\");\n pluginLog = log;\n\n // Reinstalling the same instance after a shutdown must not orphan the old\n // provider or leave the latch stuck at `true` (the new provider's shutdown\n // would otherwise be a permanent no-op).\n if (meterProvider) await shutdownProvider();\n isShutdown = false;\n\n const resource = resourceFromAttributes({\n [ATTR_SERVICE_NAME]: serviceName,\n [ATTR_SERVICE_INSTANCE_ID]: randomUUID(),\n [ATTR_HOST_NAME]: os.hostname(),\n [ATTR_PROCESS_PID]: process.pid,\n ...attributes,\n });\n\n const exporter = await createExporter();\n\n const metricReader = new PeriodicExportingMetricReader({\n exporter,\n exportIntervalMillis: exportIntervalMs,\n });\n\n meterProvider = new MeterProvider({\n resource,\n readers: [metricReader],\n });\n\n metrics.setGlobalMeterProvider(meterProvider);\n\n const meter = meterProvider.getMeter(\"@goopil/clusterkit\", PLUGIN_VERSION);\n\n if (cluster.isPrimary) {\n clearPrimaryListeners();\n log?.debug(\"Plugin installed on primary process\");\n primaryOrchestrator = orchestrator;\n\n const activeWorkersGauge = meter.createObservableGauge(`${prefix}active_workers`, {\n description: \"Number of active cluster workers\",\n });\n activeWorkersGauge.addCallback((result) => {\n result.observe(orchestrator.getMetrics().activeWorkers);\n });\n\n const workerRestartsCounter = meter.createCounter(`${prefix}worker.restarts`, {\n description: \"Total number of worker restarts\",\n });\n const workerCrashesCounter = meter.createCounter(`${prefix}worker.crashes`, {\n description: \"Total number of worker crashes\",\n });\n const circuitBreakerTripsCounter = meter.createCounter(`${prefix}circuit_breaker.trips`, {\n description: \"Total number of circuit breaker trips\",\n });\n\n const bind = (event: PrimaryEvent, listener: () => void): void => {\n orchestrator.on(event, listener);\n primaryListeners.push({ event, listener });\n };\n\n bind(\"worker:crash\", () => {\n workerCrashesCounter.add(1);\n });\n bind(\"worker:restart\", () => {\n workerRestartsCounter.add(1);\n });\n bind(\"circuit-breaker:tripped\", () => {\n circuitBreakerTripsCounter.add(1);\n });\n\n const singleWorker = orchestrator.workerCount === 1;\n if (singleWorker && instrumentation) {\n await startHostMetrics(meterProvider);\n }\n } else {\n log?.debug(\"Plugin installed on worker process\");\n\n if (instrumentation) {\n await startHostMetrics(meterProvider);\n }\n }\n\n orchestrator.registerOnShutdown(async () => {\n await shutdownProvider();\n });\n },\n\n async uninstall(): Promise<void> {\n clearPrimaryListeners();\n await shutdownProvider();\n },\n\n async shutdown(): Promise<void> {\n await shutdownProvider();\n },\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["pkgJson.version","withLoggerPrefix","resourceFromAttributes","ATTR_SERVICE_NAME","ATTR_SERVICE_INSTANCE_ID","randomUUID","os","PeriodicExportingMetricReader","MeterProvider","metrics","cluster"],"sources":["../package.json","../src/index.ts"],"sourcesContent":["","import cluster from \"node:cluster\";\nimport { randomUUID } from \"node:crypto\";\nimport os from \"node:os\";\nimport { type Logger, type Orchestrator, type ResolvedConfig, withLoggerPrefix } from \"@goopil/clusterkit\";\nimport { metrics } from \"@opentelemetry/api\";\nimport { resourceFromAttributes } from \"@opentelemetry/resources\";\nimport { MeterProvider, PeriodicExportingMetricReader, type PushMetricExporter } from \"@opentelemetry/sdk-metrics\";\nimport { ATTR_SERVICE_INSTANCE_ID, ATTR_SERVICE_NAME } from \"@opentelemetry/semantic-conventions\";\nimport pkgJson from \"../package.json\" with { type: \"json\" };\nimport type { OtlpMeterPlugin, OtlpMeterPluginOptions } from \"./types.js\";\n\nexport type { OtlpMeterPlugin, OtlpMeterPluginOptions } from \"./types.js\";\n\ntype PrimaryEvent = \"worker:crash\" | \"worker:restart\" | \"circuit-breaker:tripped\";\n\nconst DEFAULT_HTTP_ENDPOINT = \"http://localhost:4318/v1/metrics\";\nconst DEFAULT_GRPC_ENDPOINT = \"localhost:4317\";\n\nconst ATTR_HOST_NAME = \"host.name\";\nconst ATTR_PROCESS_PID = \"process.pid\";\n\nconst PLUGIN_VERSION: string = pkgJson.version;\n\nfunction isMissingModuleError(err: unknown): boolean {\n const code = (err as NodeJS.ErrnoException | undefined)?.code;\n return code === \"ERR_MODULE_NOT_FOUND\" || code === \"MODULE_NOT_FOUND\";\n}\n\nexport function createOtlpMeterPlugin(options: OtlpMeterPluginOptions = {}): OtlpMeterPlugin {\n const {\n protocol = \"http\",\n instrumentation = true,\n prefix = \"clusterkit.\",\n attributes = {},\n exportIntervalMs = 60_000,\n serviceName = \"clusterkit\",\n endpoint,\n headers,\n } = options;\n\n if (!Number.isFinite(exportIntervalMs) || exportIntervalMs < 1_000) {\n throw new TypeError(\n \"otlp-meter plugin: exportIntervalMs must be a finite number >= 1000 (minimum 1s to avoid flooding the collector)\",\n );\n }\n\n const resolvedEndpoint = endpoint ?? (protocol === \"grpc\" ? DEFAULT_GRPC_ENDPOINT : DEFAULT_HTTP_ENDPOINT);\n\n if (protocol === \"http\") {\n try {\n new URL(resolvedEndpoint);\n } catch {\n throw new TypeError(`otlp-meter plugin: invalid endpoint URL \"${resolvedEndpoint}\"`);\n }\n }\n\n let meterProvider: MeterProvider | undefined;\n let isShutdown = false;\n let pluginSetGlobalProvider = false;\n let pluginLog: Logger | null = null;\n let primaryOrchestrator: Orchestrator | undefined;\n const primaryListeners: Array<{ event: PrimaryEvent; listener: () => void }> = [];\n\n const clearPrimaryListeners = (): void => {\n if (!primaryOrchestrator) return;\n for (const { event, listener } of primaryListeners) {\n primaryOrchestrator.off(event, listener);\n }\n primaryListeners.length = 0;\n primaryOrchestrator = undefined;\n };\n\n async function createExporter(): Promise<PushMetricExporter> {\n const exporterModuleName =\n protocol === \"grpc\" ? \"@opentelemetry/exporter-metrics-otlp-grpc\" : \"@opentelemetry/exporter-metrics-otlp-http\";\n\n // The gRPC exporter config omits `headers` (gRPC uses `metadata` instead):\n // forwarding them would be silently dropped, leaving exports unauthenticated.\n if (protocol === \"grpc\" && headers && Object.keys(headers).length > 0) {\n pluginLog?.warn(\n \"headers are not supported by the gRPC exporter — configure metadata via the exporter's own options or use protocol: 'http'\",\n );\n }\n\n try {\n const mod = await import(exporterModuleName);\n const config = protocol === \"grpc\" ? { url: resolvedEndpoint } : { url: resolvedEndpoint, headers };\n return new mod.OTLPMetricExporter(config) as PushMetricExporter;\n } catch (err) {\n if (isMissingModuleError(err)) {\n throw new Error(\n `otlp-meter plugin: protocol '${protocol}' requires ${exporterModuleName} — install it or use protocol '${protocol === \"grpc\" ? \"http\" : \"grpc\"}'`,\n );\n }\n throw err;\n }\n }\n\n async function startHostMetrics(provider: MeterProvider): Promise<void> {\n try {\n const mod = await import(\"@opentelemetry/host-metrics\");\n const hostMetrics = new mod.HostMetrics({ meterProvider: provider });\n hostMetrics.start();\n } catch (err) {\n if (isMissingModuleError(err)) {\n pluginLog?.warn(\"host-metrics package not installed — skipping process metrics\");\n } else {\n throw err;\n }\n }\n }\n\n async function shutdownProvider(): Promise<void> {\n if (isShutdown || !meterProvider) return;\n isShutdown = true;\n await meterProvider.shutdown();\n meterProvider = undefined;\n }\n\n return {\n name: \"otlp-meter\",\n\n get meterProvider() {\n return meterProvider;\n },\n\n async install(orchestrator: Orchestrator, logger: Logger | null, _config: ResolvedConfig): Promise<void> {\n const log = withLoggerPrefix(logger, \"clusterkit:otlp-meter\");\n pluginLog = log;\n\n // Reinstalling the same instance after a shutdown must not orphan the old\n // provider or leave the latch stuck at `true` (the new provider's shutdown\n // would otherwise be a permanent no-op).\n if (meterProvider) await shutdownProvider();\n isShutdown = false;\n\n const resource = resourceFromAttributes({\n [ATTR_SERVICE_NAME]: serviceName,\n [ATTR_SERVICE_INSTANCE_ID]: randomUUID(),\n [ATTR_HOST_NAME]: os.hostname(),\n [ATTR_PROCESS_PID]: process.pid,\n ...attributes,\n });\n\n const exporter = await createExporter();\n\n const metricReader = new PeriodicExportingMetricReader({\n exporter,\n exportIntervalMillis: exportIntervalMs,\n });\n\n meterProvider = new MeterProvider({\n resource,\n readers: [metricReader],\n });\n\n // setGlobalMeterProvider() refuses to override an existing registration\n // and returns false — never clobber the app's own OTel setup.\n if (metrics.setGlobalMeterProvider(meterProvider)) {\n pluginSetGlobalProvider = true;\n } else {\n log?.warn(\n \"A global OpenTelemetry meter provider is already registered — leaving it untouched; clusterkit meters use the plugin's own provider\",\n );\n }\n\n const meter = meterProvider.getMeter(\"@goopil/clusterkit\", PLUGIN_VERSION);\n\n if (cluster.isPrimary) {\n clearPrimaryListeners();\n log?.debug(\"Plugin installed on primary process\");\n primaryOrchestrator = orchestrator;\n\n const activeWorkersGauge = meter.createObservableGauge(`${prefix}active_workers`, {\n description: \"Number of active cluster workers\",\n });\n activeWorkersGauge.addCallback((result) => {\n result.observe(orchestrator.getMetrics().activeWorkers);\n });\n\n const workerRestartsCounter = meter.createCounter(`${prefix}worker.restarts`, {\n description: \"Total number of worker restarts\",\n });\n const workerCrashesCounter = meter.createCounter(`${prefix}worker.crashes`, {\n description: \"Total number of worker crashes\",\n });\n const circuitBreakerTripsCounter = meter.createCounter(`${prefix}circuit_breaker.trips`, {\n description: \"Total number of circuit breaker trips\",\n });\n\n const bind = (event: PrimaryEvent, listener: () => void): void => {\n orchestrator.on(event, listener);\n primaryListeners.push({ event, listener });\n };\n\n bind(\"worker:crash\", () => {\n workerCrashesCounter.add(1);\n });\n bind(\"worker:restart\", () => {\n workerRestartsCounter.add(1);\n });\n bind(\"circuit-breaker:tripped\", () => {\n circuitBreakerTripsCounter.add(1);\n });\n\n const singleWorker = orchestrator.workerCount === 1;\n if (singleWorker && instrumentation) {\n await startHostMetrics(meterProvider);\n }\n } else {\n log?.debug(\"Plugin installed on worker process\");\n\n if (instrumentation) {\n await startHostMetrics(meterProvider);\n }\n }\n\n orchestrator.registerOnShutdown(async () => {\n await shutdownProvider();\n });\n },\n\n async uninstall(): Promise<void> {\n clearPrimaryListeners();\n // If the plugin's provider became the global one, release the slot:\n // metrics.disable() is the API's public unregister and restores the exact\n // pre-install state (getMeterProvider() falls back to the noop provider).\n // Restoring the captured noop prior via setGlobalMeterProvider() instead\n // would leave the slot occupied and block the app from registering later.\n if (pluginSetGlobalProvider) {\n metrics.disable();\n pluginSetGlobalProvider = false;\n }\n await shutdownProvider();\n },\n\n async shutdown(): Promise<void> {\n await shutdownProvider();\n },\n };\n}\n"],"mappings":"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"}
|
package/dist/index.d.cts
CHANGED
|
@@ -9,6 +9,14 @@ interface OtlpMeterPluginOptions {
|
|
|
9
9
|
* If you pass a full URL, it overrides the default for the selected protocol.
|
|
10
10
|
*/
|
|
11
11
|
endpoint?: string;
|
|
12
|
+
/**
|
|
13
|
+
* Custom headers attached to every OTLP export request
|
|
14
|
+
* (e.g. `Authorization` for authenticated collectors).
|
|
15
|
+
* Applies to `protocol: 'http'` only — with `'grpc'` the gRPC exporter
|
|
16
|
+
* does not support headers; they are ignored and a warning is logged.
|
|
17
|
+
* Configure metadata on the exporter directly instead.
|
|
18
|
+
*/
|
|
19
|
+
headers?: Record<string, string>;
|
|
12
20
|
/** OTLP transport protocol. @default 'http' */
|
|
13
21
|
protocol?: "http" | "grpc";
|
|
14
22
|
/** Collect Node.js host/process metrics (CPU, memory, GC, event loop). @default true */
|
|
@@ -24,8 +32,8 @@ interface OtlpMeterPluginOptions {
|
|
|
24
32
|
}
|
|
25
33
|
interface OtlpMeterPlugin extends OrchestratorPlugin {
|
|
26
34
|
/**
|
|
27
|
-
* The OpenTelemetry MeterProvider (primary
|
|
28
|
-
*
|
|
35
|
+
* The OpenTelemetry MeterProvider (created in primary and worker processes;
|
|
36
|
+
* undefined before install).
|
|
29
37
|
*/
|
|
30
38
|
readonly meterProvider: MeterProvider | undefined;
|
|
31
39
|
/**
|
package/dist/index.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/types.ts","../src/index.ts"],"mappings":";;;UAGiB;;;;;;;EAOf;;
|
|
1
|
+
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/types.ts","../src/index.ts"],"mappings":";;;UAGiB;;;;;;;EAOf;;;;;;;;EASA,UAAU;;EAGV;;EAGA;;EAGA;;EAGA,aAAa;;EAGb;;EAGA;;UAGe,wBAAwB;;;;;WAK9B,eAAe;;;;;EAMxB,YAAY;;;;iBCvBE,sBAAsB,UAAS,yBAA8B"}
|
package/dist/index.d.mts
CHANGED
|
@@ -9,6 +9,14 @@ interface OtlpMeterPluginOptions {
|
|
|
9
9
|
* If you pass a full URL, it overrides the default for the selected protocol.
|
|
10
10
|
*/
|
|
11
11
|
endpoint?: string;
|
|
12
|
+
/**
|
|
13
|
+
* Custom headers attached to every OTLP export request
|
|
14
|
+
* (e.g. `Authorization` for authenticated collectors).
|
|
15
|
+
* Applies to `protocol: 'http'` only — with `'grpc'` the gRPC exporter
|
|
16
|
+
* does not support headers; they are ignored and a warning is logged.
|
|
17
|
+
* Configure metadata on the exporter directly instead.
|
|
18
|
+
*/
|
|
19
|
+
headers?: Record<string, string>;
|
|
12
20
|
/** OTLP transport protocol. @default 'http' */
|
|
13
21
|
protocol?: "http" | "grpc";
|
|
14
22
|
/** Collect Node.js host/process metrics (CPU, memory, GC, event loop). @default true */
|
|
@@ -24,8 +32,8 @@ interface OtlpMeterPluginOptions {
|
|
|
24
32
|
}
|
|
25
33
|
interface OtlpMeterPlugin extends OrchestratorPlugin {
|
|
26
34
|
/**
|
|
27
|
-
* The OpenTelemetry MeterProvider (primary
|
|
28
|
-
*
|
|
35
|
+
* The OpenTelemetry MeterProvider (created in primary and worker processes;
|
|
36
|
+
* undefined before install).
|
|
29
37
|
*/
|
|
30
38
|
readonly meterProvider: MeterProvider | undefined;
|
|
31
39
|
/**
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/types.ts","../src/index.ts"],"mappings":";;;UAGiB;;;;;;;EAOf;;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/types.ts","../src/index.ts"],"mappings":";;;UAGiB;;;;;;;EAOf;;;;;;;;EASA,UAAU;;EAGV;;EAGA;;EAGA;;EAGA,aAAa;;EAGb;;EAGA;;UAGe,wBAAwB;;;;;WAK9B,eAAe;;;;;EAMxB,YAAY;;;;iBCvBE,sBAAsB,UAAS,yBAA8B"}
|
package/dist/index.mjs
CHANGED
|
@@ -8,7 +8,7 @@ import { MeterProvider, PeriodicExportingMetricReader } from "@opentelemetry/sdk
|
|
|
8
8
|
import { ATTR_SERVICE_INSTANCE_ID, ATTR_SERVICE_NAME } from "@opentelemetry/semantic-conventions";
|
|
9
9
|
|
|
10
10
|
//#region package.json
|
|
11
|
-
var version = "1.
|
|
11
|
+
var version = "1.2.0";
|
|
12
12
|
|
|
13
13
|
//#endregion
|
|
14
14
|
//#region src/index.ts
|
|
@@ -22,7 +22,7 @@ function isMissingModuleError(err) {
|
|
|
22
22
|
return code === "ERR_MODULE_NOT_FOUND" || code === "MODULE_NOT_FOUND";
|
|
23
23
|
}
|
|
24
24
|
function createOtlpMeterPlugin(options = {}) {
|
|
25
|
-
const { protocol = "http", instrumentation = true, prefix = "clusterkit.", attributes = {}, exportIntervalMs = 6e4, serviceName = "clusterkit", endpoint } = options;
|
|
25
|
+
const { protocol = "http", instrumentation = true, prefix = "clusterkit.", attributes = {}, exportIntervalMs = 6e4, serviceName = "clusterkit", endpoint, headers } = options;
|
|
26
26
|
if (!Number.isFinite(exportIntervalMs) || exportIntervalMs < 1e3) throw new TypeError("otlp-meter plugin: exportIntervalMs must be a finite number >= 1000 (minimum 1s to avoid flooding the collector)");
|
|
27
27
|
const resolvedEndpoint = endpoint ?? (protocol === "grpc" ? DEFAULT_GRPC_ENDPOINT : DEFAULT_HTTP_ENDPOINT);
|
|
28
28
|
if (protocol === "http") try {
|
|
@@ -32,6 +32,7 @@ function createOtlpMeterPlugin(options = {}) {
|
|
|
32
32
|
}
|
|
33
33
|
let meterProvider;
|
|
34
34
|
let isShutdown = false;
|
|
35
|
+
let pluginSetGlobalProvider = false;
|
|
35
36
|
let pluginLog = null;
|
|
36
37
|
let primaryOrchestrator;
|
|
37
38
|
const primaryListeners = [];
|
|
@@ -43,8 +44,14 @@ function createOtlpMeterPlugin(options = {}) {
|
|
|
43
44
|
};
|
|
44
45
|
async function createExporter() {
|
|
45
46
|
const exporterModuleName = protocol === "grpc" ? "@opentelemetry/exporter-metrics-otlp-grpc" : "@opentelemetry/exporter-metrics-otlp-http";
|
|
47
|
+
if (protocol === "grpc" && headers && Object.keys(headers).length > 0) pluginLog?.warn("headers are not supported by the gRPC exporter — configure metadata via the exporter's own options or use protocol: 'http'");
|
|
46
48
|
try {
|
|
47
|
-
|
|
49
|
+
const mod = await import(exporterModuleName);
|
|
50
|
+
const config = protocol === "grpc" ? { url: resolvedEndpoint } : {
|
|
51
|
+
url: resolvedEndpoint,
|
|
52
|
+
headers
|
|
53
|
+
};
|
|
54
|
+
return new mod.OTLPMetricExporter(config);
|
|
48
55
|
} catch (err) {
|
|
49
56
|
if (isMissingModuleError(err)) throw new Error(`otlp-meter plugin: protocol '${protocol}' requires ${exporterModuleName} — install it or use protocol '${protocol === "grpc" ? "http" : "grpc"}'`);
|
|
50
57
|
throw err;
|
|
@@ -90,7 +97,8 @@ function createOtlpMeterPlugin(options = {}) {
|
|
|
90
97
|
resource,
|
|
91
98
|
readers: [metricReader]
|
|
92
99
|
});
|
|
93
|
-
metrics.setGlobalMeterProvider(meterProvider);
|
|
100
|
+
if (metrics.setGlobalMeterProvider(meterProvider)) pluginSetGlobalProvider = true;
|
|
101
|
+
else log?.warn("A global OpenTelemetry meter provider is already registered — leaving it untouched; clusterkit meters use the plugin's own provider");
|
|
94
102
|
const meter = meterProvider.getMeter("@goopil/clusterkit", PLUGIN_VERSION);
|
|
95
103
|
if (cluster.isPrimary) {
|
|
96
104
|
clearPrimaryListeners();
|
|
@@ -129,6 +137,10 @@ function createOtlpMeterPlugin(options = {}) {
|
|
|
129
137
|
},
|
|
130
138
|
async uninstall() {
|
|
131
139
|
clearPrimaryListeners();
|
|
140
|
+
if (pluginSetGlobalProvider) {
|
|
141
|
+
metrics.disable();
|
|
142
|
+
pluginSetGlobalProvider = false;
|
|
143
|
+
}
|
|
132
144
|
await shutdownProvider();
|
|
133
145
|
},
|
|
134
146
|
async shutdown() {
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["pkgJson.version"],"sources":["../package.json","../src/index.ts"],"sourcesContent":["","import cluster from \"node:cluster\";\nimport { randomUUID } from \"node:crypto\";\nimport os from \"node:os\";\nimport { type Logger, type Orchestrator, type ResolvedConfig, withLoggerPrefix } from \"@goopil/clusterkit\";\nimport { metrics } from \"@opentelemetry/api\";\nimport { resourceFromAttributes } from \"@opentelemetry/resources\";\nimport { MeterProvider, PeriodicExportingMetricReader, type PushMetricExporter } from \"@opentelemetry/sdk-metrics\";\nimport { ATTR_SERVICE_INSTANCE_ID, ATTR_SERVICE_NAME } from \"@opentelemetry/semantic-conventions\";\nimport pkgJson from \"../package.json\" with { type: \"json\" };\nimport type { OtlpMeterPlugin, OtlpMeterPluginOptions } from \"./types.js\";\n\nexport type { OtlpMeterPlugin, OtlpMeterPluginOptions } from \"./types.js\";\n\ntype PrimaryEvent = \"worker:crash\" | \"worker:restart\" | \"circuit-breaker:tripped\";\n\nconst DEFAULT_HTTP_ENDPOINT = \"http://localhost:4318/v1/metrics\";\nconst DEFAULT_GRPC_ENDPOINT = \"localhost:4317\";\n\nconst ATTR_HOST_NAME = \"host.name\";\nconst ATTR_PROCESS_PID = \"process.pid\";\n\nconst PLUGIN_VERSION: string = pkgJson.version;\n\nfunction isMissingModuleError(err: unknown): boolean {\n const code = (err as NodeJS.ErrnoException | undefined)?.code;\n return code === \"ERR_MODULE_NOT_FOUND\" || code === \"MODULE_NOT_FOUND\";\n}\n\nexport function createOtlpMeterPlugin(options: OtlpMeterPluginOptions = {}): OtlpMeterPlugin {\n const {\n protocol = \"http\",\n instrumentation = true,\n prefix = \"clusterkit.\",\n attributes = {},\n exportIntervalMs = 60_000,\n serviceName = \"clusterkit\",\n endpoint,\n } = options;\n\n if (!Number.isFinite(exportIntervalMs) || exportIntervalMs < 1_000) {\n throw new TypeError(\n \"otlp-meter plugin: exportIntervalMs must be a finite number >= 1000 (minimum 1s to avoid flooding the collector)\",\n );\n }\n\n const resolvedEndpoint = endpoint ?? (protocol === \"grpc\" ? DEFAULT_GRPC_ENDPOINT : DEFAULT_HTTP_ENDPOINT);\n\n if (protocol === \"http\") {\n try {\n new URL(resolvedEndpoint);\n } catch {\n throw new TypeError(`otlp-meter plugin: invalid endpoint URL \"${resolvedEndpoint}\"`);\n }\n }\n\n let meterProvider: MeterProvider | undefined;\n let isShutdown = false;\n let pluginLog: Logger | null = null;\n let primaryOrchestrator: Orchestrator | undefined;\n const primaryListeners: Array<{ event: PrimaryEvent; listener: () => void }> = [];\n\n const clearPrimaryListeners = (): void => {\n if (!primaryOrchestrator) return;\n for (const { event, listener } of primaryListeners) {\n primaryOrchestrator.off(event, listener);\n }\n primaryListeners.length = 0;\n primaryOrchestrator = undefined;\n };\n\n async function createExporter(): Promise<PushMetricExporter> {\n const exporterModuleName =\n protocol === \"grpc\" ? \"@opentelemetry/exporter-metrics-otlp-grpc\" : \"@opentelemetry/exporter-metrics-otlp-http\";\n\n try {\n const mod = await import(exporterModuleName);\n return new mod.OTLPMetricExporter({ url: resolvedEndpoint }) as PushMetricExporter;\n } catch (err) {\n if (isMissingModuleError(err)) {\n throw new Error(\n `otlp-meter plugin: protocol '${protocol}' requires ${exporterModuleName} — install it or use protocol '${protocol === \"grpc\" ? \"http\" : \"grpc\"}'`,\n );\n }\n throw err;\n }\n }\n\n async function startHostMetrics(provider: MeterProvider): Promise<void> {\n try {\n const mod = await import(\"@opentelemetry/host-metrics\");\n const hostMetrics = new mod.HostMetrics({ meterProvider: provider });\n hostMetrics.start();\n } catch (err) {\n if (isMissingModuleError(err)) {\n pluginLog?.warn(\"host-metrics package not installed — skipping process metrics\");\n } else {\n throw err;\n }\n }\n }\n\n async function shutdownProvider(): Promise<void> {\n if (isShutdown || !meterProvider) return;\n isShutdown = true;\n await meterProvider.shutdown();\n meterProvider = undefined;\n }\n\n return {\n name: \"otlp-meter\",\n\n get meterProvider() {\n return meterProvider;\n },\n\n async install(orchestrator: Orchestrator, logger: Logger | null, _config: ResolvedConfig): Promise<void> {\n const log = withLoggerPrefix(logger, \"clusterkit:otlp-meter\");\n pluginLog = log;\n\n // Reinstalling the same instance after a shutdown must not orphan the old\n // provider or leave the latch stuck at `true` (the new provider's shutdown\n // would otherwise be a permanent no-op).\n if (meterProvider) await shutdownProvider();\n isShutdown = false;\n\n const resource = resourceFromAttributes({\n [ATTR_SERVICE_NAME]: serviceName,\n [ATTR_SERVICE_INSTANCE_ID]: randomUUID(),\n [ATTR_HOST_NAME]: os.hostname(),\n [ATTR_PROCESS_PID]: process.pid,\n ...attributes,\n });\n\n const exporter = await createExporter();\n\n const metricReader = new PeriodicExportingMetricReader({\n exporter,\n exportIntervalMillis: exportIntervalMs,\n });\n\n meterProvider = new MeterProvider({\n resource,\n readers: [metricReader],\n });\n\n metrics.setGlobalMeterProvider(meterProvider);\n\n const meter = meterProvider.getMeter(\"@goopil/clusterkit\", PLUGIN_VERSION);\n\n if (cluster.isPrimary) {\n clearPrimaryListeners();\n log?.debug(\"Plugin installed on primary process\");\n primaryOrchestrator = orchestrator;\n\n const activeWorkersGauge = meter.createObservableGauge(`${prefix}active_workers`, {\n description: \"Number of active cluster workers\",\n });\n activeWorkersGauge.addCallback((result) => {\n result.observe(orchestrator.getMetrics().activeWorkers);\n });\n\n const workerRestartsCounter = meter.createCounter(`${prefix}worker.restarts`, {\n description: \"Total number of worker restarts\",\n });\n const workerCrashesCounter = meter.createCounter(`${prefix}worker.crashes`, {\n description: \"Total number of worker crashes\",\n });\n const circuitBreakerTripsCounter = meter.createCounter(`${prefix}circuit_breaker.trips`, {\n description: \"Total number of circuit breaker trips\",\n });\n\n const bind = (event: PrimaryEvent, listener: () => void): void => {\n orchestrator.on(event, listener);\n primaryListeners.push({ event, listener });\n };\n\n bind(\"worker:crash\", () => {\n workerCrashesCounter.add(1);\n });\n bind(\"worker:restart\", () => {\n workerRestartsCounter.add(1);\n });\n bind(\"circuit-breaker:tripped\", () => {\n circuitBreakerTripsCounter.add(1);\n });\n\n const singleWorker = orchestrator.workerCount === 1;\n if (singleWorker && instrumentation) {\n await startHostMetrics(meterProvider);\n }\n } else {\n log?.debug(\"Plugin installed on worker process\");\n\n if (instrumentation) {\n await startHostMetrics(meterProvider);\n }\n }\n\n orchestrator.registerOnShutdown(async () => {\n await shutdownProvider();\n });\n },\n\n async uninstall(): Promise<void> {\n clearPrimaryListeners();\n await shutdownProvider();\n },\n\n async shutdown(): Promise<void> {\n await shutdownProvider();\n },\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["pkgJson.version"],"sources":["../package.json","../src/index.ts"],"sourcesContent":["","import cluster from \"node:cluster\";\nimport { randomUUID } from \"node:crypto\";\nimport os from \"node:os\";\nimport { type Logger, type Orchestrator, type ResolvedConfig, withLoggerPrefix } from \"@goopil/clusterkit\";\nimport { metrics } from \"@opentelemetry/api\";\nimport { resourceFromAttributes } from \"@opentelemetry/resources\";\nimport { MeterProvider, PeriodicExportingMetricReader, type PushMetricExporter } from \"@opentelemetry/sdk-metrics\";\nimport { ATTR_SERVICE_INSTANCE_ID, ATTR_SERVICE_NAME } from \"@opentelemetry/semantic-conventions\";\nimport pkgJson from \"../package.json\" with { type: \"json\" };\nimport type { OtlpMeterPlugin, OtlpMeterPluginOptions } from \"./types.js\";\n\nexport type { OtlpMeterPlugin, OtlpMeterPluginOptions } from \"./types.js\";\n\ntype PrimaryEvent = \"worker:crash\" | \"worker:restart\" | \"circuit-breaker:tripped\";\n\nconst DEFAULT_HTTP_ENDPOINT = \"http://localhost:4318/v1/metrics\";\nconst DEFAULT_GRPC_ENDPOINT = \"localhost:4317\";\n\nconst ATTR_HOST_NAME = \"host.name\";\nconst ATTR_PROCESS_PID = \"process.pid\";\n\nconst PLUGIN_VERSION: string = pkgJson.version;\n\nfunction isMissingModuleError(err: unknown): boolean {\n const code = (err as NodeJS.ErrnoException | undefined)?.code;\n return code === \"ERR_MODULE_NOT_FOUND\" || code === \"MODULE_NOT_FOUND\";\n}\n\nexport function createOtlpMeterPlugin(options: OtlpMeterPluginOptions = {}): OtlpMeterPlugin {\n const {\n protocol = \"http\",\n instrumentation = true,\n prefix = \"clusterkit.\",\n attributes = {},\n exportIntervalMs = 60_000,\n serviceName = \"clusterkit\",\n endpoint,\n headers,\n } = options;\n\n if (!Number.isFinite(exportIntervalMs) || exportIntervalMs < 1_000) {\n throw new TypeError(\n \"otlp-meter plugin: exportIntervalMs must be a finite number >= 1000 (minimum 1s to avoid flooding the collector)\",\n );\n }\n\n const resolvedEndpoint = endpoint ?? (protocol === \"grpc\" ? DEFAULT_GRPC_ENDPOINT : DEFAULT_HTTP_ENDPOINT);\n\n if (protocol === \"http\") {\n try {\n new URL(resolvedEndpoint);\n } catch {\n throw new TypeError(`otlp-meter plugin: invalid endpoint URL \"${resolvedEndpoint}\"`);\n }\n }\n\n let meterProvider: MeterProvider | undefined;\n let isShutdown = false;\n let pluginSetGlobalProvider = false;\n let pluginLog: Logger | null = null;\n let primaryOrchestrator: Orchestrator | undefined;\n const primaryListeners: Array<{ event: PrimaryEvent; listener: () => void }> = [];\n\n const clearPrimaryListeners = (): void => {\n if (!primaryOrchestrator) return;\n for (const { event, listener } of primaryListeners) {\n primaryOrchestrator.off(event, listener);\n }\n primaryListeners.length = 0;\n primaryOrchestrator = undefined;\n };\n\n async function createExporter(): Promise<PushMetricExporter> {\n const exporterModuleName =\n protocol === \"grpc\" ? \"@opentelemetry/exporter-metrics-otlp-grpc\" : \"@opentelemetry/exporter-metrics-otlp-http\";\n\n // The gRPC exporter config omits `headers` (gRPC uses `metadata` instead):\n // forwarding them would be silently dropped, leaving exports unauthenticated.\n if (protocol === \"grpc\" && headers && Object.keys(headers).length > 0) {\n pluginLog?.warn(\n \"headers are not supported by the gRPC exporter — configure metadata via the exporter's own options or use protocol: 'http'\",\n );\n }\n\n try {\n const mod = await import(exporterModuleName);\n const config = protocol === \"grpc\" ? { url: resolvedEndpoint } : { url: resolvedEndpoint, headers };\n return new mod.OTLPMetricExporter(config) as PushMetricExporter;\n } catch (err) {\n if (isMissingModuleError(err)) {\n throw new Error(\n `otlp-meter plugin: protocol '${protocol}' requires ${exporterModuleName} — install it or use protocol '${protocol === \"grpc\" ? \"http\" : \"grpc\"}'`,\n );\n }\n throw err;\n }\n }\n\n async function startHostMetrics(provider: MeterProvider): Promise<void> {\n try {\n const mod = await import(\"@opentelemetry/host-metrics\");\n const hostMetrics = new mod.HostMetrics({ meterProvider: provider });\n hostMetrics.start();\n } catch (err) {\n if (isMissingModuleError(err)) {\n pluginLog?.warn(\"host-metrics package not installed — skipping process metrics\");\n } else {\n throw err;\n }\n }\n }\n\n async function shutdownProvider(): Promise<void> {\n if (isShutdown || !meterProvider) return;\n isShutdown = true;\n await meterProvider.shutdown();\n meterProvider = undefined;\n }\n\n return {\n name: \"otlp-meter\",\n\n get meterProvider() {\n return meterProvider;\n },\n\n async install(orchestrator: Orchestrator, logger: Logger | null, _config: ResolvedConfig): Promise<void> {\n const log = withLoggerPrefix(logger, \"clusterkit:otlp-meter\");\n pluginLog = log;\n\n // Reinstalling the same instance after a shutdown must not orphan the old\n // provider or leave the latch stuck at `true` (the new provider's shutdown\n // would otherwise be a permanent no-op).\n if (meterProvider) await shutdownProvider();\n isShutdown = false;\n\n const resource = resourceFromAttributes({\n [ATTR_SERVICE_NAME]: serviceName,\n [ATTR_SERVICE_INSTANCE_ID]: randomUUID(),\n [ATTR_HOST_NAME]: os.hostname(),\n [ATTR_PROCESS_PID]: process.pid,\n ...attributes,\n });\n\n const exporter = await createExporter();\n\n const metricReader = new PeriodicExportingMetricReader({\n exporter,\n exportIntervalMillis: exportIntervalMs,\n });\n\n meterProvider = new MeterProvider({\n resource,\n readers: [metricReader],\n });\n\n // setGlobalMeterProvider() refuses to override an existing registration\n // and returns false — never clobber the app's own OTel setup.\n if (metrics.setGlobalMeterProvider(meterProvider)) {\n pluginSetGlobalProvider = true;\n } else {\n log?.warn(\n \"A global OpenTelemetry meter provider is already registered — leaving it untouched; clusterkit meters use the plugin's own provider\",\n );\n }\n\n const meter = meterProvider.getMeter(\"@goopil/clusterkit\", PLUGIN_VERSION);\n\n if (cluster.isPrimary) {\n clearPrimaryListeners();\n log?.debug(\"Plugin installed on primary process\");\n primaryOrchestrator = orchestrator;\n\n const activeWorkersGauge = meter.createObservableGauge(`${prefix}active_workers`, {\n description: \"Number of active cluster workers\",\n });\n activeWorkersGauge.addCallback((result) => {\n result.observe(orchestrator.getMetrics().activeWorkers);\n });\n\n const workerRestartsCounter = meter.createCounter(`${prefix}worker.restarts`, {\n description: \"Total number of worker restarts\",\n });\n const workerCrashesCounter = meter.createCounter(`${prefix}worker.crashes`, {\n description: \"Total number of worker crashes\",\n });\n const circuitBreakerTripsCounter = meter.createCounter(`${prefix}circuit_breaker.trips`, {\n description: \"Total number of circuit breaker trips\",\n });\n\n const bind = (event: PrimaryEvent, listener: () => void): void => {\n orchestrator.on(event, listener);\n primaryListeners.push({ event, listener });\n };\n\n bind(\"worker:crash\", () => {\n workerCrashesCounter.add(1);\n });\n bind(\"worker:restart\", () => {\n workerRestartsCounter.add(1);\n });\n bind(\"circuit-breaker:tripped\", () => {\n circuitBreakerTripsCounter.add(1);\n });\n\n const singleWorker = orchestrator.workerCount === 1;\n if (singleWorker && instrumentation) {\n await startHostMetrics(meterProvider);\n }\n } else {\n log?.debug(\"Plugin installed on worker process\");\n\n if (instrumentation) {\n await startHostMetrics(meterProvider);\n }\n }\n\n orchestrator.registerOnShutdown(async () => {\n await shutdownProvider();\n });\n },\n\n async uninstall(): Promise<void> {\n clearPrimaryListeners();\n // If the plugin's provider became the global one, release the slot:\n // metrics.disable() is the API's public unregister and restores the exact\n // pre-install state (getMeterProvider() falls back to the noop provider).\n // Restoring the captured noop prior via setGlobalMeterProvider() instead\n // would leave the slot occupied and block the app from registering later.\n if (pluginSetGlobalProvider) {\n metrics.disable();\n pluginSetGlobalProvider = false;\n }\n await shutdownProvider();\n },\n\n async shutdown(): Promise<void> {\n await shutdownProvider();\n },\n };\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goopil/clusterkit-otlp-meter",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "OpenTelemetry OTLP metrics plugin for @goopil/clusterkit: exports orchestration counters/gauges and per-worker host metrics via OTLP/HTTP or OTLP/gRPC to a collector.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"opentelemetry",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"@opentelemetry/resources": "^2.0.0",
|
|
53
53
|
"@opentelemetry/semantic-conventions": "^1.30.0",
|
|
54
54
|
"@opentelemetry/sdk-metrics": "^2.0.0",
|
|
55
|
-
"@goopil/clusterkit": "^1.2.
|
|
55
|
+
"@goopil/clusterkit": "^1.2.6"
|
|
56
56
|
},
|
|
57
57
|
"peerDependenciesMeta": {
|
|
58
58
|
"@opentelemetry/exporter-metrics-otlp-http": {
|
|
@@ -79,7 +79,7 @@
|
|
|
79
79
|
"tsdown": "^0.22.14",
|
|
80
80
|
"typescript": "^7.0.2",
|
|
81
81
|
"vitest": "^4.1.11",
|
|
82
|
-
"@goopil/clusterkit": "1.2.
|
|
82
|
+
"@goopil/clusterkit": "1.2.6"
|
|
83
83
|
},
|
|
84
84
|
"scripts": {
|
|
85
85
|
"build": "tsdown",
|