@atrim/instrument-node 0.7.2-dev.e12da4b.20260116190926 → 0.8.1-dev.ae570af.20260116211540

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/README.md CHANGED
@@ -80,6 +80,45 @@ const process = Effect.gen(function* () {
80
80
 
81
81
  Available: `annotateUser`, `annotateBatch`, `annotateDataSize`, `annotateLLM`, `annotateQuery`, `annotateHttpRequest`, `annotateError`, `annotatePriority`, `annotateCache`
82
82
 
83
+ ### Operation Tracing (Automatic)
84
+
85
+ Automatically trace `Effect.all`, `Effect.forEach`, and other operations without manual `Effect.withSpan()` calls:
86
+
87
+ ```typescript
88
+ import { Effect } from 'effect'
89
+ import { withOperationTracing } from '@atrim/instrument-node/effect/auto'
90
+
91
+ const program = Effect.gen(function* () {
92
+ // Automatically creates span "effect.all (index.ts:42)" with item_count=3
93
+ yield* Effect.all([fetchUser(), fetchOrders(), fetchPrefs()])
94
+
95
+ // Automatically creates span "effect.forEach (index.ts:45)"
96
+ yield* Effect.forEach(items, processItem)
97
+ }).pipe(withOperationTracing)
98
+
99
+ await Effect.runPromise(program)
100
+ ```
101
+
102
+ **What you get:**
103
+ - Span name with source location: `effect.all (index.ts:42)`
104
+ - Attributes: `effect.operation`, `effect.item_count`, `code.filepath`, `code.lineno`
105
+ - Zero code changes to existing Effect code
106
+
107
+ **Configuration (optional):**
108
+
109
+ ```yaml
110
+ effect:
111
+ operation_tracing:
112
+ enabled: true
113
+ operations:
114
+ - name: all
115
+ include_count: true
116
+ - name: forEach
117
+ include_count: true
118
+ ```
119
+
120
+ > **Note:** Requires `@clayroach/effect` fork with OperationMeta support. See [examples/effect-op-tracing](../../examples/effect-op-tracing/) for a complete example.
121
+
83
122
  ### Effect Fiber Metadata Extraction
84
123
 
85
124
  To add Effect fiber metadata (fiber ID, status, parent span info) to your spans, you must **explicitly call** the enrichment functions:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atrim/instrument-node",
3
- "version": "0.7.2-dev.e12da4b.20260116190926",
3
+ "version": "0.8.1-dev.ae570af.20260116211540",
4
4
  "description": "OpenTelemetry instrumentation for Node.js with centralized YAML configuration",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -89,7 +89,7 @@
89
89
  "tsx": "^4.7.0",
90
90
  "typescript": "^5.7.2",
91
91
  "vitest": "^4.0.16",
92
- "@atrim/instrument-core": "0.6.0"
92
+ "@atrim/instrument-core": "0.7.0"
93
93
  },
94
94
  "peerDependencies": {
95
95
  "@opentelemetry/api": "^1.0.0",
@@ -14,7 +14,7 @@ var yaml = require('yaml');
14
14
  var zod = require('zod');
15
15
  var fs = require('fs');
16
16
  var path2 = require('path');
17
- var EffectTracer3 = require('effect/Tracer');
17
+ var TracerModule = require('effect/Tracer');
18
18
 
19
19
  function _interopNamespace(e) {
20
20
  if (e && e.__esModule) return e;
@@ -40,7 +40,7 @@ var HttpClientRequest__namespace = /*#__PURE__*/_interopNamespace(HttpClientRequ
40
40
  var yaml__namespace = /*#__PURE__*/_interopNamespace(yaml);
41
41
  var fs__namespace = /*#__PURE__*/_interopNamespace(fs);
42
42
  var path2__namespace = /*#__PURE__*/_interopNamespace(path2);
43
- var EffectTracer3__namespace = /*#__PURE__*/_interopNamespace(EffectTracer3);
43
+ var TracerModule__namespace = /*#__PURE__*/_interopNamespace(TracerModule);
44
44
 
45
45
  var __defProp = Object.defineProperty;
46
46
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
@@ -1573,6 +1573,8 @@ var SourceCaptureSupervisor = class extends effect.Supervisor.AbstractSupervisor
1573
1573
  this.config = config;
1574
1574
  // WeakMap to associate fibers with their OTel spans
1575
1575
  __publicField(this, "fiberSpans", /* @__PURE__ */ new WeakMap());
1576
+ // WeakMap to associate fibers with their OTel contexts (for child fiber lookups)
1577
+ __publicField(this, "fiberContexts", /* @__PURE__ */ new WeakMap());
1576
1578
  // WeakMap for fiber start times (for min_duration filtering)
1577
1579
  __publicField(this, "fiberStartTimes", /* @__PURE__ */ new WeakMap());
1578
1580
  // OpenTelemetry tracer - lazily initialized
@@ -1668,25 +1670,51 @@ var SourceCaptureSupervisor = class extends effect.Supervisor.AbstractSupervisor
1668
1670
  }
1669
1671
  let parentContext = OtelApi__namespace.ROOT_CONTEXT;
1670
1672
  let parentFiberId;
1671
- const maybeEffectParentSpan = effect.Context.getOption(_context, effect.Tracer.ParentSpan);
1672
- if (effect.Option.isSome(maybeEffectParentSpan)) {
1673
- const effectSpan = maybeEffectParentSpan.value;
1674
- logger.log(
1675
- `@atrim/source-capture: Found ParentSpan - traceId=${effectSpan.traceId}, spanId=${effectSpan.spanId}`
1676
- );
1677
- const otelSpanContext = {
1678
- traceId: effectSpan.traceId,
1679
- spanId: effectSpan.spanId,
1680
- traceFlags: effectSpan.sampled ? OtelApi__namespace.TraceFlags.SAMPLED : OtelApi__namespace.TraceFlags.NONE,
1681
- isRemote: false
1682
- };
1683
- const wrappedSpan = OtelApi__namespace.trace.wrapSpanContext(otelSpanContext);
1684
- parentContext = OtelApi__namespace.trace.setSpan(OtelApi__namespace.ROOT_CONTEXT, wrappedSpan);
1685
- } else if (effect.Option.isSome(parent)) {
1686
- parentFiberId = parent.value.id().id;
1687
- const parentSpan = this.fiberSpans.get(parent.value);
1688
- if (parentSpan) {
1689
- parentContext = OtelApi__namespace.trace.setSpan(OtelApi__namespace.ROOT_CONTEXT, parentSpan);
1673
+ const inheritedOtelContext = effect.FiberRefs.getOrDefault(
1674
+ fiberRefsValue,
1675
+ TracerModule__namespace.currentOtelSpanContext
1676
+ );
1677
+ if (inheritedOtelContext) {
1678
+ parentContext = inheritedOtelContext;
1679
+ const inheritedSpan = OtelApi__namespace.trace.getSpan(inheritedOtelContext);
1680
+ if (inheritedSpan) {
1681
+ const spanCtx = inheritedSpan.spanContext();
1682
+ logger.log(
1683
+ `@atrim/source-capture: Using inherited OTel context - traceId=${spanCtx.traceId}, spanId=${spanCtx.spanId}`
1684
+ );
1685
+ }
1686
+ } else {
1687
+ const maybeEffectParentSpan = effect.Context.getOption(_context, effect.Tracer.ParentSpan);
1688
+ if (effect.Option.isSome(maybeEffectParentSpan)) {
1689
+ const effectSpan = maybeEffectParentSpan.value;
1690
+ logger.log(
1691
+ `@atrim/source-capture: Found ParentSpan - traceId=${effectSpan.traceId}, spanId=${effectSpan.spanId}`
1692
+ );
1693
+ const otelSpanContext = {
1694
+ traceId: effectSpan.traceId,
1695
+ spanId: effectSpan.spanId,
1696
+ traceFlags: effectSpan.sampled ? OtelApi__namespace.TraceFlags.SAMPLED : OtelApi__namespace.TraceFlags.NONE,
1697
+ isRemote: false
1698
+ };
1699
+ const wrappedSpan = OtelApi__namespace.trace.wrapSpanContext(otelSpanContext);
1700
+ parentContext = OtelApi__namespace.trace.setSpan(OtelApi__namespace.ROOT_CONTEXT, wrappedSpan);
1701
+ } else if (effect.Option.isSome(parent)) {
1702
+ parentFiberId = parent.value.id().id;
1703
+ const parentOtelContext = this.fiberContexts.get(parent.value);
1704
+ if (parentOtelContext) {
1705
+ parentContext = parentOtelContext;
1706
+ const parentSpan = OtelApi__namespace.trace.getSpan(parentOtelContext);
1707
+ if (parentSpan) {
1708
+ logger.log(
1709
+ `@atrim/source-capture: Using parent fiber's OTel context - traceId=${parentSpan.spanContext().traceId}`
1710
+ );
1711
+ }
1712
+ } else {
1713
+ const parentSpan = this.fiberSpans.get(parent.value);
1714
+ if (parentSpan) {
1715
+ parentContext = OtelApi__namespace.trace.setSpan(OtelApi__namespace.ROOT_CONTEXT, parentSpan);
1716
+ }
1717
+ }
1690
1718
  }
1691
1719
  }
1692
1720
  if (effect.Option.isSome(parent)) {
@@ -1715,7 +1743,9 @@ var SourceCaptureSupervisor = class extends effect.Supervisor.AbstractSupervisor
1715
1743
  parentContext
1716
1744
  );
1717
1745
  logger.log(`@atrim/source-capture: Created span "${spanName}" for fiber ${fiberId}`);
1746
+ const newContext = OtelApi__namespace.trace.setSpan(parentContext, span);
1718
1747
  this.fiberSpans.set(fiber, span);
1748
+ this.fiberContexts.set(fiber, newContext);
1719
1749
  this.fiberStartTimes.set(fiber, process.hrtime.bigint());
1720
1750
  this.activeFiberCount++;
1721
1751
  }
@@ -1736,6 +1766,7 @@ var SourceCaptureSupervisor = class extends effect.Supervisor.AbstractSupervisor
1736
1766
  const minDuration = this.parseMinDuration(this.config.performance?.min_duration);
1737
1767
  if (minDuration > 0 && duration < minDuration) {
1738
1768
  this.fiberSpans.delete(fiber);
1769
+ this.fiberContexts.delete(fiber);
1739
1770
  this.fiberStartTimes.delete(fiber);
1740
1771
  this.activeFiberCount--;
1741
1772
  return;
@@ -1753,6 +1784,7 @@ var SourceCaptureSupervisor = class extends effect.Supervisor.AbstractSupervisor
1753
1784
  span.end();
1754
1785
  logger.log(`@atrim/source-capture: Ended span for fiber ${fiberId}`);
1755
1786
  this.fiberSpans.delete(fiber);
1787
+ this.fiberContexts.delete(fiber);
1756
1788
  this.fiberStartTimes.delete(fiber);
1757
1789
  this.activeFiberCount--;
1758
1790
  }
@@ -2010,7 +2042,7 @@ var OperationTracingSupervisor = class extends effect.Supervisor.AbstractSupervi
2010
2042
  }
2011
2043
  let parentContext = OtelApi__namespace.ROOT_CONTEXT;
2012
2044
  const fiberContext = fiber.currentContext;
2013
- const maybeEffectParentSpan = effect.Context.getOption(fiberContext, EffectTracer3__namespace.ParentSpan);
2045
+ const maybeEffectParentSpan = effect.Context.getOption(fiberContext, TracerModule__namespace.ParentSpan);
2014
2046
  if (effect.Option.isSome(maybeEffectParentSpan)) {
2015
2047
  const effectSpan = maybeEffectParentSpan.value;
2016
2048
  parentContext = OtelApi__namespace.trace.setSpan(
@@ -2088,8 +2120,8 @@ var defaultOperations = [
2088
2120
  { name: "all", includeCount: true, includeStack: true },
2089
2121
  { name: "forEach", includeCount: true, includeStack: true }
2090
2122
  ];
2091
- var makeOperationTracingLayer = (operations = defaultOperations) => {
2092
- const supervisor = new OperationTracingSupervisor(operations);
2123
+ var makeOperationTracingLayer = (operations = defaultOperations, spanNaming = defaultSpanNaming) => {
2124
+ const supervisor = new OperationTracingSupervisor(operations, spanNaming);
2093
2125
  return effect.Supervisor.addSupervisor(supervisor);
2094
2126
  };
2095
2127
  var enableOpSupervision = (effect$1) => effect.Effect.withRuntimeFlagsPatch(effect$1, effect.RuntimeFlagsPatch.enable(effect.RuntimeFlags.OpSupervision));
@@ -2107,9 +2139,16 @@ var loadOperationTracingLayer = () => effect.Layer.unwrapEffect(
2107
2139
  logger.log("@atrim/operation-tracing: Operation tracing disabled in config");
2108
2140
  return effect.Layer.empty;
2109
2141
  }
2142
+ const spanNaming = {
2143
+ includeLocation: opTracingConfig?.span_naming?.include_location ?? true,
2144
+ template: opTracingConfig?.span_naming?.template ?? "effect.{op} ({filename}:{line})"
2145
+ };
2146
+ logger.log(
2147
+ `@atrim/operation-tracing: Span naming - includeLocation=${spanNaming.includeLocation}, template="${spanNaming.template}"`
2148
+ );
2110
2149
  const operations = opTracingConfig?.operations ? opTracingConfig.operations.map((op) => ({
2111
2150
  name: op.name,
2112
- ...op.span_name && { spanName: op.span_name },
2151
+ ...op.span_name && { spanNameTemplate: op.span_name },
2113
2152
  includeCount: op.include_count ?? true,
2114
2153
  includeStack: op.include_stack ?? true
2115
2154
  })) : defaultOperations;
@@ -2117,7 +2156,7 @@ var loadOperationTracingLayer = () => effect.Layer.unwrapEffect(
2117
2156
  logger.log(
2118
2157
  `@atrim/operation-tracing: Loaded ${operations.length} operation configs from ${source}`
2119
2158
  );
2120
- return makeOperationTracingLayer(operations);
2159
+ return makeOperationTracingLayer(operations, spanNaming);
2121
2160
  })
2122
2161
  );
2123
2162
  var OperationTracingLive = loadOperationTracingLayer();