@breadstone/archipel-platform-telemetry 0.0.13 → 0.0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -14,11 +14,12 @@ import { TelemetryModule } from '@breadstone/archipel-platform-telemetry';
14
14
 
15
15
  ## Features
16
16
 
17
- - Metrics collection
18
- - Performance tracking
19
- - Health checks
20
- - Monitoring integration
21
- - Custom metrics
17
+ - **Metrics collection**: Counter, histogram, and gauge recording via `TelemetryFacade`
18
+ - **Distributed tracing**: OpenTelemetry SDK integration with automatic span creation
19
+ - **Operation interceptor**: `OperationTelemetryInterceptor` wraps controller methods in telemetry operations (returns `Observable<unknown>`)
20
+ - **Graceful degradation**: Falls back to `NoopTelemetryFacade` when disabled — zero overhead
21
+ - **Health checks**: Integration with health module for observability readiness
22
+ - **Structured logging**: `TelemetryLoggerService` with request correlation IDs
22
23
 
23
24
  ## Development
24
25
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@breadstone/archipel-platform-telemetry",
3
- "version": "0.0.13",
3
+ "version": "0.0.15",
4
4
  "description": "OpenTelemetry integration with tracing, metrics, and graceful no-op fallback for NestJS.",
5
5
  "type": "commonjs",
6
6
  "main": "./src/index.js",
@@ -28,8 +28,8 @@
28
28
  "README.md"
29
29
  ],
30
30
  "dependencies": {
31
- "@breadstone/archipel-platform-caching": "0.0.13",
32
- "@breadstone/archipel-platform-core": "0.0.13",
31
+ "@breadstone/archipel-platform-caching": "0.0.15",
32
+ "@breadstone/archipel-platform-core": "0.0.15",
33
33
  "@opentelemetry/api": "1.9.1",
34
34
  "@opentelemetry/exporter-metrics-otlp-http": "0.215.0",
35
35
  "@opentelemetry/exporter-trace-otlp-http": "0.215.0",
@@ -0,0 +1,19 @@
1
+ import { OnApplicationShutdown } from '@nestjs/common';
2
+ /**
3
+ * Holds a reference to the OpenTelemetry NodeSDK instance and
4
+ * performs a graceful shutdown when the application terminates.
5
+ *
6
+ * Replaces the former closure-based `otelSdkRef` pattern (TEL-1).
7
+ *
8
+ * @public
9
+ */
10
+ export declare class OtelSdkHolder implements OnApplicationShutdown {
11
+ private readonly _logger;
12
+ private _sdk?;
13
+ /**
14
+ * Stores the SDK reference so it can be shut down later.
15
+ */
16
+ setSdk(sdk: unknown): void;
17
+ /** @inheritdoc */
18
+ onApplicationShutdown(): Promise<void>;
19
+ }
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ // #region Imports
3
+ var OtelSdkHolder_1;
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ exports.OtelSdkHolder = void 0;
6
+ const tslib_1 = require("tslib");
7
+ const common_1 = require("@nestjs/common");
8
+ // #endregion
9
+ /**
10
+ * Holds a reference to the OpenTelemetry NodeSDK instance and
11
+ * performs a graceful shutdown when the application terminates.
12
+ *
13
+ * Replaces the former closure-based `otelSdkRef` pattern (TEL-1).
14
+ *
15
+ * @public
16
+ */
17
+ let OtelSdkHolder = OtelSdkHolder_1 = class OtelSdkHolder {
18
+ constructor() {
19
+ // #region Fields
20
+ this._logger = new common_1.Logger(OtelSdkHolder_1.name);
21
+ // #endregion
22
+ }
23
+ // #endregion
24
+ // #region Methods
25
+ /**
26
+ * Stores the SDK reference so it can be shut down later.
27
+ */
28
+ setSdk(sdk) {
29
+ this._sdk = sdk;
30
+ }
31
+ /** @inheritdoc */
32
+ async onApplicationShutdown() {
33
+ if (!this._sdk) {
34
+ return;
35
+ }
36
+ try {
37
+ await this._sdk.shutdown();
38
+ this._logger.log('OpenTelemetry SDK shut down successfully.');
39
+ }
40
+ catch (error) {
41
+ this._logger.error('Failed to shut down OpenTelemetry SDK.', error.stack);
42
+ }
43
+ }
44
+ };
45
+ exports.OtelSdkHolder = OtelSdkHolder;
46
+ exports.OtelSdkHolder = OtelSdkHolder = OtelSdkHolder_1 = tslib_1.__decorate([
47
+ (0, common_1.Injectable)()
48
+ ], OtelSdkHolder);
49
+ //# sourceMappingURL=OtelSdkHolder.js.map
@@ -5,75 +5,71 @@ exports.TelemetryModule = void 0;
5
5
  const tslib_1 = require("tslib");
6
6
  const common_1 = require("@nestjs/common");
7
7
  const core_1 = require("@nestjs/core");
8
- const TelemetryOptions_1 = require("./options/TelemetryOptions");
9
- const tokens_1 = require("./tokens");
10
8
  const OtelInitializer_1 = require("./bootstrap/OtelInitializer");
11
9
  const NoopTelemetryFacade_1 = require("./core/NoopTelemetryFacade");
12
10
  const TelemetryFacade_1 = require("./core/TelemetryFacade");
13
- const TelemetryRuleEngine_1 = require("./rules/TelemetryRuleEngine");
14
- const TelemetryLoggerService_1 = require("./logger/TelemetryLoggerService");
15
11
  const OperationTelemetryInterceptor_1 = require("./interceptor/OperationTelemetryInterceptor");
16
- const ShutdownHook_1 = require("./ShutdownHook");
12
+ const TelemetryLoggerService_1 = require("./logger/TelemetryLoggerService");
13
+ const MetricsService_1 = require("./metrics/MetricsService");
14
+ const TelemetryOptions_1 = require("./options/TelemetryOptions");
15
+ const OtelSdkHolder_1 = require("./OtelSdkHolder");
16
+ const TelemetryRuleEngine_1 = require("./rules/TelemetryRuleEngine");
17
+ const tokens_1 = require("./tokens");
17
18
  let TelemetryModule = TelemetryModule_1 = class TelemetryModule {
18
19
  static register(options) {
19
- let otelSdkRef;
20
20
  return {
21
21
  module: TelemetryModule_1,
22
22
  providers: [
23
23
  {
24
24
  provide: tokens_1.TELEMETRY_OPTIONS,
25
- useValue: {
26
- ...TelemetryOptions_1.defaultTelemetryOptions,
27
- ...options
28
- }
25
+ useFactory: () => {
26
+ const merged = {
27
+ ...TelemetryOptions_1.defaultTelemetryOptions,
28
+ ...options,
29
+ };
30
+ (0, TelemetryOptions_1.validateTelemetryOptions)(merged);
31
+ return merged;
32
+ },
29
33
  },
30
34
  {
31
35
  provide: tokens_1.TELEMETRY_ENABLED,
32
36
  inject: [tokens_1.TELEMETRY_OPTIONS],
33
- useFactory: (cfg) => cfg.enabled
37
+ useFactory: (cfg) => cfg.enabled,
34
38
  },
35
39
  {
36
40
  provide: TelemetryRuleEngine_1.TelemetryRuleEngine,
37
- inject: [tokens_1.TELEMETRY_OPTIONS],
38
- useFactory: (cfg) => new TelemetryRuleEngine_1.TelemetryRuleEngine(cfg)
41
+ inject: [tokens_1.TELEMETRY_OPTIONS, MetricsService_1.MetricsService],
42
+ useFactory: (cfg, metricsService) => new TelemetryRuleEngine_1.TelemetryRuleEngine(cfg, metricsService),
39
43
  },
40
44
  TelemetryLoggerService_1.TelemetryLoggerService,
41
- ShutdownHook_1.TelemetryShutdownHook,
45
+ OtelSdkHolder_1.OtelSdkHolder,
46
+ MetricsService_1.MetricsService,
42
47
  {
43
48
  provide: tokens_1.TELEMETRY_FACADE,
44
- inject: [tokens_1.TELEMETRY_OPTIONS, tokens_1.TELEMETRY_ENABLED],
45
- useFactory: async (cfg, enabled) => {
49
+ inject: [tokens_1.TELEMETRY_OPTIONS, tokens_1.TELEMETRY_ENABLED, OtelSdkHolder_1.OtelSdkHolder],
50
+ useFactory: async (cfg, enabled, sdkHolder) => {
46
51
  if (!enabled)
47
52
  return new NoopTelemetryFacade_1.NoopTelemetryFacade();
48
53
  try {
49
54
  const { sdk } = await (0, OtelInitializer_1.initializeOtel)(cfg);
50
- otelSdkRef = sdk;
55
+ sdkHolder.setSdk(sdk);
51
56
  return new TelemetryFacade_1.TelemetryFacade();
52
57
  }
53
- catch {
58
+ catch (error) {
59
+ new common_1.Logger('TelemetryModule').error('OpenTelemetry SDK initialization failed, falling back to noop', error.stack);
54
60
  return new NoopTelemetryFacade_1.NoopTelemetryFacade();
55
61
  }
56
- }
57
- },
58
- {
59
- provide: tokens_1.TELEMETRY_SHUTDOWN,
60
- inject: [tokens_1.TELEMETRY_ENABLED],
61
- useFactory: (enabled) => async () => {
62
- if (enabled) {
63
- try {
64
- await otelSdkRef?.shutdown?.();
65
- }
66
- catch { /* noop */ }
67
- }
68
- }
62
+ },
69
63
  },
70
64
  {
71
65
  provide: core_1.APP_INTERCEPTOR,
72
66
  inject: [tokens_1.TELEMETRY_FACADE, tokens_1.TELEMETRY_OPTIONS],
73
- useFactory: (facade, cfg) => cfg.interceptor?.operation ? new OperationTelemetryInterceptor_1.OperationTelemetryInterceptor(facade) : { intercept: (_, handler) => handler }
74
- }
67
+ useFactory: (facade, cfg) => cfg.interceptor?.operation
68
+ ? new OperationTelemetryInterceptor_1.OperationTelemetryInterceptor(facade)
69
+ : { intercept: (_, next) => next.handle() },
70
+ },
75
71
  ],
76
- exports: [tokens_1.TELEMETRY_FACADE]
72
+ exports: [tokens_1.TELEMETRY_FACADE, MetricsService_1.MetricsService],
77
73
  };
78
74
  }
79
75
  };
@@ -1,6 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.initializeOtel = initializeOtel;
4
+ const common_1 = require("@nestjs/common");
5
+ const logger = new common_1.Logger('OtelInitializer');
4
6
  // Lazy initialization to avoid module load costs when disabled
5
7
  async function initializeOtel(options) {
6
8
  if (!options.enabled)
@@ -28,23 +30,25 @@ async function initializeOtel(options) {
28
30
  const metricExp = require('@opentelemetry/exporter-metrics-otlp-http');
29
31
  metricReader = new sdkNode.PeriodicExportingMetricReader({
30
32
  exporter: new metricExp.OTLPMetricExporter(),
31
- exportIntervalMillis: options.metrics?.intervalMs ?? 60000
33
+ exportIntervalMillis: options.metrics?.intervalMs ?? 60000,
32
34
  });
33
35
  }
34
36
  const resource = new resources.Resource({
35
37
  [semconv.SemanticResourceAttributes.SERVICE_NAME]: options.serviceName,
36
- environment: options.environment ?? 'local'
38
+ environment: options.environment ?? 'local',
37
39
  });
38
40
  const sdk = new sdkNode.NodeSDK({
39
41
  resource,
40
42
  traceExporter,
41
43
  metricReader,
42
- instrumentations
44
+ instrumentations,
43
45
  });
44
46
  await sdk.start();
47
+ logger.log('OpenTelemetry SDK initialized successfully.');
45
48
  return { sdk };
46
49
  }
47
- catch {
50
+ catch (error) {
51
+ logger.error('Failed to initialize OpenTelemetry SDK. Telemetry will be degraded.', error.stack);
48
52
  return {};
49
53
  }
50
54
  }
@@ -1,7 +1,8 @@
1
- import { ITelemetryFacade } from './NoopTelemetryFacade';
2
1
  import { Logger } from '@nestjs/common';
2
+ import { ITelemetryFacade } from './NoopTelemetryFacade';
3
3
  export declare class TelemetryFacade implements ITelemetryFacade {
4
4
  private readonly _logger;
5
+ constructor(logger?: Logger);
5
6
  runOperation<T>(name: string, fn: () => Promise<T> | T): Promise<T>;
6
7
  recordCacheEvent(event: {
7
8
  cache: string;
@@ -3,9 +3,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.TelemetryFacade = void 0;
4
4
  const common_1 = require("@nestjs/common");
5
5
  class TelemetryFacade {
6
- constructor() {
7
- this._logger = new common_1.Logger('TelemetryFacade');
6
+ // #endregion
7
+ // #region Ctor
8
+ constructor(logger) {
9
+ this._logger = logger ?? new common_1.Logger(TelemetryFacade.name);
8
10
  }
11
+ // #endregion
12
+ // #region Methods
9
13
  async runOperation(name, fn) {
10
14
  const start = Date.now();
11
15
  try {
@@ -25,12 +29,15 @@ class TelemetryFacade {
25
29
  }
26
30
  recordClientMetric(name, type, value, labels) {
27
31
  const labelStr = labels
28
- ? Object.entries(labels).map(([k, v]) => `${k}=${v}`)
32
+ ? Object.entries(labels)
33
+ .map(([k, v]) => `${k}=${v}`)
29
34
  .join(' ')
30
35
  : '';
31
36
  this._logger.debug(`client.metric name=${name} type=${type} value=${value}${labelStr ? ' ' + labelStr : ''}`);
32
37
  }
33
- getLogger() { return this._logger; }
38
+ getLogger() {
39
+ return this._logger;
40
+ }
34
41
  }
35
42
  exports.TelemetryFacade = TelemetryFacade;
36
43
  //# sourceMappingURL=TelemetryFacade.js.map
package/src/index.d.ts CHANGED
@@ -1,8 +1,10 @@
1
- export { TelemetryModule } from './TelemetryModule';
2
1
  export * from './core/NoopTelemetryFacade';
3
- export * from './core/TelemetryFacade';
4
2
  export * from './core/TelemetryCacheMetricsRecorder';
5
- export * from './rules/TelemetryRuleEngine';
3
+ export * from './core/TelemetryFacade';
6
4
  export * from './logger/TelemetryLoggerService';
5
+ export * from './metrics/MetricsService';
7
6
  export * from './options/TelemetryOptions';
7
+ export { OtelSdkHolder } from './OtelSdkHolder';
8
+ export * from './rules/TelemetryRuleEngine';
9
+ export { TelemetryModule } from './TelemetryModule';
8
10
  export * from './tokens';
package/src/index.js CHANGED
@@ -1,14 +1,17 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.TelemetryModule = void 0;
3
+ exports.TelemetryModule = exports.OtelSdkHolder = void 0;
4
4
  const tslib_1 = require("tslib");
5
- var TelemetryModule_1 = require("./TelemetryModule");
6
- Object.defineProperty(exports, "TelemetryModule", { enumerable: true, get: function () { return TelemetryModule_1.TelemetryModule; } });
7
5
  tslib_1.__exportStar(require("./core/NoopTelemetryFacade"), exports);
8
- tslib_1.__exportStar(require("./core/TelemetryFacade"), exports);
9
6
  tslib_1.__exportStar(require("./core/TelemetryCacheMetricsRecorder"), exports);
10
- tslib_1.__exportStar(require("./rules/TelemetryRuleEngine"), exports);
7
+ tslib_1.__exportStar(require("./core/TelemetryFacade"), exports);
11
8
  tslib_1.__exportStar(require("./logger/TelemetryLoggerService"), exports);
9
+ tslib_1.__exportStar(require("./metrics/MetricsService"), exports);
12
10
  tslib_1.__exportStar(require("./options/TelemetryOptions"), exports);
11
+ var OtelSdkHolder_1 = require("./OtelSdkHolder");
12
+ Object.defineProperty(exports, "OtelSdkHolder", { enumerable: true, get: function () { return OtelSdkHolder_1.OtelSdkHolder; } });
13
+ tslib_1.__exportStar(require("./rules/TelemetryRuleEngine"), exports);
14
+ var TelemetryModule_1 = require("./TelemetryModule");
15
+ Object.defineProperty(exports, "TelemetryModule", { enumerable: true, get: function () { return TelemetryModule_1.TelemetryModule; } });
13
16
  tslib_1.__exportStar(require("./tokens"), exports);
14
17
  //# sourceMappingURL=index.js.map
@@ -4,5 +4,5 @@ import { ITelemetryFacade } from '../core/NoopTelemetryFacade';
4
4
  export declare class OperationTelemetryInterceptor implements NestInterceptor {
5
5
  private readonly _facade;
6
6
  constructor(facade: ITelemetryFacade);
7
- intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> | Promise<Observable<unknown>>;
7
+ intercept(context: ExecutionContext, next: CallHandler): Observable<unknown>;
8
8
  }
@@ -7,14 +7,16 @@ const rxjs_1 = require("rxjs");
7
7
  let OperationTelemetryInterceptor = class OperationTelemetryInterceptor {
8
8
  // #endregion
9
9
  // #region Ctor
10
- constructor(facade) { this._facade = facade; }
10
+ constructor(facade) {
11
+ this._facade = facade;
12
+ }
11
13
  // #endregion
12
14
  // #region Methods
13
15
  intercept(context, next) {
14
16
  const handler = context.getHandler().name;
15
17
  const cls = context.getClass().name;
16
18
  const name = `${cls}.${handler}`;
17
- return this._facade.runOperation(name, async () => await (0, rxjs_1.firstValueFrom)(next.handle()));
19
+ return (0, rxjs_1.from)(this._facade.runOperation(name, () => (0, rxjs_1.firstValueFrom)(next.handle())));
18
20
  }
19
21
  };
20
22
  exports.OperationTelemetryInterceptor = OperationTelemetryInterceptor;
@@ -0,0 +1,20 @@
1
+ export interface IMetricInstruments {
2
+ cacheCounter?: unknown;
3
+ opCounter?: unknown;
4
+ opDuration?: unknown;
5
+ }
6
+ /**
7
+ * Provides lazily-initialized OpenTelemetry metric instruments.
8
+ *
9
+ * Replaces the former module-level `let instruments` closure (TEL-4).
10
+ *
11
+ * @public
12
+ */
13
+ export declare class MetricsService {
14
+ private readonly _logger;
15
+ private _instruments?;
16
+ /**
17
+ * Returns the cached metric instruments, creating them on first access.
18
+ */
19
+ getInstruments(): IMetricInstruments | undefined;
20
+ }
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ // #region Imports
3
+ var MetricsService_1;
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ exports.MetricsService = void 0;
6
+ const tslib_1 = require("tslib");
7
+ const common_1 = require("@nestjs/common");
8
+ // #endregion
9
+ /**
10
+ * Provides lazily-initialized OpenTelemetry metric instruments.
11
+ *
12
+ * Replaces the former module-level `let instruments` closure (TEL-4).
13
+ *
14
+ * @public
15
+ */
16
+ let MetricsService = MetricsService_1 = class MetricsService {
17
+ constructor() {
18
+ // #region Fields
19
+ this._logger = new common_1.Logger(MetricsService_1.name);
20
+ // #endregion
21
+ }
22
+ // #endregion
23
+ // #region Methods
24
+ /**
25
+ * Returns the cached metric instruments, creating them on first access.
26
+ */
27
+ getInstruments() {
28
+ if (this._instruments) {
29
+ return this._instruments;
30
+ }
31
+ try {
32
+ const api = require('@opentelemetry/api');
33
+ const meter = api.metrics.getMeter('telemetry');
34
+ this._instruments = {
35
+ cacheCounter: meter.createCounter('cache_event_total', { description: 'Cache events (hit/miss/save)' }),
36
+ opCounter: meter.createCounter('operation_event_total', { description: 'Operation success/error events' }),
37
+ opDuration: meter.createHistogram('operation_duration_ms', { description: 'Operation durations ms' }),
38
+ };
39
+ return this._instruments;
40
+ }
41
+ catch (error) {
42
+ this._logger.warn('Failed to initialize metric instruments.', error.message);
43
+ return undefined;
44
+ }
45
+ }
46
+ };
47
+ exports.MetricsService = MetricsService;
48
+ exports.MetricsService = MetricsService = MetricsService_1 = tslib_1.__decorate([
49
+ (0, common_1.Injectable)()
50
+ ], MetricsService);
51
+ //# sourceMappingURL=MetricsService.js.map
@@ -28,3 +28,7 @@ export interface ITelemetryOptions {
28
28
  };
29
29
  }
30
30
  export declare const defaultTelemetryOptions: ITelemetryOptions;
31
+ /**
32
+ * Validates telemetry options and throws on invalid values.
33
+ */
34
+ export declare function validateTelemetryOptions(options: ITelemetryOptions): void;
@@ -2,31 +2,48 @@
2
2
  // #region Interfaces
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
4
  exports.defaultTelemetryOptions = void 0;
5
+ exports.validateTelemetryOptions = validateTelemetryOptions;
5
6
  exports.defaultTelemetryOptions = {
6
7
  enabled: true,
7
8
  serviceName: 'app',
8
9
  traces: {
9
10
  enabled: true,
10
11
  samplingRatio: 0.05,
11
- exporter: 'otlp-http'
12
+ exporter: 'otlp-http',
12
13
  },
13
14
  metrics: {
14
15
  enabled: true,
15
16
  intervalMs: 60000,
16
- exporter: 'otlp-http'
17
+ exporter: 'otlp-http',
17
18
  },
18
19
  logCorrelation: true,
19
20
  instrumentation: {
20
21
  http: true,
21
- express: true
22
+ express: true,
22
23
  },
23
24
  pattern: {
24
25
  enableCacheMetrics: true,
25
- enableOperationMetrics: true
26
+ enableOperationMetrics: true,
26
27
  },
27
28
  interceptor: {
28
- operation: true
29
- }
29
+ operation: true,
30
+ },
30
31
  };
32
+ /**
33
+ * Validates telemetry options and throws on invalid values.
34
+ */
35
+ function validateTelemetryOptions(options) {
36
+ if (options.enabled && (!options.serviceName || options.serviceName.trim().length === 0)) {
37
+ throw new Error('TelemetryOptions: serviceName must be a non-empty string when telemetry is enabled.');
38
+ }
39
+ const ratio = options.traces?.samplingRatio;
40
+ if (ratio !== undefined && (ratio < 0 || ratio > 1)) {
41
+ throw new Error(`TelemetryOptions: traces.samplingRatio must be between 0 and 1, got ${ratio}.`);
42
+ }
43
+ const interval = options.metrics?.intervalMs;
44
+ if (interval !== undefined && interval <= 0) {
45
+ throw new Error(`TelemetryOptions: metrics.intervalMs must be positive, got ${interval}.`);
46
+ }
47
+ }
31
48
  // #endregion
32
49
  //# sourceMappingURL=TelemetryOptions.js.map
@@ -1,7 +1,9 @@
1
+ import { MetricsService } from '../metrics/MetricsService';
1
2
  import { ITelemetryOptions } from '../options/TelemetryOptions';
2
3
  export declare class TelemetryRuleEngine {
3
4
  private readonly _options;
4
- constructor(options: ITelemetryOptions);
5
+ private readonly _metricsService;
6
+ constructor(options: ITelemetryOptions, metricsService: MetricsService);
5
7
  process(message: string): void;
6
8
  private handleCache;
7
9
  private handleOperation;
@@ -1,11 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.TelemetryRuleEngine = void 0;
4
- const MetricsHelper_1 = require("../metrics/MetricsHelper");
5
4
  class TelemetryRuleEngine {
6
5
  // #endregion
7
6
  // #region Ctor
8
- constructor(options) { this._options = options; }
7
+ constructor(options, metricsService) {
8
+ this._options = options;
9
+ this._metricsService = metricsService;
10
+ }
9
11
  // #endregion
10
12
  // #region Methods
11
13
  process(message) {
@@ -24,7 +26,7 @@ class TelemetryRuleEngine {
24
26
  if (!this._options.pattern?.enableCacheMetrics)
25
27
  return;
26
28
  const tokens = this.parseTokens(line);
27
- const instruments = (0, MetricsHelper_1.getInstruments)();
29
+ const instruments = this._metricsService.getInstruments();
28
30
  const counter = instruments?.cacheCounter;
29
31
  if (!counter)
30
32
  return;
@@ -32,67 +34,68 @@ class TelemetryRuleEngine {
32
34
  counter.add(1, {
33
35
  cache: tokens['cache'] ?? 'unknown',
34
36
  outcome: tokens['outcome'] ?? 'unknown',
35
- phase: tokens['phase'] ?? 'none'
37
+ phase: tokens['phase'] ?? 'none',
36
38
  });
37
39
  }
38
- catch { /* noop */ }
40
+ catch {
41
+ /* noop */
42
+ }
39
43
  }
40
44
  handleOperation(line) {
41
45
  if (!this._options.pattern?.enableOperationMetrics)
42
46
  return;
43
47
  const success = line.startsWith('op.success');
44
48
  const tokens = this.parseTokens(line);
45
- const instruments = (0, MetricsHelper_1.getInstruments)();
49
+ const instruments = this._metricsService.getInstruments();
46
50
  if (!instruments)
47
51
  return;
48
52
  const counter = instruments.opCounter;
49
53
  const histogram = instruments.opDuration;
50
54
  const labels = {
51
55
  name: tokens['name'] ?? 'unknown',
52
- status: success ? 'success' : 'error'
56
+ status: success ? 'success' : 'error',
53
57
  };
54
58
  try {
55
59
  counter?.add(1, labels);
56
60
  }
57
- catch { /* noop */ }
61
+ catch {
62
+ /* noop */
63
+ }
58
64
  const dur = tokens['duration_ms'] ? Number(tokens['duration_ms']) : undefined;
59
65
  if (dur !== undefined && !Number.isNaN(dur)) {
60
66
  try {
61
67
  histogram?.record(dur, labels);
62
68
  }
63
- catch { /* noop */ }
69
+ catch {
70
+ /* noop */
71
+ }
64
72
  }
65
73
  }
66
74
  handleClientMetric(line) {
67
- // Always enabled if arrives; client submission is already filtered front-side.
68
75
  const tokens = this.parseTokens(line);
69
- const instruments = (0, MetricsHelper_1.getInstruments)();
76
+ const instruments = this._metricsService.getInstruments();
70
77
  if (!instruments)
71
78
  return;
72
- const counter = instruments.opCounter; // reuse op counter; could separate later.
79
+ const counter = instruments.opCounter;
73
80
  const histogram = instruments.opDuration;
74
81
  const name = tokens['name'] ?? 'unknown';
75
82
  const valueRaw = tokens['value'] ? Number(tokens['value']) : undefined;
76
83
  const type = tokens['type'] ?? 'counter';
77
- const baseLabels = {
78
- source: 'client',
79
- name
80
- };
84
+ const baseLabels = { source: 'client', name };
81
85
  try {
82
86
  if (type === 'counter') {
83
87
  counter?.add(1, baseLabels);
84
88
  }
85
89
  else if (type === 'gauge' && valueRaw !== undefined && !Number.isNaN(valueRaw)) {
86
- histogram?.record(valueRaw, {
87
- ...baseLabels,
88
- gauge: true
89
- });
90
+ histogram?.record(valueRaw, { ...baseLabels, gauge: true });
90
91
  }
91
92
  else if (type === 'histogram' && valueRaw !== undefined && !Number.isNaN(valueRaw)) {
92
93
  histogram?.record(valueRaw, baseLabels);
93
94
  }
94
95
  }
95
- catch { /* noop */ }
96
+ catch {
97
+ /* noop */
98
+ }
96
99
  }
97
100
  parseTokens(line) {
98
101
  const parts = line.split(/\s+/).slice(1);
package/src/tokens.d.ts CHANGED
@@ -1,4 +1,3 @@
1
1
  export declare const TELEMETRY_OPTIONS: unique symbol;
2
2
  export declare const TELEMETRY_FACADE: unique symbol;
3
3
  export declare const TELEMETRY_ENABLED: unique symbol;
4
- export declare const TELEMETRY_SHUTDOWN: unique symbol;
package/src/tokens.js CHANGED
@@ -1,9 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.TELEMETRY_SHUTDOWN = exports.TELEMETRY_ENABLED = exports.TELEMETRY_FACADE = exports.TELEMETRY_OPTIONS = void 0;
3
+ exports.TELEMETRY_ENABLED = exports.TELEMETRY_FACADE = exports.TELEMETRY_OPTIONS = void 0;
4
4
  // Internal DI tokens (not exposing OTel types)
5
5
  exports.TELEMETRY_OPTIONS = Symbol('TELEMETRY_OPTIONS');
6
6
  exports.TELEMETRY_FACADE = Symbol('TELEMETRY_FACADE');
7
7
  exports.TELEMETRY_ENABLED = Symbol('TELEMETRY_ENABLED');
8
- exports.TELEMETRY_SHUTDOWN = Symbol('TELEMETRY_SHUTDOWN');
9
8
  //# sourceMappingURL=tokens.js.map
@@ -1,6 +0,0 @@
1
- import { OnApplicationShutdown } from '@nestjs/common';
2
- export declare class TelemetryShutdownHook implements OnApplicationShutdown {
3
- private readonly _shutdown;
4
- constructor(shutdown: () => Promise<void>);
5
- onApplicationShutdown(): Promise<void>;
6
- }
@@ -1,21 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.TelemetryShutdownHook = void 0;
4
- const tslib_1 = require("tslib");
5
- const common_1 = require("@nestjs/common");
6
- const tokens_1 = require("./tokens");
7
- let TelemetryShutdownHook = class TelemetryShutdownHook {
8
- // #endregion
9
- // #region Ctor
10
- constructor(shutdown) { this._shutdown = shutdown; }
11
- // #endregion
12
- // #region Methods
13
- async onApplicationShutdown() { await this._shutdown(); }
14
- };
15
- exports.TelemetryShutdownHook = TelemetryShutdownHook;
16
- exports.TelemetryShutdownHook = TelemetryShutdownHook = tslib_1.__decorate([
17
- (0, common_1.Injectable)(),
18
- tslib_1.__param(0, (0, common_1.Inject)(tokens_1.TELEMETRY_SHUTDOWN)),
19
- tslib_1.__metadata("design:paramtypes", [Function])
20
- ], TelemetryShutdownHook);
21
- //# sourceMappingURL=ShutdownHook.js.map
@@ -1,6 +0,0 @@
1
- export interface IMetricInstruments {
2
- cacheCounter?: unknown;
3
- opCounter?: unknown;
4
- opDuration?: unknown;
5
- }
6
- export declare function getInstruments(): IMetricInstruments | undefined;
@@ -1,22 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getInstruments = getInstruments;
4
- let instruments;
5
- function getInstruments() {
6
- if (instruments)
7
- return instruments;
8
- try {
9
- const api = require('@opentelemetry/api');
10
- const meter = api.metrics.getMeter('telemetry');
11
- instruments = {
12
- cacheCounter: meter.createCounter('cache_event_total', { description: 'Cache events (hit/miss/save)' }),
13
- opCounter: meter.createCounter('operation_event_total', { description: 'Operation success/error events' }),
14
- opDuration: meter.createHistogram('operation_duration_ms', { description: 'Operation durations ms' })
15
- };
16
- return instruments;
17
- }
18
- catch {
19
- return undefined;
20
- }
21
- }
22
- //# sourceMappingURL=MetricsHelper.js.map