@ryanzeng/nest-observe 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -89,7 +89,9 @@ function resolveObserveConfig(options = {}, env = process.env) {
89
89
  allowedHeaders,
90
90
  exportTimeoutMillis: positiveInteger(options.exportTimeoutMillis ?? env.OTEL_EXPORTER_OTLP_TIMEOUT, 1e4),
91
91
  metricExportIntervalMillis: positiveInteger(options.metricExportIntervalMillis ?? env.OTEL_METRIC_EXPORT_INTERVAL, 6e4),
92
- resourceAttributes
92
+ resourceAttributes,
93
+ diagnosticLogging: booleanValue(options.diagnosticLogging, true),
94
+ failFast: booleanValue(options.failFast, false)
93
95
  };
94
96
  if (options.exporters) result.exporters = options.exporters;
95
97
  return result;
@@ -180,6 +182,11 @@ function invokeWithSpan(tracer, spanName, attributes, invoke, onFinish) {
180
182
  }
181
183
 
182
184
  // src/decorators/trace.decorator.ts
185
+ function copyOwnMetadata(source, target) {
186
+ for (const metadataKey of Reflect.getOwnMetadataKeys(source)) {
187
+ Reflect.defineMetadata(metadataKey, Reflect.getOwnMetadata(metadataKey, source), target);
188
+ }
189
+ }
183
190
  function decorateMethod(target, propertyKey, descriptor, options) {
184
191
  const original = descriptor.value;
185
192
  if (typeof original !== "function" || isTraceIgnored(original, target.constructor))
@@ -204,6 +211,7 @@ function decorateMethod(target, propertyKey, descriptor, options) {
204
211
  value: original.name,
205
212
  configurable: true
206
213
  });
214
+ copyOwnMetadata(original, wrapped);
207
215
  markTraceDecorated(wrapped);
208
216
  descriptor.value = wrapped;
209
217
  }
@@ -284,7 +292,8 @@ var SEVERITY = {
284
292
  fatal: "FATAL"
285
293
  };
286
294
  function bodyValue(message) {
287
- if (message instanceof Error) return redactText(message.stack ?? `${message.name}: ${message.message}`);
295
+ if (message instanceof Error)
296
+ return redactText(message.stack ?? `${message.name}: ${message.message}`);
288
297
  return redact(message);
289
298
  }
290
299
  var NestLoggerInstrumentation = class {
@@ -329,7 +338,8 @@ var NestLoggerInstrumentation = class {
329
338
  disable() {
330
339
  if (!this.enabled) return;
331
340
  const prototype = ConsoleLogger.prototype;
332
- for (const [method, original] of this.originals) prototype[method] = original;
341
+ for (const [method, original] of this.originals)
342
+ prototype[method] = original;
333
343
  this.originals.clear();
334
344
  this.enabled = false;
335
345
  }
@@ -408,7 +418,7 @@ var NestMethodInstrumenter = class {
408
418
  if (!descriptor || typeof original !== "function" || isTraceIgnored(original, type) || isTraceDecorated(original)) continue;
409
419
  const existing = Reflect.get(target, methodName);
410
420
  if (typeof existing === "function" && existing !== original && isTraceDecorated(existing)) continue;
411
- const name = componentName || type.name || "Anonymous";
421
+ const name = typeof componentName === "symbol" ? String(componentName) : typeof componentName === "string" && componentName ? componentName : type.name || "Anonymous";
412
422
  const attributes = {
413
423
  [`nestjs.${kind}`]: name,
414
424
  "nestjs.method": methodName
@@ -461,7 +471,7 @@ var CompatibleNestInstrumentation = class extends NestInstrumentation {
461
471
  const instance = await original.apply(this, args);
462
472
  try {
463
473
  const wrapper = args[1];
464
- if (!instance || typeof instance !== "object" || !wrapper?.metatype) return instance;
474
+ if (!instance || typeof instance !== "object" || !wrapper?.metatype || wrapper.inject !== void 0) return instance;
465
475
  if (["InternalCoreModule", "ObserveModule", "DiscoveryModule"].includes(wrapper.host?.name ?? "")) {
466
476
  return instance;
467
477
  }
@@ -522,9 +532,14 @@ var HttpRequestMetrics = class {
522
532
  errorCount;
523
533
  startedAt = /* @__PURE__ */ new WeakMap();
524
534
  start(request) {
535
+ if (this.startedAt.has(request)) return false;
525
536
  this.startedAt.set(request, process.hrtime.bigint());
537
+ return true;
526
538
  }
527
539
  record(request, response) {
540
+ const started = this.startedAt.get(request);
541
+ if (started === void 0) return false;
542
+ this.startedAt.delete(request);
528
543
  const statusCode = Number.isFinite(response.statusCode) ? response.statusCode : 0;
529
544
  const attributes = {
530
545
  "service.name": this.serviceName,
@@ -533,12 +548,9 @@ var HttpRequestMetrics = class {
533
548
  "http.response.status_code": statusCode
534
549
  };
535
550
  this.requestCount.add(1, attributes);
536
- const started = this.startedAt.get(request);
537
- if (started !== void 0) {
538
- this.requestDuration.record(Number(process.hrtime.bigint() - started) / 1e9, attributes);
539
- this.startedAt.delete(request);
540
- }
551
+ this.requestDuration.record(Number(process.hrtime.bigint() - started) / 1e9, attributes);
541
552
  if (statusCode >= 500) this.errorCount.add(1, attributes);
553
+ return true;
542
554
  }
543
555
  };
544
556
 
@@ -624,9 +636,15 @@ var RuntimeMetrics = class {
624
636
  };
625
637
 
626
638
  // src/nest/observe.module.ts
627
- import { Global, Inject, Injectable, Module } from "@nestjs/common";
628
- import { DiscoveryModule, DiscoveryService } from "@nestjs/core";
639
+ import {
640
+ Global,
641
+ Inject,
642
+ Injectable,
643
+ Module
644
+ } from "@nestjs/common";
645
+ import { APP_INTERCEPTOR, DiscoveryModule, DiscoveryService } from "@nestjs/core";
629
646
  import { metrics as metrics2, trace as trace5 } from "@opentelemetry/api";
647
+ import { finalize as finalize2 } from "rxjs";
630
648
 
631
649
  // src/sdk.ts
632
650
  import { logs as logs2 } from "@opentelemetry/api-logs";
@@ -642,6 +660,113 @@ import { AggregationType, MeterProvider, PeriodicExportingMetricReader } from "@
642
660
  import { AlwaysOffSampler, BatchSpanProcessor, ParentBasedSampler, TraceIdRatioBasedSampler } from "@opentelemetry/sdk-trace-base";
643
661
  import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
644
662
 
663
+ // src/diagnostics.ts
664
+ function toError(value) {
665
+ if (value instanceof Error) return value;
666
+ if (typeof value === "string") return new Error(value);
667
+ try {
668
+ return new Error(JSON.stringify(value));
669
+ } catch {
670
+ return new Error(String(value));
671
+ }
672
+ }
673
+ function sanitizeError(error) {
674
+ const sanitized = new Error(redactText(error.message));
675
+ sanitized.name = error.name;
676
+ if (error.stack) sanitized.stack = redactText(error.stack);
677
+ return sanitized;
678
+ }
679
+ var ObserveDiagnostics = class {
680
+ constructor(logging, onError) {
681
+ this.logging = logging;
682
+ this.onError = onError;
683
+ }
684
+ logging;
685
+ onError;
686
+ currentStatus = "starting";
687
+ currentError;
688
+ failedSignals = /* @__PURE__ */ new Set();
689
+ reported = /* @__PURE__ */ new Set();
690
+ get status() {
691
+ return this.currentStatus;
692
+ }
693
+ get lastError() {
694
+ return this.currentError;
695
+ }
696
+ activate() {
697
+ if (this.currentStatus === "starting") this.currentStatus = "active";
698
+ }
699
+ inactive() {
700
+ this.currentStatus = "inactive";
701
+ }
702
+ stop() {
703
+ this.currentStatus = "stopped";
704
+ }
705
+ success(signal) {
706
+ this.failedSignals.delete(signal);
707
+ for (const fingerprint of this.reported) {
708
+ if (fingerprint.startsWith(`${signal}\0`)) this.reported.delete(fingerprint);
709
+ }
710
+ if (this.currentStatus === "degraded" && this.failedSignals.size === 0) {
711
+ this.currentStatus = "active";
712
+ }
713
+ }
714
+ failure(signal, stage, value) {
715
+ const error = sanitizeError(toError(value));
716
+ const event = { signal, stage, error, timestamp: Date.now() };
717
+ this.currentError = event;
718
+ this.failedSignals.add(signal);
719
+ if (this.currentStatus !== "inactive" && this.currentStatus !== "stopped") {
720
+ this.currentStatus = "degraded";
721
+ }
722
+ const fingerprint = `${signal}\0${stage}\0${error.name}\0${error.message}`;
723
+ if (this.reported.has(fingerprint)) return;
724
+ this.reported.add(fingerprint);
725
+ try {
726
+ this.onError?.(event);
727
+ } catch {
728
+ }
729
+ if (!this.logging) return;
730
+ try {
731
+ process.stderr.write(
732
+ `[nest-observe] ${signal} ${stage} failed: ${redactText(error.message)}
733
+ `
734
+ );
735
+ } catch {
736
+ }
737
+ }
738
+ };
739
+ function withExporterDiagnostics(exporter, signal, diagnostics) {
740
+ const delegate = exporter;
741
+ return new Proxy(exporter, {
742
+ get(target, property) {
743
+ if (property === "export") {
744
+ return (items, callback) => {
745
+ try {
746
+ delegate.export.call(target, items, (result) => {
747
+ if (result.code === 0) {
748
+ diagnostics.success(signal);
749
+ } else {
750
+ diagnostics.failure(
751
+ signal,
752
+ "export",
753
+ result.error ?? new Error(`${signal} export failed`)
754
+ );
755
+ }
756
+ callback(result);
757
+ });
758
+ } catch (error) {
759
+ diagnostics.failure(signal, "export", error);
760
+ throw error;
761
+ }
762
+ };
763
+ }
764
+ const value = Reflect.get(target, property, target);
765
+ return typeof value === "function" ? value.bind(target) : value;
766
+ }
767
+ });
768
+ }
769
+
645
770
  // src/exceptions/process-exception-capture.ts
646
771
  import { trace as trace3, SpanStatusCode as SpanStatusCode2 } from "@opentelemetry/api";
647
772
  function asError(value) {
@@ -694,8 +819,118 @@ import {
694
819
  resourceFromAttributes
695
820
  } from "@opentelemetry/resources";
696
821
  import { hostname } from "os";
697
- var SDK_NAME = "@ryanzeng/nest-observe";
698
- var SDK_VERSION = "0.1.0";
822
+
823
+ // package.json
824
+ var package_default = {
825
+ name: "@ryanzeng/nest-observe",
826
+ version: "0.1.3",
827
+ description: "Zero-config, vendor-neutral OpenTelemetry observability for NestJS",
828
+ keywords: [
829
+ "nestjs",
830
+ "opentelemetry",
831
+ "observability",
832
+ "otlp",
833
+ "tracing",
834
+ "metrics",
835
+ "logging"
836
+ ],
837
+ license: "MIT",
838
+ homepage: "https://github.com/ryanzen9/nest-observe#readme",
839
+ bugs: {
840
+ url: "https://github.com/ryanzen9/nest-observe/issues"
841
+ },
842
+ repository: {
843
+ type: "git",
844
+ url: "git+https://github.com/ryanzen9/nest-observe.git"
845
+ },
846
+ packageManager: "pnpm@10.13.1",
847
+ sideEffects: [
848
+ "./dist/register.js",
849
+ "./dist/register.mjs"
850
+ ],
851
+ main: "./dist/index.js",
852
+ module: "./dist/index.mjs",
853
+ types: "./dist/index.d.ts",
854
+ exports: {
855
+ ".": {
856
+ import: {
857
+ types: "./dist/index.d.mts",
858
+ default: "./dist/index.mjs"
859
+ },
860
+ require: {
861
+ types: "./dist/index.d.ts",
862
+ default: "./dist/index.js"
863
+ }
864
+ },
865
+ "./register": {
866
+ import: {
867
+ types: "./dist/register.d.mts",
868
+ default: "./dist/register.mjs"
869
+ },
870
+ require: {
871
+ types: "./dist/register.d.ts",
872
+ default: "./dist/register.js"
873
+ }
874
+ }
875
+ },
876
+ files: [
877
+ "dist",
878
+ "README.md",
879
+ "CHANGELOG.md",
880
+ "LICENSE"
881
+ ],
882
+ scripts: {
883
+ build: "tsup",
884
+ test: "vitest run",
885
+ "test:watch": "vitest",
886
+ typecheck: "tsc --noEmit",
887
+ check: "pnpm typecheck && pnpm test && pnpm build"
888
+ },
889
+ engines: {
890
+ node: ">=20"
891
+ },
892
+ peerDependencies: {
893
+ "@nestjs/common": ">=10 <13",
894
+ "@nestjs/core": ">=10 <13",
895
+ "reflect-metadata": ">=0.1.12 <1",
896
+ rxjs: "^7.1.0"
897
+ },
898
+ dependencies: {
899
+ "@opentelemetry/api": "^1.9.0",
900
+ "@opentelemetry/api-logs": "^0.221.0",
901
+ "@opentelemetry/exporter-logs-otlp-proto": "^0.221.0",
902
+ "@opentelemetry/exporter-metrics-otlp-proto": "^0.221.0",
903
+ "@opentelemetry/exporter-trace-otlp-proto": "^0.221.0",
904
+ "@opentelemetry/instrumentation": "^0.221.0",
905
+ "@opentelemetry/instrumentation-http": "^0.221.0",
906
+ "@opentelemetry/instrumentation-nestjs-core": "^0.67.0",
907
+ "@opentelemetry/resources": "^2.10.0",
908
+ "@opentelemetry/sdk-logs": "^0.221.0",
909
+ "@opentelemetry/sdk-metrics": "^2.10.0",
910
+ "@opentelemetry/sdk-trace-base": "^2.10.0",
911
+ "@opentelemetry/sdk-trace-node": "^2.10.0",
912
+ "@prisma/instrumentation": "^7.10.0"
913
+ },
914
+ devDependencies: {
915
+ "@nestjs/common": "^12.0.1",
916
+ "@nestjs/core": "^12.0.1",
917
+ "@nestjs/testing": "^12.0.1",
918
+ "@opentelemetry/sdk-trace-base": "^2.10.0",
919
+ "@types/node": "^24.0.0",
920
+ "reflect-metadata": "^0.2.2",
921
+ rxjs: "^7.8.2",
922
+ tsup: "^8.5.1",
923
+ typescript: "^5.9.3",
924
+ vitest: "^4.1.11"
925
+ },
926
+ publishConfig: {
927
+ access: "public"
928
+ }
929
+ };
930
+
931
+ // src/resource.ts
932
+ var SDK_NAME = package_default.name;
933
+ var SDK_VERSION = package_default.version;
699
934
  function createObserveResource(config) {
700
935
  return defaultResource().merge(
701
936
  resourceFromAttributes({
@@ -752,14 +987,23 @@ var SpanRedactionProcessor = class {
752
987
 
753
988
  // src/sdk.ts
754
989
  var InactiveObserveHandle = class {
755
- constructor(config) {
990
+ constructor(config, diagnostics) {
756
991
  this.config = config;
992
+ this.diagnostics = diagnostics;
757
993
  }
758
994
  config;
995
+ diagnostics;
759
996
  started = false;
760
997
  tracerProvider = void 0;
761
998
  meterProvider = void 0;
762
999
  loggerProvider = void 0;
1000
+ httpRequestMetrics = void 0;
1001
+ get status() {
1002
+ return this.diagnostics.status;
1003
+ }
1004
+ get lastError() {
1005
+ return this.diagnostics.lastError;
1006
+ }
763
1007
  forceFlush() {
764
1008
  return Promise.resolve();
765
1009
  }
@@ -768,52 +1012,73 @@ var InactiveObserveHandle = class {
768
1012
  }
769
1013
  };
770
1014
  var ActiveObserveHandle = class {
771
- constructor(config, tracerProvider, meterProvider, loggerProvider, runtimeMetrics, loggerInstrumentation, exceptionCapture, instrumentations) {
1015
+ constructor(config, tracerProvider, meterProvider, loggerProvider, httpRequestMetrics, runtimeMetrics, loggerInstrumentation, exceptionCapture, instrumentations, diagnostics) {
772
1016
  this.config = config;
773
1017
  this.tracerProvider = tracerProvider;
774
1018
  this.meterProvider = meterProvider;
775
1019
  this.loggerProvider = loggerProvider;
1020
+ this.httpRequestMetrics = httpRequestMetrics;
776
1021
  this.runtimeMetrics = runtimeMetrics;
777
1022
  this.loggerInstrumentation = loggerInstrumentation;
778
1023
  this.exceptionCapture = exceptionCapture;
779
1024
  this.instrumentations = instrumentations;
1025
+ this.diagnostics = diagnostics;
780
1026
  }
781
1027
  config;
782
1028
  tracerProvider;
783
1029
  meterProvider;
784
1030
  loggerProvider;
1031
+ httpRequestMetrics;
785
1032
  runtimeMetrics;
786
1033
  loggerInstrumentation;
787
1034
  exceptionCapture;
788
1035
  instrumentations;
1036
+ diagnostics;
789
1037
  started = true;
790
1038
  stopped = false;
1039
+ get status() {
1040
+ return this.diagnostics.status;
1041
+ }
1042
+ get lastError() {
1043
+ return this.diagnostics.lastError;
1044
+ }
791
1045
  async forceFlush() {
792
- await Promise.allSettled([
793
- this.tracerProvider?.forceFlush(),
794
- this.meterProvider?.forceFlush(),
795
- this.loggerProvider?.forceFlush({ timeoutMillis: this.config.exportTimeoutMillis })
796
- ].filter((item) => Boolean(item)));
1046
+ await this.runSafely("forceFlush", [
1047
+ ...this.config.traces && this.tracerProvider ? [["traces", () => this.tracerProvider.forceFlush()]] : [],
1048
+ ...this.config.metrics && this.meterProvider ? [["metrics", () => this.meterProvider.forceFlush()]] : [],
1049
+ ...this.config.logs && this.loggerProvider ? [["logs", () => this.loggerProvider.forceFlush({ timeoutMillis: this.config.exportTimeoutMillis })]] : []
1050
+ ]);
797
1051
  }
798
1052
  async shutdown() {
799
1053
  if (this.stopped) return;
800
1054
  this.stopped = true;
801
1055
  this.loggerInstrumentation?.disable();
802
- this.runtimeMetrics?.stop();
803
1056
  this.exceptionCapture.stop();
804
1057
  for (const instrumentation of this.instrumentations) {
805
1058
  try {
806
1059
  instrumentation.disable();
807
- } catch {
1060
+ } catch (error) {
1061
+ this.diagnostics.failure("sdk", "shutdown", error);
808
1062
  }
809
1063
  }
810
1064
  await this.forceFlush();
811
- await Promise.allSettled([
812
- this.tracerProvider?.shutdown(),
813
- this.meterProvider?.shutdown(),
814
- this.loggerProvider?.shutdown()
815
- ].filter((item) => Boolean(item)));
1065
+ this.runtimeMetrics?.stop();
1066
+ await this.runSafely("shutdown", [
1067
+ ...this.tracerProvider ? [["traces", () => this.tracerProvider.shutdown()]] : [],
1068
+ ...this.meterProvider ? [["metrics", () => this.meterProvider.shutdown()]] : [],
1069
+ ...this.loggerProvider ? [["logs", () => this.loggerProvider.shutdown()]] : []
1070
+ ]);
816
1071
  if (activeRuntime === this) activeRuntime = void 0;
1072
+ this.diagnostics.stop();
1073
+ }
1074
+ async runSafely(stage, operations) {
1075
+ await Promise.all(operations.map(async ([signal, operation]) => {
1076
+ try {
1077
+ await operation();
1078
+ } catch (error) {
1079
+ this.diagnostics.failure(signal, stage, error);
1080
+ }
1081
+ }));
817
1082
  }
818
1083
  };
819
1084
  var activeRuntime;
@@ -824,12 +1089,16 @@ function exporterConfig(url, config) {
824
1089
  timeoutMillis: config.exportTimeoutMillis
825
1090
  };
826
1091
  }
827
- function createTraceProvider(config, resource) {
1092
+ function createTraceProvider(config, resource, diagnostics) {
828
1093
  if (!config.traces && !config.metrics) return void 0;
829
1094
  const spanProcessors = [new SpanRedactionProcessor()];
830
1095
  if (config.traces) {
831
1096
  const exporter = config.exporters?.span ?? new OTLPTraceExporter(exporterConfig(config.endpoints.traces, config));
832
- spanProcessors.push(new BatchSpanProcessor(exporter, {
1097
+ spanProcessors.push(new BatchSpanProcessor(withExporterDiagnostics(
1098
+ exporter,
1099
+ "traces",
1100
+ diagnostics
1101
+ ), {
833
1102
  exportTimeoutMillis: config.exportTimeoutMillis,
834
1103
  maxQueueSize: 2048,
835
1104
  maxExportBatchSize: 512
@@ -844,14 +1113,18 @@ function createTraceProvider(config, resource) {
844
1113
  provider.register();
845
1114
  return provider;
846
1115
  }
847
- function createMeterProvider(config, resource) {
1116
+ function createMeterProvider(config, resource, diagnostics) {
848
1117
  if (!config.metrics) return void 0;
849
1118
  let reader;
850
1119
  if (config.exporters?.metricReader) {
851
1120
  reader = config.exporters.metricReader;
852
1121
  } else {
853
1122
  reader = new PeriodicExportingMetricReader({
854
- exporter: new OTLPMetricExporter(exporterConfig(config.endpoints.metrics, config)),
1123
+ exporter: withExporterDiagnostics(
1124
+ new OTLPMetricExporter(exporterConfig(config.endpoints.metrics, config)),
1125
+ "metrics",
1126
+ diagnostics
1127
+ ),
855
1128
  exportIntervalMillis: config.metricExportIntervalMillis,
856
1129
  exportTimeoutMillis: Math.min(config.exportTimeoutMillis, config.metricExportIntervalMillis),
857
1130
  cardinalityLimits: { default: 2e3, histogram: 2e3 }
@@ -869,13 +1142,13 @@ function createMeterProvider(config, resource) {
869
1142
  metrics.setGlobalMeterProvider(provider);
870
1143
  return provider;
871
1144
  }
872
- function createLoggerProvider(config, resource) {
1145
+ function createLoggerProvider(config, resource, diagnostics) {
873
1146
  if (!config.logs) return void 0;
874
1147
  const exporter = config.exporters?.log ?? new OTLPLogExporter(exporterConfig(config.endpoints.logs, config));
875
1148
  const provider = new LoggerProvider({
876
1149
  resource,
877
1150
  processors: [new BatchLogRecordProcessor({
878
- exporter,
1151
+ exporter: withExporterDiagnostics(exporter, "logs", diagnostics),
879
1152
  exportTimeoutMillis: config.exportTimeoutMillis,
880
1153
  maxQueueSize: 2048,
881
1154
  maxExportBatchSize: 512
@@ -887,7 +1160,11 @@ function createLoggerProvider(config, resource) {
887
1160
  function observe(options = {}) {
888
1161
  if (activeRuntime?.started) return activeRuntime;
889
1162
  const config = resolveObserveConfig(options);
890
- if (!config.enabled) return new InactiveObserveHandle(config);
1163
+ const diagnostics = new ObserveDiagnostics(config.diagnosticLogging, options.onError);
1164
+ if (!config.enabled) {
1165
+ diagnostics.inactive();
1166
+ return new InactiveObserveHandle(config, diagnostics);
1167
+ }
891
1168
  let tracerProvider;
892
1169
  let meterProvider;
893
1170
  let loggerProvider;
@@ -897,10 +1174,11 @@ function observe(options = {}) {
897
1174
  const instrumentations = [];
898
1175
  try {
899
1176
  const resource = createObserveResource(config);
900
- meterProvider = createMeterProvider(config, resource);
1177
+ meterProvider = createMeterProvider(config, resource, diagnostics);
901
1178
  const meter = meterProvider?.getMeter(SDK_NAME, SDK_VERSION);
902
- tracerProvider = createTraceProvider(config, resource);
903
- loggerProvider = createLoggerProvider(config, resource);
1179
+ const httpRequestMetrics = meter ? new HttpRequestMetrics(meter, config.serviceName) : void 0;
1180
+ tracerProvider = createTraceProvider(config, resource, diagnostics);
1181
+ loggerProvider = createLoggerProvider(config, resource, diagnostics);
904
1182
  runtimeMetrics = meter ? new RuntimeMetrics(meter) : void 0;
905
1183
  runtimeMetrics?.start();
906
1184
  const otelLogger = loggerProvider?.getLogger(SDK_NAME, SDK_VERSION);
@@ -915,7 +1193,6 @@ function observe(options = {}) {
915
1193
  exceptionCapture.start();
916
1194
  if (config.traces || config.metrics) {
917
1195
  const safeHeaders = config.allowedHeaders.filter((header) => !isSensitiveKey(header));
918
- const httpRequestMetrics = meter ? new HttpRequestMetrics(meter, config.serviceName) : void 0;
919
1196
  instrumentations.push(new HttpInstrumentation({
920
1197
  requireParentforOutgoingSpans: false,
921
1198
  headersToSpanAttributes: {
@@ -959,14 +1236,18 @@ function observe(options = {}) {
959
1236
  tracerProvider,
960
1237
  meterProvider,
961
1238
  loggerProvider,
1239
+ httpRequestMetrics,
962
1240
  runtimeMetrics,
963
1241
  loggerInstrumentation,
964
1242
  exceptionCapture,
965
- instrumentations
1243
+ instrumentations,
1244
+ diagnostics
966
1245
  );
1246
+ diagnostics.activate();
967
1247
  activeRuntime = runtime;
968
1248
  return runtime;
969
- } catch {
1249
+ } catch (error) {
1250
+ diagnostics.failure("sdk", "initialization", error);
970
1251
  loggerInstrumentation?.disable();
971
1252
  runtimeMetrics?.stop();
972
1253
  exceptionCapture?.stop();
@@ -981,7 +1262,9 @@ function observe(options = {}) {
981
1262
  meterProvider?.shutdown(),
982
1263
  loggerProvider?.shutdown()
983
1264
  ].filter((item) => Boolean(item)));
984
- return new InactiveObserveHandle(config);
1265
+ diagnostics.inactive();
1266
+ if (config.failFast) throw toError(error);
1267
+ return new InactiveObserveHandle(config, diagnostics);
985
1268
  }
986
1269
  }
987
1270
  function getObserveRuntime() {
@@ -991,6 +1274,37 @@ function getObserveRuntime() {
991
1274
  // src/nest/observe.module.ts
992
1275
  var OBSERVE_OPTIONS = /* @__PURE__ */ Symbol("OBSERVE_OPTIONS");
993
1276
  var OBSERVE_HANDLE = /* @__PURE__ */ Symbol("OBSERVE_HANDLE");
1277
+ var NestHttpMetricsInterceptor = class {
1278
+ constructor(handle) {
1279
+ this.handle = handle;
1280
+ }
1281
+ handle;
1282
+ intercept(context3, next) {
1283
+ if (context3.getType() !== "http" || !this.handle.started || !this.handle.httpRequestMetrics) {
1284
+ return next.handle();
1285
+ }
1286
+ const http = context3.switchToHttp();
1287
+ const request = http.getRequest();
1288
+ const response = http.getResponse();
1289
+ const started = this.handle.httpRequestMetrics.start(request);
1290
+ const result = next.handle();
1291
+ if (!started) return result;
1292
+ const responseEventSource = response.raw ?? response;
1293
+ if (typeof responseEventSource.once === "function") {
1294
+ responseEventSource.once("finish", () => {
1295
+ this.handle.httpRequestMetrics?.record(request, response);
1296
+ });
1297
+ return result;
1298
+ }
1299
+ return result.pipe(finalize2(() => {
1300
+ this.handle.httpRequestMetrics?.record(request, response);
1301
+ }));
1302
+ }
1303
+ };
1304
+ NestHttpMetricsInterceptor = __decorateClass([
1305
+ Injectable(),
1306
+ __decorateParam(0, Inject(OBSERVE_HANDLE))
1307
+ ], NestHttpMetricsInterceptor);
994
1308
  var NestObserveExplorer = class {
995
1309
  constructor(discovery, options, handle) {
996
1310
  this.discovery = discovery;
@@ -1015,7 +1329,7 @@ var NestObserveExplorer = class {
1015
1329
  }
1016
1330
  instrument(wrappers, instrumenter, kind) {
1017
1331
  for (const wrapper of wrappers) {
1018
- if (!wrapper.metatype) continue;
1332
+ if (!wrapper.metatype || wrapper.inject !== void 0) continue;
1019
1333
  if (["ObserveModule", "DiscoveryModule", "InternalCoreModule"].includes(wrapper.host?.name ?? "")) continue;
1020
1334
  if (wrapper.metatype === NestObserveExplorer) continue;
1021
1335
  if (wrapper.instance) {
@@ -1044,6 +1358,7 @@ var ObserveModule = class {
1044
1358
  providers: [
1045
1359
  { provide: OBSERVE_OPTIONS, useValue: options },
1046
1360
  { provide: OBSERVE_HANDLE, useFactory: () => observe(options) },
1361
+ { provide: APP_INTERCEPTOR, useClass: NestHttpMetricsInterceptor },
1047
1362
  NestObserveExplorer
1048
1363
  ],
1049
1364
  exports: [OBSERVE_HANDLE]
@@ -1058,6 +1373,7 @@ export {
1058
1373
  CompatibleNestInstrumentation,
1059
1374
  HttpRequestMetrics,
1060
1375
  IgnoreTrace,
1376
+ NestHttpMetricsInterceptor,
1061
1377
  NestLoggerInstrumentation,
1062
1378
  NestMethodInstrumenter,
1063
1379
  OBSERVE_HANDLE,