@atrim/instrument-node 0.7.1-dev.14fdea7.20260109021705 → 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: []
@@ -817,42 +839,85 @@ var AutoTracingSupervisor = class extends effect.Supervisor.AbstractSupervisor {
817
839
  if (!this.shouldTrace(spanName)) {
818
840
  return;
819
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";
820
845
  let parentContext = OtelApi__namespace.ROOT_CONTEXT;
821
846
  let parentFiberId;
847
+ let spanLinks = [];
822
848
  const maybeEffectParentSpan = effect.Context.getOption(_context, effect.Tracer.ParentSpan);
823
849
  if (effect.Option.isSome(maybeEffectParentSpan)) {
824
850
  const effectSpan = maybeEffectParentSpan.value;
825
- logger.log(`@atrim/auto-trace: Found ParentSpan - traceId=${effectSpan.traceId.slice(0, 8)}..., spanId=${effectSpan.spanId.slice(0, 8)}...`);
851
+ logger.log(
852
+ `@atrim/auto-trace: Found ParentSpan - traceId=${effectSpan.traceId.slice(0, 8)}..., spanId=${effectSpan.spanId.slice(0, 8)}...`
853
+ );
826
854
  const otelSpanContext = {
827
855
  traceId: effectSpan.traceId,
828
856
  spanId: effectSpan.spanId,
829
857
  traceFlags: effectSpan.sampled ? OtelApi__namespace.TraceFlags.SAMPLED : OtelApi__namespace.TraceFlags.NONE,
830
858
  isRemote: false
831
859
  };
832
- const wrappedSpan = OtelApi__namespace.trace.wrapSpanContext(otelSpanContext);
833
- parentContext = OtelApi__namespace.trace.setSpan(OtelApi__namespace.ROOT_CONTEXT, wrappedSpan);
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
+ }
834
872
  } else if (effect.Option.isSome(parent)) {
835
873
  parentFiberId = parent.value.id().id;
836
874
  const parentSpan = this.fiberSpans.get(parent.value);
837
875
  if (parentSpan) {
838
- parentContext = OtelApi__namespace.trace.setSpan(OtelApi__namespace.ROOT_CONTEXT, parentSpan);
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
+ }
839
886
  } else if (this._rootSpan) {
840
- parentContext = OtelApi__namespace.trace.setSpan(OtelApi__namespace.ROOT_CONTEXT, this._rootSpan);
887
+ if (useParentChild) {
888
+ parentContext = OtelApi__namespace.trace.setSpan(OtelApi__namespace.ROOT_CONTEXT, this._rootSpan);
889
+ }
890
+ if (useSpanLinks) {
891
+ const linkAttributes = this.getLinkAttributes();
892
+ spanLinks.push({
893
+ context: this._rootSpan.spanContext(),
894
+ attributes: linkAttributes
895
+ });
896
+ }
841
897
  }
842
898
  } else if (this._rootSpan) {
843
- parentContext = OtelApi__namespace.trace.setSpan(OtelApi__namespace.ROOT_CONTEXT, this._rootSpan);
899
+ if (useParentChild) {
900
+ parentContext = OtelApi__namespace.trace.setSpan(OtelApi__namespace.ROOT_CONTEXT, this._rootSpan);
901
+ }
902
+ if (useSpanLinks) {
903
+ const linkAttributes = this.getLinkAttributes();
904
+ spanLinks.push({
905
+ context: this._rootSpan.spanContext(),
906
+ attributes: linkAttributes
907
+ });
908
+ }
844
909
  }
845
910
  if (effect.Option.isSome(parent)) {
846
911
  parentFiberId = parent.value.id().id;
847
912
  }
848
- const span = this.tracer.startSpan(
849
- spanName,
850
- {
851
- kind: OtelApi__namespace.SpanKind.INTERNAL,
852
- attributes: this.getInitialAttributes(fiber, sourceInfo, parentFiberId)
853
- },
854
- parentContext
855
- );
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);
856
921
  logger.log(`@atrim/auto-trace: Created span "${spanName}" for fiber ${fiber.id().id}`);
857
922
  this.fiberSpans.set(fiber, span);
858
923
  this.fiberStartTimes.set(fiber, process.hrtime.bigint());
@@ -862,8 +927,12 @@ var AutoTracingSupervisor = class extends effect.Supervisor.AbstractSupervisor {
862
927
  * Called when a fiber completes (success or failure)
863
928
  */
864
929
  onEnd(exit, fiber) {
930
+ logger.log(`@atrim/auto-trace: onEnd called for fiber ${fiber.id().id}`);
865
931
  const span = this.fiberSpans.get(fiber);
866
932
  if (!span) {
933
+ logger.log(
934
+ `@atrim/auto-trace: No span found for fiber ${fiber.id().id} (skipped or filtered)`
935
+ );
867
936
  return;
868
937
  }
869
938
  const startTime = this.fiberStartTimes.get(fiber);
@@ -887,10 +956,26 @@ var AutoTracingSupervisor = class extends effect.Supervisor.AbstractSupervisor {
887
956
  span.setAttribute("effect.fiber.failed", true);
888
957
  }
889
958
  span.end();
959
+ logger.log(`@atrim/auto-trace: Ended span for fiber ${fiber.id().id}`);
890
960
  this.fiberSpans.delete(fiber);
891
961
  this.fiberStartTimes.delete(fiber);
892
962
  this.activeFiberCount--;
893
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
+ }
894
979
  /**
895
980
  * Check if a span name should be traced based on filter patterns
896
981
  */
@@ -1062,7 +1147,9 @@ var createExporterLayer = (exporterConfig, serviceName, serviceVersion) => {
1062
1147
  };
1063
1148
  if (config.headers) {
1064
1149
  exporterConfig2.headers = config.headers;
1065
- 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
+ );
1066
1153
  }
1067
1154
  return new exporterTraceOtlpHttp.OTLPTraceExporter(exporterConfig2);
1068
1155
  };
@@ -1155,7 +1242,9 @@ var createEffectTracingLayer = () => {
1155
1242
  };
1156
1243
  if (exporterConfig.headers) {
1157
1244
  otlpConfig.headers = exporterConfig.headers;
1158
- 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
+ );
1159
1248
  }
1160
1249
  const exporter = new exporterTraceOtlpHttp.OTLPTraceExporter(otlpConfig);
1161
1250
  if (exporterConfig.processor === "simple") {
@@ -1203,6 +1292,9 @@ var createCombinedTracingLayer = () => {
1203
1292
  infer_from_source: true,
1204
1293
  rules: []
1205
1294
  },
1295
+ span_relationships: {
1296
+ type: "parent-child"
1297
+ },
1206
1298
  filter: { include: [], exclude: [] },
1207
1299
  performance: { sampling_rate: 1, min_duration: "0ms", max_concurrent: 0 },
1208
1300
  metadata: { fiber_info: true, source_location: true, parent_fiber: true }
@@ -1214,10 +1306,10 @@ var createCombinedTracingLayer = () => {
1214
1306
  logger.log('@atrim/auto-trace: Exporter type is "none", using empty layer');
1215
1307
  return effect.Layer.empty;
1216
1308
  }
1217
- const createSpanProcessor = () => {
1309
+ const createSpanExporter = () => {
1218
1310
  if (exporterConfig.type === "console") {
1219
- logger.log("@atrim/auto-trace: Using ConsoleSpanExporter with SimpleSpanProcessor");
1220
- return new sdkTraceBase.SimpleSpanProcessor(new sdkTraceBase.ConsoleSpanExporter());
1311
+ logger.log("@atrim/auto-trace: Using ConsoleSpanExporter");
1312
+ return new sdkTraceBase.ConsoleSpanExporter();
1221
1313
  }
1222
1314
  const endpoint = exporterConfig.endpoint || process.env.OTEL_EXPORTER_OTLP_ENDPOINT || "http://localhost:4318";
1223
1315
  logger.log(`@atrim/auto-trace: Using OTLPTraceExporter (${endpoint})`);
@@ -1226,10 +1318,15 @@ var createCombinedTracingLayer = () => {
1226
1318
  };
1227
1319
  if (exporterConfig.headers) {
1228
1320
  otlpConfig.headers = exporterConfig.headers;
1229
- 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
+ );
1230
1324
  }
1231
- const exporter = new exporterTraceOtlpHttp.OTLPTraceExporter(otlpConfig);
1232
- 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") {
1233
1330
  logger.log("@atrim/auto-trace: Using SimpleSpanProcessor");
1234
1331
  return new sdkTraceBase.SimpleSpanProcessor(exporter);
1235
1332
  }
@@ -1243,34 +1340,31 @@ var createCombinedTracingLayer = () => {
1243
1340
  maxExportBatchSize: batchConfig.max_export_batch_size
1244
1341
  });
1245
1342
  };
1246
- const sdkLayer = opentelemetry.NodeSdk.layer(() => ({
1247
- resource: {
1248
- serviceName,
1249
- serviceVersion
1250
- },
1251
- spanProcessor: createSpanProcessor()
1252
- }));
1253
- const supervisorLayer = effect.Layer.unwrapScoped(
1254
- effect.Effect.gen(function* () {
1255
- const tracerProvider = yield* effect.Effect.serviceOption(opentelemetry.Tracer.OtelTracerProvider);
1256
- if (effect.Option.isSome(tracerProvider)) {
1257
- logger.log("@atrim/auto-trace: Got TracerProvider from NodeSdk layer");
1258
- const supervisor = createAutoTracingSupervisor(autoConfig, tracerProvider.value);
1259
- logger.log("@atrim/auto-trace: Combined layer created");
1260
- logger.log(" - HTTP requests: auto-traced via Effect platform");
1261
- logger.log(" - Forked fibers: auto-traced via Supervisor");
1262
- return effect.Supervisor.addSupervisor(supervisor);
1263
- } else {
1264
- logger.log("@atrim/auto-trace: WARNING: No TracerProvider found, creating supervisor without it");
1265
- const supervisor = createAutoTracingSupervisor(autoConfig);
1266
- logger.log("@atrim/auto-trace: Combined layer created (fallback)");
1267
- logger.log(" - HTTP requests: auto-traced via Effect platform");
1268
- logger.log(" - Forked fibers: auto-traced via Supervisor");
1269
- return effect.Supervisor.addSupervisor(supervisor);
1270
- }
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");
1271
1354
  })
1272
1355
  );
1273
- return supervisorLayer.pipe(effect.Layer.provide(effect.Layer.discard(sdkLayer)));
1356
+ const resourceLayer = opentelemetry.Resource.layer({
1357
+ serviceName,
1358
+ serviceVersion
1359
+ });
1360
+ const effectTracerLayer = opentelemetry.Tracer.layerGlobal;
1361
+ const supervisor = createAutoTracingSupervisor(autoConfig);
1362
+ const supervisorLayer = effect.Supervisor.addSupervisor(supervisor);
1363
+ logger.log("@atrim/auto-trace: Combined layer created");
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);
1274
1368
  })
1275
1369
  );
1276
1370
  };