@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.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;
@@ -230,6 +233,11 @@ function invokeWithSpan(tracer, spanName, attributes, invoke, onFinish) {
230
233
  }
231
234
 
232
235
  // src/decorators/trace.decorator.ts
236
+ function copyOwnMetadata(source, target) {
237
+ for (const metadataKey of Reflect.getOwnMetadataKeys(source)) {
238
+ Reflect.defineMetadata(metadataKey, Reflect.getOwnMetadata(metadataKey, source), target);
239
+ }
240
+ }
233
241
  function decorateMethod(target, propertyKey, descriptor, options) {
234
242
  const original = descriptor.value;
235
243
  if (typeof original !== "function" || isTraceIgnored(original, target.constructor))
@@ -254,6 +262,7 @@ function decorateMethod(target, propertyKey, descriptor, options) {
254
262
  value: original.name,
255
263
  configurable: true
256
264
  });
265
+ copyOwnMetadata(original, wrapped);
257
266
  markTraceDecorated(wrapped);
258
267
  descriptor.value = wrapped;
259
268
  }
@@ -334,7 +343,8 @@ var SEVERITY = {
334
343
  fatal: "FATAL"
335
344
  };
336
345
  function bodyValue(message) {
337
- if (message instanceof Error) return redactText(message.stack ?? `${message.name}: ${message.message}`);
346
+ if (message instanceof Error)
347
+ return redactText(message.stack ?? `${message.name}: ${message.message}`);
338
348
  return redact(message);
339
349
  }
340
350
  var NestLoggerInstrumentation = class {
@@ -379,7 +389,8 @@ var NestLoggerInstrumentation = class {
379
389
  disable() {
380
390
  if (!this.enabled) return;
381
391
  const prototype = import_common.ConsoleLogger.prototype;
382
- for (const [method, original] of this.originals) prototype[method] = original;
392
+ for (const [method, original] of this.originals)
393
+ prototype[method] = original;
383
394
  this.originals.clear();
384
395
  this.enabled = false;
385
396
  }
@@ -455,7 +466,7 @@ var NestMethodInstrumenter = class {
455
466
  if (!descriptor || typeof original !== "function" || isTraceIgnored(original, type) || isTraceDecorated(original)) continue;
456
467
  const existing = Reflect.get(target, methodName);
457
468
  if (typeof existing === "function" && existing !== original && isTraceDecorated(existing)) continue;
458
- const name = componentName || type.name || "Anonymous";
469
+ const name = typeof componentName === "symbol" ? String(componentName) : typeof componentName === "string" && componentName ? componentName : type.name || "Anonymous";
459
470
  const attributes = {
460
471
  [`nestjs.${kind}`]: name,
461
472
  "nestjs.method": methodName
@@ -508,7 +519,7 @@ var CompatibleNestInstrumentation = class extends import_instrumentation_nestjs_
508
519
  const instance = await original.apply(this, args);
509
520
  try {
510
521
  const wrapper = args[1];
511
- if (!instance || typeof instance !== "object" || !wrapper?.metatype) return instance;
522
+ if (!instance || typeof instance !== "object" || !wrapper?.metatype || wrapper.inject !== void 0) return instance;
512
523
  if (["InternalCoreModule", "ObserveModule", "DiscoveryModule"].includes(wrapper.host?.name ?? "")) {
513
524
  return instance;
514
525
  }
@@ -569,9 +580,14 @@ var HttpRequestMetrics = class {
569
580
  errorCount;
570
581
  startedAt = /* @__PURE__ */ new WeakMap();
571
582
  start(request) {
583
+ if (this.startedAt.has(request)) return false;
572
584
  this.startedAt.set(request, process.hrtime.bigint());
585
+ return true;
573
586
  }
574
587
  record(request, response) {
588
+ const started = this.startedAt.get(request);
589
+ if (started === void 0) return false;
590
+ this.startedAt.delete(request);
575
591
  const statusCode = Number.isFinite(response.statusCode) ? response.statusCode : 0;
576
592
  const attributes = {
577
593
  "service.name": this.serviceName,
@@ -580,12 +596,9 @@ var HttpRequestMetrics = class {
580
596
  "http.response.status_code": statusCode
581
597
  };
582
598
  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
- }
599
+ this.requestDuration.record(Number(process.hrtime.bigint() - started) / 1e9, attributes);
588
600
  if (statusCode >= 500) this.errorCount.add(1, attributes);
601
+ return true;
589
602
  }
590
603
  };
591
604
 
@@ -674,6 +687,7 @@ var RuntimeMetrics = class {
674
687
  var import_common2 = require("@nestjs/common");
675
688
  var import_core = require("@nestjs/core");
676
689
  var import_api7 = require("@opentelemetry/api");
690
+ var import_rxjs2 = require("rxjs");
677
691
 
678
692
  // src/sdk.ts
679
693
  var import_api_logs2 = require("@opentelemetry/api-logs");
@@ -689,6 +703,113 @@ var import_sdk_metrics = require("@opentelemetry/sdk-metrics");
689
703
  var import_sdk_trace_base = require("@opentelemetry/sdk-trace-base");
690
704
  var import_sdk_trace_node = require("@opentelemetry/sdk-trace-node");
691
705
 
706
+ // src/diagnostics.ts
707
+ function toError(value) {
708
+ if (value instanceof Error) return value;
709
+ if (typeof value === "string") return new Error(value);
710
+ try {
711
+ return new Error(JSON.stringify(value));
712
+ } catch {
713
+ return new Error(String(value));
714
+ }
715
+ }
716
+ function sanitizeError(error) {
717
+ const sanitized = new Error(redactText(error.message));
718
+ sanitized.name = error.name;
719
+ if (error.stack) sanitized.stack = redactText(error.stack);
720
+ return sanitized;
721
+ }
722
+ var ObserveDiagnostics = class {
723
+ constructor(logging, onError) {
724
+ this.logging = logging;
725
+ this.onError = onError;
726
+ }
727
+ logging;
728
+ onError;
729
+ currentStatus = "starting";
730
+ currentError;
731
+ failedSignals = /* @__PURE__ */ new Set();
732
+ reported = /* @__PURE__ */ new Set();
733
+ get status() {
734
+ return this.currentStatus;
735
+ }
736
+ get lastError() {
737
+ return this.currentError;
738
+ }
739
+ activate() {
740
+ if (this.currentStatus === "starting") this.currentStatus = "active";
741
+ }
742
+ inactive() {
743
+ this.currentStatus = "inactive";
744
+ }
745
+ stop() {
746
+ this.currentStatus = "stopped";
747
+ }
748
+ success(signal) {
749
+ this.failedSignals.delete(signal);
750
+ for (const fingerprint of this.reported) {
751
+ if (fingerprint.startsWith(`${signal}\0`)) this.reported.delete(fingerprint);
752
+ }
753
+ if (this.currentStatus === "degraded" && this.failedSignals.size === 0) {
754
+ this.currentStatus = "active";
755
+ }
756
+ }
757
+ failure(signal, stage, value) {
758
+ const error = sanitizeError(toError(value));
759
+ const event = { signal, stage, error, timestamp: Date.now() };
760
+ this.currentError = event;
761
+ this.failedSignals.add(signal);
762
+ if (this.currentStatus !== "inactive" && this.currentStatus !== "stopped") {
763
+ this.currentStatus = "degraded";
764
+ }
765
+ const fingerprint = `${signal}\0${stage}\0${error.name}\0${error.message}`;
766
+ if (this.reported.has(fingerprint)) return;
767
+ this.reported.add(fingerprint);
768
+ try {
769
+ this.onError?.(event);
770
+ } catch {
771
+ }
772
+ if (!this.logging) return;
773
+ try {
774
+ process.stderr.write(
775
+ `[nest-observe] ${signal} ${stage} failed: ${redactText(error.message)}
776
+ `
777
+ );
778
+ } catch {
779
+ }
780
+ }
781
+ };
782
+ function withExporterDiagnostics(exporter, signal, diagnostics) {
783
+ const delegate = exporter;
784
+ return new Proxy(exporter, {
785
+ get(target, property) {
786
+ if (property === "export") {
787
+ return (items, callback) => {
788
+ try {
789
+ delegate.export.call(target, items, (result) => {
790
+ if (result.code === 0) {
791
+ diagnostics.success(signal);
792
+ } else {
793
+ diagnostics.failure(
794
+ signal,
795
+ "export",
796
+ result.error ?? new Error(`${signal} export failed`)
797
+ );
798
+ }
799
+ callback(result);
800
+ });
801
+ } catch (error) {
802
+ diagnostics.failure(signal, "export", error);
803
+ throw error;
804
+ }
805
+ };
806
+ }
807
+ const value = Reflect.get(target, property, target);
808
+ return typeof value === "function" ? value.bind(target) : value;
809
+ }
810
+ });
811
+ }
812
+
692
813
  // src/exceptions/process-exception-capture.ts
693
814
  var import_api5 = require("@opentelemetry/api");
694
815
  function asError(value) {
@@ -738,8 +859,118 @@ var ProcessExceptionCapture = class {
738
859
  // src/resource.ts
739
860
  var import_resources = require("@opentelemetry/resources");
740
861
  var import_node_os2 = require("os");
741
- var SDK_NAME = "@ryanzeng/nest-observe";
742
- var SDK_VERSION = "0.1.0";
862
+
863
+ // package.json
864
+ var package_default = {
865
+ name: "@ryanzeng/nest-observe",
866
+ version: "0.1.3",
867
+ description: "Zero-config, vendor-neutral OpenTelemetry observability for NestJS",
868
+ keywords: [
869
+ "nestjs",
870
+ "opentelemetry",
871
+ "observability",
872
+ "otlp",
873
+ "tracing",
874
+ "metrics",
875
+ "logging"
876
+ ],
877
+ license: "MIT",
878
+ homepage: "https://github.com/ryanzen9/nest-observe#readme",
879
+ bugs: {
880
+ url: "https://github.com/ryanzen9/nest-observe/issues"
881
+ },
882
+ repository: {
883
+ type: "git",
884
+ url: "git+https://github.com/ryanzen9/nest-observe.git"
885
+ },
886
+ packageManager: "pnpm@10.13.1",
887
+ sideEffects: [
888
+ "./dist/register.js",
889
+ "./dist/register.mjs"
890
+ ],
891
+ main: "./dist/index.js",
892
+ module: "./dist/index.mjs",
893
+ types: "./dist/index.d.ts",
894
+ exports: {
895
+ ".": {
896
+ import: {
897
+ types: "./dist/index.d.mts",
898
+ default: "./dist/index.mjs"
899
+ },
900
+ require: {
901
+ types: "./dist/index.d.ts",
902
+ default: "./dist/index.js"
903
+ }
904
+ },
905
+ "./register": {
906
+ import: {
907
+ types: "./dist/register.d.mts",
908
+ default: "./dist/register.mjs"
909
+ },
910
+ require: {
911
+ types: "./dist/register.d.ts",
912
+ default: "./dist/register.js"
913
+ }
914
+ }
915
+ },
916
+ files: [
917
+ "dist",
918
+ "README.md",
919
+ "CHANGELOG.md",
920
+ "LICENSE"
921
+ ],
922
+ scripts: {
923
+ build: "tsup",
924
+ test: "vitest run",
925
+ "test:watch": "vitest",
926
+ typecheck: "tsc --noEmit",
927
+ check: "pnpm typecheck && pnpm test && pnpm build"
928
+ },
929
+ engines: {
930
+ node: ">=20"
931
+ },
932
+ peerDependencies: {
933
+ "@nestjs/common": ">=10 <13",
934
+ "@nestjs/core": ">=10 <13",
935
+ "reflect-metadata": ">=0.1.12 <1",
936
+ rxjs: "^7.1.0"
937
+ },
938
+ dependencies: {
939
+ "@opentelemetry/api": "^1.9.0",
940
+ "@opentelemetry/api-logs": "^0.221.0",
941
+ "@opentelemetry/exporter-logs-otlp-proto": "^0.221.0",
942
+ "@opentelemetry/exporter-metrics-otlp-proto": "^0.221.0",
943
+ "@opentelemetry/exporter-trace-otlp-proto": "^0.221.0",
944
+ "@opentelemetry/instrumentation": "^0.221.0",
945
+ "@opentelemetry/instrumentation-http": "^0.221.0",
946
+ "@opentelemetry/instrumentation-nestjs-core": "^0.67.0",
947
+ "@opentelemetry/resources": "^2.10.0",
948
+ "@opentelemetry/sdk-logs": "^0.221.0",
949
+ "@opentelemetry/sdk-metrics": "^2.10.0",
950
+ "@opentelemetry/sdk-trace-base": "^2.10.0",
951
+ "@opentelemetry/sdk-trace-node": "^2.10.0",
952
+ "@prisma/instrumentation": "^7.10.0"
953
+ },
954
+ devDependencies: {
955
+ "@nestjs/common": "^12.0.1",
956
+ "@nestjs/core": "^12.0.1",
957
+ "@nestjs/testing": "^12.0.1",
958
+ "@opentelemetry/sdk-trace-base": "^2.10.0",
959
+ "@types/node": "^24.0.0",
960
+ "reflect-metadata": "^0.2.2",
961
+ rxjs: "^7.8.2",
962
+ tsup: "^8.5.1",
963
+ typescript: "^5.9.3",
964
+ vitest: "^4.1.11"
965
+ },
966
+ publishConfig: {
967
+ access: "public"
968
+ }
969
+ };
970
+
971
+ // src/resource.ts
972
+ var SDK_NAME = package_default.name;
973
+ var SDK_VERSION = package_default.version;
743
974
  function createObserveResource(config) {
744
975
  return (0, import_resources.defaultResource)().merge(
745
976
  (0, import_resources.resourceFromAttributes)({
@@ -796,14 +1027,23 @@ var SpanRedactionProcessor = class {
796
1027
 
797
1028
  // src/sdk.ts
798
1029
  var InactiveObserveHandle = class {
799
- constructor(config) {
1030
+ constructor(config, diagnostics) {
800
1031
  this.config = config;
1032
+ this.diagnostics = diagnostics;
801
1033
  }
802
1034
  config;
1035
+ diagnostics;
803
1036
  started = false;
804
1037
  tracerProvider = void 0;
805
1038
  meterProvider = void 0;
806
1039
  loggerProvider = void 0;
1040
+ httpRequestMetrics = void 0;
1041
+ get status() {
1042
+ return this.diagnostics.status;
1043
+ }
1044
+ get lastError() {
1045
+ return this.diagnostics.lastError;
1046
+ }
807
1047
  forceFlush() {
808
1048
  return Promise.resolve();
809
1049
  }
@@ -812,52 +1052,73 @@ var InactiveObserveHandle = class {
812
1052
  }
813
1053
  };
814
1054
  var ActiveObserveHandle = class {
815
- constructor(config, tracerProvider, meterProvider, loggerProvider, runtimeMetrics, loggerInstrumentation, exceptionCapture, instrumentations) {
1055
+ constructor(config, tracerProvider, meterProvider, loggerProvider, httpRequestMetrics, runtimeMetrics, loggerInstrumentation, exceptionCapture, instrumentations, diagnostics) {
816
1056
  this.config = config;
817
1057
  this.tracerProvider = tracerProvider;
818
1058
  this.meterProvider = meterProvider;
819
1059
  this.loggerProvider = loggerProvider;
1060
+ this.httpRequestMetrics = httpRequestMetrics;
820
1061
  this.runtimeMetrics = runtimeMetrics;
821
1062
  this.loggerInstrumentation = loggerInstrumentation;
822
1063
  this.exceptionCapture = exceptionCapture;
823
1064
  this.instrumentations = instrumentations;
1065
+ this.diagnostics = diagnostics;
824
1066
  }
825
1067
  config;
826
1068
  tracerProvider;
827
1069
  meterProvider;
828
1070
  loggerProvider;
1071
+ httpRequestMetrics;
829
1072
  runtimeMetrics;
830
1073
  loggerInstrumentation;
831
1074
  exceptionCapture;
832
1075
  instrumentations;
1076
+ diagnostics;
833
1077
  started = true;
834
1078
  stopped = false;
1079
+ get status() {
1080
+ return this.diagnostics.status;
1081
+ }
1082
+ get lastError() {
1083
+ return this.diagnostics.lastError;
1084
+ }
835
1085
  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)));
1086
+ await this.runSafely("forceFlush", [
1087
+ ...this.config.traces && this.tracerProvider ? [["traces", () => this.tracerProvider.forceFlush()]] : [],
1088
+ ...this.config.metrics && this.meterProvider ? [["metrics", () => this.meterProvider.forceFlush()]] : [],
1089
+ ...this.config.logs && this.loggerProvider ? [["logs", () => this.loggerProvider.forceFlush({ timeoutMillis: this.config.exportTimeoutMillis })]] : []
1090
+ ]);
841
1091
  }
842
1092
  async shutdown() {
843
1093
  if (this.stopped) return;
844
1094
  this.stopped = true;
845
1095
  this.loggerInstrumentation?.disable();
846
- this.runtimeMetrics?.stop();
847
1096
  this.exceptionCapture.stop();
848
1097
  for (const instrumentation of this.instrumentations) {
849
1098
  try {
850
1099
  instrumentation.disable();
851
- } catch {
1100
+ } catch (error) {
1101
+ this.diagnostics.failure("sdk", "shutdown", error);
852
1102
  }
853
1103
  }
854
1104
  await this.forceFlush();
855
- await Promise.allSettled([
856
- this.tracerProvider?.shutdown(),
857
- this.meterProvider?.shutdown(),
858
- this.loggerProvider?.shutdown()
859
- ].filter((item) => Boolean(item)));
1105
+ this.runtimeMetrics?.stop();
1106
+ await this.runSafely("shutdown", [
1107
+ ...this.tracerProvider ? [["traces", () => this.tracerProvider.shutdown()]] : [],
1108
+ ...this.meterProvider ? [["metrics", () => this.meterProvider.shutdown()]] : [],
1109
+ ...this.loggerProvider ? [["logs", () => this.loggerProvider.shutdown()]] : []
1110
+ ]);
860
1111
  if (activeRuntime === this) activeRuntime = void 0;
1112
+ this.diagnostics.stop();
1113
+ }
1114
+ async runSafely(stage, operations) {
1115
+ await Promise.all(operations.map(async ([signal, operation]) => {
1116
+ try {
1117
+ await operation();
1118
+ } catch (error) {
1119
+ this.diagnostics.failure(signal, stage, error);
1120
+ }
1121
+ }));
861
1122
  }
862
1123
  };
863
1124
  var activeRuntime;
@@ -868,12 +1129,16 @@ function exporterConfig(url, config) {
868
1129
  timeoutMillis: config.exportTimeoutMillis
869
1130
  };
870
1131
  }
871
- function createTraceProvider(config, resource) {
1132
+ function createTraceProvider(config, resource, diagnostics) {
872
1133
  if (!config.traces && !config.metrics) return void 0;
873
1134
  const spanProcessors = [new SpanRedactionProcessor()];
874
1135
  if (config.traces) {
875
1136
  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, {
1137
+ spanProcessors.push(new import_sdk_trace_base.BatchSpanProcessor(withExporterDiagnostics(
1138
+ exporter,
1139
+ "traces",
1140
+ diagnostics
1141
+ ), {
877
1142
  exportTimeoutMillis: config.exportTimeoutMillis,
878
1143
  maxQueueSize: 2048,
879
1144
  maxExportBatchSize: 512
@@ -888,14 +1153,18 @@ function createTraceProvider(config, resource) {
888
1153
  provider.register();
889
1154
  return provider;
890
1155
  }
891
- function createMeterProvider(config, resource) {
1156
+ function createMeterProvider(config, resource, diagnostics) {
892
1157
  if (!config.metrics) return void 0;
893
1158
  let reader;
894
1159
  if (config.exporters?.metricReader) {
895
1160
  reader = config.exporters.metricReader;
896
1161
  } else {
897
1162
  reader = new import_sdk_metrics.PeriodicExportingMetricReader({
898
- exporter: new import_exporter_metrics_otlp_proto.OTLPMetricExporter(exporterConfig(config.endpoints.metrics, config)),
1163
+ exporter: withExporterDiagnostics(
1164
+ new import_exporter_metrics_otlp_proto.OTLPMetricExporter(exporterConfig(config.endpoints.metrics, config)),
1165
+ "metrics",
1166
+ diagnostics
1167
+ ),
899
1168
  exportIntervalMillis: config.metricExportIntervalMillis,
900
1169
  exportTimeoutMillis: Math.min(config.exportTimeoutMillis, config.metricExportIntervalMillis),
901
1170
  cardinalityLimits: { default: 2e3, histogram: 2e3 }
@@ -913,13 +1182,13 @@ function createMeterProvider(config, resource) {
913
1182
  import_api6.metrics.setGlobalMeterProvider(provider);
914
1183
  return provider;
915
1184
  }
916
- function createLoggerProvider(config, resource) {
1185
+ function createLoggerProvider(config, resource, diagnostics) {
917
1186
  if (!config.logs) return void 0;
918
1187
  const exporter = config.exporters?.log ?? new import_exporter_logs_otlp_proto.OTLPLogExporter(exporterConfig(config.endpoints.logs, config));
919
1188
  const provider = new import_sdk_logs.LoggerProvider({
920
1189
  resource,
921
1190
  processors: [new import_sdk_logs.BatchLogRecordProcessor({
922
- exporter,
1191
+ exporter: withExporterDiagnostics(exporter, "logs", diagnostics),
923
1192
  exportTimeoutMillis: config.exportTimeoutMillis,
924
1193
  maxQueueSize: 2048,
925
1194
  maxExportBatchSize: 512
@@ -931,7 +1200,11 @@ function createLoggerProvider(config, resource) {
931
1200
  function observe(options = {}) {
932
1201
  if (activeRuntime?.started) return activeRuntime;
933
1202
  const config = resolveObserveConfig(options);
934
- if (!config.enabled) return new InactiveObserveHandle(config);
1203
+ const diagnostics = new ObserveDiagnostics(config.diagnosticLogging, options.onError);
1204
+ if (!config.enabled) {
1205
+ diagnostics.inactive();
1206
+ return new InactiveObserveHandle(config, diagnostics);
1207
+ }
935
1208
  let tracerProvider;
936
1209
  let meterProvider;
937
1210
  let loggerProvider;
@@ -941,10 +1214,11 @@ function observe(options = {}) {
941
1214
  const instrumentations = [];
942
1215
  try {
943
1216
  const resource = createObserveResource(config);
944
- meterProvider = createMeterProvider(config, resource);
1217
+ meterProvider = createMeterProvider(config, resource, diagnostics);
945
1218
  const meter = meterProvider?.getMeter(SDK_NAME, SDK_VERSION);
946
- tracerProvider = createTraceProvider(config, resource);
947
- loggerProvider = createLoggerProvider(config, resource);
1219
+ const httpRequestMetrics = meter ? new HttpRequestMetrics(meter, config.serviceName) : void 0;
1220
+ tracerProvider = createTraceProvider(config, resource, diagnostics);
1221
+ loggerProvider = createLoggerProvider(config, resource, diagnostics);
948
1222
  runtimeMetrics = meter ? new RuntimeMetrics(meter) : void 0;
949
1223
  runtimeMetrics?.start();
950
1224
  const otelLogger = loggerProvider?.getLogger(SDK_NAME, SDK_VERSION);
@@ -959,7 +1233,6 @@ function observe(options = {}) {
959
1233
  exceptionCapture.start();
960
1234
  if (config.traces || config.metrics) {
961
1235
  const safeHeaders = config.allowedHeaders.filter((header) => !isSensitiveKey(header));
962
- const httpRequestMetrics = meter ? new HttpRequestMetrics(meter, config.serviceName) : void 0;
963
1236
  instrumentations.push(new import_instrumentation_http.HttpInstrumentation({
964
1237
  requireParentforOutgoingSpans: false,
965
1238
  headersToSpanAttributes: {
@@ -1003,14 +1276,18 @@ function observe(options = {}) {
1003
1276
  tracerProvider,
1004
1277
  meterProvider,
1005
1278
  loggerProvider,
1279
+ httpRequestMetrics,
1006
1280
  runtimeMetrics,
1007
1281
  loggerInstrumentation,
1008
1282
  exceptionCapture,
1009
- instrumentations
1283
+ instrumentations,
1284
+ diagnostics
1010
1285
  );
1286
+ diagnostics.activate();
1011
1287
  activeRuntime = runtime;
1012
1288
  return runtime;
1013
- } catch {
1289
+ } catch (error) {
1290
+ diagnostics.failure("sdk", "initialization", error);
1014
1291
  loggerInstrumentation?.disable();
1015
1292
  runtimeMetrics?.stop();
1016
1293
  exceptionCapture?.stop();
@@ -1025,7 +1302,9 @@ function observe(options = {}) {
1025
1302
  meterProvider?.shutdown(),
1026
1303
  loggerProvider?.shutdown()
1027
1304
  ].filter((item) => Boolean(item)));
1028
- return new InactiveObserveHandle(config);
1305
+ diagnostics.inactive();
1306
+ if (config.failFast) throw toError(error);
1307
+ return new InactiveObserveHandle(config, diagnostics);
1029
1308
  }
1030
1309
  }
1031
1310
  function getObserveRuntime() {
@@ -1035,6 +1314,37 @@ function getObserveRuntime() {
1035
1314
  // src/nest/observe.module.ts
1036
1315
  var OBSERVE_OPTIONS = /* @__PURE__ */ Symbol("OBSERVE_OPTIONS");
1037
1316
  var OBSERVE_HANDLE = /* @__PURE__ */ Symbol("OBSERVE_HANDLE");
1317
+ var NestHttpMetricsInterceptor = class {
1318
+ constructor(handle) {
1319
+ this.handle = handle;
1320
+ }
1321
+ handle;
1322
+ intercept(context3, next) {
1323
+ if (context3.getType() !== "http" || !this.handle.started || !this.handle.httpRequestMetrics) {
1324
+ return next.handle();
1325
+ }
1326
+ const http = context3.switchToHttp();
1327
+ const request = http.getRequest();
1328
+ const response = http.getResponse();
1329
+ const started = this.handle.httpRequestMetrics.start(request);
1330
+ const result = next.handle();
1331
+ if (!started) return result;
1332
+ const responseEventSource = response.raw ?? response;
1333
+ if (typeof responseEventSource.once === "function") {
1334
+ responseEventSource.once("finish", () => {
1335
+ this.handle.httpRequestMetrics?.record(request, response);
1336
+ });
1337
+ return result;
1338
+ }
1339
+ return result.pipe((0, import_rxjs2.finalize)(() => {
1340
+ this.handle.httpRequestMetrics?.record(request, response);
1341
+ }));
1342
+ }
1343
+ };
1344
+ NestHttpMetricsInterceptor = __decorateClass([
1345
+ (0, import_common2.Injectable)(),
1346
+ __decorateParam(0, (0, import_common2.Inject)(OBSERVE_HANDLE))
1347
+ ], NestHttpMetricsInterceptor);
1038
1348
  var NestObserveExplorer = class {
1039
1349
  constructor(discovery, options, handle) {
1040
1350
  this.discovery = discovery;
@@ -1059,7 +1369,7 @@ var NestObserveExplorer = class {
1059
1369
  }
1060
1370
  instrument(wrappers, instrumenter, kind) {
1061
1371
  for (const wrapper of wrappers) {
1062
- if (!wrapper.metatype) continue;
1372
+ if (!wrapper.metatype || wrapper.inject !== void 0) continue;
1063
1373
  if (["ObserveModule", "DiscoveryModule", "InternalCoreModule"].includes(wrapper.host?.name ?? "")) continue;
1064
1374
  if (wrapper.metatype === NestObserveExplorer) continue;
1065
1375
  if (wrapper.instance) {
@@ -1088,6 +1398,7 @@ var ObserveModule = class {
1088
1398
  providers: [
1089
1399
  { provide: OBSERVE_OPTIONS, useValue: options },
1090
1400
  { provide: OBSERVE_HANDLE, useFactory: () => observe(options) },
1401
+ { provide: import_core.APP_INTERCEPTOR, useClass: NestHttpMetricsInterceptor },
1091
1402
  NestObserveExplorer
1092
1403
  ],
1093
1404
  exports: [OBSERVE_HANDLE]
@@ -1103,6 +1414,7 @@ ObserveModule = __decorateClass([
1103
1414
  CompatibleNestInstrumentation,
1104
1415
  HttpRequestMetrics,
1105
1416
  IgnoreTrace,
1417
+ NestHttpMetricsInterceptor,
1106
1418
  NestLoggerInstrumentation,
1107
1419
  NestMethodInstrumenter,
1108
1420
  OBSERVE_HANDLE,