@twin.org/telemetry-connector-opentelemetry 0.9.1 → 0.9.2-next.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/es/models/IOpenTelemetryTelemetryConnectorConfig.js.map +1 -1
- package/dist/es/openTelemetryTelemetryConnector.js +154 -94
- package/dist/es/openTelemetryTelemetryConnector.js.map +1 -1
- package/dist/types/models/IOpenTelemetryTelemetryConnectorConfig.d.ts +4 -0
- package/dist/types/openTelemetryTelemetryConnector.d.ts +12 -23
- package/docs/changelog.md +45 -0
- package/docs/reference/classes/OpenTelemetryTelemetryConnector.md +15 -23
- package/docs/reference/interfaces/IOpenTelemetryTelemetryConnectorConfig.md +8 -0
- package/locales/en.json +0 -5
- package/package.json +9 -7
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"IOpenTelemetryTelemetryConnectorConfig.js","sourceRoot":"","sources":["../../../src/models/IOpenTelemetryTelemetryConnectorConfig.ts"],"names":[],"mappings":"","sourcesContent":["// Copyright 2026 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type { IOpenTelemetryReaderConfig } from \"./IOpenTelemetryReaderConfig.js\";\n\n/**\n * The options for the OpenTelemetry telemetry connector constructor.\n */\nexport interface IOpenTelemetryTelemetryConnectorConfig {\n\t/**\n\t * The name of the OpenTelemetry meter used to create instruments.\n\t * @default twin-telemetry\n\t */\n\tmeterName?: string;\n\n\t/**\n\t * The version reported by the OpenTelemetry meter.\n\t * @default 1.0.0\n\t */\n\tmeterVersion?: string;\n\n\t/**\n\t * Named metric-reader configurations keyed by an arbitrary id.\n\t * Each entry's `type` field determines which exporter the connector instantiates\n\t * in start(). Omit or pass an empty object for a no-op provider (useful for tests).\n\t */\n\treaders?: { [id: string]: IOpenTelemetryReaderConfig };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"IOpenTelemetryTelemetryConnectorConfig.js","sourceRoot":"","sources":["../../../src/models/IOpenTelemetryTelemetryConnectorConfig.ts"],"names":[],"mappings":"","sourcesContent":["// Copyright 2026 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type { IOpenTelemetryReaderConfig } from \"./IOpenTelemetryReaderConfig.js\";\n\n/**\n * The options for the OpenTelemetry telemetry connector constructor.\n */\nexport interface IOpenTelemetryTelemetryConnectorConfig {\n\t/**\n\t * Timeout in milliseconds for acquiring the inner entity-storage metric write mutex lock.\n\t */\n\tmutexTimeoutMs?: number;\n\n\t/**\n\t * The name of the OpenTelemetry meter used to create instruments.\n\t * @default twin-telemetry\n\t */\n\tmeterName?: string;\n\n\t/**\n\t * The version reported by the OpenTelemetry meter.\n\t * @default 1.0.0\n\t */\n\tmeterVersion?: string;\n\n\t/**\n\t * Named metric-reader configurations keyed by an arbitrary id.\n\t * Each entry's `type` field determines which exporter the connector instantiates\n\t * in start(). Omit or pass an empty object for a no-op provider (useful for tests).\n\t */\n\treaders?: { [id: string]: IOpenTelemetryReaderConfig };\n}\n"]}
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { PrometheusExporter } from "@opentelemetry/exporter-prometheus";
|
|
2
|
+
import { resourceFromAttributes } from "@opentelemetry/resources";
|
|
2
3
|
import { MeterProvider } from "@opentelemetry/sdk-metrics";
|
|
3
|
-
import {
|
|
4
|
+
import { ContextIdKeys, ContextIdStore } from "@twin.org/context";
|
|
5
|
+
import { AlreadyExistsError, BaseError, ComponentFactory, Guards, Is } from "@twin.org/core";
|
|
4
6
|
import { EntityStorageTelemetryConnector } from "@twin.org/telemetry-connector-entity-storage";
|
|
5
7
|
import { MetricCounterOperation, MetricType } from "@twin.org/telemetry-models";
|
|
8
|
+
import { OpenTelemetryReaderTypes } from "./models/openTelemetryReaderTypes.js";
|
|
6
9
|
/**
|
|
7
10
|
* Class for performing telemetry operations using OpenTelemetry instruments.
|
|
8
11
|
* Metric definitions and value history are persisted via an internal
|
|
9
12
|
* EntityStorageTelemetryConnector instance created at construction time.
|
|
10
|
-
* Call `start()` to initialise the MeterProvider and exporters; metrics can be
|
|
11
|
-
* created and queried before start() — OTEL forwarding is simply skipped until
|
|
12
|
-
* the MeterProvider is running.
|
|
13
13
|
*/
|
|
14
14
|
export class OpenTelemetryTelemetryConnector {
|
|
15
15
|
/**
|
|
@@ -21,47 +21,55 @@ export class OpenTelemetryTelemetryConnector {
|
|
|
21
21
|
*/
|
|
22
22
|
static CLASS_NAME = "OpenTelemetryTelemetryConnector";
|
|
23
23
|
/**
|
|
24
|
-
* Config options
|
|
24
|
+
* Config options stored so provider creation can initialise readers and meter identity.
|
|
25
25
|
* @internal
|
|
26
26
|
*/
|
|
27
27
|
_config;
|
|
28
28
|
/**
|
|
29
29
|
* Internal entity-storage connector that owns all metric metadata and value history.
|
|
30
|
-
* Created at construction time — fails fast if entity storage is not set up.
|
|
31
30
|
* @internal
|
|
32
31
|
*/
|
|
33
32
|
_inner;
|
|
34
33
|
/**
|
|
35
|
-
*
|
|
36
|
-
* so addMetricValue can dispatch without querying storage.
|
|
37
|
-
* These are runtime objects and cannot be persisted.
|
|
34
|
+
* Whether start() has been called. Drives OTEL forwarding on/off.
|
|
38
35
|
* @internal
|
|
39
36
|
*/
|
|
40
|
-
|
|
37
|
+
_started;
|
|
41
38
|
/**
|
|
42
|
-
*
|
|
39
|
+
* Cache of MeterProvider+Meter+instruments keyed by "nodeId/tenantId".
|
|
40
|
+
* Providers are created on demand when the first metric measurement arrives
|
|
41
|
+
* for a given tenant/node pair.
|
|
43
42
|
* @internal
|
|
44
43
|
*/
|
|
45
|
-
|
|
44
|
+
_providers;
|
|
46
45
|
/**
|
|
47
|
-
*
|
|
46
|
+
* Metric definition cache keyed by metric id, populated in createMetric and on first
|
|
47
|
+
* addMetricValue after a process restart. Used to register instruments on new providers
|
|
48
|
+
* without re-querying entity storage on every call.
|
|
48
49
|
* @internal
|
|
49
50
|
*/
|
|
50
|
-
|
|
51
|
+
_metricDefs;
|
|
51
52
|
/**
|
|
52
53
|
* Create a new instance of OpenTelemetryTelemetryConnector.
|
|
53
|
-
* Eagerly constructs the inner EntityStorageTelemetryConnector — if the required
|
|
54
|
-
* entity storage types are not registered this constructor will throw (fail fast).
|
|
55
54
|
* @param options The options for the connector.
|
|
55
|
+
* @throws GuardError When a reader config specifies an unsupported type.
|
|
56
56
|
*/
|
|
57
57
|
constructor(options) {
|
|
58
58
|
this._config = options?.config ?? {};
|
|
59
|
+
for (const [, readerConfig] of Object.entries(this._config.readers ?? {})) {
|
|
60
|
+
Guards.arrayOneOf(OpenTelemetryTelemetryConnector.CLASS_NAME, "readerConfig.type", readerConfig.type, Object.values(OpenTelemetryReaderTypes));
|
|
61
|
+
}
|
|
59
62
|
this._inner = new EntityStorageTelemetryConnector({
|
|
60
63
|
loggingComponentType: options?.loggingComponentType,
|
|
61
64
|
telemetryMetricStorageConnectorType: options?.telemetryMetricStorageConnectorType,
|
|
62
|
-
telemetryMetricValueStorageConnectorType: options?.telemetryMetricValueStorageConnectorType
|
|
65
|
+
telemetryMetricValueStorageConnectorType: options?.telemetryMetricValueStorageConnectorType,
|
|
66
|
+
config: {
|
|
67
|
+
mutexTimeoutMs: this._config.mutexTimeoutMs
|
|
68
|
+
}
|
|
63
69
|
});
|
|
64
|
-
this.
|
|
70
|
+
this._started = false;
|
|
71
|
+
this._providers = {};
|
|
72
|
+
this._metricDefs = {};
|
|
65
73
|
}
|
|
66
74
|
/**
|
|
67
75
|
* Returns the class name of the component.
|
|
@@ -71,34 +79,16 @@ export class OpenTelemetryTelemetryConnector {
|
|
|
71
79
|
return OpenTelemetryTelemetryConnector.CLASS_NAME;
|
|
72
80
|
}
|
|
73
81
|
/**
|
|
74
|
-
*
|
|
82
|
+
* Enable OTEL forwarding. Subsequent calls to createMetric and addMetricValue will
|
|
83
|
+
* create per-tenant/node MeterProviders on demand.
|
|
75
84
|
* @param nodeLoggingComponentType The node logging component type.
|
|
76
|
-
* @returns A promise that resolves when
|
|
85
|
+
* @returns A promise that resolves when OTEL forwarding is enabled.
|
|
77
86
|
*/
|
|
78
87
|
async start(nodeLoggingComponentType) {
|
|
79
|
-
if (
|
|
88
|
+
if (this._started) {
|
|
80
89
|
return;
|
|
81
90
|
}
|
|
82
|
-
|
|
83
|
-
for (const [, config] of Object.entries(this._config.readers ?? {})) {
|
|
84
|
-
if (config.type === "prometheus") {
|
|
85
|
-
readers.push(new PrometheusExporter({
|
|
86
|
-
port: config.port,
|
|
87
|
-
endpoint: config.endpoint,
|
|
88
|
-
// PrometheusExporter uses preventServerStart (inverted); our config
|
|
89
|
-
// exposes the more intuitive startServer (defaults to true).
|
|
90
|
-
preventServerStart: !(config.startServer ?? true),
|
|
91
|
-
prefix: config.prefix
|
|
92
|
-
}));
|
|
93
|
-
}
|
|
94
|
-
else {
|
|
95
|
-
throw new GeneralError(OpenTelemetryTelemetryConnector.CLASS_NAME, "unknownReaderType", {
|
|
96
|
-
type: config.type
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
this._meterProvider = new MeterProvider({ readers });
|
|
101
|
-
this._meter = this._meterProvider.getMeter(this._config.meterName ?? "twin-telemetry", this._config.meterVersion ?? "1.0.0");
|
|
91
|
+
this._started = true;
|
|
102
92
|
const nodeLogging = ComponentFactory.getIfExists(nodeLoggingComponentType);
|
|
103
93
|
await nodeLogging?.log({
|
|
104
94
|
source: OpenTelemetryTelemetryConnector.CLASS_NAME,
|
|
@@ -108,17 +98,19 @@ export class OpenTelemetryTelemetryConnector {
|
|
|
108
98
|
});
|
|
109
99
|
}
|
|
110
100
|
/**
|
|
111
|
-
* Shut down
|
|
112
|
-
* Calling stop() on a connector that has not been started is a no-op.
|
|
101
|
+
* Shut down all cached MeterProviders and disable OTEL forwarding.
|
|
113
102
|
* @param nodeLoggingComponentType The node logging component type.
|
|
114
|
-
* @returns A promise that resolves when
|
|
103
|
+
* @returns A promise that resolves when all MeterProviders have shut down.
|
|
115
104
|
*/
|
|
116
105
|
async stop(nodeLoggingComponentType) {
|
|
117
|
-
if (
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
this.
|
|
106
|
+
if (this._started) {
|
|
107
|
+
for (const { meterProvider } of Object.values(this._providers)) {
|
|
108
|
+
await meterProvider.shutdown();
|
|
109
|
+
}
|
|
110
|
+
for (const key of Object.keys(this._providers)) {
|
|
111
|
+
delete this._providers[key];
|
|
112
|
+
}
|
|
113
|
+
this._started = false;
|
|
122
114
|
const nodeLogging = ComponentFactory.getIfExists(nodeLoggingComponentType);
|
|
123
115
|
await nodeLogging?.log({
|
|
124
116
|
source: OpenTelemetryTelemetryConnector.CLASS_NAME,
|
|
@@ -130,10 +122,8 @@ export class OpenTelemetryTelemetryConnector {
|
|
|
130
122
|
}
|
|
131
123
|
/**
|
|
132
124
|
* Create a new metric.
|
|
133
|
-
* The definition is always persisted via the inner entity-storage connector.
|
|
134
|
-
* If the MeterProvider is running the corresponding OTEL instrument is also registered.
|
|
135
125
|
* @param metric The metric details.
|
|
136
|
-
* @returns A promise that resolves when the metric has been persisted
|
|
126
|
+
* @returns A promise that resolves when the metric has been persisted.
|
|
137
127
|
*/
|
|
138
128
|
async createMetric(metric) {
|
|
139
129
|
try {
|
|
@@ -147,26 +137,15 @@ export class OpenTelemetryTelemetryConnector {
|
|
|
147
137
|
throw err;
|
|
148
138
|
}
|
|
149
139
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
let instrument;
|
|
160
|
-
if (metric.type === MetricType.Counter) {
|
|
161
|
-
instrument = meter.createCounter(metric.id, instrumentOptions);
|
|
162
|
-
}
|
|
163
|
-
else if (metric.type === MetricType.IncDecCounter) {
|
|
164
|
-
instrument = meter.createUpDownCounter(metric.id, instrumentOptions);
|
|
165
|
-
}
|
|
166
|
-
else {
|
|
167
|
-
instrument = meter.createGauge(metric.id, instrumentOptions);
|
|
168
|
-
}
|
|
169
|
-
this._instruments.set(metric.id, { metricType: metric.type, instrument });
|
|
140
|
+
this._metricDefs[metric.id] = {
|
|
141
|
+
type: metric.type,
|
|
142
|
+
description: metric.description,
|
|
143
|
+
unit: metric.unit
|
|
144
|
+
};
|
|
145
|
+
if (this._started) {
|
|
146
|
+
const contextIds = (await ContextIdStore.getContextIds()) ?? {};
|
|
147
|
+
const { meter, instruments } = this.getOrCreateProvider(contextIds);
|
|
148
|
+
this.registerInstrument(metric.id, metric.type, metric.description, metric.unit, meter, instruments);
|
|
170
149
|
}
|
|
171
150
|
}
|
|
172
151
|
/**
|
|
@@ -176,9 +155,6 @@ export class OpenTelemetryTelemetryConnector {
|
|
|
176
155
|
*/
|
|
177
156
|
async getMetric(id) {
|
|
178
157
|
const result = await this._inner.getMetric(id);
|
|
179
|
-
// When the metric has no recorded values yet the inner connector returns
|
|
180
|
-
// entities[0] = undefined via queryValues. Return a placeholder so callers
|
|
181
|
-
// are not surprised by a null value field — treat id="" as "no measurements yet".
|
|
182
158
|
const value = result.value ?? { id: "", ts: 0, value: 0 };
|
|
183
159
|
return { metric: result.metric, value };
|
|
184
160
|
}
|
|
@@ -194,9 +170,7 @@ export class OpenTelemetryTelemetryConnector {
|
|
|
194
170
|
/**
|
|
195
171
|
* Update the metric metadata.
|
|
196
172
|
* Note: OpenTelemetry instrument descriptors are immutable once created.
|
|
197
|
-
* This method updates the persisted metadata mirror
|
|
198
|
-
* are NOT propagated to the registered MeterProvider and will not appear at the
|
|
199
|
-
* OTEL backend (Prometheus, OTLP, etc.).
|
|
173
|
+
* This method updates the persisted metadata mirror only.
|
|
200
174
|
* @param metric The metric details (type cannot be changed).
|
|
201
175
|
* @returns A promise that resolves when the persisted metadata has been updated.
|
|
202
176
|
*/
|
|
@@ -205,26 +179,32 @@ export class OpenTelemetryTelemetryConnector {
|
|
|
205
179
|
}
|
|
206
180
|
/**
|
|
207
181
|
* Record a metric value.
|
|
208
|
-
*
|
|
209
|
-
*
|
|
210
|
-
* Counter accepts positive integers or "inc".
|
|
211
|
-
* UpDownCounter accepts integers (positive or negative) or "inc"/"dec".
|
|
212
|
-
* Gauge accepts any number.
|
|
182
|
+
* The current tenant and node IDs are read from `ContextIdStore` and used to
|
|
183
|
+
* select (or create) the matching per-tenant/node `MeterProvider`.
|
|
213
184
|
* @param id The id of the metric.
|
|
214
185
|
* @param value The value for the operation.
|
|
215
186
|
* @param customData Optional custom data forwarded as OTEL attributes.
|
|
216
187
|
* @returns The id of the new metric value entry.
|
|
217
188
|
*/
|
|
218
189
|
async addMetricValue(id, value, customData) {
|
|
219
|
-
// Entity storage validates and persists first; throws NotFoundError if metric not found.
|
|
220
190
|
const valueId = await this._inner.addMetricValue(id, value, customData);
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
const
|
|
191
|
+
if (this._started) {
|
|
192
|
+
const contextIds = (await ContextIdStore.getContextIds()) ?? {};
|
|
193
|
+
const { meter, instruments } = this.getOrCreateProvider(contextIds);
|
|
194
|
+
if (!(id in instruments)) {
|
|
195
|
+
let def = this._metricDefs[id];
|
|
196
|
+
if (def === undefined) {
|
|
197
|
+
// Process restart path: definition not yet cached, fetch from storage.
|
|
198
|
+
const { metric } = await this._inner.getMetric(id);
|
|
199
|
+
def = { type: metric.type, description: metric.description, unit: metric.unit };
|
|
200
|
+
this._metricDefs[id] = def;
|
|
201
|
+
}
|
|
202
|
+
this.registerInstrument(id, def.type, def.description, def.unit, meter, instruments);
|
|
203
|
+
}
|
|
204
|
+
const entry = instruments[id];
|
|
224
205
|
if (entry !== undefined) {
|
|
225
206
|
const attributes = this.toAttributes(customData);
|
|
226
207
|
const { metricType, instrument } = entry;
|
|
227
|
-
// Value already validated by inner connector — dispatch unconditionally.
|
|
228
208
|
if (metricType === MetricType.Counter) {
|
|
229
209
|
instrument.add(value === MetricCounterOperation.Increment ? 1 : value, attributes);
|
|
230
210
|
}
|
|
@@ -252,14 +232,16 @@ export class OpenTelemetryTelemetryConnector {
|
|
|
252
232
|
* Remove a metric and its persisted value history.
|
|
253
233
|
* Note: OpenTelemetry exposes no API to deregister an instrument from a Meter,
|
|
254
234
|
* so the underlying Counter/UpDownCounter/Gauge remains resident for the lifetime
|
|
255
|
-
* of the
|
|
256
|
-
* is therefore not safe.
|
|
235
|
+
* of the MeterProvider. Re-creating a metric with the same id but a different
|
|
236
|
+
* MetricType is therefore not safe.
|
|
257
237
|
* @param id The id of the metric.
|
|
258
238
|
* @returns A promise that resolves when the metric and its value history have been removed.
|
|
259
239
|
*/
|
|
260
240
|
async removeMetric(id) {
|
|
261
|
-
this.
|
|
262
|
-
|
|
241
|
+
for (const { instruments } of Object.values(this._providers)) {
|
|
242
|
+
delete instruments[id];
|
|
243
|
+
}
|
|
244
|
+
delete this._metricDefs[id];
|
|
263
245
|
return this._inner.removeMetric(id);
|
|
264
246
|
}
|
|
265
247
|
/**
|
|
@@ -284,10 +266,88 @@ export class OpenTelemetryTelemetryConnector {
|
|
|
284
266
|
async queryValues(id, timeStart, timeEnd, cursor, limit) {
|
|
285
267
|
return this._inner.queryValues(id, timeStart, timeEnd, cursor, limit);
|
|
286
268
|
}
|
|
269
|
+
/**
|
|
270
|
+
* Return or create the MeterProvider for the given tenant/node pair.
|
|
271
|
+
* Providers are keyed by "nodeId/tenantId" and carry OTEL resource attributes
|
|
272
|
+
* service.namespace=tenantId and service.instance.id=nodeId when those values
|
|
273
|
+
* are present.
|
|
274
|
+
* @param contextIds The current execution context IDs.
|
|
275
|
+
* @returns The cached or newly created provider entry.
|
|
276
|
+
* @internal
|
|
277
|
+
*/
|
|
278
|
+
getOrCreateProvider(contextIds) {
|
|
279
|
+
const node = contextIds[ContextIdKeys.Node];
|
|
280
|
+
const tenantId = contextIds[ContextIdKeys.Tenant];
|
|
281
|
+
const key = `${node ?? ""}/${tenantId ?? ""}`;
|
|
282
|
+
let cached = this._providers[key];
|
|
283
|
+
if (!Is.undefined(cached)) {
|
|
284
|
+
return cached;
|
|
285
|
+
}
|
|
286
|
+
const readers = this.createReaders();
|
|
287
|
+
const resourcePrefix = "service";
|
|
288
|
+
const resourceAttrs = {};
|
|
289
|
+
if (Is.stringValue(node)) {
|
|
290
|
+
resourceAttrs[`${resourcePrefix}.instance.id`] = node;
|
|
291
|
+
}
|
|
292
|
+
if (Is.stringValue(tenantId)) {
|
|
293
|
+
resourceAttrs[`${resourcePrefix}.namespace`] = tenantId;
|
|
294
|
+
}
|
|
295
|
+
const resource = Object.keys(resourceAttrs).length > 0 ? resourceFromAttributes(resourceAttrs) : undefined;
|
|
296
|
+
const meterProvider = new MeterProvider({ readers, resource });
|
|
297
|
+
const meter = meterProvider.getMeter(this._config.meterName ?? "twin-telemetry", this._config.meterVersion ?? "1.0.0");
|
|
298
|
+
cached = { meterProvider, meter, instruments: {} };
|
|
299
|
+
this._providers[key] = cached;
|
|
300
|
+
return cached;
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Instantiate fresh MetricReader instances from the connector config.
|
|
304
|
+
* Called each time a new MeterProvider is created for a tenant/node pair.
|
|
305
|
+
* @returns The list of reader instances.
|
|
306
|
+
* @internal
|
|
307
|
+
*/
|
|
308
|
+
createReaders() {
|
|
309
|
+
const readers = [];
|
|
310
|
+
for (const [, config] of Object.entries(this._config.readers ?? {})) {
|
|
311
|
+
if (config.type === OpenTelemetryReaderTypes.Prometheus) {
|
|
312
|
+
readers.push(new PrometheusExporter({
|
|
313
|
+
port: config.port,
|
|
314
|
+
endpoint: config.endpoint,
|
|
315
|
+
preventServerStart: !(config.startServer ?? true),
|
|
316
|
+
prefix: config.prefix
|
|
317
|
+
}));
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
return readers;
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Register an OTEL instrument on a provider's instruments map if not already present.
|
|
324
|
+
* @param id The metric id.
|
|
325
|
+
* @param type The metric type.
|
|
326
|
+
* @param description The metric description.
|
|
327
|
+
* @param unit The metric unit.
|
|
328
|
+
* @param meter The meter to create instruments on.
|
|
329
|
+
* @param instruments The per-provider instruments map to update.
|
|
330
|
+
* @internal
|
|
331
|
+
*/
|
|
332
|
+
registerInstrument(id, type, description, unit, meter, instruments) {
|
|
333
|
+
if (id in instruments) {
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
const instrumentOptions = { description, unit };
|
|
337
|
+
let instrument;
|
|
338
|
+
if (type === MetricType.Counter) {
|
|
339
|
+
instrument = meter.createCounter(id, instrumentOptions);
|
|
340
|
+
}
|
|
341
|
+
else if (type === MetricType.IncDecCounter) {
|
|
342
|
+
instrument = meter.createUpDownCounter(id, instrumentOptions);
|
|
343
|
+
}
|
|
344
|
+
else {
|
|
345
|
+
instrument = meter.createGauge(id, instrumentOptions);
|
|
346
|
+
}
|
|
347
|
+
instruments[id] = { metricType: type, instrument };
|
|
348
|
+
}
|
|
287
349
|
/**
|
|
288
350
|
* Convert customData to OTEL-compatible Attributes.
|
|
289
|
-
* Scalar values (string, number, boolean) and uniform primitive arrays
|
|
290
|
-
* (string[], number[], boolean[]) are forwarded; other values are silently dropped.
|
|
291
351
|
* @param customData The raw custom data map.
|
|
292
352
|
* @returns An OTEL Attributes object.
|
|
293
353
|
* @internal
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"openTelemetryTelemetryConnector.js","sourceRoot":"","sources":["../../src/openTelemetryTelemetryConnector.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2026 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type { Attributes, Counter, Gauge, Meter, UpDownCounter } from \"@opentelemetry/api\";\nimport { PrometheusExporter } from \"@opentelemetry/exporter-prometheus\";\nimport { MeterProvider, type MetricReader } from \"@opentelemetry/sdk-metrics\";\nimport { AlreadyExistsError, BaseError, ComponentFactory, GeneralError, Is } from \"@twin.org/core\";\nimport type { ILoggingComponent } from \"@twin.org/logging-models\";\nimport { nameof } from \"@twin.org/nameof\";\nimport { EntityStorageTelemetryConnector } from \"@twin.org/telemetry-connector-entity-storage\";\nimport {\n\ttype ITelemetryConnector,\n\ttype ITelemetryMetric,\n\ttype ITelemetryMetricValue,\n\tMetricCounterOperation,\n\tMetricType\n} from \"@twin.org/telemetry-models\";\nimport type { IOpenTelemetryTelemetryConnectorConfig } from \"./models/IOpenTelemetryTelemetryConnectorConfig.js\";\nimport type { IOpenTelemetryTelemetryConnectorConstructorOptions } from \"./models/IOpenTelemetryTelemetryConnectorConstructorOptions.js\";\n\n/**\n * Class for performing telemetry operations using OpenTelemetry instruments.\n * Metric definitions and value history are persisted via an internal\n * EntityStorageTelemetryConnector instance created at construction time.\n * Call `start()` to initialise the MeterProvider and exporters; metrics can be\n * created and queried before start() — OTEL forwarding is simply skipped until\n * the MeterProvider is running.\n */\nexport class OpenTelemetryTelemetryConnector implements ITelemetryConnector {\n\t/**\n\t * The namespace supported by the telemetry connector.\n\t */\n\tpublic static readonly NAMESPACE: string = \"opentelemetry\";\n\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<OpenTelemetryTelemetryConnector>();\n\n\t/**\n\t * Config options, stored so start() can initialise the MeterProvider.\n\t * @internal\n\t */\n\tprivate readonly _config: IOpenTelemetryTelemetryConnectorConfig;\n\n\t/**\n\t * Internal entity-storage connector that owns all metric metadata and value history.\n\t * Created at construction time — fails fast if entity storage is not set up.\n\t * @internal\n\t */\n\tprivate readonly _inner: EntityStorageTelemetryConnector;\n\n\t/**\n\t * Live OTEL instrument handles keyed by metric id, paired with the metric type\n\t * so addMetricValue can dispatch without querying storage.\n\t * These are runtime objects and cannot be persisted.\n\t * @internal\n\t */\n\tprivate readonly _instruments: Map<\n\t\tstring,\n\t\t{ metricType: MetricType; instrument: Counter | UpDownCounter | Gauge }\n\t>;\n\n\t/**\n\t * The MeterProvider that owns all instruments. Set by start(), cleared by stop().\n\t * @internal\n\t */\n\tprivate _meterProvider?: MeterProvider;\n\n\t/**\n\t * The Meter used to create instruments. Set by start(), cleared by stop().\n\t * @internal\n\t */\n\tprivate _meter?: Meter;\n\n\t/**\n\t * Create a new instance of OpenTelemetryTelemetryConnector.\n\t * Eagerly constructs the inner EntityStorageTelemetryConnector — if the required\n\t * entity storage types are not registered this constructor will throw (fail fast).\n\t * @param options The options for the connector.\n\t */\n\tconstructor(options?: IOpenTelemetryTelemetryConnectorConstructorOptions) {\n\t\tthis._config = options?.config ?? {};\n\t\tthis._inner = new EntityStorageTelemetryConnector({\n\t\t\tloggingComponentType: options?.loggingComponentType,\n\t\t\ttelemetryMetricStorageConnectorType: options?.telemetryMetricStorageConnectorType,\n\t\t\ttelemetryMetricValueStorageConnectorType: options?.telemetryMetricValueStorageConnectorType\n\t\t});\n\t\tthis._instruments = new Map();\n\t}\n\n\t/**\n\t * Returns the class name of the component.\n\t * @returns The class name of the component.\n\t */\n\tpublic className(): string {\n\t\treturn OpenTelemetryTelemetryConnector.CLASS_NAME;\n\t}\n\n\t/**\n\t * Initialise the MeterProvider and configured exporters.\n\t * @param nodeLoggingComponentType The node logging component type.\n\t * @returns A promise that resolves when the MeterProvider is running.\n\t */\n\tpublic async start(nodeLoggingComponentType?: string): Promise<void> {\n\t\tif (!Is.undefined(this._meterProvider)) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst readers: MetricReader[] = [];\n\t\tfor (const [, config] of Object.entries(this._config.readers ?? {})) {\n\t\t\tif (config.type === \"prometheus\") {\n\t\t\t\treaders.push(\n\t\t\t\t\tnew PrometheusExporter({\n\t\t\t\t\t\tport: config.port,\n\t\t\t\t\t\tendpoint: config.endpoint,\n\t\t\t\t\t\t// PrometheusExporter uses preventServerStart (inverted); our config\n\t\t\t\t\t\t// exposes the more intuitive startServer (defaults to true).\n\t\t\t\t\t\tpreventServerStart: !(config.startServer ?? true),\n\t\t\t\t\t\tprefix: config.prefix\n\t\t\t\t\t})\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tthrow new GeneralError(OpenTelemetryTelemetryConnector.CLASS_NAME, \"unknownReaderType\", {\n\t\t\t\t\ttype: config.type\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\tthis._meterProvider = new MeterProvider({ readers });\n\t\tthis._meter = this._meterProvider.getMeter(\n\t\t\tthis._config.meterName ?? \"twin-telemetry\",\n\t\t\tthis._config.meterVersion ?? \"1.0.0\"\n\t\t);\n\n\t\tconst nodeLogging = ComponentFactory.getIfExists<ILoggingComponent>(nodeLoggingComponentType);\n\t\tawait nodeLogging?.log({\n\t\t\tsource: OpenTelemetryTelemetryConnector.CLASS_NAME,\n\t\t\tmessage: \"connectorStarted\",\n\t\t\tlevel: \"info\",\n\t\t\tdata: { readerCount: Object.keys(this._config.readers ?? {}).length }\n\t\t});\n\t}\n\n\t/**\n\t * Shut down the MeterProvider and release resources.\n\t * Calling stop() on a connector that has not been started is a no-op.\n\t * @param nodeLoggingComponentType The node logging component type.\n\t * @returns A promise that resolves when the MeterProvider has shut down.\n\t */\n\tpublic async stop(nodeLoggingComponentType?: string): Promise<void> {\n\t\tif (!Is.undefined(this._meterProvider)) {\n\t\t\tawait this._meterProvider.shutdown();\n\t\t\tthis._meterProvider = undefined;\n\t\t\tthis._meter = undefined;\n\t\t\tthis._instruments.clear();\n\n\t\t\tconst nodeLogging = ComponentFactory.getIfExists<ILoggingComponent>(nodeLoggingComponentType);\n\t\t\tawait nodeLogging?.log({\n\t\t\t\tsource: OpenTelemetryTelemetryConnector.CLASS_NAME,\n\t\t\t\tmessage: \"connectorStopped\",\n\t\t\t\tlevel: \"info\",\n\t\t\t\tdata: {}\n\t\t\t});\n\t\t}\n\t}\n\n\t/**\n\t * Create a new metric.\n\t * The definition is always persisted via the inner entity-storage connector.\n\t * If the MeterProvider is running the corresponding OTEL instrument is also registered.\n\t * @param metric The metric details.\n\t * @returns A promise that resolves when the metric has been persisted and the OTEL instrument registered.\n\t */\n\tpublic async createMetric(metric: ITelemetryMetric): Promise<void> {\n\t\ttry {\n\t\t\tawait this._inner.createMetric(metric);\n\t\t} catch (err) {\n\t\t\t// Entity storage performs all validation and throws AlreadyExistsError on duplicates.\n\t\t\t// OTP doesn't care about duplicates and will create a new instrument instance on each call\n\t\t\t// so we catch this error and ignore it to allow the OTEL instruments to be registered.\n\t\t\tif (!BaseError.isErrorName(err, AlreadyExistsError.CLASS_NAME)) {\n\t\t\t\tthrow err;\n\t\t\t}\n\t\t}\n\n\t\t// Register an OTEL instrument when the MeterProvider is running.\n\t\t// This runs even when the metric already exists in storage so instruments\n\t\t// survive process restarts (AlreadyExistsError path).\n\t\tconst meter = this._meter;\n\t\tif (meter !== undefined && !this._instruments.has(metric.id)) {\n\t\t\tconst instrumentOptions = {\n\t\t\t\tdescription: metric.description,\n\t\t\t\tunit: metric.unit\n\t\t\t};\n\t\t\tlet instrument: Counter | UpDownCounter | Gauge;\n\t\t\tif (metric.type === MetricType.Counter) {\n\t\t\t\tinstrument = meter.createCounter(metric.id, instrumentOptions);\n\t\t\t} else if (metric.type === MetricType.IncDecCounter) {\n\t\t\t\tinstrument = meter.createUpDownCounter(metric.id, instrumentOptions);\n\t\t\t} else {\n\t\t\t\tinstrument = meter.createGauge(metric.id, instrumentOptions);\n\t\t\t}\n\t\t\tthis._instruments.set(metric.id, { metricType: metric.type, instrument });\n\t\t}\n\t}\n\n\t/**\n\t * Get the metric details and its most recent value.\n\t * @param id The metric id.\n\t * @returns The metric details and its most recent value.\n\t */\n\tpublic async getMetric(id: string): Promise<{\n\t\tmetric: ITelemetryMetric;\n\t\tvalue: ITelemetryMetricValue;\n\t}> {\n\t\tconst result = await this._inner.getMetric(id);\n\n\t\t// When the metric has no recorded values yet the inner connector returns\n\t\t// entities[0] = undefined via queryValues. Return a placeholder so callers\n\t\t// are not surprised by a null value field — treat id=\"\" as \"no measurements yet\".\n\t\tconst value = result.value ?? { id: \"\", ts: 0, value: 0 };\n\t\treturn { metric: result.metric, value };\n\t}\n\n\t/**\n\t * Get a specific metric value by its id.\n\t * @param id The id of the metric.\n\t * @param valueId The id of the metric value.\n\t * @returns The metric value.\n\t */\n\tpublic async getMetricValue(id: string, valueId: string): Promise<ITelemetryMetricValue> {\n\t\treturn this._inner.getMetricValue(id, valueId);\n\t}\n\n\t/**\n\t * Update the metric metadata.\n\t * Note: OpenTelemetry instrument descriptors are immutable once created.\n\t * This method updates the persisted metadata mirror; the description/unit changes\n\t * are NOT propagated to the registered MeterProvider and will not appear at the\n\t * OTEL backend (Prometheus, OTLP, etc.).\n\t * @param metric The metric details (type cannot be changed).\n\t * @returns A promise that resolves when the persisted metadata has been updated.\n\t */\n\tpublic async updateMetric(metric: Omit<ITelemetryMetric, \"type\">): Promise<void> {\n\t\treturn this._inner.updateMetric(metric);\n\t}\n\n\t/**\n\t * Record a metric value.\n\t * Entity storage always receives the value first and performs all validation.\n\t * If the MeterProvider is running the measurement is also forwarded to the OTEL instrument.\n\t * Counter accepts positive integers or \"inc\".\n\t * UpDownCounter accepts integers (positive or negative) or \"inc\"/\"dec\".\n\t * Gauge accepts any number.\n\t * @param id The id of the metric.\n\t * @param value The value for the operation.\n\t * @param customData Optional custom data forwarded as OTEL attributes.\n\t * @returns The id of the new metric value entry.\n\t */\n\tpublic async addMetricValue(\n\t\tid: string,\n\t\tvalue: MetricCounterOperation | number,\n\t\tcustomData?: { [key: string]: unknown }\n\t): Promise<string> {\n\t\t// Entity storage validates and persists first; throws NotFoundError if metric not found.\n\t\tconst valueId = await this._inner.addMetricValue(id, value, customData);\n\n\t\t// Forward to the OTEL instrument only when the MeterProvider is running.\n\t\tif (this._meter !== undefined) {\n\t\t\tconst entry = this._instruments.get(id);\n\t\t\tif (entry !== undefined) {\n\t\t\t\tconst attributes = this.toAttributes(customData);\n\t\t\t\tconst { metricType, instrument } = entry;\n\n\t\t\t\t// Value already validated by inner connector — dispatch unconditionally.\n\t\t\t\tif (metricType === MetricType.Counter) {\n\t\t\t\t\t(instrument as Counter).add(\n\t\t\t\t\t\tvalue === MetricCounterOperation.Increment ? 1 : (value as number),\n\t\t\t\t\t\tattributes\n\t\t\t\t\t);\n\t\t\t\t} else if (metricType === MetricType.IncDecCounter) {\n\t\t\t\t\tlet delta: number;\n\t\t\t\t\tif (value === MetricCounterOperation.Increment) {\n\t\t\t\t\t\tdelta = 1;\n\t\t\t\t\t} else if (value === MetricCounterOperation.Decrement) {\n\t\t\t\t\t\tdelta = -1;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelta = value;\n\t\t\t\t\t}\n\t\t\t\t\t(instrument as UpDownCounter).add(delta, attributes);\n\t\t\t\t} else {\n\t\t\t\t\t(instrument as Gauge).record(value as number, attributes);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn valueId;\n\t}\n\n\t/**\n\t * Remove a metric and its persisted value history.\n\t * Note: OpenTelemetry exposes no API to deregister an instrument from a Meter,\n\t * so the underlying Counter/UpDownCounter/Gauge remains resident for the lifetime\n\t * of the process. Re-creating a metric with the same id but a different MetricType\n\t * is therefore not safe.\n\t * @param id The id of the metric.\n\t * @returns A promise that resolves when the metric and its value history have been removed.\n\t */\n\tpublic async removeMetric(id: string): Promise<void> {\n\t\tthis._instruments.delete(id);\n\t\t// The inner connector cascades and removes all associated metric values.\n\t\treturn this._inner.removeMetric(id);\n\t}\n\n\t/**\n\t * Query the registered metrics, optionally filtered by type.\n\t * @param type The type of the metric.\n\t * @param cursor The cursor to request the next page.\n\t * @param limit Limit the number of entities to return.\n\t * @returns The matching metrics and an optional cursor for the next page.\n\t */\n\tpublic async query(\n\t\ttype?: MetricType,\n\t\tcursor?: string,\n\t\tlimit?: number\n\t): Promise<{\n\t\tentities: ITelemetryMetric[];\n\t\tcursor?: string;\n\t}> {\n\t\treturn this._inner.query(type, cursor, limit);\n\t}\n\n\t/**\n\t * Query the recorded values for a metric, ordered by most recent first.\n\t * @param id The id of the metric.\n\t * @param timeStart The inclusive start time (epoch ms).\n\t * @param timeEnd The inclusive end time (epoch ms).\n\t * @param cursor The cursor returned by the previous call.\n\t * @param limit Limit the number of values to return.\n\t * @returns The metric details, matching values, and an optional cursor for the next page.\n\t */\n\tpublic async queryValues(\n\t\tid: string,\n\t\ttimeStart?: number,\n\t\ttimeEnd?: number,\n\t\tcursor?: string,\n\t\tlimit?: number\n\t): Promise<{\n\t\tmetric: ITelemetryMetric;\n\t\tentities: ITelemetryMetricValue[];\n\t\tcursor?: string;\n\t}> {\n\t\treturn this._inner.queryValues(id, timeStart, timeEnd, cursor, limit);\n\t}\n\n\t/**\n\t * Convert customData to OTEL-compatible Attributes.\n\t * Scalar values (string, number, boolean) and uniform primitive arrays\n\t * (string[], number[], boolean[]) are forwarded; other values are silently dropped.\n\t * @param customData The raw custom data map.\n\t * @returns An OTEL Attributes object.\n\t * @internal\n\t */\n\tprivate toAttributes(customData?: { [key: string]: unknown }): Attributes {\n\t\tif (Is.empty(customData)) {\n\t\t\treturn {};\n\t\t}\n\t\tconst attrs: Attributes = {};\n\t\tfor (const [key, val] of Object.entries(customData)) {\n\t\t\tif (Is.string(val) || Is.number(val) || Is.boolean(val)) {\n\t\t\t\tattrs[key] = val;\n\t\t\t} else if (Is.arrayValue(val)) {\n\t\t\t\tif (\n\t\t\t\t\t(Is.string(val[0]) || Is.number(val[0]) || Is.boolean(val[0])) &&\n\t\t\t\t\tval.every(el => typeof el === typeof val[0])\n\t\t\t\t) {\n\t\t\t\t\tattrs[key] = val as string[] | number[] | boolean[];\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn attrs;\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"openTelemetryTelemetryConnector.js","sourceRoot":"","sources":["../../src/openTelemetryTelemetryConnector.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2026 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type { Attributes, Counter, Gauge, Meter, UpDownCounter } from \"@opentelemetry/api\";\nimport { PrometheusExporter } from \"@opentelemetry/exporter-prometheus\";\nimport { resourceFromAttributes } from \"@opentelemetry/resources\";\nimport { MeterProvider, type MetricReader } from \"@opentelemetry/sdk-metrics\";\nimport { ContextIdKeys, ContextIdStore, type IContextIds } from \"@twin.org/context\";\nimport { AlreadyExistsError, BaseError, ComponentFactory, Guards, Is } from \"@twin.org/core\";\nimport type { ILoggingComponent } from \"@twin.org/logging-models\";\nimport { nameof } from \"@twin.org/nameof\";\nimport { EntityStorageTelemetryConnector } from \"@twin.org/telemetry-connector-entity-storage\";\nimport {\n\ttype ITelemetryConnector,\n\ttype ITelemetryMetric,\n\ttype ITelemetryMetricValue,\n\tMetricCounterOperation,\n\tMetricType\n} from \"@twin.org/telemetry-models\";\nimport type { IOpenTelemetryTelemetryConnectorConfig } from \"./models/IOpenTelemetryTelemetryConnectorConfig.js\";\nimport type { IOpenTelemetryTelemetryConnectorConstructorOptions } from \"./models/IOpenTelemetryTelemetryConnectorConstructorOptions.js\";\nimport { OpenTelemetryReaderTypes } from \"./models/openTelemetryReaderTypes.js\";\n\n/**\n * Class for performing telemetry operations using OpenTelemetry instruments.\n * Metric definitions and value history are persisted via an internal\n * EntityStorageTelemetryConnector instance created at construction time.\n */\nexport class OpenTelemetryTelemetryConnector implements ITelemetryConnector {\n\t/**\n\t * The namespace supported by the telemetry connector.\n\t */\n\tpublic static readonly NAMESPACE: string = \"opentelemetry\";\n\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<OpenTelemetryTelemetryConnector>();\n\n\t/**\n\t * Config options stored so provider creation can initialise readers and meter identity.\n\t * @internal\n\t */\n\tprivate readonly _config: IOpenTelemetryTelemetryConnectorConfig;\n\n\t/**\n\t * Internal entity-storage connector that owns all metric metadata and value history.\n\t * @internal\n\t */\n\tprivate readonly _inner: EntityStorageTelemetryConnector;\n\n\t/**\n\t * Whether start() has been called. Drives OTEL forwarding on/off.\n\t * @internal\n\t */\n\tprivate _started: boolean;\n\n\t/**\n\t * Cache of MeterProvider+Meter+instruments keyed by \"nodeId/tenantId\".\n\t * Providers are created on demand when the first metric measurement arrives\n\t * for a given tenant/node pair.\n\t * @internal\n\t */\n\tprivate readonly _providers: {\n\t\t[key: string]: {\n\t\t\tmeterProvider: MeterProvider;\n\t\t\tmeter: Meter;\n\t\t\tinstruments: {\n\t\t\t\t[id: string]: { metricType: MetricType; instrument: Counter | UpDownCounter | Gauge };\n\t\t\t};\n\t\t};\n\t};\n\n\t/**\n\t * Metric definition cache keyed by metric id, populated in createMetric and on first\n\t * addMetricValue after a process restart. Used to register instruments on new providers\n\t * without re-querying entity storage on every call.\n\t * @internal\n\t */\n\tprivate readonly _metricDefs: {\n\t\t[id: string]: { type: MetricType; description?: string; unit?: string };\n\t};\n\n\t/**\n\t * Create a new instance of OpenTelemetryTelemetryConnector.\n\t * @param options The options for the connector.\n\t * @throws GuardError When a reader config specifies an unsupported type.\n\t */\n\tconstructor(options?: IOpenTelemetryTelemetryConnectorConstructorOptions) {\n\t\tthis._config = options?.config ?? {};\n\n\t\tfor (const [, readerConfig] of Object.entries(this._config.readers ?? {})) {\n\t\t\tGuards.arrayOneOf(\n\t\t\t\tOpenTelemetryTelemetryConnector.CLASS_NAME,\n\t\t\t\tnameof(readerConfig.type),\n\t\t\t\treaderConfig.type,\n\t\t\t\tObject.values(OpenTelemetryReaderTypes)\n\t\t\t);\n\t\t}\n\n\t\tthis._inner = new EntityStorageTelemetryConnector({\n\t\t\tloggingComponentType: options?.loggingComponentType,\n\t\t\ttelemetryMetricStorageConnectorType: options?.telemetryMetricStorageConnectorType,\n\t\t\ttelemetryMetricValueStorageConnectorType: options?.telemetryMetricValueStorageConnectorType,\n\t\t\tconfig: {\n\t\t\t\tmutexTimeoutMs: this._config.mutexTimeoutMs\n\t\t\t}\n\t\t});\n\t\tthis._started = false;\n\t\tthis._providers = {};\n\t\tthis._metricDefs = {};\n\t}\n\n\t/**\n\t * Returns the class name of the component.\n\t * @returns The class name of the component.\n\t */\n\tpublic className(): string {\n\t\treturn OpenTelemetryTelemetryConnector.CLASS_NAME;\n\t}\n\n\t/**\n\t * Enable OTEL forwarding. Subsequent calls to createMetric and addMetricValue will\n\t * create per-tenant/node MeterProviders on demand.\n\t * @param nodeLoggingComponentType The node logging component type.\n\t * @returns A promise that resolves when OTEL forwarding is enabled.\n\t */\n\tpublic async start(nodeLoggingComponentType?: string): Promise<void> {\n\t\tif (this._started) {\n\t\t\treturn;\n\t\t}\n\t\tthis._started = true;\n\n\t\tconst nodeLogging = ComponentFactory.getIfExists<ILoggingComponent>(nodeLoggingComponentType);\n\t\tawait nodeLogging?.log({\n\t\t\tsource: OpenTelemetryTelemetryConnector.CLASS_NAME,\n\t\t\tmessage: \"connectorStarted\",\n\t\t\tlevel: \"info\",\n\t\t\tdata: { readerCount: Object.keys(this._config.readers ?? {}).length }\n\t\t});\n\t}\n\n\t/**\n\t * Shut down all cached MeterProviders and disable OTEL forwarding.\n\t * @param nodeLoggingComponentType The node logging component type.\n\t * @returns A promise that resolves when all MeterProviders have shut down.\n\t */\n\tpublic async stop(nodeLoggingComponentType?: string): Promise<void> {\n\t\tif (this._started) {\n\t\t\tfor (const { meterProvider } of Object.values(this._providers)) {\n\t\t\t\tawait meterProvider.shutdown();\n\t\t\t}\n\t\t\tfor (const key of Object.keys(this._providers)) {\n\t\t\t\tdelete this._providers[key];\n\t\t\t}\n\t\t\tthis._started = false;\n\n\t\t\tconst nodeLogging = ComponentFactory.getIfExists<ILoggingComponent>(nodeLoggingComponentType);\n\t\t\tawait nodeLogging?.log({\n\t\t\t\tsource: OpenTelemetryTelemetryConnector.CLASS_NAME,\n\t\t\t\tmessage: \"connectorStopped\",\n\t\t\t\tlevel: \"info\",\n\t\t\t\tdata: {}\n\t\t\t});\n\t\t}\n\t}\n\n\t/**\n\t * Create a new metric.\n\t * @param metric The metric details.\n\t * @returns A promise that resolves when the metric has been persisted.\n\t */\n\tpublic async createMetric(metric: ITelemetryMetric): Promise<void> {\n\t\ttry {\n\t\t\tawait this._inner.createMetric(metric);\n\t\t} catch (err) {\n\t\t\t// Entity storage performs all validation and throws AlreadyExistsError on duplicates.\n\t\t\t// OTP doesn't care about duplicates and will create a new instrument instance on each call\n\t\t\t// so we catch this error and ignore it to allow the OTEL instruments to be registered.\n\t\t\tif (!BaseError.isErrorName(err, AlreadyExistsError.CLASS_NAME)) {\n\t\t\t\tthrow err;\n\t\t\t}\n\t\t}\n\n\t\tthis._metricDefs[metric.id] = {\n\t\t\ttype: metric.type,\n\t\t\tdescription: metric.description,\n\t\t\tunit: metric.unit\n\t\t};\n\n\t\tif (this._started) {\n\t\t\tconst contextIds = (await ContextIdStore.getContextIds()) ?? {};\n\t\t\tconst { meter, instruments } = this.getOrCreateProvider(contextIds);\n\t\t\tthis.registerInstrument(\n\t\t\t\tmetric.id,\n\t\t\t\tmetric.type,\n\t\t\t\tmetric.description,\n\t\t\t\tmetric.unit,\n\t\t\t\tmeter,\n\t\t\t\tinstruments\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Get the metric details and its most recent value.\n\t * @param id The metric id.\n\t * @returns The metric details and its most recent value.\n\t */\n\tpublic async getMetric(id: string): Promise<{\n\t\tmetric: ITelemetryMetric;\n\t\tvalue: ITelemetryMetricValue;\n\t}> {\n\t\tconst result = await this._inner.getMetric(id);\n\t\tconst value = result.value ?? { id: \"\", ts: 0, value: 0 };\n\t\treturn { metric: result.metric, value };\n\t}\n\n\t/**\n\t * Get a specific metric value by its id.\n\t * @param id The id of the metric.\n\t * @param valueId The id of the metric value.\n\t * @returns The metric value.\n\t */\n\tpublic async getMetricValue(id: string, valueId: string): Promise<ITelemetryMetricValue> {\n\t\treturn this._inner.getMetricValue(id, valueId);\n\t}\n\n\t/**\n\t * Update the metric metadata.\n\t * Note: OpenTelemetry instrument descriptors are immutable once created.\n\t * This method updates the persisted metadata mirror only.\n\t * @param metric The metric details (type cannot be changed).\n\t * @returns A promise that resolves when the persisted metadata has been updated.\n\t */\n\tpublic async updateMetric(metric: Omit<ITelemetryMetric, \"type\">): Promise<void> {\n\t\treturn this._inner.updateMetric(metric);\n\t}\n\n\t/**\n\t * Record a metric value.\n\t * The current tenant and node IDs are read from `ContextIdStore` and used to\n\t * select (or create) the matching per-tenant/node `MeterProvider`.\n\t * @param id The id of the metric.\n\t * @param value The value for the operation.\n\t * @param customData Optional custom data forwarded as OTEL attributes.\n\t * @returns The id of the new metric value entry.\n\t */\n\tpublic async addMetricValue(\n\t\tid: string,\n\t\tvalue: MetricCounterOperation | number,\n\t\tcustomData?: { [key: string]: unknown }\n\t): Promise<string> {\n\t\tconst valueId = await this._inner.addMetricValue(id, value, customData);\n\n\t\tif (this._started) {\n\t\t\tconst contextIds = (await ContextIdStore.getContextIds()) ?? {};\n\t\t\tconst { meter, instruments } = this.getOrCreateProvider(contextIds);\n\n\t\t\tif (!(id in instruments)) {\n\t\t\t\tlet def = this._metricDefs[id];\n\t\t\t\tif (def === undefined) {\n\t\t\t\t\t// Process restart path: definition not yet cached, fetch from storage.\n\t\t\t\t\tconst { metric } = await this._inner.getMetric(id);\n\t\t\t\t\tdef = { type: metric.type, description: metric.description, unit: metric.unit };\n\t\t\t\t\tthis._metricDefs[id] = def;\n\t\t\t\t}\n\t\t\t\tthis.registerInstrument(id, def.type, def.description, def.unit, meter, instruments);\n\t\t\t}\n\n\t\t\tconst entry = instruments[id];\n\t\t\tif (entry !== undefined) {\n\t\t\t\tconst attributes = this.toAttributes(customData);\n\t\t\t\tconst { metricType, instrument } = entry;\n\n\t\t\t\tif (metricType === MetricType.Counter) {\n\t\t\t\t\t(instrument as Counter).add(\n\t\t\t\t\t\tvalue === MetricCounterOperation.Increment ? 1 : (value as number),\n\t\t\t\t\t\tattributes\n\t\t\t\t\t);\n\t\t\t\t} else if (metricType === MetricType.IncDecCounter) {\n\t\t\t\t\tlet delta: number;\n\t\t\t\t\tif (value === MetricCounterOperation.Increment) {\n\t\t\t\t\t\tdelta = 1;\n\t\t\t\t\t} else if (value === MetricCounterOperation.Decrement) {\n\t\t\t\t\t\tdelta = -1;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelta = value;\n\t\t\t\t\t}\n\t\t\t\t\t(instrument as UpDownCounter).add(delta, attributes);\n\t\t\t\t} else {\n\t\t\t\t\t(instrument as Gauge).record(value as number, attributes);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn valueId;\n\t}\n\n\t/**\n\t * Remove a metric and its persisted value history.\n\t * Note: OpenTelemetry exposes no API to deregister an instrument from a Meter,\n\t * so the underlying Counter/UpDownCounter/Gauge remains resident for the lifetime\n\t * of the MeterProvider. Re-creating a metric with the same id but a different\n\t * MetricType is therefore not safe.\n\t * @param id The id of the metric.\n\t * @returns A promise that resolves when the metric and its value history have been removed.\n\t */\n\tpublic async removeMetric(id: string): Promise<void> {\n\t\tfor (const { instruments } of Object.values(this._providers)) {\n\t\t\tdelete instruments[id];\n\t\t}\n\t\tdelete this._metricDefs[id];\n\t\treturn this._inner.removeMetric(id);\n\t}\n\n\t/**\n\t * Query the registered metrics, optionally filtered by type.\n\t * @param type The type of the metric.\n\t * @param cursor The cursor to request the next page.\n\t * @param limit Limit the number of entities to return.\n\t * @returns The matching metrics and an optional cursor for the next page.\n\t */\n\tpublic async query(\n\t\ttype?: MetricType,\n\t\tcursor?: string,\n\t\tlimit?: number\n\t): Promise<{\n\t\tentities: ITelemetryMetric[];\n\t\tcursor?: string;\n\t}> {\n\t\treturn this._inner.query(type, cursor, limit);\n\t}\n\n\t/**\n\t * Query the recorded values for a metric, ordered by most recent first.\n\t * @param id The id of the metric.\n\t * @param timeStart The inclusive start time (epoch ms).\n\t * @param timeEnd The inclusive end time (epoch ms).\n\t * @param cursor The cursor returned by the previous call.\n\t * @param limit Limit the number of values to return.\n\t * @returns The metric details, matching values, and an optional cursor for the next page.\n\t */\n\tpublic async queryValues(\n\t\tid: string,\n\t\ttimeStart?: number,\n\t\ttimeEnd?: number,\n\t\tcursor?: string,\n\t\tlimit?: number\n\t): Promise<{\n\t\tmetric: ITelemetryMetric;\n\t\tentities: ITelemetryMetricValue[];\n\t\tcursor?: string;\n\t}> {\n\t\treturn this._inner.queryValues(id, timeStart, timeEnd, cursor, limit);\n\t}\n\n\t/**\n\t * Return or create the MeterProvider for the given tenant/node pair.\n\t * Providers are keyed by \"nodeId/tenantId\" and carry OTEL resource attributes\n\t * service.namespace=tenantId and service.instance.id=nodeId when those values\n\t * are present.\n\t * @param contextIds The current execution context IDs.\n\t * @returns The cached or newly created provider entry.\n\t * @internal\n\t */\n\tprivate getOrCreateProvider(contextIds: IContextIds): {\n\t\tmeterProvider: MeterProvider;\n\t\tmeter: Meter;\n\t\tinstruments: {\n\t\t\t[id: string]: { metricType: MetricType; instrument: Counter | UpDownCounter | Gauge };\n\t\t};\n\t} {\n\t\tconst node = contextIds[ContextIdKeys.Node];\n\t\tconst tenantId = contextIds[ContextIdKeys.Tenant];\n\n\t\tconst key = `${node ?? \"\"}/${tenantId ?? \"\"}`;\n\n\t\tlet cached = this._providers[key];\n\t\tif (!Is.undefined(cached)) {\n\t\t\treturn cached;\n\t\t}\n\n\t\tconst readers = this.createReaders();\n\t\tconst resourcePrefix = \"service\";\n\t\tconst resourceAttrs: { [id: string]: string } = {};\n\t\tif (Is.stringValue(node)) {\n\t\t\tresourceAttrs[`${resourcePrefix}.instance.id`] = node;\n\t\t}\n\t\tif (Is.stringValue(tenantId)) {\n\t\t\tresourceAttrs[`${resourcePrefix}.namespace`] = tenantId;\n\t\t}\n\t\tconst resource =\n\t\t\tObject.keys(resourceAttrs).length > 0 ? resourceFromAttributes(resourceAttrs) : undefined;\n\t\tconst meterProvider = new MeterProvider({ readers, resource });\n\t\tconst meter = meterProvider.getMeter(\n\t\t\tthis._config.meterName ?? \"twin-telemetry\",\n\t\t\tthis._config.meterVersion ?? \"1.0.0\"\n\t\t);\n\t\tcached = { meterProvider, meter, instruments: {} };\n\t\tthis._providers[key] = cached;\n\n\t\treturn cached;\n\t}\n\n\t/**\n\t * Instantiate fresh MetricReader instances from the connector config.\n\t * Called each time a new MeterProvider is created for a tenant/node pair.\n\t * @returns The list of reader instances.\n\t * @internal\n\t */\n\tprivate createReaders(): MetricReader[] {\n\t\tconst readers: MetricReader[] = [];\n\t\tfor (const [, config] of Object.entries(this._config.readers ?? {})) {\n\t\t\tif (config.type === OpenTelemetryReaderTypes.Prometheus) {\n\t\t\t\treaders.push(\n\t\t\t\t\tnew PrometheusExporter({\n\t\t\t\t\t\tport: config.port,\n\t\t\t\t\t\tendpoint: config.endpoint,\n\t\t\t\t\t\tpreventServerStart: !(config.startServer ?? true),\n\t\t\t\t\t\tprefix: config.prefix\n\t\t\t\t\t})\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t\treturn readers;\n\t}\n\n\t/**\n\t * Register an OTEL instrument on a provider's instruments map if not already present.\n\t * @param id The metric id.\n\t * @param type The metric type.\n\t * @param description The metric description.\n\t * @param unit The metric unit.\n\t * @param meter The meter to create instruments on.\n\t * @param instruments The per-provider instruments map to update.\n\t * @internal\n\t */\n\tprivate registerInstrument(\n\t\tid: string,\n\t\ttype: MetricType,\n\t\tdescription: string | undefined,\n\t\tunit: string | undefined,\n\t\tmeter: Meter,\n\t\tinstruments: {\n\t\t\t[id: string]: { metricType: MetricType; instrument: Counter | UpDownCounter | Gauge };\n\t\t}\n\t): void {\n\t\tif (id in instruments) {\n\t\t\treturn;\n\t\t}\n\t\tconst instrumentOptions = { description, unit };\n\t\tlet instrument: Counter | UpDownCounter | Gauge;\n\t\tif (type === MetricType.Counter) {\n\t\t\tinstrument = meter.createCounter(id, instrumentOptions);\n\t\t} else if (type === MetricType.IncDecCounter) {\n\t\t\tinstrument = meter.createUpDownCounter(id, instrumentOptions);\n\t\t} else {\n\t\t\tinstrument = meter.createGauge(id, instrumentOptions);\n\t\t}\n\t\tinstruments[id] = { metricType: type, instrument };\n\t}\n\n\t/**\n\t * Convert customData to OTEL-compatible Attributes.\n\t * @param customData The raw custom data map.\n\t * @returns An OTEL Attributes object.\n\t * @internal\n\t */\n\tprivate toAttributes(customData?: { [key: string]: unknown }): Attributes {\n\t\tif (Is.empty(customData)) {\n\t\t\treturn {};\n\t\t}\n\t\tconst attrs: Attributes = {};\n\t\tfor (const [key, val] of Object.entries(customData)) {\n\t\t\tif (Is.string(val) || Is.number(val) || Is.boolean(val)) {\n\t\t\t\tattrs[key] = val;\n\t\t\t} else if (Is.arrayValue(val)) {\n\t\t\t\tif (\n\t\t\t\t\t(Is.string(val[0]) || Is.number(val[0]) || Is.boolean(val[0])) &&\n\t\t\t\t\tval.every(el => typeof el === typeof val[0])\n\t\t\t\t) {\n\t\t\t\t\tattrs[key] = val as string[] | number[] | boolean[];\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn attrs;\n\t}\n}\n"]}
|
|
@@ -3,6 +3,10 @@ import type { IOpenTelemetryReaderConfig } from "./IOpenTelemetryReaderConfig.js
|
|
|
3
3
|
* The options for the OpenTelemetry telemetry connector constructor.
|
|
4
4
|
*/
|
|
5
5
|
export interface IOpenTelemetryTelemetryConnectorConfig {
|
|
6
|
+
/**
|
|
7
|
+
* Timeout in milliseconds for acquiring the inner entity-storage metric write mutex lock.
|
|
8
|
+
*/
|
|
9
|
+
mutexTimeoutMs?: number;
|
|
6
10
|
/**
|
|
7
11
|
* The name of the OpenTelemetry meter used to create instruments.
|
|
8
12
|
* @default twin-telemetry
|
|
@@ -4,9 +4,6 @@ import type { IOpenTelemetryTelemetryConnectorConstructorOptions } from "./model
|
|
|
4
4
|
* Class for performing telemetry operations using OpenTelemetry instruments.
|
|
5
5
|
* Metric definitions and value history are persisted via an internal
|
|
6
6
|
* EntityStorageTelemetryConnector instance created at construction time.
|
|
7
|
-
* Call `start()` to initialise the MeterProvider and exporters; metrics can be
|
|
8
|
-
* created and queried before start() — OTEL forwarding is simply skipped until
|
|
9
|
-
* the MeterProvider is running.
|
|
10
7
|
*/
|
|
11
8
|
export declare class OpenTelemetryTelemetryConnector implements ITelemetryConnector {
|
|
12
9
|
/**
|
|
@@ -19,9 +16,8 @@ export declare class OpenTelemetryTelemetryConnector implements ITelemetryConnec
|
|
|
19
16
|
static readonly CLASS_NAME: string;
|
|
20
17
|
/**
|
|
21
18
|
* Create a new instance of OpenTelemetryTelemetryConnector.
|
|
22
|
-
* Eagerly constructs the inner EntityStorageTelemetryConnector — if the required
|
|
23
|
-
* entity storage types are not registered this constructor will throw (fail fast).
|
|
24
19
|
* @param options The options for the connector.
|
|
20
|
+
* @throws GuardError When a reader config specifies an unsupported type.
|
|
25
21
|
*/
|
|
26
22
|
constructor(options?: IOpenTelemetryTelemetryConnectorConstructorOptions);
|
|
27
23
|
/**
|
|
@@ -30,24 +26,22 @@ export declare class OpenTelemetryTelemetryConnector implements ITelemetryConnec
|
|
|
30
26
|
*/
|
|
31
27
|
className(): string;
|
|
32
28
|
/**
|
|
33
|
-
*
|
|
29
|
+
* Enable OTEL forwarding. Subsequent calls to createMetric and addMetricValue will
|
|
30
|
+
* create per-tenant/node MeterProviders on demand.
|
|
34
31
|
* @param nodeLoggingComponentType The node logging component type.
|
|
35
|
-
* @returns A promise that resolves when
|
|
32
|
+
* @returns A promise that resolves when OTEL forwarding is enabled.
|
|
36
33
|
*/
|
|
37
34
|
start(nodeLoggingComponentType?: string): Promise<void>;
|
|
38
35
|
/**
|
|
39
|
-
* Shut down
|
|
40
|
-
* Calling stop() on a connector that has not been started is a no-op.
|
|
36
|
+
* Shut down all cached MeterProviders and disable OTEL forwarding.
|
|
41
37
|
* @param nodeLoggingComponentType The node logging component type.
|
|
42
|
-
* @returns A promise that resolves when
|
|
38
|
+
* @returns A promise that resolves when all MeterProviders have shut down.
|
|
43
39
|
*/
|
|
44
40
|
stop(nodeLoggingComponentType?: string): Promise<void>;
|
|
45
41
|
/**
|
|
46
42
|
* Create a new metric.
|
|
47
|
-
* The definition is always persisted via the inner entity-storage connector.
|
|
48
|
-
* If the MeterProvider is running the corresponding OTEL instrument is also registered.
|
|
49
43
|
* @param metric The metric details.
|
|
50
|
-
* @returns A promise that resolves when the metric has been persisted
|
|
44
|
+
* @returns A promise that resolves when the metric has been persisted.
|
|
51
45
|
*/
|
|
52
46
|
createMetric(metric: ITelemetryMetric): Promise<void>;
|
|
53
47
|
/**
|
|
@@ -69,20 +63,15 @@ export declare class OpenTelemetryTelemetryConnector implements ITelemetryConnec
|
|
|
69
63
|
/**
|
|
70
64
|
* Update the metric metadata.
|
|
71
65
|
* Note: OpenTelemetry instrument descriptors are immutable once created.
|
|
72
|
-
* This method updates the persisted metadata mirror
|
|
73
|
-
* are NOT propagated to the registered MeterProvider and will not appear at the
|
|
74
|
-
* OTEL backend (Prometheus, OTLP, etc.).
|
|
66
|
+
* This method updates the persisted metadata mirror only.
|
|
75
67
|
* @param metric The metric details (type cannot be changed).
|
|
76
68
|
* @returns A promise that resolves when the persisted metadata has been updated.
|
|
77
69
|
*/
|
|
78
70
|
updateMetric(metric: Omit<ITelemetryMetric, "type">): Promise<void>;
|
|
79
71
|
/**
|
|
80
72
|
* Record a metric value.
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
* Counter accepts positive integers or "inc".
|
|
84
|
-
* UpDownCounter accepts integers (positive or negative) or "inc"/"dec".
|
|
85
|
-
* Gauge accepts any number.
|
|
73
|
+
* The current tenant and node IDs are read from `ContextIdStore` and used to
|
|
74
|
+
* select (or create) the matching per-tenant/node `MeterProvider`.
|
|
86
75
|
* @param id The id of the metric.
|
|
87
76
|
* @param value The value for the operation.
|
|
88
77
|
* @param customData Optional custom data forwarded as OTEL attributes.
|
|
@@ -95,8 +84,8 @@ export declare class OpenTelemetryTelemetryConnector implements ITelemetryConnec
|
|
|
95
84
|
* Remove a metric and its persisted value history.
|
|
96
85
|
* Note: OpenTelemetry exposes no API to deregister an instrument from a Meter,
|
|
97
86
|
* so the underlying Counter/UpDownCounter/Gauge remains resident for the lifetime
|
|
98
|
-
* of the
|
|
99
|
-
* is therefore not safe.
|
|
87
|
+
* of the MeterProvider. Re-creating a metric with the same id but a different
|
|
88
|
+
* MetricType is therefore not safe.
|
|
100
89
|
* @param id The id of the metric.
|
|
101
90
|
* @returns A promise that resolves when the metric and its value history have been removed.
|
|
102
91
|
*/
|
package/docs/changelog.md
CHANGED
|
@@ -1,5 +1,50 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.9.2-next.2](https://github.com/iotaledger/twin-telemetry/compare/telemetry-connector-opentelemetry-v0.9.2-next.1...telemetry-connector-opentelemetry-v0.9.2-next.2) (2026-08-03)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* resource partitioning ([#77](https://github.com/iotaledger/twin-telemetry/issues/77)) ([bae3093](https://github.com/iotaledger/twin-telemetry/commit/bae3093d285b379f96dd95c9bbed59bb0895a2cd))
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Dependencies
|
|
12
|
+
|
|
13
|
+
* The following workspace dependencies were updated
|
|
14
|
+
* dependencies
|
|
15
|
+
* @twin.org/telemetry-connector-entity-storage bumped from 0.9.2-next.1 to 0.9.2-next.2
|
|
16
|
+
* @twin.org/telemetry-models bumped from 0.9.2-next.1 to 0.9.2-next.2
|
|
17
|
+
|
|
18
|
+
## [0.9.2-next.1](https://github.com/iotaledger/twin-telemetry/compare/telemetry-connector-opentelemetry-v0.9.2-next.0...telemetry-connector-opentelemetry-v0.9.2-next.1) (2026-07-30)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
### Features
|
|
22
|
+
|
|
23
|
+
* add helper and command types ([7166013](https://github.com/iotaledger/twin-telemetry/commit/7166013f8a0693a1d92101cb600c37a1aba66417))
|
|
24
|
+
* add metrics producer infrastructure ([#30](https://github.com/iotaledger/twin-telemetry/issues/30)) ([8990b99](https://github.com/iotaledger/twin-telemetry/commit/8990b990d22f331d44562c2781c9b6ddf846db88))
|
|
25
|
+
* add OpenTelemetry connector, tests and a graphana example ([#22](https://github.com/iotaledger/twin-telemetry/issues/22)) ([ae3e08f](https://github.com/iotaledger/twin-telemetry/commit/ae3e08f459cb20ff5d2a149982676de8c2f90ae1))
|
|
26
|
+
* improve open telemetry config ([8803201](https://github.com/iotaledger/twin-telemetry/commit/8803201611b1799a00cd657e0675f85596c4edce))
|
|
27
|
+
* rest enhancement ([#56](https://github.com/iotaledger/twin-telemetry/issues/56)) ([28b79fc](https://github.com/iotaledger/twin-telemetry/commit/28b79fc2fb66f1e5c28cde325d018e5878d899b5))
|
|
28
|
+
* typescript 6 update ([0acecc2](https://github.com/iotaledger/twin-telemetry/commit/0acecc2692b174fbaec6a8a89bd9277fa3530b5f))
|
|
29
|
+
* update components ([a8583f3](https://github.com/iotaledger/twin-telemetry/commit/a8583f361218e65f5b85cf8ac4f8114d6d2c0079))
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
### Bug Fixes
|
|
33
|
+
|
|
34
|
+
* docs ([acc23a6](https://github.com/iotaledger/twin-telemetry/commit/acc23a674e75752e3817e2717d6b029d0b1404ff))
|
|
35
|
+
* metric timestamp ordering ([#73](https://github.com/iotaledger/twin-telemetry/issues/73)) ([67829ac](https://github.com/iotaledger/twin-telemetry/commit/67829acdfa4d0272bde5ad51d71d0ada039c4ccc))
|
|
36
|
+
* open-telemetry config options ([c35ee76](https://github.com/iotaledger/twin-telemetry/commit/c35ee764ccad9168985fcac6efc3461422884ce4))
|
|
37
|
+
* use async getStore in tests ([da1ea59](https://github.com/iotaledger/twin-telemetry/commit/da1ea5948c7ece9c614d9e9056fe6a4efab16180))
|
|
38
|
+
* use async getStore in tests ([38dade0](https://github.com/iotaledger/twin-telemetry/commit/38dade0537b30e10ef598d3303328136f5c113eb))
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
### Dependencies
|
|
42
|
+
|
|
43
|
+
* The following workspace dependencies were updated
|
|
44
|
+
* dependencies
|
|
45
|
+
* @twin.org/telemetry-connector-entity-storage bumped from 0.9.2-next.0 to 0.9.2-next.1
|
|
46
|
+
* @twin.org/telemetry-models bumped from 0.9.2-next.0 to 0.9.2-next.1
|
|
47
|
+
|
|
3
48
|
## [0.9.1](https://github.com/iotaledger/twin-telemetry/compare/telemetry-connector-opentelemetry-v0.9.1...telemetry-connector-opentelemetry-v0.9.1) (2026-07-27)
|
|
4
49
|
|
|
5
50
|
|
|
@@ -3,9 +3,6 @@
|
|
|
3
3
|
Class for performing telemetry operations using OpenTelemetry instruments.
|
|
4
4
|
Metric definitions and value history are persisted via an internal
|
|
5
5
|
EntityStorageTelemetryConnector instance created at construction time.
|
|
6
|
-
Call `start()` to initialise the MeterProvider and exporters; metrics can be
|
|
7
|
-
created and queried before start() — OTEL forwarding is simply skipped until
|
|
8
|
-
the MeterProvider is running.
|
|
9
6
|
|
|
10
7
|
## Implements
|
|
11
8
|
|
|
@@ -18,8 +15,6 @@ the MeterProvider is running.
|
|
|
18
15
|
> **new OpenTelemetryTelemetryConnector**(`options?`): `OpenTelemetryTelemetryConnector`
|
|
19
16
|
|
|
20
17
|
Create a new instance of OpenTelemetryTelemetryConnector.
|
|
21
|
-
Eagerly constructs the inner EntityStorageTelemetryConnector — if the required
|
|
22
|
-
entity storage types are not registered this constructor will throw (fail fast).
|
|
23
18
|
|
|
24
19
|
#### Parameters
|
|
25
20
|
|
|
@@ -33,6 +28,10 @@ The options for the connector.
|
|
|
33
28
|
|
|
34
29
|
`OpenTelemetryTelemetryConnector`
|
|
35
30
|
|
|
31
|
+
#### Throws
|
|
32
|
+
|
|
33
|
+
GuardError When a reader config specifies an unsupported type.
|
|
34
|
+
|
|
36
35
|
## Properties
|
|
37
36
|
|
|
38
37
|
### NAMESPACE {#namespace}
|
|
@@ -73,7 +72,8 @@ The class name of the component.
|
|
|
73
72
|
|
|
74
73
|
> **start**(`nodeLoggingComponentType?`): `Promise`\<`void`\>
|
|
75
74
|
|
|
76
|
-
|
|
75
|
+
Enable OTEL forwarding. Subsequent calls to createMetric and addMetricValue will
|
|
76
|
+
create per-tenant/node MeterProviders on demand.
|
|
77
77
|
|
|
78
78
|
#### Parameters
|
|
79
79
|
|
|
@@ -87,7 +87,7 @@ The node logging component type.
|
|
|
87
87
|
|
|
88
88
|
`Promise`\<`void`\>
|
|
89
89
|
|
|
90
|
-
A promise that resolves when
|
|
90
|
+
A promise that resolves when OTEL forwarding is enabled.
|
|
91
91
|
|
|
92
92
|
#### Implementation of
|
|
93
93
|
|
|
@@ -99,8 +99,7 @@ A promise that resolves when the MeterProvider is running.
|
|
|
99
99
|
|
|
100
100
|
> **stop**(`nodeLoggingComponentType?`): `Promise`\<`void`\>
|
|
101
101
|
|
|
102
|
-
Shut down
|
|
103
|
-
Calling stop() on a connector that has not been started is a no-op.
|
|
102
|
+
Shut down all cached MeterProviders and disable OTEL forwarding.
|
|
104
103
|
|
|
105
104
|
#### Parameters
|
|
106
105
|
|
|
@@ -114,7 +113,7 @@ The node logging component type.
|
|
|
114
113
|
|
|
115
114
|
`Promise`\<`void`\>
|
|
116
115
|
|
|
117
|
-
A promise that resolves when
|
|
116
|
+
A promise that resolves when all MeterProviders have shut down.
|
|
118
117
|
|
|
119
118
|
#### Implementation of
|
|
120
119
|
|
|
@@ -127,8 +126,6 @@ A promise that resolves when the MeterProvider has shut down.
|
|
|
127
126
|
> **createMetric**(`metric`): `Promise`\<`void`\>
|
|
128
127
|
|
|
129
128
|
Create a new metric.
|
|
130
|
-
The definition is always persisted via the inner entity-storage connector.
|
|
131
|
-
If the MeterProvider is running the corresponding OTEL instrument is also registered.
|
|
132
129
|
|
|
133
130
|
#### Parameters
|
|
134
131
|
|
|
@@ -142,7 +139,7 @@ The metric details.
|
|
|
142
139
|
|
|
143
140
|
`Promise`\<`void`\>
|
|
144
141
|
|
|
145
|
-
A promise that resolves when the metric has been persisted
|
|
142
|
+
A promise that resolves when the metric has been persisted.
|
|
146
143
|
|
|
147
144
|
#### Implementation of
|
|
148
145
|
|
|
@@ -214,9 +211,7 @@ The metric value.
|
|
|
214
211
|
|
|
215
212
|
Update the metric metadata.
|
|
216
213
|
Note: OpenTelemetry instrument descriptors are immutable once created.
|
|
217
|
-
This method updates the persisted metadata mirror
|
|
218
|
-
are NOT propagated to the registered MeterProvider and will not appear at the
|
|
219
|
-
OTEL backend (Prometheus, OTLP, etc.).
|
|
214
|
+
This method updates the persisted metadata mirror only.
|
|
220
215
|
|
|
221
216
|
#### Parameters
|
|
222
217
|
|
|
@@ -243,11 +238,8 @@ A promise that resolves when the persisted metadata has been updated.
|
|
|
243
238
|
> **addMetricValue**(`id`, `value`, `customData?`): `Promise`\<`string`\>
|
|
244
239
|
|
|
245
240
|
Record a metric value.
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
Counter accepts positive integers or "inc".
|
|
249
|
-
UpDownCounter accepts integers (positive or negative) or "inc"/"dec".
|
|
250
|
-
Gauge accepts any number.
|
|
241
|
+
The current tenant and node IDs are read from `ContextIdStore` and used to
|
|
242
|
+
select (or create) the matching per-tenant/node `MeterProvider`.
|
|
251
243
|
|
|
252
244
|
#### Parameters
|
|
253
245
|
|
|
@@ -286,8 +278,8 @@ The id of the new metric value entry.
|
|
|
286
278
|
Remove a metric and its persisted value history.
|
|
287
279
|
Note: OpenTelemetry exposes no API to deregister an instrument from a Meter,
|
|
288
280
|
so the underlying Counter/UpDownCounter/Gauge remains resident for the lifetime
|
|
289
|
-
of the
|
|
290
|
-
is therefore not safe.
|
|
281
|
+
of the MeterProvider. Re-creating a metric with the same id but a different
|
|
282
|
+
MetricType is therefore not safe.
|
|
291
283
|
|
|
292
284
|
#### Parameters
|
|
293
285
|
|
|
@@ -4,6 +4,14 @@ The options for the OpenTelemetry telemetry connector constructor.
|
|
|
4
4
|
|
|
5
5
|
## Properties
|
|
6
6
|
|
|
7
|
+
### mutexTimeoutMs? {#mutextimeoutms}
|
|
8
|
+
|
|
9
|
+
> `optional` **mutexTimeoutMs?**: `number`
|
|
10
|
+
|
|
11
|
+
Timeout in milliseconds for acquiring the inner entity-storage metric write mutex lock.
|
|
12
|
+
|
|
13
|
+
***
|
|
14
|
+
|
|
7
15
|
### meterName? {#metername}
|
|
8
16
|
|
|
9
17
|
> `optional` **meterName?**: `string`
|
package/locales/en.json
CHANGED
|
@@ -1,9 +1,4 @@
|
|
|
1
1
|
{
|
|
2
|
-
"error": {
|
|
3
|
-
"openTelemetryTelemetryConnector": {
|
|
4
|
-
"unknownReaderType": "The reader type \"{type}\" is not supported, valid values are: prometheus"
|
|
5
|
-
}
|
|
6
|
-
},
|
|
7
2
|
"info": {
|
|
8
3
|
"openTelemetryTelemetryConnector": {
|
|
9
4
|
"connectorStarted": "The connector was started with \"{readerCount}\" metric reader(s)",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@twin.org/telemetry-connector-opentelemetry",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.2-next.2",
|
|
4
4
|
"description": "OpenTelemetry connector for pushing telemetry metrics to OTEL-compatible backends.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -16,13 +16,15 @@
|
|
|
16
16
|
"dependencies": {
|
|
17
17
|
"@opentelemetry/api": "1.9.1",
|
|
18
18
|
"@opentelemetry/exporter-prometheus": "0.221.0",
|
|
19
|
+
"@opentelemetry/resources": "2.10.0",
|
|
19
20
|
"@opentelemetry/sdk-metrics": "2.10.0",
|
|
20
|
-
"@twin.org/
|
|
21
|
-
"@twin.org/
|
|
22
|
-
"@twin.org/
|
|
23
|
-
"@twin.org/
|
|
24
|
-
"@twin.org/
|
|
25
|
-
"@twin.org/telemetry-
|
|
21
|
+
"@twin.org/context": "next",
|
|
22
|
+
"@twin.org/core": "next",
|
|
23
|
+
"@twin.org/entity-storage-models": "next",
|
|
24
|
+
"@twin.org/logging-models": "next",
|
|
25
|
+
"@twin.org/nameof": "next",
|
|
26
|
+
"@twin.org/telemetry-connector-entity-storage": "0.9.2-next.2",
|
|
27
|
+
"@twin.org/telemetry-models": "0.9.2-next.2"
|
|
26
28
|
},
|
|
27
29
|
"main": "./dist/es/index.js",
|
|
28
30
|
"types": "./dist/types/index.d.ts",
|