@twin.org/telemetry-connector-opentelemetry 0.9.2-next.7 → 0.9.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.
@@ -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 * Timeout in milliseconds for acquiring the inner entity-storage metric write mutex lock.\n\t */\n\tmutexTimeoutMs?: number;\n\n\t/**\n\t * Number of metric value entries to accumulate before flushing to entity storage.\n\t * @default 10\n\t */\n\tbatchSize?: number;\n\n\t/**\n\t * Interval in milliseconds between automatic batch flushes to entity storage.\n\t * @default 5000\n\t */\n\tbatchIntervalMs?: number;\n\n\t/**\n\t * Maximum number of entries retained in the write-ahead cache if a flush fails.\n\t * @default 1000\n\t */\n\tmaxCacheSize?: 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
+ {"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 +1 @@
1
- {"version":3,"file":"IOpenTelemetryTelemetryConnectorConstructorOptions.js","sourceRoot":"","sources":["../../../src/models/IOpenTelemetryTelemetryConnectorConstructorOptions.ts"],"names":[],"mappings":"","sourcesContent":["// Copyright 2026 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type { IOpenTelemetryTelemetryConnectorConfig } from \"./IOpenTelemetryTelemetryConnectorConfig.js\";\n\n/**\n * The options for the OpenTelemetry telemetry connector constructor.\n */\nexport interface IOpenTelemetryTelemetryConnectorConstructorOptions {\n\t/**\n\t * The component type to use for logging inside the connector and the inner\n\t * entity-storage connector. When omitted logging is disabled.\n\t */\n\tloggingComponentType?: string;\n\n\t/**\n\t * The entity storage connector type to use for storing metric definitions.\n\t * Must be registered in `EntityStorageConnectorFactory` before calling `start()`.\n\t * @default telemetry-metric\n\t */\n\ttelemetryMetricStorageConnectorType?: string;\n\n\t/**\n\t * The entity storage connector type to use for storing metric values.\n\t * Must be registered in `EntityStorageConnectorFactory` before calling `start()`.\n\t * @default telemetry-metric-value\n\t */\n\ttelemetryMetricValueStorageConnectorType?: string;\n\n\t/**\n\t * The config for the telemetry connector.\n\t */\n\tconfig?: IOpenTelemetryTelemetryConnectorConfig;\n}\n"]}
1
+ {"version":3,"file":"IOpenTelemetryTelemetryConnectorConstructorOptions.js","sourceRoot":"","sources":["../../../src/models/IOpenTelemetryTelemetryConnectorConstructorOptions.ts"],"names":[],"mappings":"","sourcesContent":["// Copyright 2026 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type { IOpenTelemetryTelemetryConnectorConfig } from \"./IOpenTelemetryTelemetryConnectorConfig.js\";\n\n/**\n * The options for the OpenTelemetry telemetry connector constructor.\n */\nexport interface IOpenTelemetryTelemetryConnectorConstructorOptions {\n\t/**\n\t * The config for the telemetry connector.\n\t */\n\tconfig?: IOpenTelemetryTelemetryConnectorConfig;\n}\n"]}
@@ -3,14 +3,13 @@ import { PrometheusExporter } from "@opentelemetry/exporter-prometheus";
3
3
  import { resourceFromAttributes } from "@opentelemetry/resources";
4
4
  import { MeterProvider, PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics";
5
5
  import { ContextIdKeys, ContextIdStore } from "@twin.org/context";
6
- import { AlreadyExistsError, BaseError, ComponentFactory, Guards, Is } from "@twin.org/core";
7
- import { EntityStorageTelemetryConnector } from "@twin.org/telemetry-connector-entity-storage";
6
+ import { ComponentFactory, Converter, GeneralError, Guards, Is, NotFoundError, RandomHelper } from "@twin.org/core";
8
7
  import { MetricCounterOperation, MetricType } from "@twin.org/telemetry-models";
9
8
  import { OpenTelemetryReaderTypes } from "./models/openTelemetryReaderTypes.js";
10
9
  /**
11
10
  * Class for performing telemetry operations using OpenTelemetry instruments.
12
- * Metric definitions and value history are persisted via an internal
13
- * EntityStorageTelemetryConnector instance created at construction time.
11
+ * Metric definitions are held in memory. Persistence and querying are not supported;
12
+ * use a multi-connector with an EntityStorageTelemetryConnector for those capabilities.
14
13
  */
15
14
  export class OpenTelemetryTelemetryConnector {
16
15
  /**
@@ -26,11 +25,6 @@ export class OpenTelemetryTelemetryConnector {
26
25
  * @internal
27
26
  */
28
27
  _config;
29
- /**
30
- * Internal entity-storage connector that owns all metric metadata and value history.
31
- * @internal
32
- */
33
- _inner;
34
28
  /**
35
29
  * Whether start() has been called. Drives OTEL forwarding on/off.
36
30
  * @internal
@@ -44,9 +38,7 @@ export class OpenTelemetryTelemetryConnector {
44
38
  */
45
39
  _providers;
46
40
  /**
47
- * Metric definition cache keyed by metric id, populated in createMetric and on first
48
- * addMetricValue after a process restart. Used to register instruments on new providers
49
- * without re-querying entity storage on every call.
41
+ * In-memory metric definition registry keyed by metric id.
50
42
  * @internal
51
43
  */
52
44
  _metricDefs;
@@ -60,17 +52,6 @@ export class OpenTelemetryTelemetryConnector {
60
52
  for (const [, readerConfig] of Object.entries(this._config.readers ?? {})) {
61
53
  Guards.arrayOneOf(OpenTelemetryTelemetryConnector.CLASS_NAME, "readerConfig.type", readerConfig.type, Object.values(OpenTelemetryReaderTypes));
62
54
  }
63
- this._inner = new EntityStorageTelemetryConnector({
64
- loggingComponentType: options?.loggingComponentType,
65
- telemetryMetricStorageConnectorType: options?.telemetryMetricStorageConnectorType,
66
- telemetryMetricValueStorageConnectorType: options?.telemetryMetricValueStorageConnectorType,
67
- config: {
68
- mutexTimeoutMs: this._config.mutexTimeoutMs,
69
- batchSize: this._config.batchSize,
70
- batchIntervalMs: this._config.batchIntervalMs,
71
- maxCacheSize: this._config.maxCacheSize
72
- }
73
- });
74
55
  this._started = false;
75
56
  this._providers = {};
76
57
  this._metricDefs = {};
@@ -83,8 +64,8 @@ export class OpenTelemetryTelemetryConnector {
83
64
  return OpenTelemetryTelemetryConnector.CLASS_NAME;
84
65
  }
85
66
  /**
86
- * Enable OTEL forwarding. Subsequent calls to createMetric and addMetricValue will
87
- * create per-tenant/node MeterProviders on demand.
67
+ * Enable OTEL forwarding. Subsequent calls to addMetricValue will create
68
+ * per-tenant/node MeterProviders on demand.
88
69
  * @param nodeLoggingComponentType The node logging component type.
89
70
  * @returns A promise that resolves when OTEL forwarding is enabled.
90
71
  */
@@ -125,24 +106,18 @@ export class OpenTelemetryTelemetryConnector {
125
106
  }
126
107
  }
127
108
  /**
128
- * Create a new metric.
109
+ * Register a new metric definition. If the connector is already started,
110
+ * the OTEL instrument is registered immediately for the current context.
129
111
  * @param metric The metric details.
130
- * @returns A promise that resolves when the metric has been persisted.
112
+ * @returns A promise that resolves when the metric has been registered.
131
113
  */
132
114
  async createMetric(metric) {
133
- try {
134
- await this._inner.createMetric(metric);
135
- }
136
- catch (err) {
137
- // Entity storage performs all validation and throws AlreadyExistsError on duplicates.
138
- // OTP doesn't care about duplicates and will create a new instrument instance on each call
139
- // so we catch this error and ignore it to allow the OTEL instruments to be registered.
140
- if (!BaseError.isErrorName(err, AlreadyExistsError.CLASS_NAME)) {
141
- throw err;
142
- }
143
- }
115
+ Guards.object(OpenTelemetryTelemetryConnector.CLASS_NAME, "metric", metric);
116
+ Guards.stringValue(OpenTelemetryTelemetryConnector.CLASS_NAME, "metric.id", metric.id);
117
+ Guards.stringValue(OpenTelemetryTelemetryConnector.CLASS_NAME, "metric.label", metric.label);
144
118
  this._metricDefs[metric.id] = {
145
119
  type: metric.type,
120
+ label: metric.label,
146
121
  description: metric.description,
147
122
  unit: metric.unit
148
123
  };
@@ -153,56 +128,57 @@ export class OpenTelemetryTelemetryConnector {
153
128
  }
154
129
  }
155
130
  /**
156
- * Get the metric details and its most recent value.
157
- * @param id The metric id.
158
- * @returns The metric details and its most recent value.
159
- */
160
- async getMetric(id) {
161
- const result = await this._inner.getMetric(id);
162
- const value = result.value ?? { id: "", ts: 0, value: 0 };
163
- return { metric: result.metric, value };
164
- }
165
- /**
166
- * Get a specific metric value by its id.
167
- * @param id The id of the metric.
168
- * @param valueId The id of the metric value.
169
- * @returns The metric value.
170
- */
171
- async getMetricValue(id, valueId) {
172
- return this._inner.getMetricValue(id, valueId);
173
- }
174
- /**
175
- * Update the metric metadata.
176
- * Note: OpenTelemetry instrument descriptors are immutable once created.
177
- * This method updates the persisted metadata mirror only.
131
+ * Update the in-memory metadata for a registered metric.
132
+ * OpenTelemetry instrument descriptors are immutable once created; only the
133
+ * cached label, description and unit are updated.
178
134
  * @param metric The metric details (type cannot be changed).
179
- * @returns A promise that resolves when the persisted metadata has been updated.
135
+ * @returns A promise that resolves when the metadata has been updated.
180
136
  */
181
137
  async updateMetric(metric) {
182
- return this._inner.updateMetric(metric);
138
+ Guards.object(OpenTelemetryTelemetryConnector.CLASS_NAME, "metric", metric);
139
+ Guards.stringValue(OpenTelemetryTelemetryConnector.CLASS_NAME, "metric.id", metric.id);
140
+ const def = this._metricDefs[metric.id];
141
+ if (Is.undefined(def)) {
142
+ throw new NotFoundError(OpenTelemetryTelemetryConnector.CLASS_NAME, "metricNotFound", metric.id);
143
+ }
144
+ this._metricDefs[metric.id] = {
145
+ type: def.type,
146
+ label: metric.label,
147
+ description: metric.description,
148
+ unit: metric.unit
149
+ };
183
150
  }
184
151
  /**
185
- * Record a metric value.
152
+ * Record a metric value and forward it to the appropriate OTEL instrument.
186
153
  * The current tenant and node IDs are read from `ContextIdStore` and used to
187
154
  * select (or create) the matching per-tenant/node `MeterProvider`.
188
155
  * @param id The id of the metric.
189
156
  * @param value The value for the operation.
190
157
  * @param customData Optional custom data forwarded as OTEL attributes.
191
- * @returns The id of the new metric value entry.
158
+ * @returns A generated 32-character hex id for the recorded value.
192
159
  */
193
160
  async addMetricValue(id, value, customData) {
194
- const valueId = await this._inner.addMetricValue(id, value, customData);
161
+ Guards.stringValue(OpenTelemetryTelemetryConnector.CLASS_NAME, "id", id);
162
+ Guards.defined(OpenTelemetryTelemetryConnector.CLASS_NAME, "value", value);
163
+ const def = this._metricDefs[id];
164
+ if (Is.undefined(def)) {
165
+ throw new NotFoundError(OpenTelemetryTelemetryConnector.CLASS_NAME, "metricNotFound", id);
166
+ }
167
+ if (def.type === MetricType.Counter) {
168
+ if (value !== MetricCounterOperation.Increment && (!Is.integer(value) || value <= 0)) {
169
+ throw new GeneralError(OpenTelemetryTelemetryConnector.CLASS_NAME, "counterIncOnly");
170
+ }
171
+ }
172
+ else if (def.type === MetricType.Gauge) {
173
+ if (!Is.number(value)) {
174
+ throw new GeneralError(OpenTelemetryTelemetryConnector.CLASS_NAME, "gaugeNoIncDec");
175
+ }
176
+ }
177
+ const valueId = Converter.bytesToHex(RandomHelper.generate(16));
195
178
  if (this._started) {
196
179
  const contextIds = (await ContextIdStore.getContextIds()) ?? {};
197
180
  const { meter, instruments } = this.getOrCreateProvider(contextIds);
198
181
  if (!(id in instruments)) {
199
- let def = this._metricDefs[id];
200
- if (def === undefined) {
201
- // Process restart path: definition not yet cached, fetch from storage.
202
- const { metric } = await this._inner.getMetric(id);
203
- def = { type: metric.type, description: metric.description, unit: metric.unit };
204
- this._metricDefs[id] = def;
205
- }
206
182
  this.registerInstrument(id, def.type, def.description, def.unit, meter, instruments);
207
183
  }
208
184
  const entry = instruments[id];
@@ -233,48 +209,22 @@ export class OpenTelemetryTelemetryConnector {
233
209
  return valueId;
234
210
  }
235
211
  /**
236
- * Remove a metric and its persisted value history.
237
- * Note: OpenTelemetry exposes no API to deregister an instrument from a Meter,
238
- * so the underlying Counter/UpDownCounter/Gauge remains resident for the lifetime
239
- * of the MeterProvider. Re-creating a metric with the same id but a different
240
- * MetricType is therefore not safe.
212
+ * Remove a metric from the in-memory registry and from all cached provider instrument maps.
241
213
  * @param id The id of the metric.
242
- * @returns A promise that resolves when the metric and its value history have been removed.
214
+ * @returns A promise that resolves when the metric has been removed.
243
215
  */
244
216
  async removeMetric(id) {
217
+ Guards.stringValue(OpenTelemetryTelemetryConnector.CLASS_NAME, "id", id);
218
+ if (Is.undefined(this._metricDefs[id])) {
219
+ throw new NotFoundError(OpenTelemetryTelemetryConnector.CLASS_NAME, "metricNotFound", id);
220
+ }
245
221
  for (const { instruments } of Object.values(this._providers)) {
246
222
  delete instruments[id];
247
223
  }
248
224
  delete this._metricDefs[id];
249
- return this._inner.removeMetric(id);
250
- }
251
- /**
252
- * Query the registered metrics, optionally filtered by type.
253
- * @param type The type of the metric.
254
- * @param cursor The cursor to request the next page.
255
- * @param limit Limit the number of entities to return.
256
- * @returns The matching metrics and an optional cursor for the next page.
257
- */
258
- async query(type, cursor, limit) {
259
- return this._inner.query(type, cursor, limit);
260
- }
261
- /**
262
- * Query the recorded values for a metric, ordered by most recent first.
263
- * @param id The id of the metric.
264
- * @param timeStart The inclusive start time (epoch ms).
265
- * @param timeEnd The inclusive end time (epoch ms).
266
- * @param cursor The cursor returned by the previous call.
267
- * @param limit Limit the number of values to return.
268
- * @returns The metric details, matching values, and an optional cursor for the next page.
269
- */
270
- async queryValues(id, timeStart, timeEnd, cursor, limit) {
271
- return this._inner.queryValues(id, timeStart, timeEnd, cursor, limit);
272
225
  }
273
226
  /**
274
227
  * Return or create the MeterProvider for the given tenant/node pair.
275
- * Providers are keyed by "nodeId/tenantId" and carry OTEL resource attributes
276
- * service.namespace=tenantId and service.instance.id=nodeId when those values
277
- * are present.
278
228
  * @param contextIds The current execution context IDs.
279
229
  * @returns The cached or newly created provider entry.
280
230
  * @internal
@@ -305,7 +255,6 @@ export class OpenTelemetryTelemetryConnector {
305
255
  }
306
256
  /**
307
257
  * Instantiate fresh MetricReader instances from the connector config.
308
- * Called each time a new MeterProvider is created for a tenant/node pair.
309
258
  * @returns The list of reader instances.
310
259
  * @internal
311
260
  */
@@ -369,19 +318,30 @@ export class OpenTelemetryTelemetryConnector {
369
318
  if (Is.empty(customData)) {
370
319
  return {};
371
320
  }
372
- const attrs = {};
321
+ const attributes = {};
373
322
  for (const [key, val] of Object.entries(customData)) {
374
323
  if (Is.string(val) || Is.number(val) || Is.boolean(val)) {
375
- attrs[key] = val;
324
+ attributes[key] = val;
376
325
  }
377
326
  else if (Is.arrayValue(val)) {
378
- if ((Is.string(val[0]) || Is.number(val[0]) || Is.boolean(val[0])) &&
379
- val.every(el => typeof el === typeof val[0])) {
380
- attrs[key] = val;
327
+ if (Is.string(val[0]) && val.every(el => Is.string(el))) {
328
+ attributes[key] = val;
381
329
  }
330
+ else if (Is.number(val[0]) && val.every(el => Is.number(el))) {
331
+ attributes[key] = val;
332
+ }
333
+ else if (Is.boolean(val[0]) && val.every(el => Is.boolean(el))) {
334
+ attributes[key] = val;
335
+ }
336
+ else {
337
+ attributes[key] = JSON.stringify(val);
338
+ }
339
+ }
340
+ else if (!Is.undefined(val)) {
341
+ attributes[key] = JSON.stringify(val);
382
342
  }
383
343
  }
384
- return attrs;
344
+ return attributes;
385
345
  }
386
346
  }
387
347
  //# sourceMappingURL=openTelemetryTelemetryConnector.js.map
@@ -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 { OTLPMetricExporter } from \"@opentelemetry/exporter-metrics-otlp-http\";\nimport { PrometheusExporter } from \"@opentelemetry/exporter-prometheus\";\nimport { resourceFromAttributes } from \"@opentelemetry/resources\";\nimport {\n\tMeterProvider,\n\tPeriodicExportingMetricReader,\n\ttype MetricReader\n} 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 = \"open-telemetry\";\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\tbatchSize: this._config.batchSize,\n\t\t\t\tbatchIntervalMs: this._config.batchIntervalMs,\n\t\t\t\tmaxCacheSize: this._config.maxCacheSize\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} else if (config.type === OpenTelemetryReaderTypes.OtlpHttp) {\n\t\t\t\treaders.push(\n\t\t\t\t\tnew PeriodicExportingMetricReader({\n\t\t\t\t\t\texporter: new OTLPMetricExporter({\n\t\t\t\t\t\t\turl: config.url,\n\t\t\t\t\t\t\theaders: config.headers\n\t\t\t\t\t\t}),\n\t\t\t\t\t\texportIntervalMillis: config.exportIntervalMs\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"]}
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 { OTLPMetricExporter } from \"@opentelemetry/exporter-metrics-otlp-http\";\nimport { PrometheusExporter } from \"@opentelemetry/exporter-prometheus\";\nimport { resourceFromAttributes } from \"@opentelemetry/resources\";\nimport {\n\tMeterProvider,\n\tPeriodicExportingMetricReader,\n\ttype MetricReader\n} from \"@opentelemetry/sdk-metrics\";\nimport { ContextIdKeys, ContextIdStore, type IContextIds } from \"@twin.org/context\";\nimport {\n\tComponentFactory,\n\tConverter,\n\tGeneralError,\n\tGuards,\n\tIs,\n\tNotFoundError,\n\tRandomHelper\n} from \"@twin.org/core\";\nimport type { ILoggingComponent } from \"@twin.org/logging-models\";\nimport { nameof } from \"@twin.org/nameof\";\nimport {\n\ttype ITelemetryConnector,\n\ttype ITelemetryMetric,\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 are held in memory. Persistence and querying are not supported;\n * use a multi-connector with an EntityStorageTelemetryConnector for those capabilities.\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 = \"open-telemetry\";\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 * 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 * In-memory metric definition registry keyed by metric id.\n\t * @internal\n\t */\n\tprivate readonly _metricDefs: {\n\t\t[id: string]: { type: MetricType; label: string; 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._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 addMetricValue will create\n\t * 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 * Register a new metric definition. If the connector is already started,\n\t * the OTEL instrument is registered immediately for the current context.\n\t * @param metric The metric details.\n\t * @returns A promise that resolves when the metric has been registered.\n\t */\n\tpublic async createMetric(metric: ITelemetryMetric): Promise<void> {\n\t\tGuards.object<ITelemetryMetric>(\n\t\t\tOpenTelemetryTelemetryConnector.CLASS_NAME,\n\t\t\tnameof(metric),\n\t\t\tmetric\n\t\t);\n\t\tGuards.stringValue(OpenTelemetryTelemetryConnector.CLASS_NAME, nameof(metric.id), metric.id);\n\t\tGuards.stringValue(\n\t\t\tOpenTelemetryTelemetryConnector.CLASS_NAME,\n\t\t\tnameof(metric.label),\n\t\t\tmetric.label\n\t\t);\n\n\t\tthis._metricDefs[metric.id] = {\n\t\t\ttype: metric.type,\n\t\t\tlabel: metric.label,\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 * Update the in-memory metadata for a registered metric.\n\t * OpenTelemetry instrument descriptors are immutable once created; only the\n\t * cached label, description and unit are updated.\n\t * @param metric The metric details (type cannot be changed).\n\t * @returns A promise that resolves when the metadata has been updated.\n\t */\n\tpublic async updateMetric(metric: Omit<ITelemetryMetric, \"type\">): Promise<void> {\n\t\tGuards.object<ITelemetryMetric>(\n\t\t\tOpenTelemetryTelemetryConnector.CLASS_NAME,\n\t\t\tnameof(metric),\n\t\t\tmetric\n\t\t);\n\t\tGuards.stringValue(OpenTelemetryTelemetryConnector.CLASS_NAME, nameof(metric.id), metric.id);\n\n\t\tconst def = this._metricDefs[metric.id];\n\t\tif (Is.undefined(def)) {\n\t\t\tthrow new NotFoundError(\n\t\t\t\tOpenTelemetryTelemetryConnector.CLASS_NAME,\n\t\t\t\t\"metricNotFound\",\n\t\t\t\tmetric.id\n\t\t\t);\n\t\t}\n\n\t\tthis._metricDefs[metric.id] = {\n\t\t\ttype: def.type,\n\t\t\tlabel: metric.label,\n\t\t\tdescription: metric.description,\n\t\t\tunit: metric.unit\n\t\t};\n\t}\n\n\t/**\n\t * Record a metric value and forward it to the appropriate OTEL instrument.\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 A generated 32-character hex id for the recorded value.\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\tGuards.stringValue(OpenTelemetryTelemetryConnector.CLASS_NAME, nameof(id), id);\n\t\tGuards.defined(OpenTelemetryTelemetryConnector.CLASS_NAME, nameof(value), value);\n\n\t\tconst def = this._metricDefs[id];\n\t\tif (Is.undefined(def)) {\n\t\t\tthrow new NotFoundError(OpenTelemetryTelemetryConnector.CLASS_NAME, \"metricNotFound\", id);\n\t\t}\n\n\t\tif (def.type === MetricType.Counter) {\n\t\t\tif (value !== MetricCounterOperation.Increment && (!Is.integer(value) || value <= 0)) {\n\t\t\t\tthrow new GeneralError(OpenTelemetryTelemetryConnector.CLASS_NAME, \"counterIncOnly\");\n\t\t\t}\n\t\t} else if (def.type === MetricType.Gauge) {\n\t\t\tif (!Is.number(value)) {\n\t\t\t\tthrow new GeneralError(OpenTelemetryTelemetryConnector.CLASS_NAME, \"gaugeNoIncDec\");\n\t\t\t}\n\t\t}\n\n\t\tconst valueId = Converter.bytesToHex(RandomHelper.generate(16));\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\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 from the in-memory registry and from all cached provider instrument maps.\n\t * @param id The id of the metric.\n\t * @returns A promise that resolves when the metric has been removed.\n\t */\n\tpublic async removeMetric(id: string): Promise<void> {\n\t\tGuards.stringValue(OpenTelemetryTelemetryConnector.CLASS_NAME, nameof(id), id);\n\n\t\tif (Is.undefined(this._metricDefs[id])) {\n\t\t\tthrow new NotFoundError(OpenTelemetryTelemetryConnector.CLASS_NAME, \"metricNotFound\", id);\n\t\t}\n\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}\n\n\t/**\n\t * Return or create the MeterProvider for the given tenant/node pair.\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 * @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} else if (config.type === OpenTelemetryReaderTypes.OtlpHttp) {\n\t\t\t\treaders.push(\n\t\t\t\t\tnew PeriodicExportingMetricReader({\n\t\t\t\t\t\texporter: new OTLPMetricExporter({\n\t\t\t\t\t\t\turl: config.url,\n\t\t\t\t\t\t\theaders: config.headers\n\t\t\t\t\t\t}),\n\t\t\t\t\t\texportIntervalMillis: config.exportIntervalMs\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 attributes: 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\tattributes[key] = val;\n\t\t\t} else if (Is.arrayValue(val)) {\n\t\t\t\tif (Is.string(val[0]) && val.every(el => Is.string(el))) {\n\t\t\t\t\tattributes[key] = val;\n\t\t\t\t} else if (Is.number(val[0]) && val.every(el => Is.number(el))) {\n\t\t\t\t\tattributes[key] = val;\n\t\t\t\t} else if (Is.boolean(val[0]) && val.every(el => Is.boolean(el))) {\n\t\t\t\t\tattributes[key] = val;\n\t\t\t\t} else {\n\t\t\t\t\tattributes[key] = JSON.stringify(val);\n\t\t\t\t}\n\t\t\t} else if (!Is.undefined(val)) {\n\t\t\t\tattributes[key] = JSON.stringify(val);\n\t\t\t}\n\t\t}\n\t\treturn attributes;\n\t}\n}\n"]}
@@ -3,25 +3,6 @@ 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;
10
- /**
11
- * Number of metric value entries to accumulate before flushing to entity storage.
12
- * @default 10
13
- */
14
- batchSize?: number;
15
- /**
16
- * Interval in milliseconds between automatic batch flushes to entity storage.
17
- * @default 5000
18
- */
19
- batchIntervalMs?: number;
20
- /**
21
- * Maximum number of entries retained in the write-ahead cache if a flush fails.
22
- * @default 1000
23
- */
24
- maxCacheSize?: number;
25
6
  /**
26
7
  * The name of the OpenTelemetry meter used to create instruments.
27
8
  * @default twin-telemetry
@@ -3,23 +3,6 @@ import type { IOpenTelemetryTelemetryConnectorConfig } from "./IOpenTelemetryTel
3
3
  * The options for the OpenTelemetry telemetry connector constructor.
4
4
  */
5
5
  export interface IOpenTelemetryTelemetryConnectorConstructorOptions {
6
- /**
7
- * The component type to use for logging inside the connector and the inner
8
- * entity-storage connector. When omitted logging is disabled.
9
- */
10
- loggingComponentType?: string;
11
- /**
12
- * The entity storage connector type to use for storing metric definitions.
13
- * Must be registered in `EntityStorageConnectorFactory` before calling `start()`.
14
- * @default telemetry-metric
15
- */
16
- telemetryMetricStorageConnectorType?: string;
17
- /**
18
- * The entity storage connector type to use for storing metric values.
19
- * Must be registered in `EntityStorageConnectorFactory` before calling `start()`.
20
- * @default telemetry-metric-value
21
- */
22
- telemetryMetricValueStorageConnectorType?: string;
23
6
  /**
24
7
  * The config for the telemetry connector.
25
8
  */
@@ -1,9 +1,9 @@
1
- import { type ITelemetryConnector, type ITelemetryMetric, type ITelemetryMetricValue, MetricCounterOperation, MetricType } from "@twin.org/telemetry-models";
1
+ import { type ITelemetryConnector, type ITelemetryMetric, MetricCounterOperation } from "@twin.org/telemetry-models";
2
2
  import type { IOpenTelemetryTelemetryConnectorConstructorOptions } from "./models/IOpenTelemetryTelemetryConnectorConstructorOptions.js";
3
3
  /**
4
4
  * Class for performing telemetry operations using OpenTelemetry instruments.
5
- * Metric definitions and value history are persisted via an internal
6
- * EntityStorageTelemetryConnector instance created at construction time.
5
+ * Metric definitions are held in memory. Persistence and querying are not supported;
6
+ * use a multi-connector with an EntityStorageTelemetryConnector for those capabilities.
7
7
  */
8
8
  export declare class OpenTelemetryTelemetryConnector implements ITelemetryConnector {
9
9
  /**
@@ -26,8 +26,8 @@ export declare class OpenTelemetryTelemetryConnector implements ITelemetryConnec
26
26
  */
27
27
  className(): string;
28
28
  /**
29
- * Enable OTEL forwarding. Subsequent calls to createMetric and addMetricValue will
30
- * create per-tenant/node MeterProviders on demand.
29
+ * Enable OTEL forwarding. Subsequent calls to addMetricValue will create
30
+ * per-tenant/node MeterProviders on demand.
31
31
  * @param nodeLoggingComponentType The node logging component type.
32
32
  * @returns A promise that resolves when OTEL forwarding is enabled.
33
33
  */
@@ -39,80 +39,36 @@ export declare class OpenTelemetryTelemetryConnector implements ITelemetryConnec
39
39
  */
40
40
  stop(nodeLoggingComponentType?: string): Promise<void>;
41
41
  /**
42
- * Create a new metric.
42
+ * Register a new metric definition. If the connector is already started,
43
+ * the OTEL instrument is registered immediately for the current context.
43
44
  * @param metric The metric details.
44
- * @returns A promise that resolves when the metric has been persisted.
45
+ * @returns A promise that resolves when the metric has been registered.
45
46
  */
46
47
  createMetric(metric: ITelemetryMetric): Promise<void>;
47
48
  /**
48
- * Get the metric details and its most recent value.
49
- * @param id The metric id.
50
- * @returns The metric details and its most recent value.
51
- */
52
- getMetric(id: string): Promise<{
53
- metric: ITelemetryMetric;
54
- value: ITelemetryMetricValue;
55
- }>;
56
- /**
57
- * Get a specific metric value by its id.
58
- * @param id The id of the metric.
59
- * @param valueId The id of the metric value.
60
- * @returns The metric value.
61
- */
62
- getMetricValue(id: string, valueId: string): Promise<ITelemetryMetricValue>;
63
- /**
64
- * Update the metric metadata.
65
- * Note: OpenTelemetry instrument descriptors are immutable once created.
66
- * This method updates the persisted metadata mirror only.
49
+ * Update the in-memory metadata for a registered metric.
50
+ * OpenTelemetry instrument descriptors are immutable once created; only the
51
+ * cached label, description and unit are updated.
67
52
  * @param metric The metric details (type cannot be changed).
68
- * @returns A promise that resolves when the persisted metadata has been updated.
53
+ * @returns A promise that resolves when the metadata has been updated.
69
54
  */
70
55
  updateMetric(metric: Omit<ITelemetryMetric, "type">): Promise<void>;
71
56
  /**
72
- * Record a metric value.
57
+ * Record a metric value and forward it to the appropriate OTEL instrument.
73
58
  * The current tenant and node IDs are read from `ContextIdStore` and used to
74
59
  * select (or create) the matching per-tenant/node `MeterProvider`.
75
60
  * @param id The id of the metric.
76
61
  * @param value The value for the operation.
77
62
  * @param customData Optional custom data forwarded as OTEL attributes.
78
- * @returns The id of the new metric value entry.
63
+ * @returns A generated 32-character hex id for the recorded value.
79
64
  */
80
65
  addMetricValue(id: string, value: MetricCounterOperation | number, customData?: {
81
66
  [key: string]: unknown;
82
67
  }): Promise<string>;
83
68
  /**
84
- * Remove a metric and its persisted value history.
85
- * Note: OpenTelemetry exposes no API to deregister an instrument from a Meter,
86
- * so the underlying Counter/UpDownCounter/Gauge remains resident for the lifetime
87
- * of the MeterProvider. Re-creating a metric with the same id but a different
88
- * MetricType is therefore not safe.
69
+ * Remove a metric from the in-memory registry and from all cached provider instrument maps.
89
70
  * @param id The id of the metric.
90
- * @returns A promise that resolves when the metric and its value history have been removed.
71
+ * @returns A promise that resolves when the metric has been removed.
91
72
  */
92
73
  removeMetric(id: string): Promise<void>;
93
- /**
94
- * Query the registered metrics, optionally filtered by type.
95
- * @param type The type of the metric.
96
- * @param cursor The cursor to request the next page.
97
- * @param limit Limit the number of entities to return.
98
- * @returns The matching metrics and an optional cursor for the next page.
99
- */
100
- query(type?: MetricType, cursor?: string, limit?: number): Promise<{
101
- entities: ITelemetryMetric[];
102
- cursor?: string;
103
- }>;
104
- /**
105
- * Query the recorded values for a metric, ordered by most recent first.
106
- * @param id The id of the metric.
107
- * @param timeStart The inclusive start time (epoch ms).
108
- * @param timeEnd The inclusive end time (epoch ms).
109
- * @param cursor The cursor returned by the previous call.
110
- * @param limit Limit the number of values to return.
111
- * @returns The metric details, matching values, and an optional cursor for the next page.
112
- */
113
- queryValues(id: string, timeStart?: number, timeEnd?: number, cursor?: string, limit?: number): Promise<{
114
- metric: ITelemetryMetric;
115
- entities: ITelemetryMetricValue[];
116
- cursor?: string;
117
- }>;
118
74
  }
package/docs/changelog.md CHANGED
@@ -1,5 +1,30 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.9.2](https://github.com/iotaledger/twin-telemetry/compare/telemetry-connector-opentelemetry-v0.9.2...telemetry-connector-opentelemetry-v0.9.2) (2026-08-24)
4
+
5
+
6
+ ### Features
7
+
8
+ * release to production ([df2151d](https://github.com/iotaledger/twin-telemetry/commit/df2151d24844fd2c3e6092ce3a6f888ac16219a0))
9
+ * release to production ([#50](https://github.com/iotaledger/twin-telemetry/issues/50)) ([192bfe3](https://github.com/iotaledger/twin-telemetry/commit/192bfe3bea060163f2887e51fc6c2ceb74a7683f))
10
+ * release to production ([#68](https://github.com/iotaledger/twin-telemetry/issues/68)) ([157c6a9](https://github.com/iotaledger/twin-telemetry/commit/157c6a944553e5fb2b5a017586cda1b117c4037a))
11
+ * release to production ([#99](https://github.com/iotaledger/twin-telemetry/issues/99)) ([b9d10ef](https://github.com/iotaledger/twin-telemetry/commit/b9d10ef8722a8688718631095a8a5d03f739785a))
12
+
13
+ ## [0.9.2-next.8](https://github.com/iotaledger/twin-telemetry/compare/telemetry-connector-opentelemetry-v0.9.2-next.7...telemetry-connector-opentelemetry-v0.9.2-next.8) (2026-08-21)
14
+
15
+
16
+ ### Features
17
+
18
+ * reduce mutex locks ([#94](https://github.com/iotaledger/twin-telemetry/issues/94)) ([8deb4eb](https://github.com/iotaledger/twin-telemetry/commit/8deb4eb51649ed7d9c0828a632115fede8c6a436))
19
+
20
+
21
+ ### Dependencies
22
+
23
+ * The following workspace dependencies were updated
24
+ * dependencies
25
+ * @twin.org/telemetry-connector-entity-storage bumped from 0.9.2-next.7 to 0.9.2-next.8
26
+ * @twin.org/telemetry-models bumped from 0.9.2-next.7 to 0.9.2-next.8
27
+
3
28
  ## [0.9.2-next.7](https://github.com/iotaledger/twin-telemetry/compare/telemetry-connector-opentelemetry-v0.9.2-next.6...telemetry-connector-opentelemetry-v0.9.2-next.7) (2026-08-19)
4
29
 
5
30
 
@@ -1,8 +1,8 @@
1
1
  # Class: OpenTelemetryTelemetryConnector
2
2
 
3
3
  Class for performing telemetry operations using OpenTelemetry instruments.
4
- Metric definitions and value history are persisted via an internal
5
- EntityStorageTelemetryConnector instance created at construction time.
4
+ Metric definitions are held in memory. Persistence and querying are not supported;
5
+ use a multi-connector with an EntityStorageTelemetryConnector for those capabilities.
6
6
 
7
7
  ## Implements
8
8
 
@@ -72,8 +72,8 @@ The class name of the component.
72
72
 
73
73
  > **start**(`nodeLoggingComponentType?`): `Promise`\<`void`\>
74
74
 
75
- Enable OTEL forwarding. Subsequent calls to createMetric and addMetricValue will
76
- create per-tenant/node MeterProviders on demand.
75
+ Enable OTEL forwarding. Subsequent calls to addMetricValue will create
76
+ per-tenant/node MeterProviders on demand.
77
77
 
78
78
  #### Parameters
79
79
 
@@ -125,7 +125,8 @@ A promise that resolves when all MeterProviders have shut down.
125
125
 
126
126
  > **createMetric**(`metric`): `Promise`\<`void`\>
127
127
 
128
- Create a new metric.
128
+ Register a new metric definition. If the connector is already started,
129
+ the OTEL instrument is registered immediately for the current context.
129
130
 
130
131
  #### Parameters
131
132
 
@@ -139,7 +140,7 @@ The metric details.
139
140
 
140
141
  `Promise`\<`void`\>
141
142
 
142
- A promise that resolves when the metric has been persisted.
143
+ A promise that resolves when the metric has been registered.
143
144
 
144
145
  #### Implementation of
145
146
 
@@ -147,71 +148,13 @@ A promise that resolves when the metric has been persisted.
147
148
 
148
149
  ***
149
150
 
150
- ### getMetric() {#getmetric}
151
-
152
- > **getMetric**(`id`): `Promise`\<\{ `metric`: `ITelemetryMetric`; `value`: `ITelemetryMetricValue`; \}\>
153
-
154
- Get the metric details and its most recent value.
155
-
156
- #### Parameters
157
-
158
- ##### id
159
-
160
- `string`
161
-
162
- The metric id.
163
-
164
- #### Returns
165
-
166
- `Promise`\<\{ `metric`: `ITelemetryMetric`; `value`: `ITelemetryMetricValue`; \}\>
167
-
168
- The metric details and its most recent value.
169
-
170
- #### Implementation of
171
-
172
- `ITelemetryConnector.getMetric`
173
-
174
- ***
175
-
176
- ### getMetricValue() {#getmetricvalue}
177
-
178
- > **getMetricValue**(`id`, `valueId`): `Promise`\<`ITelemetryMetricValue`\>
179
-
180
- Get a specific metric value by its id.
181
-
182
- #### Parameters
183
-
184
- ##### id
185
-
186
- `string`
187
-
188
- The id of the metric.
189
-
190
- ##### valueId
191
-
192
- `string`
193
-
194
- The id of the metric value.
195
-
196
- #### Returns
197
-
198
- `Promise`\<`ITelemetryMetricValue`\>
199
-
200
- The metric value.
201
-
202
- #### Implementation of
203
-
204
- `ITelemetryConnector.getMetricValue`
205
-
206
- ***
207
-
208
151
  ### updateMetric() {#updatemetric}
209
152
 
210
153
  > **updateMetric**(`metric`): `Promise`\<`void`\>
211
154
 
212
- Update the metric metadata.
213
- Note: OpenTelemetry instrument descriptors are immutable once created.
214
- This method updates the persisted metadata mirror only.
155
+ Update the in-memory metadata for a registered metric.
156
+ OpenTelemetry instrument descriptors are immutable once created; only the
157
+ cached label, description and unit are updated.
215
158
 
216
159
  #### Parameters
217
160
 
@@ -225,7 +168,7 @@ The metric details (type cannot be changed).
225
168
 
226
169
  `Promise`\<`void`\>
227
170
 
228
- A promise that resolves when the persisted metadata has been updated.
171
+ A promise that resolves when the metadata has been updated.
229
172
 
230
173
  #### Implementation of
231
174
 
@@ -237,7 +180,7 @@ A promise that resolves when the persisted metadata has been updated.
237
180
 
238
181
  > **addMetricValue**(`id`, `value`, `customData?`): `Promise`\<`string`\>
239
182
 
240
- Record a metric value.
183
+ Record a metric value and forward it to the appropriate OTEL instrument.
241
184
  The current tenant and node IDs are read from `ContextIdStore` and used to
242
185
  select (or create) the matching per-tenant/node `MeterProvider`.
243
186
 
@@ -263,7 +206,7 @@ Optional custom data forwarded as OTEL attributes.
263
206
 
264
207
  `Promise`\<`string`\>
265
208
 
266
- The id of the new metric value entry.
209
+ A generated 32-character hex id for the recorded value.
267
210
 
268
211
  #### Implementation of
269
212
 
@@ -275,11 +218,7 @@ The id of the new metric value entry.
275
218
 
276
219
  > **removeMetric**(`id`): `Promise`\<`void`\>
277
220
 
278
- Remove a metric and its persisted value history.
279
- Note: OpenTelemetry exposes no API to deregister an instrument from a Meter,
280
- so the underlying Counter/UpDownCounter/Gauge remains resident for the lifetime
281
- of the MeterProvider. Re-creating a metric with the same id but a different
282
- MetricType is therefore not safe.
221
+ Remove a metric from the in-memory registry and from all cached provider instrument maps.
283
222
 
284
223
  #### Parameters
285
224
 
@@ -293,96 +232,8 @@ The id of the metric.
293
232
 
294
233
  `Promise`\<`void`\>
295
234
 
296
- A promise that resolves when the metric and its value history have been removed.
235
+ A promise that resolves when the metric has been removed.
297
236
 
298
237
  #### Implementation of
299
238
 
300
239
  `ITelemetryConnector.removeMetric`
301
-
302
- ***
303
-
304
- ### query() {#query}
305
-
306
- > **query**(`type?`, `cursor?`, `limit?`): `Promise`\<\{ `entities`: `ITelemetryMetric`[]; `cursor?`: `string`; \}\>
307
-
308
- Query the registered metrics, optionally filtered by type.
309
-
310
- #### Parameters
311
-
312
- ##### type?
313
-
314
- `MetricType`
315
-
316
- The type of the metric.
317
-
318
- ##### cursor?
319
-
320
- `string`
321
-
322
- The cursor to request the next page.
323
-
324
- ##### limit?
325
-
326
- `number`
327
-
328
- Limit the number of entities to return.
329
-
330
- #### Returns
331
-
332
- `Promise`\<\{ `entities`: `ITelemetryMetric`[]; `cursor?`: `string`; \}\>
333
-
334
- The matching metrics and an optional cursor for the next page.
335
-
336
- #### Implementation of
337
-
338
- `ITelemetryConnector.query`
339
-
340
- ***
341
-
342
- ### queryValues() {#queryvalues}
343
-
344
- > **queryValues**(`id`, `timeStart?`, `timeEnd?`, `cursor?`, `limit?`): `Promise`\<\{ `metric`: `ITelemetryMetric`; `entities`: `ITelemetryMetricValue`[]; `cursor?`: `string`; \}\>
345
-
346
- Query the recorded values for a metric, ordered by most recent first.
347
-
348
- #### Parameters
349
-
350
- ##### id
351
-
352
- `string`
353
-
354
- The id of the metric.
355
-
356
- ##### timeStart?
357
-
358
- `number`
359
-
360
- The inclusive start time (epoch ms).
361
-
362
- ##### timeEnd?
363
-
364
- `number`
365
-
366
- The inclusive end time (epoch ms).
367
-
368
- ##### cursor?
369
-
370
- `string`
371
-
372
- The cursor returned by the previous call.
373
-
374
- ##### limit?
375
-
376
- `number`
377
-
378
- Limit the number of values to return.
379
-
380
- #### Returns
381
-
382
- `Promise`\<\{ `metric`: `ITelemetryMetric`; `entities`: `ITelemetryMetricValue`[]; `cursor?`: `string`; \}\>
383
-
384
- The metric details, matching values, and an optional cursor for the next page.
385
-
386
- #### Implementation of
387
-
388
- `ITelemetryConnector.queryValues`
@@ -4,56 +4,6 @@ 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
-
15
- ### batchSize? {#batchsize}
16
-
17
- > `optional` **batchSize?**: `number`
18
-
19
- Number of metric value entries to accumulate before flushing to entity storage.
20
-
21
- #### Default
22
-
23
- ```ts
24
- 10
25
- ```
26
-
27
- ***
28
-
29
- ### batchIntervalMs? {#batchintervalms}
30
-
31
- > `optional` **batchIntervalMs?**: `number`
32
-
33
- Interval in milliseconds between automatic batch flushes to entity storage.
34
-
35
- #### Default
36
-
37
- ```ts
38
- 5000
39
- ```
40
-
41
- ***
42
-
43
- ### maxCacheSize? {#maxcachesize}
44
-
45
- > `optional` **maxCacheSize?**: `number`
46
-
47
- Maximum number of entries retained in the write-ahead cache if a flush fails.
48
-
49
- #### Default
50
-
51
- ```ts
52
- 1000
53
- ```
54
-
55
- ***
56
-
57
7
  ### meterName? {#metername}
58
8
 
59
9
  > `optional` **meterName?**: `string`
@@ -4,45 +4,6 @@ The options for the OpenTelemetry telemetry connector constructor.
4
4
 
5
5
  ## Properties
6
6
 
7
- ### loggingComponentType? {#loggingcomponenttype}
8
-
9
- > `optional` **loggingComponentType?**: `string`
10
-
11
- The component type to use for logging inside the connector and the inner
12
- entity-storage connector. When omitted logging is disabled.
13
-
14
- ***
15
-
16
- ### telemetryMetricStorageConnectorType? {#telemetrymetricstorageconnectortype}
17
-
18
- > `optional` **telemetryMetricStorageConnectorType?**: `string`
19
-
20
- The entity storage connector type to use for storing metric definitions.
21
- Must be registered in `EntityStorageConnectorFactory` before calling `start()`.
22
-
23
- #### Default
24
-
25
- ```ts
26
- telemetry-metric
27
- ```
28
-
29
- ***
30
-
31
- ### telemetryMetricValueStorageConnectorType? {#telemetrymetricvaluestorageconnectortype}
32
-
33
- > `optional` **telemetryMetricValueStorageConnectorType?**: `string`
34
-
35
- The entity storage connector type to use for storing metric values.
36
- Must be registered in `EntityStorageConnectorFactory` before calling `start()`.
37
-
38
- #### Default
39
-
40
- ```ts
41
- telemetry-metric-value
42
- ```
43
-
44
- ***
45
-
46
7
  ### config? {#config}
47
8
 
48
9
  > `optional` **config?**: [`IOpenTelemetryTelemetryConnectorConfig`](IOpenTelemetryTelemetryConnectorConfig.md)
package/locales/en.json CHANGED
@@ -4,5 +4,12 @@
4
4
  "connectorStarted": "The connector was started with \"{readerCount}\" metric reader(s)",
5
5
  "connectorStopped": "The connector was stopped"
6
6
  }
7
+ },
8
+ "error": {
9
+ "openTelemetryTelemetryConnector": {
10
+ "metricNotFound": "The metric could not be found \"{notFoundId}\"",
11
+ "counterIncOnly": "A counter metric can only be incremented, or added to with an integer",
12
+ "gaugeNoIncDec": "A gauge can not be incremented or decremented"
13
+ }
7
14
  }
8
15
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@twin.org/telemetry-connector-opentelemetry",
3
- "version": "0.9.2-next.7",
3
+ "version": "0.9.2",
4
4
  "description": "OpenTelemetry connector for pushing telemetry metrics to OTEL-compatible backends.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -19,13 +19,13 @@
19
19
  "@opentelemetry/exporter-prometheus": "0.221.0",
20
20
  "@opentelemetry/resources": "2.10.0",
21
21
  "@opentelemetry/sdk-metrics": "2.10.0",
22
- "@twin.org/context": "next",
23
- "@twin.org/core": "next",
24
- "@twin.org/entity-storage-models": "next",
25
- "@twin.org/logging-models": "next",
26
- "@twin.org/nameof": "next",
27
- "@twin.org/telemetry-connector-entity-storage": "0.9.2-next.7",
28
- "@twin.org/telemetry-models": "0.9.2-next.7"
22
+ "@twin.org/context": "^0.9.2",
23
+ "@twin.org/core": "^0.9.2",
24
+ "@twin.org/entity-storage-models": "^0.9.2",
25
+ "@twin.org/logging-models": "^0.9.2",
26
+ "@twin.org/nameof": "^0.9.2",
27
+ "@twin.org/telemetry-connector-entity-storage": "^0.9.2",
28
+ "@twin.org/telemetry-models": "^0.9.2"
29
29
  },
30
30
  "main": "./dist/es/index.js",
31
31
  "types": "./dist/types/index.d.ts",