@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.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;
@@ -232,19 +235,28 @@ function invokeWithSpan(tracer, spanName, attributes, invoke, onFinish) {
232
235
  // src/decorators/trace.decorator.ts
233
236
  function decorateMethod(target, propertyKey, descriptor, options) {
234
237
  const original = descriptor.value;
235
- if (typeof original !== "function" || isTraceIgnored(original, target.constructor)) return;
238
+ if (typeof original !== "function" || isTraceIgnored(original, target.constructor))
239
+ return;
236
240
  const className = target.constructor?.name || "Anonymous";
237
241
  const methodName = String(propertyKey);
238
242
  const wrapped = function(...args) {
239
- const tracer = import_api2.trace.getTracer("@ryanzen9/nest-observe");
240
- return invokeWithSpan(tracer, options.name ?? `${className}.${methodName}`, {
241
- "code.function.name": methodName,
242
- "nestjs.class": className,
243
- "nestjs.method": methodName,
244
- ...options.attributes
245
- }, () => original.apply(this, args));
243
+ const tracer = import_api2.trace.getTracer("@ryanzeng/nest-observe");
244
+ return invokeWithSpan(
245
+ tracer,
246
+ options.name ?? `${className}.${methodName}`,
247
+ {
248
+ "code.function.name": methodName,
249
+ "nestjs.class": className,
250
+ "nestjs.method": methodName,
251
+ ...options.attributes
252
+ },
253
+ () => original.apply(this, args)
254
+ );
246
255
  };
247
- Object.defineProperty(wrapped, "name", { value: original.name, configurable: true });
256
+ Object.defineProperty(wrapped, "name", {
257
+ value: original.name,
258
+ configurable: true
259
+ });
248
260
  markTraceDecorated(wrapped);
249
261
  descriptor.value = wrapped;
250
262
  }
@@ -326,7 +338,13 @@ var SEVERITY = {
326
338
  };
327
339
  function bodyValue(message) {
328
340
  if (message instanceof Error) return redactText(message.stack ?? `${message.name}: ${message.message}`);
329
- 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
+ }
330
348
  }
331
349
  var NestLoggerInstrumentation = class {
332
350
  constructor(emitter, resourceAttributes = {}) {
@@ -389,7 +407,7 @@ var SEVERITY_NUMBER = {
389
407
  };
390
408
  var OpenTelemetryLogEmitter = class {
391
409
  logger;
392
- constructor(name = "@ryanzen9/nest-observe", version, logger) {
410
+ constructor(name = "@ryanzeng/nest-observe", version, logger) {
393
411
  this.logger = logger ?? import_api_logs.logs.getLogger(name, version);
394
412
  }
395
413
  emit(record) {
@@ -560,9 +578,14 @@ var HttpRequestMetrics = class {
560
578
  errorCount;
561
579
  startedAt = /* @__PURE__ */ new WeakMap();
562
580
  start(request) {
581
+ if (this.startedAt.has(request)) return false;
563
582
  this.startedAt.set(request, process.hrtime.bigint());
583
+ return true;
564
584
  }
565
585
  record(request, response) {
586
+ const started = this.startedAt.get(request);
587
+ if (started === void 0) return false;
588
+ this.startedAt.delete(request);
566
589
  const statusCode = Number.isFinite(response.statusCode) ? response.statusCode : 0;
567
590
  const attributes = {
568
591
  "service.name": this.serviceName,
@@ -571,12 +594,9 @@ var HttpRequestMetrics = class {
571
594
  "http.response.status_code": statusCode
572
595
  };
573
596
  this.requestCount.add(1, attributes);
574
- const started = this.startedAt.get(request);
575
- if (started !== void 0) {
576
- this.requestDuration.record(Number(process.hrtime.bigint() - started) / 1e9, attributes);
577
- this.startedAt.delete(request);
578
- }
597
+ this.requestDuration.record(Number(process.hrtime.bigint() - started) / 1e9, attributes);
579
598
  if (statusCode >= 500) this.errorCount.add(1, attributes);
599
+ return true;
580
600
  }
581
601
  };
582
602
 
@@ -665,6 +685,7 @@ var RuntimeMetrics = class {
665
685
  var import_common2 = require("@nestjs/common");
666
686
  var import_core = require("@nestjs/core");
667
687
  var import_api7 = require("@opentelemetry/api");
688
+ var import_rxjs2 = require("rxjs");
668
689
 
669
690
  // src/sdk.ts
670
691
  var import_api_logs2 = require("@opentelemetry/api-logs");
@@ -680,6 +701,113 @@ var import_sdk_metrics = require("@opentelemetry/sdk-metrics");
680
701
  var import_sdk_trace_base = require("@opentelemetry/sdk-trace-base");
681
702
  var import_sdk_trace_node = require("@opentelemetry/sdk-trace-node");
682
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
+
683
811
  // src/exceptions/process-exception-capture.ts
684
812
  var import_api5 = require("@opentelemetry/api");
685
813
  function asError(value) {
@@ -727,21 +855,133 @@ var ProcessExceptionCapture = class {
727
855
  };
728
856
 
729
857
  // src/resource.ts
730
- var import_node_os2 = require("os");
731
858
  var import_resources = require("@opentelemetry/resources");
732
- var SDK_NAME = "@ryanzen9/nest-observe";
733
- var SDK_VERSION = "0.1.0";
859
+ var import_node_os2 = require("os");
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;
734
972
  function createObserveResource(config) {
735
- return (0, import_resources.defaultResource)().merge((0, import_resources.resourceFromAttributes)({
736
- ...config.resourceAttributes,
737
- "service.name": config.serviceName,
738
- "service.version": config.serviceVersion,
739
- "deployment.environment.name": config.environment,
740
- "service.instance.id": config.instanceId,
741
- "telemetry.sdk.name": SDK_NAME,
742
- "telemetry.sdk.version": SDK_VERSION,
743
- "host.name": config.resourceAttributes["host.name"] ?? (0, import_node_os2.hostname)()
744
- }));
973
+ return (0, import_resources.defaultResource)().merge(
974
+ (0, import_resources.resourceFromAttributes)({
975
+ ...config.resourceAttributes,
976
+ "service.name": config.serviceName,
977
+ "service.version": config.serviceVersion,
978
+ "deployment.environment.name": config.environment,
979
+ "service.instance.id": config.instanceId,
980
+ "telemetry.sdk.name": SDK_NAME,
981
+ "telemetry.sdk.version": SDK_VERSION,
982
+ "host.name": config.resourceAttributes["host.name"] ?? (0, import_node_os2.hostname)()
983
+ })
984
+ );
745
985
  }
746
986
 
747
987
  // src/security/span-redaction-processor.ts
@@ -785,14 +1025,23 @@ var SpanRedactionProcessor = class {
785
1025
 
786
1026
  // src/sdk.ts
787
1027
  var InactiveObserveHandle = class {
788
- constructor(config) {
1028
+ constructor(config, diagnostics) {
789
1029
  this.config = config;
1030
+ this.diagnostics = diagnostics;
790
1031
  }
791
1032
  config;
1033
+ diagnostics;
792
1034
  started = false;
793
1035
  tracerProvider = void 0;
794
1036
  meterProvider = void 0;
795
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
+ }
796
1045
  forceFlush() {
797
1046
  return Promise.resolve();
798
1047
  }
@@ -801,52 +1050,73 @@ var InactiveObserveHandle = class {
801
1050
  }
802
1051
  };
803
1052
  var ActiveObserveHandle = class {
804
- constructor(config, tracerProvider, meterProvider, loggerProvider, runtimeMetrics, loggerInstrumentation, exceptionCapture, instrumentations) {
1053
+ constructor(config, tracerProvider, meterProvider, loggerProvider, httpRequestMetrics, runtimeMetrics, loggerInstrumentation, exceptionCapture, instrumentations, diagnostics) {
805
1054
  this.config = config;
806
1055
  this.tracerProvider = tracerProvider;
807
1056
  this.meterProvider = meterProvider;
808
1057
  this.loggerProvider = loggerProvider;
1058
+ this.httpRequestMetrics = httpRequestMetrics;
809
1059
  this.runtimeMetrics = runtimeMetrics;
810
1060
  this.loggerInstrumentation = loggerInstrumentation;
811
1061
  this.exceptionCapture = exceptionCapture;
812
1062
  this.instrumentations = instrumentations;
1063
+ this.diagnostics = diagnostics;
813
1064
  }
814
1065
  config;
815
1066
  tracerProvider;
816
1067
  meterProvider;
817
1068
  loggerProvider;
1069
+ httpRequestMetrics;
818
1070
  runtimeMetrics;
819
1071
  loggerInstrumentation;
820
1072
  exceptionCapture;
821
1073
  instrumentations;
1074
+ diagnostics;
822
1075
  started = true;
823
1076
  stopped = false;
1077
+ get status() {
1078
+ return this.diagnostics.status;
1079
+ }
1080
+ get lastError() {
1081
+ return this.diagnostics.lastError;
1082
+ }
824
1083
  async forceFlush() {
825
- await Promise.allSettled([
826
- this.tracerProvider?.forceFlush(),
827
- this.meterProvider?.forceFlush(),
828
- this.loggerProvider?.forceFlush({ timeoutMillis: this.config.exportTimeoutMillis })
829
- ].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
+ ]);
830
1089
  }
831
1090
  async shutdown() {
832
1091
  if (this.stopped) return;
833
1092
  this.stopped = true;
834
1093
  this.loggerInstrumentation?.disable();
835
- this.runtimeMetrics?.stop();
836
1094
  this.exceptionCapture.stop();
837
1095
  for (const instrumentation of this.instrumentations) {
838
1096
  try {
839
1097
  instrumentation.disable();
840
- } catch {
1098
+ } catch (error) {
1099
+ this.diagnostics.failure("sdk", "shutdown", error);
841
1100
  }
842
1101
  }
843
1102
  await this.forceFlush();
844
- await Promise.allSettled([
845
- this.tracerProvider?.shutdown(),
846
- this.meterProvider?.shutdown(),
847
- this.loggerProvider?.shutdown()
848
- ].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
+ ]);
849
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
+ }));
850
1120
  }
851
1121
  };
852
1122
  var activeRuntime;
@@ -857,12 +1127,16 @@ function exporterConfig(url, config) {
857
1127
  timeoutMillis: config.exportTimeoutMillis
858
1128
  };
859
1129
  }
860
- function createTraceProvider(config, resource) {
1130
+ function createTraceProvider(config, resource, diagnostics) {
861
1131
  if (!config.traces && !config.metrics) return void 0;
862
1132
  const spanProcessors = [new SpanRedactionProcessor()];
863
1133
  if (config.traces) {
864
1134
  const exporter = config.exporters?.span ?? new import_exporter_trace_otlp_proto.OTLPTraceExporter(exporterConfig(config.endpoints.traces, config));
865
- 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
+ ), {
866
1140
  exportTimeoutMillis: config.exportTimeoutMillis,
867
1141
  maxQueueSize: 2048,
868
1142
  maxExportBatchSize: 512
@@ -877,14 +1151,18 @@ function createTraceProvider(config, resource) {
877
1151
  provider.register();
878
1152
  return provider;
879
1153
  }
880
- function createMeterProvider(config, resource) {
1154
+ function createMeterProvider(config, resource, diagnostics) {
881
1155
  if (!config.metrics) return void 0;
882
1156
  let reader;
883
1157
  if (config.exporters?.metricReader) {
884
1158
  reader = config.exporters.metricReader;
885
1159
  } else {
886
1160
  reader = new import_sdk_metrics.PeriodicExportingMetricReader({
887
- 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
+ ),
888
1166
  exportIntervalMillis: config.metricExportIntervalMillis,
889
1167
  exportTimeoutMillis: Math.min(config.exportTimeoutMillis, config.metricExportIntervalMillis),
890
1168
  cardinalityLimits: { default: 2e3, histogram: 2e3 }
@@ -902,13 +1180,13 @@ function createMeterProvider(config, resource) {
902
1180
  import_api6.metrics.setGlobalMeterProvider(provider);
903
1181
  return provider;
904
1182
  }
905
- function createLoggerProvider(config, resource) {
1183
+ function createLoggerProvider(config, resource, diagnostics) {
906
1184
  if (!config.logs) return void 0;
907
1185
  const exporter = config.exporters?.log ?? new import_exporter_logs_otlp_proto.OTLPLogExporter(exporterConfig(config.endpoints.logs, config));
908
1186
  const provider = new import_sdk_logs.LoggerProvider({
909
1187
  resource,
910
1188
  processors: [new import_sdk_logs.BatchLogRecordProcessor({
911
- exporter,
1189
+ exporter: withExporterDiagnostics(exporter, "logs", diagnostics),
912
1190
  exportTimeoutMillis: config.exportTimeoutMillis,
913
1191
  maxQueueSize: 2048,
914
1192
  maxExportBatchSize: 512
@@ -920,7 +1198,11 @@ function createLoggerProvider(config, resource) {
920
1198
  function observe(options = {}) {
921
1199
  if (activeRuntime?.started) return activeRuntime;
922
1200
  const config = resolveObserveConfig(options);
923
- 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
+ }
924
1206
  let tracerProvider;
925
1207
  let meterProvider;
926
1208
  let loggerProvider;
@@ -930,10 +1212,11 @@ function observe(options = {}) {
930
1212
  const instrumentations = [];
931
1213
  try {
932
1214
  const resource = createObserveResource(config);
933
- meterProvider = createMeterProvider(config, resource);
1215
+ meterProvider = createMeterProvider(config, resource, diagnostics);
934
1216
  const meter = meterProvider?.getMeter(SDK_NAME, SDK_VERSION);
935
- tracerProvider = createTraceProvider(config, resource);
936
- 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);
937
1220
  runtimeMetrics = meter ? new RuntimeMetrics(meter) : void 0;
938
1221
  runtimeMetrics?.start();
939
1222
  const otelLogger = loggerProvider?.getLogger(SDK_NAME, SDK_VERSION);
@@ -948,7 +1231,6 @@ function observe(options = {}) {
948
1231
  exceptionCapture.start();
949
1232
  if (config.traces || config.metrics) {
950
1233
  const safeHeaders = config.allowedHeaders.filter((header) => !isSensitiveKey(header));
951
- const httpRequestMetrics = meter ? new HttpRequestMetrics(meter, config.serviceName) : void 0;
952
1234
  instrumentations.push(new import_instrumentation_http.HttpInstrumentation({
953
1235
  requireParentforOutgoingSpans: false,
954
1236
  headersToSpanAttributes: {
@@ -992,14 +1274,18 @@ function observe(options = {}) {
992
1274
  tracerProvider,
993
1275
  meterProvider,
994
1276
  loggerProvider,
1277
+ httpRequestMetrics,
995
1278
  runtimeMetrics,
996
1279
  loggerInstrumentation,
997
1280
  exceptionCapture,
998
- instrumentations
1281
+ instrumentations,
1282
+ diagnostics
999
1283
  );
1284
+ diagnostics.activate();
1000
1285
  activeRuntime = runtime;
1001
1286
  return runtime;
1002
- } catch {
1287
+ } catch (error) {
1288
+ diagnostics.failure("sdk", "initialization", error);
1003
1289
  loggerInstrumentation?.disable();
1004
1290
  runtimeMetrics?.stop();
1005
1291
  exceptionCapture?.stop();
@@ -1014,7 +1300,9 @@ function observe(options = {}) {
1014
1300
  meterProvider?.shutdown(),
1015
1301
  loggerProvider?.shutdown()
1016
1302
  ].filter((item) => Boolean(item)));
1017
- return new InactiveObserveHandle(config);
1303
+ diagnostics.inactive();
1304
+ if (config.failFast) throw toError(error);
1305
+ return new InactiveObserveHandle(config, diagnostics);
1018
1306
  }
1019
1307
  }
1020
1308
  function getObserveRuntime() {
@@ -1024,6 +1312,37 @@ function getObserveRuntime() {
1024
1312
  // src/nest/observe.module.ts
1025
1313
  var OBSERVE_OPTIONS = /* @__PURE__ */ Symbol("OBSERVE_OPTIONS");
1026
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);
1027
1346
  var NestObserveExplorer = class {
1028
1347
  constructor(discovery, options, handle) {
1029
1348
  this.discovery = discovery;
@@ -1077,6 +1396,7 @@ var ObserveModule = class {
1077
1396
  providers: [
1078
1397
  { provide: OBSERVE_OPTIONS, useValue: options },
1079
1398
  { provide: OBSERVE_HANDLE, useFactory: () => observe(options) },
1399
+ { provide: import_core.APP_INTERCEPTOR, useClass: NestHttpMetricsInterceptor },
1080
1400
  NestObserveExplorer
1081
1401
  ],
1082
1402
  exports: [OBSERVE_HANDLE]
@@ -1092,6 +1412,7 @@ ObserveModule = __decorateClass([
1092
1412
  CompatibleNestInstrumentation,
1093
1413
  HttpRequestMetrics,
1094
1414
  IgnoreTrace,
1415
+ NestHttpMetricsInterceptor,
1095
1416
  NestLoggerInstrumentation,
1096
1417
  NestMethodInstrumenter,
1097
1418
  OBSERVE_HANDLE,