@atrim/instrument-node 0.7.1-dev.14fdea7.20260108232436 → 0.7.1-dev.764c183.20260110203528

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.
@@ -4,14 +4,14 @@ var effect = require('effect');
4
4
  var opentelemetry = require('@effect/opentelemetry');
5
5
  var sdkTraceBase = require('@opentelemetry/sdk-trace-base');
6
6
  var exporterTraceOtlpHttp = require('@opentelemetry/exporter-trace-otlp-http');
7
+ var resources = require('@opentelemetry/resources');
8
+ var semanticConventions = require('@opentelemetry/semantic-conventions');
9
+ var OtelApi = require('@opentelemetry/api');
7
10
  var FileSystem = require('@effect/platform/FileSystem');
8
11
  var HttpClient = require('@effect/platform/HttpClient');
9
12
  var HttpClientRequest = require('@effect/platform/HttpClientRequest');
10
13
  var yaml = require('yaml');
11
14
  var zod = require('zod');
12
- var OtelApi = require('@opentelemetry/api');
13
- var resources = require('@opentelemetry/resources');
14
- var semanticConventions = require('@opentelemetry/semantic-conventions');
15
15
  var path = require('path');
16
16
 
17
17
  function _interopNamespace(e) {
@@ -32,9 +32,9 @@ function _interopNamespace(e) {
32
32
  return Object.freeze(n);
33
33
  }
34
34
 
35
+ var OtelApi__namespace = /*#__PURE__*/_interopNamespace(OtelApi);
35
36
  var HttpClient__namespace = /*#__PURE__*/_interopNamespace(HttpClient);
36
37
  var HttpClientRequest__namespace = /*#__PURE__*/_interopNamespace(HttpClientRequest);
37
- var OtelApi__namespace = /*#__PURE__*/_interopNamespace(OtelApi);
38
38
  var path__namespace = /*#__PURE__*/_interopNamespace(path);
39
39
 
40
40
  var __defProp = Object.defineProperty;
@@ -107,6 +107,25 @@ var AutoInstrumentationConfigSchema = zod.z.object({
107
107
  // Naming rules (first match wins)
108
108
  rules: zod.z.array(SpanNamingRuleSchema).default([])
109
109
  }).default({}),
110
+ // Span relationship configuration for forked fibers
111
+ // Controls how child fiber spans relate to their parent/forking context
112
+ span_relationships: zod.z.object({
113
+ // Relationship type between forked fiber spans and their parent context
114
+ // - 'parent-child': Use parent-child relationship (default, traditional tracing)
115
+ // Parent span shows child as nested. Works well with most observability tools.
116
+ // - 'span-links': Use span links (semantically correct for async forks per OTel spec)
117
+ // Fibers get independent traces linked to parent. Better for long-running fibers.
118
+ // - 'both': Create parent-child AND add span links
119
+ // Maximum visibility but may create redundant data.
120
+ type: zod.z.enum(["parent-child", "span-links", "both"]).default("parent-child"),
121
+ // Custom attributes to add to span links (only used when type includes links)
122
+ link_attributes: zod.z.object({
123
+ // Link type identifier
124
+ "link.type": zod.z.string().default("fork"),
125
+ // Custom attributes (key-value pairs)
126
+ custom: zod.z.record(zod.z.string()).optional()
127
+ }).optional()
128
+ }).default({}),
110
129
  // Pattern-based filtering
111
130
  filter: zod.z.object({
112
131
  // Only trace spans matching these patterns (empty = trace all)
@@ -550,6 +569,9 @@ var defaultAutoTracingConfig = {
550
569
  infer_from_source: true,
551
570
  rules: []
552
571
  },
572
+ span_relationships: {
573
+ type: "parent-child"
574
+ },
553
575
  filter: {
554
576
  include: [],
555
577
  exclude: []
@@ -731,7 +753,7 @@ function sanitizeSpanName(name) {
731
753
  var AutoTracingEnabled = effect.FiberRef.unsafeMake(true);
732
754
  var AutoTracingSpanName = effect.FiberRef.unsafeMake(effect.Option.none());
733
755
  var AutoTracingSupervisor = class extends effect.Supervisor.AbstractSupervisor {
734
- constructor(config) {
756
+ constructor(config, tracerProvider) {
735
757
  super();
736
758
  this.config = config;
737
759
  // WeakMap to associate fibers with their OTel spans
@@ -740,6 +762,8 @@ var AutoTracingSupervisor = class extends effect.Supervisor.AbstractSupervisor {
740
762
  __publicField(this, "fiberStartTimes", /* @__PURE__ */ new WeakMap());
741
763
  // OpenTelemetry tracer - lazily initialized
742
764
  __publicField(this, "_tracer", null);
765
+ // Optional TracerProvider (if provided, use this instead of global)
766
+ __publicField(this, "tracerProvider", null);
743
767
  // Compiled filter patterns
744
768
  __publicField(this, "includePatterns");
745
769
  __publicField(this, "excludePatterns");
@@ -747,6 +771,10 @@ var AutoTracingSupervisor = class extends effect.Supervisor.AbstractSupervisor {
747
771
  __publicField(this, "activeFiberCount", 0);
748
772
  // Root span for parent context (set by withAutoTracing)
749
773
  __publicField(this, "_rootSpan", null);
774
+ if (tracerProvider) {
775
+ this.tracerProvider = tracerProvider;
776
+ logger.log("@atrim/auto-trace: Using provided TracerProvider");
777
+ }
750
778
  this.includePatterns = (config.filter?.include || []).map((p) => new RegExp(p));
751
779
  this.excludePatterns = (config.filter?.exclude || []).map((p) => new RegExp(p));
752
780
  logger.log("@atrim/auto-trace: Supervisor initialized");
@@ -761,11 +789,17 @@ var AutoTracingSupervisor = class extends effect.Supervisor.AbstractSupervisor {
761
789
  this._rootSpan = span;
762
790
  }
763
791
  /**
764
- * Get the tracer lazily - this allows time for the NodeSdk layer to register the global provider
792
+ * Get the tracer lazily - uses provided TracerProvider if available, otherwise uses global
765
793
  */
766
794
  get tracer() {
767
795
  if (!this._tracer) {
768
- this._tracer = OtelApi__namespace.trace.getTracer("@atrim/auto-trace", "1.0.0");
796
+ if (this.tracerProvider) {
797
+ logger.log("@atrim/auto-trace: Getting tracer from provided TracerProvider");
798
+ this._tracer = this.tracerProvider.getTracer("@atrim/auto-trace", "1.0.0");
799
+ } else {
800
+ logger.log("@atrim/auto-trace: Getting tracer from global API");
801
+ this._tracer = OtelApi__namespace.trace.getTracer("@atrim/auto-trace", "1.0.0");
802
+ }
769
803
  }
770
804
  return this._tracer;
771
805
  }
@@ -779,9 +813,11 @@ var AutoTracingSupervisor = class extends effect.Supervisor.AbstractSupervisor {
779
813
  * Called when a fiber starts executing
780
814
  */
781
815
  onStart(_context, _effect, parent, fiber) {
816
+ logger.log(`@atrim/auto-trace: onStart called for fiber ${fiber.id().id}`);
782
817
  const fiberRefsValue = fiber.getFiberRefs();
783
818
  const enabled = effect.FiberRefs.getOrDefault(fiberRefsValue, AutoTracingEnabled);
784
819
  if (!enabled) {
820
+ logger.log(`@atrim/auto-trace: Auto-tracing disabled for fiber ${fiber.id().id}`);
785
821
  return;
786
822
  }
787
823
  const samplingRate = this.config.performance?.sampling_rate ?? 1;
@@ -803,37 +839,86 @@ var AutoTracingSupervisor = class extends effect.Supervisor.AbstractSupervisor {
803
839
  if (!this.shouldTrace(spanName)) {
804
840
  return;
805
841
  }
842
+ const relationshipType = this.config.span_relationships?.type ?? "parent-child";
843
+ const useParentChild = relationshipType === "parent-child" || relationshipType === "both";
844
+ const useSpanLinks = relationshipType === "span-links" || relationshipType === "both";
806
845
  let parentContext = OtelApi__namespace.ROOT_CONTEXT;
807
846
  let parentFiberId;
808
- if (effect.Option.isSome(parent)) {
847
+ let spanLinks = [];
848
+ const maybeEffectParentSpan = effect.Context.getOption(_context, effect.Tracer.ParentSpan);
849
+ if (effect.Option.isSome(maybeEffectParentSpan)) {
850
+ const effectSpan = maybeEffectParentSpan.value;
851
+ logger.log(
852
+ `@atrim/auto-trace: Found ParentSpan - traceId=${effectSpan.traceId.slice(0, 8)}..., spanId=${effectSpan.spanId.slice(0, 8)}...`
853
+ );
854
+ const otelSpanContext = {
855
+ traceId: effectSpan.traceId,
856
+ spanId: effectSpan.spanId,
857
+ traceFlags: effectSpan.sampled ? OtelApi__namespace.TraceFlags.SAMPLED : OtelApi__namespace.TraceFlags.NONE,
858
+ isRemote: false
859
+ };
860
+ if (useParentChild) {
861
+ const wrappedSpan = OtelApi__namespace.trace.wrapSpanContext(otelSpanContext);
862
+ parentContext = OtelApi__namespace.trace.setSpan(OtelApi__namespace.ROOT_CONTEXT, wrappedSpan);
863
+ }
864
+ if (useSpanLinks) {
865
+ const linkAttributes = this.getLinkAttributes();
866
+ spanLinks.push({
867
+ context: otelSpanContext,
868
+ attributes: linkAttributes
869
+ });
870
+ logger.log(`@atrim/auto-trace: Added span link to parent (${relationshipType})`);
871
+ }
872
+ } else if (effect.Option.isSome(parent)) {
809
873
  parentFiberId = parent.value.id().id;
810
874
  const parentSpan = this.fiberSpans.get(parent.value);
811
875
  if (parentSpan) {
812
- parentContext = OtelApi__namespace.trace.setSpan(OtelApi__namespace.ROOT_CONTEXT, parentSpan);
813
- } else {
814
- const activeSpan = OtelApi__namespace.trace.getSpan(OtelApi__namespace.context.active());
815
- if (activeSpan) {
816
- parentContext = OtelApi__namespace.context.active();
817
- } else if (this._rootSpan) {
876
+ if (useParentChild) {
877
+ parentContext = OtelApi__namespace.trace.setSpan(OtelApi__namespace.ROOT_CONTEXT, parentSpan);
878
+ }
879
+ if (useSpanLinks) {
880
+ const linkAttributes = this.getLinkAttributes();
881
+ spanLinks.push({
882
+ context: parentSpan.spanContext(),
883
+ attributes: linkAttributes
884
+ });
885
+ }
886
+ } else if (this._rootSpan) {
887
+ if (useParentChild) {
818
888
  parentContext = OtelApi__namespace.trace.setSpan(OtelApi__namespace.ROOT_CONTEXT, this._rootSpan);
819
889
  }
890
+ if (useSpanLinks) {
891
+ const linkAttributes = this.getLinkAttributes();
892
+ spanLinks.push({
893
+ context: this._rootSpan.spanContext(),
894
+ attributes: linkAttributes
895
+ });
896
+ }
820
897
  }
821
- } else {
822
- const activeSpan = OtelApi__namespace.trace.getSpan(OtelApi__namespace.context.active());
823
- if (activeSpan) {
824
- parentContext = OtelApi__namespace.context.active();
825
- } else if (this._rootSpan) {
898
+ } else if (this._rootSpan) {
899
+ if (useParentChild) {
826
900
  parentContext = OtelApi__namespace.trace.setSpan(OtelApi__namespace.ROOT_CONTEXT, this._rootSpan);
827
901
  }
902
+ if (useSpanLinks) {
903
+ const linkAttributes = this.getLinkAttributes();
904
+ spanLinks.push({
905
+ context: this._rootSpan.spanContext(),
906
+ attributes: linkAttributes
907
+ });
908
+ }
828
909
  }
829
- const span = this.tracer.startSpan(
830
- spanName,
831
- {
832
- kind: OtelApi__namespace.SpanKind.INTERNAL,
833
- attributes: this.getInitialAttributes(fiber, sourceInfo, parentFiberId)
834
- },
835
- parentContext
836
- );
910
+ if (effect.Option.isSome(parent)) {
911
+ parentFiberId = parent.value.id().id;
912
+ }
913
+ const spanOptions = {
914
+ kind: OtelApi__namespace.SpanKind.INTERNAL,
915
+ attributes: this.getInitialAttributes(fiber, sourceInfo, parentFiberId)
916
+ };
917
+ if (spanLinks.length > 0) {
918
+ spanOptions.links = spanLinks;
919
+ }
920
+ const span = this.tracer.startSpan(spanName, spanOptions, parentContext);
921
+ logger.log(`@atrim/auto-trace: Created span "${spanName}" for fiber ${fiber.id().id}`);
837
922
  this.fiberSpans.set(fiber, span);
838
923
  this.fiberStartTimes.set(fiber, process.hrtime.bigint());
839
924
  this.activeFiberCount++;
@@ -842,8 +927,12 @@ var AutoTracingSupervisor = class extends effect.Supervisor.AbstractSupervisor {
842
927
  * Called when a fiber completes (success or failure)
843
928
  */
844
929
  onEnd(exit, fiber) {
930
+ logger.log(`@atrim/auto-trace: onEnd called for fiber ${fiber.id().id}`);
845
931
  const span = this.fiberSpans.get(fiber);
846
932
  if (!span) {
933
+ logger.log(
934
+ `@atrim/auto-trace: No span found for fiber ${fiber.id().id} (skipped or filtered)`
935
+ );
847
936
  return;
848
937
  }
849
938
  const startTime = this.fiberStartTimes.get(fiber);
@@ -867,10 +956,26 @@ var AutoTracingSupervisor = class extends effect.Supervisor.AbstractSupervisor {
867
956
  span.setAttribute("effect.fiber.failed", true);
868
957
  }
869
958
  span.end();
959
+ logger.log(`@atrim/auto-trace: Ended span for fiber ${fiber.id().id}`);
870
960
  this.fiberSpans.delete(fiber);
871
961
  this.fiberStartTimes.delete(fiber);
872
962
  this.activeFiberCount--;
873
963
  }
964
+ /**
965
+ * Get attributes for span links from config
966
+ */
967
+ getLinkAttributes() {
968
+ const linkConfig = this.config.span_relationships?.link_attributes;
969
+ const attrs = {
970
+ "link.type": linkConfig?.["link.type"] ?? "fork"
971
+ };
972
+ if (linkConfig?.custom) {
973
+ for (const [key, value] of Object.entries(linkConfig.custom)) {
974
+ attrs[key] = value;
975
+ }
976
+ }
977
+ return attrs;
978
+ }
874
979
  /**
875
980
  * Check if a span name should be traced based on filter patterns
876
981
  */
@@ -961,8 +1066,8 @@ var AutoTracingSupervisor = class extends effect.Supervisor.AbstractSupervisor {
961
1066
  }
962
1067
  }
963
1068
  };
964
- var createAutoTracingSupervisor = (config) => {
965
- return new AutoTracingSupervisor(config);
1069
+ var createAutoTracingSupervisor = (config, tracerProvider) => {
1070
+ return new AutoTracingSupervisor(config, tracerProvider);
966
1071
  };
967
1072
  var createAutoTracingLayer = (options) => {
968
1073
  return effect.Layer.unwrapEffect(
@@ -1042,7 +1147,9 @@ var createExporterLayer = (exporterConfig, serviceName, serviceVersion) => {
1042
1147
  };
1043
1148
  if (config.headers) {
1044
1149
  exporterConfig2.headers = config.headers;
1045
- logger.log(`@atrim/auto-trace: Using custom headers: ${Object.keys(config.headers).join(", ")}`);
1150
+ logger.log(
1151
+ `@atrim/auto-trace: Using custom headers: ${Object.keys(config.headers).join(", ")}`
1152
+ );
1046
1153
  }
1047
1154
  return new exporterTraceOtlpHttp.OTLPTraceExporter(exporterConfig2);
1048
1155
  };
@@ -1135,7 +1242,9 @@ var createEffectTracingLayer = () => {
1135
1242
  };
1136
1243
  if (exporterConfig.headers) {
1137
1244
  otlpConfig.headers = exporterConfig.headers;
1138
- logger.log(`@atrim/auto-trace: Using custom headers: ${Object.keys(exporterConfig.headers).join(", ")}`);
1245
+ logger.log(
1246
+ `@atrim/auto-trace: Using custom headers: ${Object.keys(exporterConfig.headers).join(", ")}`
1247
+ );
1139
1248
  }
1140
1249
  const exporter = new exporterTraceOtlpHttp.OTLPTraceExporter(otlpConfig);
1141
1250
  if (exporterConfig.processor === "simple") {
@@ -1183,6 +1292,9 @@ var createCombinedTracingLayer = () => {
1183
1292
  infer_from_source: true,
1184
1293
  rules: []
1185
1294
  },
1295
+ span_relationships: {
1296
+ type: "parent-child"
1297
+ },
1186
1298
  filter: { include: [], exclude: [] },
1187
1299
  performance: { sampling_rate: 1, min_duration: "0ms", max_concurrent: 0 },
1188
1300
  metadata: { fiber_info: true, source_location: true, parent_fiber: true }
@@ -1194,10 +1306,10 @@ var createCombinedTracingLayer = () => {
1194
1306
  logger.log('@atrim/auto-trace: Exporter type is "none", using empty layer');
1195
1307
  return effect.Layer.empty;
1196
1308
  }
1197
- const createSpanProcessor = () => {
1309
+ const createSpanExporter = () => {
1198
1310
  if (exporterConfig.type === "console") {
1199
- logger.log("@atrim/auto-trace: Using ConsoleSpanExporter with SimpleSpanProcessor");
1200
- return new sdkTraceBase.SimpleSpanProcessor(new sdkTraceBase.ConsoleSpanExporter());
1311
+ logger.log("@atrim/auto-trace: Using ConsoleSpanExporter");
1312
+ return new sdkTraceBase.ConsoleSpanExporter();
1201
1313
  }
1202
1314
  const endpoint = exporterConfig.endpoint || process.env.OTEL_EXPORTER_OTLP_ENDPOINT || "http://localhost:4318";
1203
1315
  logger.log(`@atrim/auto-trace: Using OTLPTraceExporter (${endpoint})`);
@@ -1206,10 +1318,15 @@ var createCombinedTracingLayer = () => {
1206
1318
  };
1207
1319
  if (exporterConfig.headers) {
1208
1320
  otlpConfig.headers = exporterConfig.headers;
1209
- logger.log(`@atrim/auto-trace: Using custom headers: ${Object.keys(exporterConfig.headers).join(", ")}`);
1321
+ logger.log(
1322
+ `@atrim/auto-trace: Using custom headers: ${Object.keys(exporterConfig.headers).join(", ")}`
1323
+ );
1210
1324
  }
1211
- const exporter = new exporterTraceOtlpHttp.OTLPTraceExporter(otlpConfig);
1212
- if (exporterConfig.processor === "simple") {
1325
+ return new exporterTraceOtlpHttp.OTLPTraceExporter(otlpConfig);
1326
+ };
1327
+ const createSpanProcessor = () => {
1328
+ const exporter = createSpanExporter();
1329
+ if (exporterConfig.processor === "simple" || exporterConfig.type === "console") {
1213
1330
  logger.log("@atrim/auto-trace: Using SimpleSpanProcessor");
1214
1331
  return new sdkTraceBase.SimpleSpanProcessor(exporter);
1215
1332
  }
@@ -1223,19 +1340,31 @@ var createCombinedTracingLayer = () => {
1223
1340
  maxExportBatchSize: batchConfig.max_export_batch_size
1224
1341
  });
1225
1342
  };
1226
- const sdkLayer = opentelemetry.NodeSdk.layer(() => ({
1227
- resource: {
1228
- serviceName,
1229
- serviceVersion
1230
- },
1231
- spanProcessor: createSpanProcessor()
1232
- }));
1343
+ const globalProviderLayer = effect.Layer.effectDiscard(
1344
+ effect.Effect.sync(() => {
1345
+ const provider = new sdkTraceBase.BasicTracerProvider({
1346
+ resource: resources.resourceFromAttributes({
1347
+ [semanticConventions.ATTR_SERVICE_NAME]: serviceName,
1348
+ [semanticConventions.ATTR_SERVICE_VERSION]: serviceVersion
1349
+ }),
1350
+ spanProcessors: [createSpanProcessor()]
1351
+ });
1352
+ OtelApi__namespace.trace.setGlobalTracerProvider(provider);
1353
+ logger.log("@atrim/auto-trace: Global TracerProvider registered");
1354
+ })
1355
+ );
1356
+ const resourceLayer = opentelemetry.Resource.layer({
1357
+ serviceName,
1358
+ serviceVersion
1359
+ });
1360
+ const effectTracerLayer = opentelemetry.Tracer.layerGlobal;
1233
1361
  const supervisor = createAutoTracingSupervisor(autoConfig);
1234
1362
  const supervisorLayer = effect.Supervisor.addSupervisor(supervisor);
1235
1363
  logger.log("@atrim/auto-trace: Combined layer created");
1236
- logger.log(" - HTTP requests: auto-traced via Effect platform");
1237
- logger.log(" - Forked fibers: auto-traced via Supervisor");
1238
- return supervisorLayer.pipe(effect.Layer.provideMerge(effect.Layer.discard(sdkLayer)));
1364
+ logger.log(" - HTTP requests: auto-traced via Effect platform (global provider)");
1365
+ logger.log(" - Forked fibers: auto-traced via Supervisor (global provider)");
1366
+ const tracerWithResource = effectTracerLayer.pipe(effect.Layer.provide(resourceLayer));
1367
+ return effect.Layer.mergeAll(globalProviderLayer, effect.Layer.discard(tracerWithResource), supervisorLayer);
1239
1368
  })
1240
1369
  );
1241
1370
  };