@ryanzeng/nest-observe 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -32,6 +32,7 @@ __export(index_exports, {
32
32
  CompatibleNestInstrumentation: () => CompatibleNestInstrumentation,
33
33
  HttpRequestMetrics: () => HttpRequestMetrics,
34
34
  IgnoreTrace: () => IgnoreTrace,
35
+ NestHttpMetricsInterceptor: () => NestHttpMetricsInterceptor,
35
36
  NestLoggerInstrumentation: () => NestLoggerInstrumentation,
36
37
  NestMethodInstrumenter: () => NestMethodInstrumenter,
37
38
  OBSERVE_HANDLE: () => OBSERVE_HANDLE,
@@ -139,7 +140,9 @@ function resolveObserveConfig(options = {}, env = process.env) {
139
140
  allowedHeaders,
140
141
  exportTimeoutMillis: positiveInteger(options.exportTimeoutMillis ?? env.OTEL_EXPORTER_OTLP_TIMEOUT, 1e4),
141
142
  metricExportIntervalMillis: positiveInteger(options.metricExportIntervalMillis ?? env.OTEL_METRIC_EXPORT_INTERVAL, 6e4),
142
- resourceAttributes
143
+ resourceAttributes,
144
+ diagnosticLogging: booleanValue(options.diagnosticLogging, true),
145
+ failFast: booleanValue(options.failFast, false)
143
146
  };
144
147
  if (options.exporters) result.exporters = options.exporters;
145
148
  return result;
@@ -335,7 +338,13 @@ var SEVERITY = {
335
338
  };
336
339
  function bodyValue(message) {
337
340
  if (message instanceof Error) return redactText(message.stack ?? `${message.name}: ${message.message}`);
338
- return redact(message);
341
+ const sanitized = redact(message);
342
+ if (sanitized === null || typeof sanitized !== "object") return sanitized;
343
+ try {
344
+ return JSON.stringify(sanitized, (_key, value) => typeof value === "bigint" ? value.toString() : value);
345
+ } catch {
346
+ return redactText(String(sanitized));
347
+ }
339
348
  }
340
349
  var NestLoggerInstrumentation = class {
341
350
  constructor(emitter, resourceAttributes = {}) {
@@ -569,9 +578,14 @@ var HttpRequestMetrics = class {
569
578
  errorCount;
570
579
  startedAt = /* @__PURE__ */ new WeakMap();
571
580
  start(request) {
581
+ if (this.startedAt.has(request)) return false;
572
582
  this.startedAt.set(request, process.hrtime.bigint());
583
+ return true;
573
584
  }
574
585
  record(request, response) {
586
+ const started = this.startedAt.get(request);
587
+ if (started === void 0) return false;
588
+ this.startedAt.delete(request);
575
589
  const statusCode = Number.isFinite(response.statusCode) ? response.statusCode : 0;
576
590
  const attributes = {
577
591
  "service.name": this.serviceName,
@@ -580,12 +594,9 @@ var HttpRequestMetrics = class {
580
594
  "http.response.status_code": statusCode
581
595
  };
582
596
  this.requestCount.add(1, attributes);
583
- const started = this.startedAt.get(request);
584
- if (started !== void 0) {
585
- this.requestDuration.record(Number(process.hrtime.bigint() - started) / 1e9, attributes);
586
- this.startedAt.delete(request);
587
- }
597
+ this.requestDuration.record(Number(process.hrtime.bigint() - started) / 1e9, attributes);
588
598
  if (statusCode >= 500) this.errorCount.add(1, attributes);
599
+ return true;
589
600
  }
590
601
  };
591
602
 
@@ -674,6 +685,7 @@ var RuntimeMetrics = class {
674
685
  var import_common2 = require("@nestjs/common");
675
686
  var import_core = require("@nestjs/core");
676
687
  var import_api7 = require("@opentelemetry/api");
688
+ var import_rxjs2 = require("rxjs");
677
689
 
678
690
  // src/sdk.ts
679
691
  var import_api_logs2 = require("@opentelemetry/api-logs");
@@ -689,6 +701,113 @@ var import_sdk_metrics = require("@opentelemetry/sdk-metrics");
689
701
  var import_sdk_trace_base = require("@opentelemetry/sdk-trace-base");
690
702
  var import_sdk_trace_node = require("@opentelemetry/sdk-trace-node");
691
703
 
704
+ // src/diagnostics.ts
705
+ function toError(value) {
706
+ if (value instanceof Error) return value;
707
+ if (typeof value === "string") return new Error(value);
708
+ try {
709
+ return new Error(JSON.stringify(value));
710
+ } catch {
711
+ return new Error(String(value));
712
+ }
713
+ }
714
+ function sanitizeError(error) {
715
+ const sanitized = new Error(redactText(error.message));
716
+ sanitized.name = error.name;
717
+ if (error.stack) sanitized.stack = redactText(error.stack);
718
+ return sanitized;
719
+ }
720
+ var ObserveDiagnostics = class {
721
+ constructor(logging, onError) {
722
+ this.logging = logging;
723
+ this.onError = onError;
724
+ }
725
+ logging;
726
+ onError;
727
+ currentStatus = "starting";
728
+ currentError;
729
+ failedSignals = /* @__PURE__ */ new Set();
730
+ reported = /* @__PURE__ */ new Set();
731
+ get status() {
732
+ return this.currentStatus;
733
+ }
734
+ get lastError() {
735
+ return this.currentError;
736
+ }
737
+ activate() {
738
+ if (this.currentStatus === "starting") this.currentStatus = "active";
739
+ }
740
+ inactive() {
741
+ this.currentStatus = "inactive";
742
+ }
743
+ stop() {
744
+ this.currentStatus = "stopped";
745
+ }
746
+ success(signal) {
747
+ this.failedSignals.delete(signal);
748
+ for (const fingerprint of this.reported) {
749
+ if (fingerprint.startsWith(`${signal}\0`)) this.reported.delete(fingerprint);
750
+ }
751
+ if (this.currentStatus === "degraded" && this.failedSignals.size === 0) {
752
+ this.currentStatus = "active";
753
+ }
754
+ }
755
+ failure(signal, stage, value) {
756
+ const error = sanitizeError(toError(value));
757
+ const event = { signal, stage, error, timestamp: Date.now() };
758
+ this.currentError = event;
759
+ this.failedSignals.add(signal);
760
+ if (this.currentStatus !== "inactive" && this.currentStatus !== "stopped") {
761
+ this.currentStatus = "degraded";
762
+ }
763
+ const fingerprint = `${signal}\0${stage}\0${error.name}\0${error.message}`;
764
+ if (this.reported.has(fingerprint)) return;
765
+ this.reported.add(fingerprint);
766
+ try {
767
+ this.onError?.(event);
768
+ } catch {
769
+ }
770
+ if (!this.logging) return;
771
+ try {
772
+ process.stderr.write(
773
+ `[nest-observe] ${signal} ${stage} failed: ${redactText(error.message)}
774
+ `
775
+ );
776
+ } catch {
777
+ }
778
+ }
779
+ };
780
+ function withExporterDiagnostics(exporter, signal, diagnostics) {
781
+ const delegate = exporter;
782
+ return new Proxy(exporter, {
783
+ get(target, property) {
784
+ if (property === "export") {
785
+ return (items, callback) => {
786
+ try {
787
+ delegate.export.call(target, items, (result) => {
788
+ if (result.code === 0) {
789
+ diagnostics.success(signal);
790
+ } else {
791
+ diagnostics.failure(
792
+ signal,
793
+ "export",
794
+ result.error ?? new Error(`${signal} export failed`)
795
+ );
796
+ }
797
+ callback(result);
798
+ });
799
+ } catch (error) {
800
+ diagnostics.failure(signal, "export", error);
801
+ throw error;
802
+ }
803
+ };
804
+ }
805
+ const value = Reflect.get(target, property, target);
806
+ return typeof value === "function" ? value.bind(target) : value;
807
+ }
808
+ });
809
+ }
810
+
692
811
  // src/exceptions/process-exception-capture.ts
693
812
  var import_api5 = require("@opentelemetry/api");
694
813
  function asError(value) {
@@ -738,8 +857,118 @@ var ProcessExceptionCapture = class {
738
857
  // src/resource.ts
739
858
  var import_resources = require("@opentelemetry/resources");
740
859
  var import_node_os2 = require("os");
741
- var SDK_NAME = "@ryanzeng/nest-observe";
742
- var SDK_VERSION = "0.1.0";
860
+
861
+ // package.json
862
+ var package_default = {
863
+ name: "@ryanzeng/nest-observe",
864
+ version: "0.1.2",
865
+ description: "Zero-config, vendor-neutral OpenTelemetry observability for NestJS",
866
+ keywords: [
867
+ "nestjs",
868
+ "opentelemetry",
869
+ "observability",
870
+ "otlp",
871
+ "tracing",
872
+ "metrics",
873
+ "logging"
874
+ ],
875
+ license: "MIT",
876
+ homepage: "https://github.com/ryanzen9/nest-observe#readme",
877
+ bugs: {
878
+ url: "https://github.com/ryanzen9/nest-observe/issues"
879
+ },
880
+ repository: {
881
+ type: "git",
882
+ url: "git+https://github.com/ryanzen9/nest-observe.git"
883
+ },
884
+ packageManager: "pnpm@10.13.1",
885
+ sideEffects: [
886
+ "./dist/register.js",
887
+ "./dist/register.mjs"
888
+ ],
889
+ main: "./dist/index.js",
890
+ module: "./dist/index.mjs",
891
+ types: "./dist/index.d.ts",
892
+ exports: {
893
+ ".": {
894
+ import: {
895
+ types: "./dist/index.d.mts",
896
+ default: "./dist/index.mjs"
897
+ },
898
+ require: {
899
+ types: "./dist/index.d.ts",
900
+ default: "./dist/index.js"
901
+ }
902
+ },
903
+ "./register": {
904
+ import: {
905
+ types: "./dist/register.d.mts",
906
+ default: "./dist/register.mjs"
907
+ },
908
+ require: {
909
+ types: "./dist/register.d.ts",
910
+ default: "./dist/register.js"
911
+ }
912
+ }
913
+ },
914
+ files: [
915
+ "dist",
916
+ "README.md",
917
+ "CHANGELOG.md",
918
+ "LICENSE"
919
+ ],
920
+ scripts: {
921
+ build: "tsup",
922
+ test: "vitest run",
923
+ "test:watch": "vitest",
924
+ typecheck: "tsc --noEmit",
925
+ check: "pnpm typecheck && pnpm test && pnpm build"
926
+ },
927
+ engines: {
928
+ node: ">=20"
929
+ },
930
+ peerDependencies: {
931
+ "@nestjs/common": ">=10 <13",
932
+ "@nestjs/core": ">=10 <13",
933
+ "reflect-metadata": ">=0.1.12 <1",
934
+ rxjs: "^7.1.0"
935
+ },
936
+ dependencies: {
937
+ "@opentelemetry/api": "^1.9.0",
938
+ "@opentelemetry/api-logs": "^0.221.0",
939
+ "@opentelemetry/exporter-logs-otlp-proto": "^0.221.0",
940
+ "@opentelemetry/exporter-metrics-otlp-proto": "^0.221.0",
941
+ "@opentelemetry/exporter-trace-otlp-proto": "^0.221.0",
942
+ "@opentelemetry/instrumentation": "^0.221.0",
943
+ "@opentelemetry/instrumentation-http": "^0.221.0",
944
+ "@opentelemetry/instrumentation-nestjs-core": "^0.67.0",
945
+ "@opentelemetry/resources": "^2.10.0",
946
+ "@opentelemetry/sdk-logs": "^0.221.0",
947
+ "@opentelemetry/sdk-metrics": "^2.10.0",
948
+ "@opentelemetry/sdk-trace-base": "^2.10.0",
949
+ "@opentelemetry/sdk-trace-node": "^2.10.0",
950
+ "@prisma/instrumentation": "^7.10.0"
951
+ },
952
+ devDependencies: {
953
+ "@nestjs/common": "^12.0.1",
954
+ "@nestjs/core": "^12.0.1",
955
+ "@nestjs/testing": "^12.0.1",
956
+ "@opentelemetry/sdk-trace-base": "^2.10.0",
957
+ "@types/node": "^24.0.0",
958
+ "reflect-metadata": "^0.2.2",
959
+ rxjs: "^7.8.2",
960
+ tsup: "^8.5.1",
961
+ typescript: "^5.9.3",
962
+ vitest: "^4.1.11"
963
+ },
964
+ publishConfig: {
965
+ access: "public"
966
+ }
967
+ };
968
+
969
+ // src/resource.ts
970
+ var SDK_NAME = package_default.name;
971
+ var SDK_VERSION = package_default.version;
743
972
  function createObserveResource(config) {
744
973
  return (0, import_resources.defaultResource)().merge(
745
974
  (0, import_resources.resourceFromAttributes)({
@@ -796,14 +1025,23 @@ var SpanRedactionProcessor = class {
796
1025
 
797
1026
  // src/sdk.ts
798
1027
  var InactiveObserveHandle = class {
799
- constructor(config) {
1028
+ constructor(config, diagnostics) {
800
1029
  this.config = config;
1030
+ this.diagnostics = diagnostics;
801
1031
  }
802
1032
  config;
1033
+ diagnostics;
803
1034
  started = false;
804
1035
  tracerProvider = void 0;
805
1036
  meterProvider = void 0;
806
1037
  loggerProvider = void 0;
1038
+ httpRequestMetrics = void 0;
1039
+ get status() {
1040
+ return this.diagnostics.status;
1041
+ }
1042
+ get lastError() {
1043
+ return this.diagnostics.lastError;
1044
+ }
807
1045
  forceFlush() {
808
1046
  return Promise.resolve();
809
1047
  }
@@ -812,52 +1050,73 @@ var InactiveObserveHandle = class {
812
1050
  }
813
1051
  };
814
1052
  var ActiveObserveHandle = class {
815
- constructor(config, tracerProvider, meterProvider, loggerProvider, runtimeMetrics, loggerInstrumentation, exceptionCapture, instrumentations) {
1053
+ constructor(config, tracerProvider, meterProvider, loggerProvider, httpRequestMetrics, runtimeMetrics, loggerInstrumentation, exceptionCapture, instrumentations, diagnostics) {
816
1054
  this.config = config;
817
1055
  this.tracerProvider = tracerProvider;
818
1056
  this.meterProvider = meterProvider;
819
1057
  this.loggerProvider = loggerProvider;
1058
+ this.httpRequestMetrics = httpRequestMetrics;
820
1059
  this.runtimeMetrics = runtimeMetrics;
821
1060
  this.loggerInstrumentation = loggerInstrumentation;
822
1061
  this.exceptionCapture = exceptionCapture;
823
1062
  this.instrumentations = instrumentations;
1063
+ this.diagnostics = diagnostics;
824
1064
  }
825
1065
  config;
826
1066
  tracerProvider;
827
1067
  meterProvider;
828
1068
  loggerProvider;
1069
+ httpRequestMetrics;
829
1070
  runtimeMetrics;
830
1071
  loggerInstrumentation;
831
1072
  exceptionCapture;
832
1073
  instrumentations;
1074
+ diagnostics;
833
1075
  started = true;
834
1076
  stopped = false;
1077
+ get status() {
1078
+ return this.diagnostics.status;
1079
+ }
1080
+ get lastError() {
1081
+ return this.diagnostics.lastError;
1082
+ }
835
1083
  async forceFlush() {
836
- await Promise.allSettled([
837
- this.tracerProvider?.forceFlush(),
838
- this.meterProvider?.forceFlush(),
839
- this.loggerProvider?.forceFlush({ timeoutMillis: this.config.exportTimeoutMillis })
840
- ].filter((item) => Boolean(item)));
1084
+ await this.runSafely("forceFlush", [
1085
+ ...this.config.traces && this.tracerProvider ? [["traces", () => this.tracerProvider.forceFlush()]] : [],
1086
+ ...this.config.metrics && this.meterProvider ? [["metrics", () => this.meterProvider.forceFlush()]] : [],
1087
+ ...this.config.logs && this.loggerProvider ? [["logs", () => this.loggerProvider.forceFlush({ timeoutMillis: this.config.exportTimeoutMillis })]] : []
1088
+ ]);
841
1089
  }
842
1090
  async shutdown() {
843
1091
  if (this.stopped) return;
844
1092
  this.stopped = true;
845
1093
  this.loggerInstrumentation?.disable();
846
- this.runtimeMetrics?.stop();
847
1094
  this.exceptionCapture.stop();
848
1095
  for (const instrumentation of this.instrumentations) {
849
1096
  try {
850
1097
  instrumentation.disable();
851
- } catch {
1098
+ } catch (error) {
1099
+ this.diagnostics.failure("sdk", "shutdown", error);
852
1100
  }
853
1101
  }
854
1102
  await this.forceFlush();
855
- await Promise.allSettled([
856
- this.tracerProvider?.shutdown(),
857
- this.meterProvider?.shutdown(),
858
- this.loggerProvider?.shutdown()
859
- ].filter((item) => Boolean(item)));
1103
+ this.runtimeMetrics?.stop();
1104
+ await this.runSafely("shutdown", [
1105
+ ...this.tracerProvider ? [["traces", () => this.tracerProvider.shutdown()]] : [],
1106
+ ...this.meterProvider ? [["metrics", () => this.meterProvider.shutdown()]] : [],
1107
+ ...this.loggerProvider ? [["logs", () => this.loggerProvider.shutdown()]] : []
1108
+ ]);
860
1109
  if (activeRuntime === this) activeRuntime = void 0;
1110
+ this.diagnostics.stop();
1111
+ }
1112
+ async runSafely(stage, operations) {
1113
+ await Promise.all(operations.map(async ([signal, operation]) => {
1114
+ try {
1115
+ await operation();
1116
+ } catch (error) {
1117
+ this.diagnostics.failure(signal, stage, error);
1118
+ }
1119
+ }));
861
1120
  }
862
1121
  };
863
1122
  var activeRuntime;
@@ -868,12 +1127,16 @@ function exporterConfig(url, config) {
868
1127
  timeoutMillis: config.exportTimeoutMillis
869
1128
  };
870
1129
  }
871
- function createTraceProvider(config, resource) {
1130
+ function createTraceProvider(config, resource, diagnostics) {
872
1131
  if (!config.traces && !config.metrics) return void 0;
873
1132
  const spanProcessors = [new SpanRedactionProcessor()];
874
1133
  if (config.traces) {
875
1134
  const exporter = config.exporters?.span ?? new import_exporter_trace_otlp_proto.OTLPTraceExporter(exporterConfig(config.endpoints.traces, config));
876
- spanProcessors.push(new import_sdk_trace_base.BatchSpanProcessor(exporter, {
1135
+ spanProcessors.push(new import_sdk_trace_base.BatchSpanProcessor(withExporterDiagnostics(
1136
+ exporter,
1137
+ "traces",
1138
+ diagnostics
1139
+ ), {
877
1140
  exportTimeoutMillis: config.exportTimeoutMillis,
878
1141
  maxQueueSize: 2048,
879
1142
  maxExportBatchSize: 512
@@ -888,14 +1151,18 @@ function createTraceProvider(config, resource) {
888
1151
  provider.register();
889
1152
  return provider;
890
1153
  }
891
- function createMeterProvider(config, resource) {
1154
+ function createMeterProvider(config, resource, diagnostics) {
892
1155
  if (!config.metrics) return void 0;
893
1156
  let reader;
894
1157
  if (config.exporters?.metricReader) {
895
1158
  reader = config.exporters.metricReader;
896
1159
  } else {
897
1160
  reader = new import_sdk_metrics.PeriodicExportingMetricReader({
898
- exporter: new import_exporter_metrics_otlp_proto.OTLPMetricExporter(exporterConfig(config.endpoints.metrics, config)),
1161
+ exporter: withExporterDiagnostics(
1162
+ new import_exporter_metrics_otlp_proto.OTLPMetricExporter(exporterConfig(config.endpoints.metrics, config)),
1163
+ "metrics",
1164
+ diagnostics
1165
+ ),
899
1166
  exportIntervalMillis: config.metricExportIntervalMillis,
900
1167
  exportTimeoutMillis: Math.min(config.exportTimeoutMillis, config.metricExportIntervalMillis),
901
1168
  cardinalityLimits: { default: 2e3, histogram: 2e3 }
@@ -913,13 +1180,13 @@ function createMeterProvider(config, resource) {
913
1180
  import_api6.metrics.setGlobalMeterProvider(provider);
914
1181
  return provider;
915
1182
  }
916
- function createLoggerProvider(config, resource) {
1183
+ function createLoggerProvider(config, resource, diagnostics) {
917
1184
  if (!config.logs) return void 0;
918
1185
  const exporter = config.exporters?.log ?? new import_exporter_logs_otlp_proto.OTLPLogExporter(exporterConfig(config.endpoints.logs, config));
919
1186
  const provider = new import_sdk_logs.LoggerProvider({
920
1187
  resource,
921
1188
  processors: [new import_sdk_logs.BatchLogRecordProcessor({
922
- exporter,
1189
+ exporter: withExporterDiagnostics(exporter, "logs", diagnostics),
923
1190
  exportTimeoutMillis: config.exportTimeoutMillis,
924
1191
  maxQueueSize: 2048,
925
1192
  maxExportBatchSize: 512
@@ -931,7 +1198,11 @@ function createLoggerProvider(config, resource) {
931
1198
  function observe(options = {}) {
932
1199
  if (activeRuntime?.started) return activeRuntime;
933
1200
  const config = resolveObserveConfig(options);
934
- if (!config.enabled) return new InactiveObserveHandle(config);
1201
+ const diagnostics = new ObserveDiagnostics(config.diagnosticLogging, options.onError);
1202
+ if (!config.enabled) {
1203
+ diagnostics.inactive();
1204
+ return new InactiveObserveHandle(config, diagnostics);
1205
+ }
935
1206
  let tracerProvider;
936
1207
  let meterProvider;
937
1208
  let loggerProvider;
@@ -941,10 +1212,11 @@ function observe(options = {}) {
941
1212
  const instrumentations = [];
942
1213
  try {
943
1214
  const resource = createObserveResource(config);
944
- meterProvider = createMeterProvider(config, resource);
1215
+ meterProvider = createMeterProvider(config, resource, diagnostics);
945
1216
  const meter = meterProvider?.getMeter(SDK_NAME, SDK_VERSION);
946
- tracerProvider = createTraceProvider(config, resource);
947
- loggerProvider = createLoggerProvider(config, resource);
1217
+ const httpRequestMetrics = meter ? new HttpRequestMetrics(meter, config.serviceName) : void 0;
1218
+ tracerProvider = createTraceProvider(config, resource, diagnostics);
1219
+ loggerProvider = createLoggerProvider(config, resource, diagnostics);
948
1220
  runtimeMetrics = meter ? new RuntimeMetrics(meter) : void 0;
949
1221
  runtimeMetrics?.start();
950
1222
  const otelLogger = loggerProvider?.getLogger(SDK_NAME, SDK_VERSION);
@@ -959,7 +1231,6 @@ function observe(options = {}) {
959
1231
  exceptionCapture.start();
960
1232
  if (config.traces || config.metrics) {
961
1233
  const safeHeaders = config.allowedHeaders.filter((header) => !isSensitiveKey(header));
962
- const httpRequestMetrics = meter ? new HttpRequestMetrics(meter, config.serviceName) : void 0;
963
1234
  instrumentations.push(new import_instrumentation_http.HttpInstrumentation({
964
1235
  requireParentforOutgoingSpans: false,
965
1236
  headersToSpanAttributes: {
@@ -1003,14 +1274,18 @@ function observe(options = {}) {
1003
1274
  tracerProvider,
1004
1275
  meterProvider,
1005
1276
  loggerProvider,
1277
+ httpRequestMetrics,
1006
1278
  runtimeMetrics,
1007
1279
  loggerInstrumentation,
1008
1280
  exceptionCapture,
1009
- instrumentations
1281
+ instrumentations,
1282
+ diagnostics
1010
1283
  );
1284
+ diagnostics.activate();
1011
1285
  activeRuntime = runtime;
1012
1286
  return runtime;
1013
- } catch {
1287
+ } catch (error) {
1288
+ diagnostics.failure("sdk", "initialization", error);
1014
1289
  loggerInstrumentation?.disable();
1015
1290
  runtimeMetrics?.stop();
1016
1291
  exceptionCapture?.stop();
@@ -1025,7 +1300,9 @@ function observe(options = {}) {
1025
1300
  meterProvider?.shutdown(),
1026
1301
  loggerProvider?.shutdown()
1027
1302
  ].filter((item) => Boolean(item)));
1028
- return new InactiveObserveHandle(config);
1303
+ diagnostics.inactive();
1304
+ if (config.failFast) throw toError(error);
1305
+ return new InactiveObserveHandle(config, diagnostics);
1029
1306
  }
1030
1307
  }
1031
1308
  function getObserveRuntime() {
@@ -1035,6 +1312,37 @@ function getObserveRuntime() {
1035
1312
  // src/nest/observe.module.ts
1036
1313
  var OBSERVE_OPTIONS = /* @__PURE__ */ Symbol("OBSERVE_OPTIONS");
1037
1314
  var OBSERVE_HANDLE = /* @__PURE__ */ Symbol("OBSERVE_HANDLE");
1315
+ var NestHttpMetricsInterceptor = class {
1316
+ constructor(handle) {
1317
+ this.handle = handle;
1318
+ }
1319
+ handle;
1320
+ intercept(context3, next) {
1321
+ if (context3.getType() !== "http" || !this.handle.started || !this.handle.httpRequestMetrics) {
1322
+ return next.handle();
1323
+ }
1324
+ const http = context3.switchToHttp();
1325
+ const request = http.getRequest();
1326
+ const response = http.getResponse();
1327
+ const started = this.handle.httpRequestMetrics.start(request);
1328
+ const result = next.handle();
1329
+ if (!started) return result;
1330
+ const responseEventSource = response.raw ?? response;
1331
+ if (typeof responseEventSource.once === "function") {
1332
+ responseEventSource.once("finish", () => {
1333
+ this.handle.httpRequestMetrics?.record(request, response);
1334
+ });
1335
+ return result;
1336
+ }
1337
+ return result.pipe((0, import_rxjs2.finalize)(() => {
1338
+ this.handle.httpRequestMetrics?.record(request, response);
1339
+ }));
1340
+ }
1341
+ };
1342
+ NestHttpMetricsInterceptor = __decorateClass([
1343
+ (0, import_common2.Injectable)(),
1344
+ __decorateParam(0, (0, import_common2.Inject)(OBSERVE_HANDLE))
1345
+ ], NestHttpMetricsInterceptor);
1038
1346
  var NestObserveExplorer = class {
1039
1347
  constructor(discovery, options, handle) {
1040
1348
  this.discovery = discovery;
@@ -1088,6 +1396,7 @@ var ObserveModule = class {
1088
1396
  providers: [
1089
1397
  { provide: OBSERVE_OPTIONS, useValue: options },
1090
1398
  { provide: OBSERVE_HANDLE, useFactory: () => observe(options) },
1399
+ { provide: import_core.APP_INTERCEPTOR, useClass: NestHttpMetricsInterceptor },
1091
1400
  NestObserveExplorer
1092
1401
  ],
1093
1402
  exports: [OBSERVE_HANDLE]
@@ -1103,6 +1412,7 @@ ObserveModule = __decorateClass([
1103
1412
  CompatibleNestInstrumentation,
1104
1413
  HttpRequestMetrics,
1105
1414
  IgnoreTrace,
1415
+ NestHttpMetricsInterceptor,
1106
1416
  NestLoggerInstrumentation,
1107
1417
  NestMethodInstrumenter,
1108
1418
  OBSERVE_HANDLE,