@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/register.mjs CHANGED
@@ -91,15 +91,14 @@ function resolveObserveConfig(options = {}, env = process.env) {
91
91
  allowedHeaders,
92
92
  exportTimeoutMillis: positiveInteger(options.exportTimeoutMillis ?? env.OTEL_EXPORTER_OTLP_TIMEOUT, 1e4),
93
93
  metricExportIntervalMillis: positiveInteger(options.metricExportIntervalMillis ?? env.OTEL_METRIC_EXPORT_INTERVAL, 6e4),
94
- resourceAttributes
94
+ resourceAttributes,
95
+ diagnosticLogging: booleanValue(options.diagnosticLogging, true),
96
+ failFast: booleanValue(options.failFast, false)
95
97
  };
96
98
  if (options.exporters) result.exporters = options.exporters;
97
99
  return result;
98
100
  }
99
101
 
100
- // src/exceptions/process-exception-capture.ts
101
- import { trace, SpanStatusCode } from "@opentelemetry/api";
102
-
103
102
  // src/security/redaction.ts
104
103
  var REDACTED = "[REDACTED]";
105
104
  var SENSITIVE_KEY = /(?:authorization|proxy-authorization|cookie|set-cookie|pass(?:word|wd)?|secret|token|api[-_]?key|phone|mobile)/i;
@@ -133,7 +132,115 @@ function redact(value) {
133
132
  return visit(value, /* @__PURE__ */ new WeakSet());
134
133
  }
135
134
 
135
+ // src/diagnostics.ts
136
+ function toError(value) {
137
+ if (value instanceof Error) return value;
138
+ if (typeof value === "string") return new Error(value);
139
+ try {
140
+ return new Error(JSON.stringify(value));
141
+ } catch {
142
+ return new Error(String(value));
143
+ }
144
+ }
145
+ function sanitizeError(error) {
146
+ const sanitized = new Error(redactText(error.message));
147
+ sanitized.name = error.name;
148
+ if (error.stack) sanitized.stack = redactText(error.stack);
149
+ return sanitized;
150
+ }
151
+ var ObserveDiagnostics = class {
152
+ constructor(logging, onError) {
153
+ this.logging = logging;
154
+ this.onError = onError;
155
+ }
156
+ logging;
157
+ onError;
158
+ currentStatus = "starting";
159
+ currentError;
160
+ failedSignals = /* @__PURE__ */ new Set();
161
+ reported = /* @__PURE__ */ new Set();
162
+ get status() {
163
+ return this.currentStatus;
164
+ }
165
+ get lastError() {
166
+ return this.currentError;
167
+ }
168
+ activate() {
169
+ if (this.currentStatus === "starting") this.currentStatus = "active";
170
+ }
171
+ inactive() {
172
+ this.currentStatus = "inactive";
173
+ }
174
+ stop() {
175
+ this.currentStatus = "stopped";
176
+ }
177
+ success(signal) {
178
+ this.failedSignals.delete(signal);
179
+ for (const fingerprint of this.reported) {
180
+ if (fingerprint.startsWith(`${signal}\0`)) this.reported.delete(fingerprint);
181
+ }
182
+ if (this.currentStatus === "degraded" && this.failedSignals.size === 0) {
183
+ this.currentStatus = "active";
184
+ }
185
+ }
186
+ failure(signal, stage, value) {
187
+ const error = sanitizeError(toError(value));
188
+ const event = { signal, stage, error, timestamp: Date.now() };
189
+ this.currentError = event;
190
+ this.failedSignals.add(signal);
191
+ if (this.currentStatus !== "inactive" && this.currentStatus !== "stopped") {
192
+ this.currentStatus = "degraded";
193
+ }
194
+ const fingerprint = `${signal}\0${stage}\0${error.name}\0${error.message}`;
195
+ if (this.reported.has(fingerprint)) return;
196
+ this.reported.add(fingerprint);
197
+ try {
198
+ this.onError?.(event);
199
+ } catch {
200
+ }
201
+ if (!this.logging) return;
202
+ try {
203
+ process.stderr.write(
204
+ `[nest-observe] ${signal} ${stage} failed: ${redactText(error.message)}
205
+ `
206
+ );
207
+ } catch {
208
+ }
209
+ }
210
+ };
211
+ function withExporterDiagnostics(exporter, signal, diagnostics) {
212
+ const delegate = exporter;
213
+ return new Proxy(exporter, {
214
+ get(target, property) {
215
+ if (property === "export") {
216
+ return (items, callback) => {
217
+ try {
218
+ delegate.export.call(target, items, (result) => {
219
+ if (result.code === 0) {
220
+ diagnostics.success(signal);
221
+ } else {
222
+ diagnostics.failure(
223
+ signal,
224
+ "export",
225
+ result.error ?? new Error(`${signal} export failed`)
226
+ );
227
+ }
228
+ callback(result);
229
+ });
230
+ } catch (error) {
231
+ diagnostics.failure(signal, "export", error);
232
+ throw error;
233
+ }
234
+ };
235
+ }
236
+ const value = Reflect.get(target, property, target);
237
+ return typeof value === "function" ? value.bind(target) : value;
238
+ }
239
+ });
240
+ }
241
+
136
242
  // src/exceptions/process-exception-capture.ts
243
+ import { trace, SpanStatusCode } from "@opentelemetry/api";
137
244
  function asError(value) {
138
245
  return value instanceof Error ? value : new Error(String(value));
139
246
  }
@@ -387,7 +494,13 @@ var SEVERITY = {
387
494
  };
388
495
  function bodyValue(message) {
389
496
  if (message instanceof Error) return redactText(message.stack ?? `${message.name}: ${message.message}`);
390
- return redact(message);
497
+ const sanitized = redact(message);
498
+ if (sanitized === null || typeof sanitized !== "object") return sanitized;
499
+ try {
500
+ return JSON.stringify(sanitized, (_key, value) => typeof value === "bigint" ? value.toString() : value);
501
+ } catch {
502
+ return redactText(String(sanitized));
503
+ }
391
504
  }
392
505
  var NestLoggerInstrumentation = class {
393
506
  constructor(emitter, resourceAttributes = {}) {
@@ -450,7 +563,7 @@ var SEVERITY_NUMBER = {
450
563
  };
451
564
  var OpenTelemetryLogEmitter = class {
452
565
  logger;
453
- constructor(name = "@ryanzen9/nest-observe", version, logger) {
566
+ constructor(name = "@ryanzeng/nest-observe", version, logger) {
454
567
  this.logger = logger ?? logs.getLogger(name, version);
455
568
  }
456
569
  emit(record) {
@@ -494,9 +607,14 @@ var HttpRequestMetrics = class {
494
607
  errorCount;
495
608
  startedAt = /* @__PURE__ */ new WeakMap();
496
609
  start(request) {
610
+ if (this.startedAt.has(request)) return false;
497
611
  this.startedAt.set(request, process.hrtime.bigint());
612
+ return true;
498
613
  }
499
614
  record(request, response) {
615
+ const started = this.startedAt.get(request);
616
+ if (started === void 0) return false;
617
+ this.startedAt.delete(request);
500
618
  const statusCode = Number.isFinite(response.statusCode) ? response.statusCode : 0;
501
619
  const attributes = {
502
620
  "service.name": this.serviceName,
@@ -505,12 +623,9 @@ var HttpRequestMetrics = class {
505
623
  "http.response.status_code": statusCode
506
624
  };
507
625
  this.requestCount.add(1, attributes);
508
- const started = this.startedAt.get(request);
509
- if (started !== void 0) {
510
- this.requestDuration.record(Number(process.hrtime.bigint() - started) / 1e9, attributes);
511
- this.startedAt.delete(request);
512
- }
626
+ this.requestDuration.record(Number(process.hrtime.bigint() - started) / 1e9, attributes);
513
627
  if (statusCode >= 500) this.errorCount.add(1, attributes);
628
+ return true;
514
629
  }
515
630
  };
516
631
 
@@ -596,21 +711,136 @@ var RuntimeMetrics = class {
596
711
  };
597
712
 
598
713
  // src/resource.ts
714
+ import {
715
+ defaultResource,
716
+ resourceFromAttributes
717
+ } from "@opentelemetry/resources";
599
718
  import { hostname } from "os";
600
- import { defaultResource, resourceFromAttributes } from "@opentelemetry/resources";
601
- var SDK_NAME = "@ryanzen9/nest-observe";
602
- var SDK_VERSION = "0.1.0";
719
+
720
+ // package.json
721
+ var package_default = {
722
+ name: "@ryanzeng/nest-observe",
723
+ version: "0.1.2",
724
+ description: "Zero-config, vendor-neutral OpenTelemetry observability for NestJS",
725
+ keywords: [
726
+ "nestjs",
727
+ "opentelemetry",
728
+ "observability",
729
+ "otlp",
730
+ "tracing",
731
+ "metrics",
732
+ "logging"
733
+ ],
734
+ license: "MIT",
735
+ homepage: "https://github.com/ryanzen9/nest-observe#readme",
736
+ bugs: {
737
+ url: "https://github.com/ryanzen9/nest-observe/issues"
738
+ },
739
+ repository: {
740
+ type: "git",
741
+ url: "git+https://github.com/ryanzen9/nest-observe.git"
742
+ },
743
+ packageManager: "pnpm@10.13.1",
744
+ sideEffects: [
745
+ "./dist/register.js",
746
+ "./dist/register.mjs"
747
+ ],
748
+ main: "./dist/index.js",
749
+ module: "./dist/index.mjs",
750
+ types: "./dist/index.d.ts",
751
+ exports: {
752
+ ".": {
753
+ import: {
754
+ types: "./dist/index.d.mts",
755
+ default: "./dist/index.mjs"
756
+ },
757
+ require: {
758
+ types: "./dist/index.d.ts",
759
+ default: "./dist/index.js"
760
+ }
761
+ },
762
+ "./register": {
763
+ import: {
764
+ types: "./dist/register.d.mts",
765
+ default: "./dist/register.mjs"
766
+ },
767
+ require: {
768
+ types: "./dist/register.d.ts",
769
+ default: "./dist/register.js"
770
+ }
771
+ }
772
+ },
773
+ files: [
774
+ "dist",
775
+ "README.md",
776
+ "CHANGELOG.md",
777
+ "LICENSE"
778
+ ],
779
+ scripts: {
780
+ build: "tsup",
781
+ test: "vitest run",
782
+ "test:watch": "vitest",
783
+ typecheck: "tsc --noEmit",
784
+ check: "pnpm typecheck && pnpm test && pnpm build"
785
+ },
786
+ engines: {
787
+ node: ">=20"
788
+ },
789
+ peerDependencies: {
790
+ "@nestjs/common": ">=10 <13",
791
+ "@nestjs/core": ">=10 <13",
792
+ "reflect-metadata": ">=0.1.12 <1",
793
+ rxjs: "^7.1.0"
794
+ },
795
+ dependencies: {
796
+ "@opentelemetry/api": "^1.9.0",
797
+ "@opentelemetry/api-logs": "^0.221.0",
798
+ "@opentelemetry/exporter-logs-otlp-proto": "^0.221.0",
799
+ "@opentelemetry/exporter-metrics-otlp-proto": "^0.221.0",
800
+ "@opentelemetry/exporter-trace-otlp-proto": "^0.221.0",
801
+ "@opentelemetry/instrumentation": "^0.221.0",
802
+ "@opentelemetry/instrumentation-http": "^0.221.0",
803
+ "@opentelemetry/instrumentation-nestjs-core": "^0.67.0",
804
+ "@opentelemetry/resources": "^2.10.0",
805
+ "@opentelemetry/sdk-logs": "^0.221.0",
806
+ "@opentelemetry/sdk-metrics": "^2.10.0",
807
+ "@opentelemetry/sdk-trace-base": "^2.10.0",
808
+ "@opentelemetry/sdk-trace-node": "^2.10.0",
809
+ "@prisma/instrumentation": "^7.10.0"
810
+ },
811
+ devDependencies: {
812
+ "@nestjs/common": "^12.0.1",
813
+ "@nestjs/core": "^12.0.1",
814
+ "@nestjs/testing": "^12.0.1",
815
+ "@opentelemetry/sdk-trace-base": "^2.10.0",
816
+ "@types/node": "^24.0.0",
817
+ "reflect-metadata": "^0.2.2",
818
+ rxjs: "^7.8.2",
819
+ tsup: "^8.5.1",
820
+ typescript: "^5.9.3",
821
+ vitest: "^4.1.11"
822
+ },
823
+ publishConfig: {
824
+ access: "public"
825
+ }
826
+ };
827
+
828
+ // src/resource.ts
829
+ var SDK_NAME = package_default.name;
830
+ var SDK_VERSION = package_default.version;
603
831
  function createObserveResource(config) {
604
- return defaultResource().merge(resourceFromAttributes({
605
- ...config.resourceAttributes,
606
- "service.name": config.serviceName,
607
- "service.version": config.serviceVersion,
608
- "deployment.environment.name": config.environment,
609
- "service.instance.id": config.instanceId,
610
- "telemetry.sdk.name": SDK_NAME,
611
- "telemetry.sdk.version": SDK_VERSION,
612
- "host.name": config.resourceAttributes["host.name"] ?? hostname()
613
- }));
832
+ return defaultResource().merge(
833
+ resourceFromAttributes({
834
+ ...config.resourceAttributes,
835
+ "service.name": config.serviceName,
836
+ "service.version": config.serviceVersion,
837
+ "deployment.environment.name": config.environment,
838
+ "service.instance.id": config.instanceId,
839
+ "telemetry.sdk.name": SDK_NAME,
840
+ "telemetry.sdk.version": SDK_VERSION,
841
+ "host.name": config.resourceAttributes["host.name"] ?? hostname()
842
+ })
843
+ );
614
844
  }
615
845
 
616
846
  // src/security/span-redaction-processor.ts
@@ -654,14 +884,23 @@ var SpanRedactionProcessor = class {
654
884
 
655
885
  // src/sdk.ts
656
886
  var InactiveObserveHandle = class {
657
- constructor(config) {
887
+ constructor(config, diagnostics) {
658
888
  this.config = config;
889
+ this.diagnostics = diagnostics;
659
890
  }
660
891
  config;
892
+ diagnostics;
661
893
  started = false;
662
894
  tracerProvider = void 0;
663
895
  meterProvider = void 0;
664
896
  loggerProvider = void 0;
897
+ httpRequestMetrics = void 0;
898
+ get status() {
899
+ return this.diagnostics.status;
900
+ }
901
+ get lastError() {
902
+ return this.diagnostics.lastError;
903
+ }
665
904
  forceFlush() {
666
905
  return Promise.resolve();
667
906
  }
@@ -670,52 +909,73 @@ var InactiveObserveHandle = class {
670
909
  }
671
910
  };
672
911
  var ActiveObserveHandle = class {
673
- constructor(config, tracerProvider, meterProvider, loggerProvider, runtimeMetrics, loggerInstrumentation, exceptionCapture, instrumentations) {
912
+ constructor(config, tracerProvider, meterProvider, loggerProvider, httpRequestMetrics, runtimeMetrics, loggerInstrumentation, exceptionCapture, instrumentations, diagnostics) {
674
913
  this.config = config;
675
914
  this.tracerProvider = tracerProvider;
676
915
  this.meterProvider = meterProvider;
677
916
  this.loggerProvider = loggerProvider;
917
+ this.httpRequestMetrics = httpRequestMetrics;
678
918
  this.runtimeMetrics = runtimeMetrics;
679
919
  this.loggerInstrumentation = loggerInstrumentation;
680
920
  this.exceptionCapture = exceptionCapture;
681
921
  this.instrumentations = instrumentations;
922
+ this.diagnostics = diagnostics;
682
923
  }
683
924
  config;
684
925
  tracerProvider;
685
926
  meterProvider;
686
927
  loggerProvider;
928
+ httpRequestMetrics;
687
929
  runtimeMetrics;
688
930
  loggerInstrumentation;
689
931
  exceptionCapture;
690
932
  instrumentations;
933
+ diagnostics;
691
934
  started = true;
692
935
  stopped = false;
936
+ get status() {
937
+ return this.diagnostics.status;
938
+ }
939
+ get lastError() {
940
+ return this.diagnostics.lastError;
941
+ }
693
942
  async forceFlush() {
694
- await Promise.allSettled([
695
- this.tracerProvider?.forceFlush(),
696
- this.meterProvider?.forceFlush(),
697
- this.loggerProvider?.forceFlush({ timeoutMillis: this.config.exportTimeoutMillis })
698
- ].filter((item) => Boolean(item)));
943
+ await this.runSafely("forceFlush", [
944
+ ...this.config.traces && this.tracerProvider ? [["traces", () => this.tracerProvider.forceFlush()]] : [],
945
+ ...this.config.metrics && this.meterProvider ? [["metrics", () => this.meterProvider.forceFlush()]] : [],
946
+ ...this.config.logs && this.loggerProvider ? [["logs", () => this.loggerProvider.forceFlush({ timeoutMillis: this.config.exportTimeoutMillis })]] : []
947
+ ]);
699
948
  }
700
949
  async shutdown() {
701
950
  if (this.stopped) return;
702
951
  this.stopped = true;
703
952
  this.loggerInstrumentation?.disable();
704
- this.runtimeMetrics?.stop();
705
953
  this.exceptionCapture.stop();
706
954
  for (const instrumentation of this.instrumentations) {
707
955
  try {
708
956
  instrumentation.disable();
709
- } catch {
957
+ } catch (error) {
958
+ this.diagnostics.failure("sdk", "shutdown", error);
710
959
  }
711
960
  }
712
961
  await this.forceFlush();
713
- await Promise.allSettled([
714
- this.tracerProvider?.shutdown(),
715
- this.meterProvider?.shutdown(),
716
- this.loggerProvider?.shutdown()
717
- ].filter((item) => Boolean(item)));
962
+ this.runtimeMetrics?.stop();
963
+ await this.runSafely("shutdown", [
964
+ ...this.tracerProvider ? [["traces", () => this.tracerProvider.shutdown()]] : [],
965
+ ...this.meterProvider ? [["metrics", () => this.meterProvider.shutdown()]] : [],
966
+ ...this.loggerProvider ? [["logs", () => this.loggerProvider.shutdown()]] : []
967
+ ]);
718
968
  if (activeRuntime === this) activeRuntime = void 0;
969
+ this.diagnostics.stop();
970
+ }
971
+ async runSafely(stage, operations) {
972
+ await Promise.all(operations.map(async ([signal, operation]) => {
973
+ try {
974
+ await operation();
975
+ } catch (error) {
976
+ this.diagnostics.failure(signal, stage, error);
977
+ }
978
+ }));
719
979
  }
720
980
  };
721
981
  var activeRuntime;
@@ -726,12 +986,16 @@ function exporterConfig(url, config) {
726
986
  timeoutMillis: config.exportTimeoutMillis
727
987
  };
728
988
  }
729
- function createTraceProvider(config, resource) {
989
+ function createTraceProvider(config, resource, diagnostics) {
730
990
  if (!config.traces && !config.metrics) return void 0;
731
991
  const spanProcessors = [new SpanRedactionProcessor()];
732
992
  if (config.traces) {
733
993
  const exporter = config.exporters?.span ?? new OTLPTraceExporter(exporterConfig(config.endpoints.traces, config));
734
- spanProcessors.push(new BatchSpanProcessor(exporter, {
994
+ spanProcessors.push(new BatchSpanProcessor(withExporterDiagnostics(
995
+ exporter,
996
+ "traces",
997
+ diagnostics
998
+ ), {
735
999
  exportTimeoutMillis: config.exportTimeoutMillis,
736
1000
  maxQueueSize: 2048,
737
1001
  maxExportBatchSize: 512
@@ -746,14 +1010,18 @@ function createTraceProvider(config, resource) {
746
1010
  provider.register();
747
1011
  return provider;
748
1012
  }
749
- function createMeterProvider(config, resource) {
1013
+ function createMeterProvider(config, resource, diagnostics) {
750
1014
  if (!config.metrics) return void 0;
751
1015
  let reader;
752
1016
  if (config.exporters?.metricReader) {
753
1017
  reader = config.exporters.metricReader;
754
1018
  } else {
755
1019
  reader = new PeriodicExportingMetricReader({
756
- exporter: new OTLPMetricExporter(exporterConfig(config.endpoints.metrics, config)),
1020
+ exporter: withExporterDiagnostics(
1021
+ new OTLPMetricExporter(exporterConfig(config.endpoints.metrics, config)),
1022
+ "metrics",
1023
+ diagnostics
1024
+ ),
757
1025
  exportIntervalMillis: config.metricExportIntervalMillis,
758
1026
  exportTimeoutMillis: Math.min(config.exportTimeoutMillis, config.metricExportIntervalMillis),
759
1027
  cardinalityLimits: { default: 2e3, histogram: 2e3 }
@@ -771,13 +1039,13 @@ function createMeterProvider(config, resource) {
771
1039
  metrics.setGlobalMeterProvider(provider);
772
1040
  return provider;
773
1041
  }
774
- function createLoggerProvider(config, resource) {
1042
+ function createLoggerProvider(config, resource, diagnostics) {
775
1043
  if (!config.logs) return void 0;
776
1044
  const exporter = config.exporters?.log ?? new OTLPLogExporter(exporterConfig(config.endpoints.logs, config));
777
1045
  const provider = new LoggerProvider({
778
1046
  resource,
779
1047
  processors: [new BatchLogRecordProcessor({
780
- exporter,
1048
+ exporter: withExporterDiagnostics(exporter, "logs", diagnostics),
781
1049
  exportTimeoutMillis: config.exportTimeoutMillis,
782
1050
  maxQueueSize: 2048,
783
1051
  maxExportBatchSize: 512
@@ -789,7 +1057,11 @@ function createLoggerProvider(config, resource) {
789
1057
  function observe(options = {}) {
790
1058
  if (activeRuntime?.started) return activeRuntime;
791
1059
  const config = resolveObserveConfig(options);
792
- if (!config.enabled) return new InactiveObserveHandle(config);
1060
+ const diagnostics = new ObserveDiagnostics(config.diagnosticLogging, options.onError);
1061
+ if (!config.enabled) {
1062
+ diagnostics.inactive();
1063
+ return new InactiveObserveHandle(config, diagnostics);
1064
+ }
793
1065
  let tracerProvider;
794
1066
  let meterProvider;
795
1067
  let loggerProvider;
@@ -799,10 +1071,11 @@ function observe(options = {}) {
799
1071
  const instrumentations = [];
800
1072
  try {
801
1073
  const resource = createObserveResource(config);
802
- meterProvider = createMeterProvider(config, resource);
1074
+ meterProvider = createMeterProvider(config, resource, diagnostics);
803
1075
  const meter = meterProvider?.getMeter(SDK_NAME, SDK_VERSION);
804
- tracerProvider = createTraceProvider(config, resource);
805
- loggerProvider = createLoggerProvider(config, resource);
1076
+ const httpRequestMetrics = meter ? new HttpRequestMetrics(meter, config.serviceName) : void 0;
1077
+ tracerProvider = createTraceProvider(config, resource, diagnostics);
1078
+ loggerProvider = createLoggerProvider(config, resource, diagnostics);
806
1079
  runtimeMetrics = meter ? new RuntimeMetrics(meter) : void 0;
807
1080
  runtimeMetrics?.start();
808
1081
  const otelLogger = loggerProvider?.getLogger(SDK_NAME, SDK_VERSION);
@@ -817,7 +1090,6 @@ function observe(options = {}) {
817
1090
  exceptionCapture.start();
818
1091
  if (config.traces || config.metrics) {
819
1092
  const safeHeaders = config.allowedHeaders.filter((header) => !isSensitiveKey(header));
820
- const httpRequestMetrics = meter ? new HttpRequestMetrics(meter, config.serviceName) : void 0;
821
1093
  instrumentations.push(new HttpInstrumentation({
822
1094
  requireParentforOutgoingSpans: false,
823
1095
  headersToSpanAttributes: {
@@ -861,14 +1133,18 @@ function observe(options = {}) {
861
1133
  tracerProvider,
862
1134
  meterProvider,
863
1135
  loggerProvider,
1136
+ httpRequestMetrics,
864
1137
  runtimeMetrics,
865
1138
  loggerInstrumentation,
866
1139
  exceptionCapture,
867
- instrumentations
1140
+ instrumentations,
1141
+ diagnostics
868
1142
  );
1143
+ diagnostics.activate();
869
1144
  activeRuntime = runtime;
870
1145
  return runtime;
871
- } catch {
1146
+ } catch (error) {
1147
+ diagnostics.failure("sdk", "initialization", error);
872
1148
  loggerInstrumentation?.disable();
873
1149
  runtimeMetrics?.stop();
874
1150
  exceptionCapture?.stop();
@@ -883,7 +1159,9 @@ function observe(options = {}) {
883
1159
  meterProvider?.shutdown(),
884
1160
  loggerProvider?.shutdown()
885
1161
  ].filter((item) => Boolean(item)));
886
- return new InactiveObserveHandle(config);
1162
+ diagnostics.inactive();
1163
+ if (config.failFast) throw toError(error);
1164
+ return new InactiveObserveHandle(config, diagnostics);
887
1165
  }
888
1166
  }
889
1167