@ryanzeng/nest-observe 0.1.0 → 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.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;
@@ -182,19 +184,28 @@ function invokeWithSpan(tracer, spanName, attributes, invoke, onFinish) {
182
184
  // src/decorators/trace.decorator.ts
183
185
  function decorateMethod(target, propertyKey, descriptor, options) {
184
186
  const original = descriptor.value;
185
- if (typeof original !== "function" || isTraceIgnored(original, target.constructor)) return;
187
+ if (typeof original !== "function" || isTraceIgnored(original, target.constructor))
188
+ return;
186
189
  const className = target.constructor?.name || "Anonymous";
187
190
  const methodName = String(propertyKey);
188
191
  const wrapped = function(...args) {
189
- const tracer = trace.getTracer("@ryanzen9/nest-observe");
190
- return invokeWithSpan(tracer, options.name ?? `${className}.${methodName}`, {
191
- "code.function.name": methodName,
192
- "nestjs.class": className,
193
- "nestjs.method": methodName,
194
- ...options.attributes
195
- }, () => original.apply(this, args));
192
+ const tracer = trace.getTracer("@ryanzeng/nest-observe");
193
+ return invokeWithSpan(
194
+ tracer,
195
+ options.name ?? `${className}.${methodName}`,
196
+ {
197
+ "code.function.name": methodName,
198
+ "nestjs.class": className,
199
+ "nestjs.method": methodName,
200
+ ...options.attributes
201
+ },
202
+ () => original.apply(this, args)
203
+ );
196
204
  };
197
- Object.defineProperty(wrapped, "name", { value: original.name, configurable: true });
205
+ Object.defineProperty(wrapped, "name", {
206
+ value: original.name,
207
+ configurable: true
208
+ });
198
209
  markTraceDecorated(wrapped);
199
210
  descriptor.value = wrapped;
200
211
  }
@@ -276,7 +287,13 @@ var SEVERITY = {
276
287
  };
277
288
  function bodyValue(message) {
278
289
  if (message instanceof Error) return redactText(message.stack ?? `${message.name}: ${message.message}`);
279
- return redact(message);
290
+ const sanitized = redact(message);
291
+ if (sanitized === null || typeof sanitized !== "object") return sanitized;
292
+ try {
293
+ return JSON.stringify(sanitized, (_key, value) => typeof value === "bigint" ? value.toString() : value);
294
+ } catch {
295
+ return redactText(String(sanitized));
296
+ }
280
297
  }
281
298
  var NestLoggerInstrumentation = class {
282
299
  constructor(emitter, resourceAttributes = {}) {
@@ -339,7 +356,7 @@ var SEVERITY_NUMBER = {
339
356
  };
340
357
  var OpenTelemetryLogEmitter = class {
341
358
  logger;
342
- constructor(name = "@ryanzen9/nest-observe", version, logger) {
359
+ constructor(name = "@ryanzeng/nest-observe", version, logger) {
343
360
  this.logger = logger ?? logs.getLogger(name, version);
344
361
  }
345
362
  emit(record) {
@@ -513,9 +530,14 @@ var HttpRequestMetrics = class {
513
530
  errorCount;
514
531
  startedAt = /* @__PURE__ */ new WeakMap();
515
532
  start(request) {
533
+ if (this.startedAt.has(request)) return false;
516
534
  this.startedAt.set(request, process.hrtime.bigint());
535
+ return true;
517
536
  }
518
537
  record(request, response) {
538
+ const started = this.startedAt.get(request);
539
+ if (started === void 0) return false;
540
+ this.startedAt.delete(request);
519
541
  const statusCode = Number.isFinite(response.statusCode) ? response.statusCode : 0;
520
542
  const attributes = {
521
543
  "service.name": this.serviceName,
@@ -524,12 +546,9 @@ var HttpRequestMetrics = class {
524
546
  "http.response.status_code": statusCode
525
547
  };
526
548
  this.requestCount.add(1, attributes);
527
- const started = this.startedAt.get(request);
528
- if (started !== void 0) {
529
- this.requestDuration.record(Number(process.hrtime.bigint() - started) / 1e9, attributes);
530
- this.startedAt.delete(request);
531
- }
549
+ this.requestDuration.record(Number(process.hrtime.bigint() - started) / 1e9, attributes);
532
550
  if (statusCode >= 500) this.errorCount.add(1, attributes);
551
+ return true;
533
552
  }
534
553
  };
535
554
 
@@ -615,9 +634,15 @@ var RuntimeMetrics = class {
615
634
  };
616
635
 
617
636
  // src/nest/observe.module.ts
618
- import { Global, Inject, Injectable, Module } from "@nestjs/common";
619
- import { DiscoveryModule, DiscoveryService } from "@nestjs/core";
637
+ import {
638
+ Global,
639
+ Inject,
640
+ Injectable,
641
+ Module
642
+ } from "@nestjs/common";
643
+ import { APP_INTERCEPTOR, DiscoveryModule, DiscoveryService } from "@nestjs/core";
620
644
  import { metrics as metrics2, trace as trace5 } from "@opentelemetry/api";
645
+ import { finalize as finalize2 } from "rxjs";
621
646
 
622
647
  // src/sdk.ts
623
648
  import { logs as logs2 } from "@opentelemetry/api-logs";
@@ -633,6 +658,113 @@ import { AggregationType, MeterProvider, PeriodicExportingMetricReader } from "@
633
658
  import { AlwaysOffSampler, BatchSpanProcessor, ParentBasedSampler, TraceIdRatioBasedSampler } from "@opentelemetry/sdk-trace-base";
634
659
  import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
635
660
 
661
+ // src/diagnostics.ts
662
+ function toError(value) {
663
+ if (value instanceof Error) return value;
664
+ if (typeof value === "string") return new Error(value);
665
+ try {
666
+ return new Error(JSON.stringify(value));
667
+ } catch {
668
+ return new Error(String(value));
669
+ }
670
+ }
671
+ function sanitizeError(error) {
672
+ const sanitized = new Error(redactText(error.message));
673
+ sanitized.name = error.name;
674
+ if (error.stack) sanitized.stack = redactText(error.stack);
675
+ return sanitized;
676
+ }
677
+ var ObserveDiagnostics = class {
678
+ constructor(logging, onError) {
679
+ this.logging = logging;
680
+ this.onError = onError;
681
+ }
682
+ logging;
683
+ onError;
684
+ currentStatus = "starting";
685
+ currentError;
686
+ failedSignals = /* @__PURE__ */ new Set();
687
+ reported = /* @__PURE__ */ new Set();
688
+ get status() {
689
+ return this.currentStatus;
690
+ }
691
+ get lastError() {
692
+ return this.currentError;
693
+ }
694
+ activate() {
695
+ if (this.currentStatus === "starting") this.currentStatus = "active";
696
+ }
697
+ inactive() {
698
+ this.currentStatus = "inactive";
699
+ }
700
+ stop() {
701
+ this.currentStatus = "stopped";
702
+ }
703
+ success(signal) {
704
+ this.failedSignals.delete(signal);
705
+ for (const fingerprint of this.reported) {
706
+ if (fingerprint.startsWith(`${signal}\0`)) this.reported.delete(fingerprint);
707
+ }
708
+ if (this.currentStatus === "degraded" && this.failedSignals.size === 0) {
709
+ this.currentStatus = "active";
710
+ }
711
+ }
712
+ failure(signal, stage, value) {
713
+ const error = sanitizeError(toError(value));
714
+ const event = { signal, stage, error, timestamp: Date.now() };
715
+ this.currentError = event;
716
+ this.failedSignals.add(signal);
717
+ if (this.currentStatus !== "inactive" && this.currentStatus !== "stopped") {
718
+ this.currentStatus = "degraded";
719
+ }
720
+ const fingerprint = `${signal}\0${stage}\0${error.name}\0${error.message}`;
721
+ if (this.reported.has(fingerprint)) return;
722
+ this.reported.add(fingerprint);
723
+ try {
724
+ this.onError?.(event);
725
+ } catch {
726
+ }
727
+ if (!this.logging) return;
728
+ try {
729
+ process.stderr.write(
730
+ `[nest-observe] ${signal} ${stage} failed: ${redactText(error.message)}
731
+ `
732
+ );
733
+ } catch {
734
+ }
735
+ }
736
+ };
737
+ function withExporterDiagnostics(exporter, signal, diagnostics) {
738
+ const delegate = exporter;
739
+ return new Proxy(exporter, {
740
+ get(target, property) {
741
+ if (property === "export") {
742
+ return (items, callback) => {
743
+ try {
744
+ delegate.export.call(target, items, (result) => {
745
+ if (result.code === 0) {
746
+ diagnostics.success(signal);
747
+ } else {
748
+ diagnostics.failure(
749
+ signal,
750
+ "export",
751
+ result.error ?? new Error(`${signal} export failed`)
752
+ );
753
+ }
754
+ callback(result);
755
+ });
756
+ } catch (error) {
757
+ diagnostics.failure(signal, "export", error);
758
+ throw error;
759
+ }
760
+ };
761
+ }
762
+ const value = Reflect.get(target, property, target);
763
+ return typeof value === "function" ? value.bind(target) : value;
764
+ }
765
+ });
766
+ }
767
+
636
768
  // src/exceptions/process-exception-capture.ts
637
769
  import { trace as trace3, SpanStatusCode as SpanStatusCode2 } from "@opentelemetry/api";
638
770
  function asError(value) {
@@ -680,21 +812,136 @@ var ProcessExceptionCapture = class {
680
812
  };
681
813
 
682
814
  // src/resource.ts
815
+ import {
816
+ defaultResource,
817
+ resourceFromAttributes
818
+ } from "@opentelemetry/resources";
683
819
  import { hostname } from "os";
684
- import { defaultResource, resourceFromAttributes } from "@opentelemetry/resources";
685
- var SDK_NAME = "@ryanzen9/nest-observe";
686
- var SDK_VERSION = "0.1.0";
820
+
821
+ // package.json
822
+ var package_default = {
823
+ name: "@ryanzeng/nest-observe",
824
+ version: "0.1.2",
825
+ description: "Zero-config, vendor-neutral OpenTelemetry observability for NestJS",
826
+ keywords: [
827
+ "nestjs",
828
+ "opentelemetry",
829
+ "observability",
830
+ "otlp",
831
+ "tracing",
832
+ "metrics",
833
+ "logging"
834
+ ],
835
+ license: "MIT",
836
+ homepage: "https://github.com/ryanzen9/nest-observe#readme",
837
+ bugs: {
838
+ url: "https://github.com/ryanzen9/nest-observe/issues"
839
+ },
840
+ repository: {
841
+ type: "git",
842
+ url: "git+https://github.com/ryanzen9/nest-observe.git"
843
+ },
844
+ packageManager: "pnpm@10.13.1",
845
+ sideEffects: [
846
+ "./dist/register.js",
847
+ "./dist/register.mjs"
848
+ ],
849
+ main: "./dist/index.js",
850
+ module: "./dist/index.mjs",
851
+ types: "./dist/index.d.ts",
852
+ exports: {
853
+ ".": {
854
+ import: {
855
+ types: "./dist/index.d.mts",
856
+ default: "./dist/index.mjs"
857
+ },
858
+ require: {
859
+ types: "./dist/index.d.ts",
860
+ default: "./dist/index.js"
861
+ }
862
+ },
863
+ "./register": {
864
+ import: {
865
+ types: "./dist/register.d.mts",
866
+ default: "./dist/register.mjs"
867
+ },
868
+ require: {
869
+ types: "./dist/register.d.ts",
870
+ default: "./dist/register.js"
871
+ }
872
+ }
873
+ },
874
+ files: [
875
+ "dist",
876
+ "README.md",
877
+ "CHANGELOG.md",
878
+ "LICENSE"
879
+ ],
880
+ scripts: {
881
+ build: "tsup",
882
+ test: "vitest run",
883
+ "test:watch": "vitest",
884
+ typecheck: "tsc --noEmit",
885
+ check: "pnpm typecheck && pnpm test && pnpm build"
886
+ },
887
+ engines: {
888
+ node: ">=20"
889
+ },
890
+ peerDependencies: {
891
+ "@nestjs/common": ">=10 <13",
892
+ "@nestjs/core": ">=10 <13",
893
+ "reflect-metadata": ">=0.1.12 <1",
894
+ rxjs: "^7.1.0"
895
+ },
896
+ dependencies: {
897
+ "@opentelemetry/api": "^1.9.0",
898
+ "@opentelemetry/api-logs": "^0.221.0",
899
+ "@opentelemetry/exporter-logs-otlp-proto": "^0.221.0",
900
+ "@opentelemetry/exporter-metrics-otlp-proto": "^0.221.0",
901
+ "@opentelemetry/exporter-trace-otlp-proto": "^0.221.0",
902
+ "@opentelemetry/instrumentation": "^0.221.0",
903
+ "@opentelemetry/instrumentation-http": "^0.221.0",
904
+ "@opentelemetry/instrumentation-nestjs-core": "^0.67.0",
905
+ "@opentelemetry/resources": "^2.10.0",
906
+ "@opentelemetry/sdk-logs": "^0.221.0",
907
+ "@opentelemetry/sdk-metrics": "^2.10.0",
908
+ "@opentelemetry/sdk-trace-base": "^2.10.0",
909
+ "@opentelemetry/sdk-trace-node": "^2.10.0",
910
+ "@prisma/instrumentation": "^7.10.0"
911
+ },
912
+ devDependencies: {
913
+ "@nestjs/common": "^12.0.1",
914
+ "@nestjs/core": "^12.0.1",
915
+ "@nestjs/testing": "^12.0.1",
916
+ "@opentelemetry/sdk-trace-base": "^2.10.0",
917
+ "@types/node": "^24.0.0",
918
+ "reflect-metadata": "^0.2.2",
919
+ rxjs: "^7.8.2",
920
+ tsup: "^8.5.1",
921
+ typescript: "^5.9.3",
922
+ vitest: "^4.1.11"
923
+ },
924
+ publishConfig: {
925
+ access: "public"
926
+ }
927
+ };
928
+
929
+ // src/resource.ts
930
+ var SDK_NAME = package_default.name;
931
+ var SDK_VERSION = package_default.version;
687
932
  function createObserveResource(config) {
688
- return defaultResource().merge(resourceFromAttributes({
689
- ...config.resourceAttributes,
690
- "service.name": config.serviceName,
691
- "service.version": config.serviceVersion,
692
- "deployment.environment.name": config.environment,
693
- "service.instance.id": config.instanceId,
694
- "telemetry.sdk.name": SDK_NAME,
695
- "telemetry.sdk.version": SDK_VERSION,
696
- "host.name": config.resourceAttributes["host.name"] ?? hostname()
697
- }));
933
+ return defaultResource().merge(
934
+ resourceFromAttributes({
935
+ ...config.resourceAttributes,
936
+ "service.name": config.serviceName,
937
+ "service.version": config.serviceVersion,
938
+ "deployment.environment.name": config.environment,
939
+ "service.instance.id": config.instanceId,
940
+ "telemetry.sdk.name": SDK_NAME,
941
+ "telemetry.sdk.version": SDK_VERSION,
942
+ "host.name": config.resourceAttributes["host.name"] ?? hostname()
943
+ })
944
+ );
698
945
  }
699
946
 
700
947
  // src/security/span-redaction-processor.ts
@@ -738,14 +985,23 @@ var SpanRedactionProcessor = class {
738
985
 
739
986
  // src/sdk.ts
740
987
  var InactiveObserveHandle = class {
741
- constructor(config) {
988
+ constructor(config, diagnostics) {
742
989
  this.config = config;
990
+ this.diagnostics = diagnostics;
743
991
  }
744
992
  config;
993
+ diagnostics;
745
994
  started = false;
746
995
  tracerProvider = void 0;
747
996
  meterProvider = void 0;
748
997
  loggerProvider = void 0;
998
+ httpRequestMetrics = void 0;
999
+ get status() {
1000
+ return this.diagnostics.status;
1001
+ }
1002
+ get lastError() {
1003
+ return this.diagnostics.lastError;
1004
+ }
749
1005
  forceFlush() {
750
1006
  return Promise.resolve();
751
1007
  }
@@ -754,52 +1010,73 @@ var InactiveObserveHandle = class {
754
1010
  }
755
1011
  };
756
1012
  var ActiveObserveHandle = class {
757
- constructor(config, tracerProvider, meterProvider, loggerProvider, runtimeMetrics, loggerInstrumentation, exceptionCapture, instrumentations) {
1013
+ constructor(config, tracerProvider, meterProvider, loggerProvider, httpRequestMetrics, runtimeMetrics, loggerInstrumentation, exceptionCapture, instrumentations, diagnostics) {
758
1014
  this.config = config;
759
1015
  this.tracerProvider = tracerProvider;
760
1016
  this.meterProvider = meterProvider;
761
1017
  this.loggerProvider = loggerProvider;
1018
+ this.httpRequestMetrics = httpRequestMetrics;
762
1019
  this.runtimeMetrics = runtimeMetrics;
763
1020
  this.loggerInstrumentation = loggerInstrumentation;
764
1021
  this.exceptionCapture = exceptionCapture;
765
1022
  this.instrumentations = instrumentations;
1023
+ this.diagnostics = diagnostics;
766
1024
  }
767
1025
  config;
768
1026
  tracerProvider;
769
1027
  meterProvider;
770
1028
  loggerProvider;
1029
+ httpRequestMetrics;
771
1030
  runtimeMetrics;
772
1031
  loggerInstrumentation;
773
1032
  exceptionCapture;
774
1033
  instrumentations;
1034
+ diagnostics;
775
1035
  started = true;
776
1036
  stopped = false;
1037
+ get status() {
1038
+ return this.diagnostics.status;
1039
+ }
1040
+ get lastError() {
1041
+ return this.diagnostics.lastError;
1042
+ }
777
1043
  async forceFlush() {
778
- await Promise.allSettled([
779
- this.tracerProvider?.forceFlush(),
780
- this.meterProvider?.forceFlush(),
781
- this.loggerProvider?.forceFlush({ timeoutMillis: this.config.exportTimeoutMillis })
782
- ].filter((item) => Boolean(item)));
1044
+ await this.runSafely("forceFlush", [
1045
+ ...this.config.traces && this.tracerProvider ? [["traces", () => this.tracerProvider.forceFlush()]] : [],
1046
+ ...this.config.metrics && this.meterProvider ? [["metrics", () => this.meterProvider.forceFlush()]] : [],
1047
+ ...this.config.logs && this.loggerProvider ? [["logs", () => this.loggerProvider.forceFlush({ timeoutMillis: this.config.exportTimeoutMillis })]] : []
1048
+ ]);
783
1049
  }
784
1050
  async shutdown() {
785
1051
  if (this.stopped) return;
786
1052
  this.stopped = true;
787
1053
  this.loggerInstrumentation?.disable();
788
- this.runtimeMetrics?.stop();
789
1054
  this.exceptionCapture.stop();
790
1055
  for (const instrumentation of this.instrumentations) {
791
1056
  try {
792
1057
  instrumentation.disable();
793
- } catch {
1058
+ } catch (error) {
1059
+ this.diagnostics.failure("sdk", "shutdown", error);
794
1060
  }
795
1061
  }
796
1062
  await this.forceFlush();
797
- await Promise.allSettled([
798
- this.tracerProvider?.shutdown(),
799
- this.meterProvider?.shutdown(),
800
- this.loggerProvider?.shutdown()
801
- ].filter((item) => Boolean(item)));
1063
+ this.runtimeMetrics?.stop();
1064
+ await this.runSafely("shutdown", [
1065
+ ...this.tracerProvider ? [["traces", () => this.tracerProvider.shutdown()]] : [],
1066
+ ...this.meterProvider ? [["metrics", () => this.meterProvider.shutdown()]] : [],
1067
+ ...this.loggerProvider ? [["logs", () => this.loggerProvider.shutdown()]] : []
1068
+ ]);
802
1069
  if (activeRuntime === this) activeRuntime = void 0;
1070
+ this.diagnostics.stop();
1071
+ }
1072
+ async runSafely(stage, operations) {
1073
+ await Promise.all(operations.map(async ([signal, operation]) => {
1074
+ try {
1075
+ await operation();
1076
+ } catch (error) {
1077
+ this.diagnostics.failure(signal, stage, error);
1078
+ }
1079
+ }));
803
1080
  }
804
1081
  };
805
1082
  var activeRuntime;
@@ -810,12 +1087,16 @@ function exporterConfig(url, config) {
810
1087
  timeoutMillis: config.exportTimeoutMillis
811
1088
  };
812
1089
  }
813
- function createTraceProvider(config, resource) {
1090
+ function createTraceProvider(config, resource, diagnostics) {
814
1091
  if (!config.traces && !config.metrics) return void 0;
815
1092
  const spanProcessors = [new SpanRedactionProcessor()];
816
1093
  if (config.traces) {
817
1094
  const exporter = config.exporters?.span ?? new OTLPTraceExporter(exporterConfig(config.endpoints.traces, config));
818
- spanProcessors.push(new BatchSpanProcessor(exporter, {
1095
+ spanProcessors.push(new BatchSpanProcessor(withExporterDiagnostics(
1096
+ exporter,
1097
+ "traces",
1098
+ diagnostics
1099
+ ), {
819
1100
  exportTimeoutMillis: config.exportTimeoutMillis,
820
1101
  maxQueueSize: 2048,
821
1102
  maxExportBatchSize: 512
@@ -830,14 +1111,18 @@ function createTraceProvider(config, resource) {
830
1111
  provider.register();
831
1112
  return provider;
832
1113
  }
833
- function createMeterProvider(config, resource) {
1114
+ function createMeterProvider(config, resource, diagnostics) {
834
1115
  if (!config.metrics) return void 0;
835
1116
  let reader;
836
1117
  if (config.exporters?.metricReader) {
837
1118
  reader = config.exporters.metricReader;
838
1119
  } else {
839
1120
  reader = new PeriodicExportingMetricReader({
840
- exporter: new OTLPMetricExporter(exporterConfig(config.endpoints.metrics, config)),
1121
+ exporter: withExporterDiagnostics(
1122
+ new OTLPMetricExporter(exporterConfig(config.endpoints.metrics, config)),
1123
+ "metrics",
1124
+ diagnostics
1125
+ ),
841
1126
  exportIntervalMillis: config.metricExportIntervalMillis,
842
1127
  exportTimeoutMillis: Math.min(config.exportTimeoutMillis, config.metricExportIntervalMillis),
843
1128
  cardinalityLimits: { default: 2e3, histogram: 2e3 }
@@ -855,13 +1140,13 @@ function createMeterProvider(config, resource) {
855
1140
  metrics.setGlobalMeterProvider(provider);
856
1141
  return provider;
857
1142
  }
858
- function createLoggerProvider(config, resource) {
1143
+ function createLoggerProvider(config, resource, diagnostics) {
859
1144
  if (!config.logs) return void 0;
860
1145
  const exporter = config.exporters?.log ?? new OTLPLogExporter(exporterConfig(config.endpoints.logs, config));
861
1146
  const provider = new LoggerProvider({
862
1147
  resource,
863
1148
  processors: [new BatchLogRecordProcessor({
864
- exporter,
1149
+ exporter: withExporterDiagnostics(exporter, "logs", diagnostics),
865
1150
  exportTimeoutMillis: config.exportTimeoutMillis,
866
1151
  maxQueueSize: 2048,
867
1152
  maxExportBatchSize: 512
@@ -873,7 +1158,11 @@ function createLoggerProvider(config, resource) {
873
1158
  function observe(options = {}) {
874
1159
  if (activeRuntime?.started) return activeRuntime;
875
1160
  const config = resolveObserveConfig(options);
876
- if (!config.enabled) return new InactiveObserveHandle(config);
1161
+ const diagnostics = new ObserveDiagnostics(config.diagnosticLogging, options.onError);
1162
+ if (!config.enabled) {
1163
+ diagnostics.inactive();
1164
+ return new InactiveObserveHandle(config, diagnostics);
1165
+ }
877
1166
  let tracerProvider;
878
1167
  let meterProvider;
879
1168
  let loggerProvider;
@@ -883,10 +1172,11 @@ function observe(options = {}) {
883
1172
  const instrumentations = [];
884
1173
  try {
885
1174
  const resource = createObserveResource(config);
886
- meterProvider = createMeterProvider(config, resource);
1175
+ meterProvider = createMeterProvider(config, resource, diagnostics);
887
1176
  const meter = meterProvider?.getMeter(SDK_NAME, SDK_VERSION);
888
- tracerProvider = createTraceProvider(config, resource);
889
- loggerProvider = createLoggerProvider(config, resource);
1177
+ const httpRequestMetrics = meter ? new HttpRequestMetrics(meter, config.serviceName) : void 0;
1178
+ tracerProvider = createTraceProvider(config, resource, diagnostics);
1179
+ loggerProvider = createLoggerProvider(config, resource, diagnostics);
890
1180
  runtimeMetrics = meter ? new RuntimeMetrics(meter) : void 0;
891
1181
  runtimeMetrics?.start();
892
1182
  const otelLogger = loggerProvider?.getLogger(SDK_NAME, SDK_VERSION);
@@ -901,7 +1191,6 @@ function observe(options = {}) {
901
1191
  exceptionCapture.start();
902
1192
  if (config.traces || config.metrics) {
903
1193
  const safeHeaders = config.allowedHeaders.filter((header) => !isSensitiveKey(header));
904
- const httpRequestMetrics = meter ? new HttpRequestMetrics(meter, config.serviceName) : void 0;
905
1194
  instrumentations.push(new HttpInstrumentation({
906
1195
  requireParentforOutgoingSpans: false,
907
1196
  headersToSpanAttributes: {
@@ -945,14 +1234,18 @@ function observe(options = {}) {
945
1234
  tracerProvider,
946
1235
  meterProvider,
947
1236
  loggerProvider,
1237
+ httpRequestMetrics,
948
1238
  runtimeMetrics,
949
1239
  loggerInstrumentation,
950
1240
  exceptionCapture,
951
- instrumentations
1241
+ instrumentations,
1242
+ diagnostics
952
1243
  );
1244
+ diagnostics.activate();
953
1245
  activeRuntime = runtime;
954
1246
  return runtime;
955
- } catch {
1247
+ } catch (error) {
1248
+ diagnostics.failure("sdk", "initialization", error);
956
1249
  loggerInstrumentation?.disable();
957
1250
  runtimeMetrics?.stop();
958
1251
  exceptionCapture?.stop();
@@ -967,7 +1260,9 @@ function observe(options = {}) {
967
1260
  meterProvider?.shutdown(),
968
1261
  loggerProvider?.shutdown()
969
1262
  ].filter((item) => Boolean(item)));
970
- return new InactiveObserveHandle(config);
1263
+ diagnostics.inactive();
1264
+ if (config.failFast) throw toError(error);
1265
+ return new InactiveObserveHandle(config, diagnostics);
971
1266
  }
972
1267
  }
973
1268
  function getObserveRuntime() {
@@ -977,6 +1272,37 @@ function getObserveRuntime() {
977
1272
  // src/nest/observe.module.ts
978
1273
  var OBSERVE_OPTIONS = /* @__PURE__ */ Symbol("OBSERVE_OPTIONS");
979
1274
  var OBSERVE_HANDLE = /* @__PURE__ */ Symbol("OBSERVE_HANDLE");
1275
+ var NestHttpMetricsInterceptor = class {
1276
+ constructor(handle) {
1277
+ this.handle = handle;
1278
+ }
1279
+ handle;
1280
+ intercept(context3, next) {
1281
+ if (context3.getType() !== "http" || !this.handle.started || !this.handle.httpRequestMetrics) {
1282
+ return next.handle();
1283
+ }
1284
+ const http = context3.switchToHttp();
1285
+ const request = http.getRequest();
1286
+ const response = http.getResponse();
1287
+ const started = this.handle.httpRequestMetrics.start(request);
1288
+ const result = next.handle();
1289
+ if (!started) return result;
1290
+ const responseEventSource = response.raw ?? response;
1291
+ if (typeof responseEventSource.once === "function") {
1292
+ responseEventSource.once("finish", () => {
1293
+ this.handle.httpRequestMetrics?.record(request, response);
1294
+ });
1295
+ return result;
1296
+ }
1297
+ return result.pipe(finalize2(() => {
1298
+ this.handle.httpRequestMetrics?.record(request, response);
1299
+ }));
1300
+ }
1301
+ };
1302
+ NestHttpMetricsInterceptor = __decorateClass([
1303
+ Injectable(),
1304
+ __decorateParam(0, Inject(OBSERVE_HANDLE))
1305
+ ], NestHttpMetricsInterceptor);
980
1306
  var NestObserveExplorer = class {
981
1307
  constructor(discovery, options, handle) {
982
1308
  this.discovery = discovery;
@@ -1030,6 +1356,7 @@ var ObserveModule = class {
1030
1356
  providers: [
1031
1357
  { provide: OBSERVE_OPTIONS, useValue: options },
1032
1358
  { provide: OBSERVE_HANDLE, useFactory: () => observe(options) },
1359
+ { provide: APP_INTERCEPTOR, useClass: NestHttpMetricsInterceptor },
1033
1360
  NestObserveExplorer
1034
1361
  ],
1035
1362
  exports: [OBSERVE_HANDLE]
@@ -1044,6 +1371,7 @@ export {
1044
1371
  CompatibleNestInstrumentation,
1045
1372
  HttpRequestMetrics,
1046
1373
  IgnoreTrace,
1374
+ NestHttpMetricsInterceptor,
1047
1375
  NestLoggerInstrumentation,
1048
1376
  NestMethodInstrumenter,
1049
1377
  OBSERVE_HANDLE,