@atrim/instrument-node 0.7.0-b9eaf74-20260108193056 → 0.7.1-dev.14fdea7.20260108220010

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.
@@ -1,4 +1,5 @@
1
1
  import { Layer, Supervisor, Effect, Context, Option, Fiber, Exit, FiberRef } from 'effect';
2
+ import * as OtelApi from '@opentelemetry/api';
2
3
  import { AutoInstrumentationConfig, InstrumentationConfig } from '@atrim/instrument-core';
3
4
  export { AutoInstrumentationConfig } from '@atrim/instrument-core';
4
5
 
@@ -39,11 +40,20 @@ declare class AutoTracingSupervisor extends Supervisor.AbstractSupervisor<void>
39
40
  private readonly config;
40
41
  private readonly fiberSpans;
41
42
  private readonly fiberStartTimes;
42
- private readonly tracer;
43
+ private _tracer;
43
44
  private readonly includePatterns;
44
45
  private readonly excludePatterns;
45
46
  private activeFiberCount;
47
+ private _rootSpan;
46
48
  constructor(config: AutoInstrumentationConfig);
49
+ /**
50
+ * Set the root span for parent context propagation
51
+ */
52
+ setRootSpan(span: OtelApi.Span): void;
53
+ /**
54
+ * Get the tracer lazily - this allows time for the NodeSdk layer to register the global provider
55
+ */
56
+ private get tracer();
47
57
  /**
48
58
  * Returns the current value (void for this supervisor)
49
59
  */
@@ -94,6 +104,23 @@ declare const createAutoTracingSupervisor: (config: AutoInstrumentationConfig) =
94
104
  declare const createAutoTracingLayer: (options?: {
95
105
  config?: AutoInstrumentationConfig;
96
106
  }) => Layer.Layer<never>;
107
+ /**
108
+ * Wrap an Effect with auto-tracing supervision
109
+ *
110
+ * This creates a span for the main effect AND supervises all child fibers.
111
+ * Use this when you need to ensure all fibers in an effect are traced.
112
+ *
113
+ * @example
114
+ * ```typescript
115
+ * const program = Effect.gen(function* () {
116
+ * yield* doWork() // Automatically traced!
117
+ * })
118
+ *
119
+ * const tracedProgram = withAutoTracing(program, { enabled: true, ... })
120
+ * Effect.runPromise(tracedProgram)
121
+ * ```
122
+ */
123
+ declare const withAutoTracing: <A, E, R>(effect: Effect.Effect<A, E, R>, config: AutoInstrumentationConfig, mainSpanName?: string) => Effect.Effect<A, E, R>;
97
124
  /**
98
125
  * Zero-config auto-tracing layer
99
126
  *
@@ -133,6 +160,60 @@ declare const withoutAutoTracing: <A, E, R>(effect: Effect.Effect<A, E, R>) => E
133
160
  * ```
134
161
  */
135
162
  declare const setSpanName: (name: string) => <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>;
163
+ /**
164
+ * Create a fully YAML-driven auto-instrumentation layer
165
+ *
166
+ * This layer reads all configuration from instrumentation.yaml including:
167
+ * - Exporter configuration (type, endpoint, processor)
168
+ * - Auto-tracing configuration (naming rules, filters, performance)
169
+ * - Service metadata (name, version)
170
+ *
171
+ * @example
172
+ * ```typescript
173
+ * import { createFullAutoTracingLayer } from '@atrim/instrument-node/effect/auto'
174
+ *
175
+ * // Everything configured via instrumentation.yaml - no code config needed!
176
+ * const AppLive = createFullAutoTracingLayer()
177
+ *
178
+ * Effect.runPromise(program.pipe(Effect.provide(AppLive)))
179
+ * ```
180
+ */
181
+ declare const createFullAutoTracingLayer: () => Layer.Layer<never>;
182
+ /**
183
+ * Fully YAML-driven auto-instrumentation layer
184
+ *
185
+ * This is the recommended way to use auto-instrumentation. All configuration
186
+ * comes from instrumentation.yaml - no code configuration needed.
187
+ *
188
+ * @example
189
+ * ```yaml
190
+ * # instrumentation.yaml
191
+ * effect:
192
+ * auto_instrumentation:
193
+ * enabled: true
194
+ * span_naming:
195
+ * default: "effect.{function}"
196
+ * rules:
197
+ * - match: { function: "internal.*" }
198
+ * name: "internal.{function}"
199
+ * filter:
200
+ * exclude:
201
+ * - "^internal\\."
202
+ * exporter_config:
203
+ * type: otlp # or 'console' for dev
204
+ * endpoint: http://localhost:4318
205
+ * processor: batch # or 'simple' for dev
206
+ * ```
207
+ *
208
+ * @example
209
+ * ```typescript
210
+ * import { FullAutoTracingLive } from '@atrim/instrument-node/effect/auto'
211
+ *
212
+ * // Just provide the layer - everything else from YAML!
213
+ * Effect.runPromise(program.pipe(Effect.provide(FullAutoTracingLive)))
214
+ * ```
215
+ */
216
+ declare const FullAutoTracingLive: Layer.Layer<never>;
136
217
 
137
218
  /**
138
219
  * Node.js configuration loader
@@ -275,4 +356,4 @@ declare function inferSpanName(fiberId: number, sourceInfo: SourceInfo | undefin
275
356
  */
276
357
  declare function sanitizeSpanName(name: string): string;
277
358
 
278
- export { AutoTracingConfig, AutoTracingConfigLayer, AutoTracingConfigLive, AutoTracingEnabled, AutoTracingLive, AutoTracingSpanName, AutoTracingSupervisor, type SourceInfo, type TemplateVariables, createAutoTracingLayer, createAutoTracingSupervisor, defaultAutoTracingConfig, inferSpanName, loadAutoTracingConfig, loadAutoTracingConfigSync, sanitizeSpanName, setSpanName, withoutAutoTracing };
359
+ export { AutoTracingConfig, AutoTracingConfigLayer, AutoTracingConfigLive, AutoTracingEnabled, AutoTracingLive, AutoTracingSpanName, AutoTracingSupervisor, FullAutoTracingLive, type SourceInfo, type TemplateVariables, createAutoTracingLayer, createAutoTracingSupervisor, createFullAutoTracingLayer, defaultAutoTracingConfig, inferSpanName, loadAutoTracingConfig, loadAutoTracingConfigSync, sanitizeSpanName, setSpanName, withAutoTracing, withoutAutoTracing };
@@ -1,5 +1,9 @@
1
1
  import { Data, Context, Effect, Layer, FiberRef, Option, Supervisor, FiberRefs, Exit } from 'effect';
2
2
  import * as OtelApi from '@opentelemetry/api';
3
+ import { BasicTracerProvider, SimpleSpanProcessor, BatchSpanProcessor, ConsoleSpanExporter } from '@opentelemetry/sdk-trace-base';
4
+ import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
5
+ import { resourceFromAttributes } from '@opentelemetry/resources';
6
+ import { ATTR_SERVICE_VERSION, ATTR_SERVICE_NAME } from '@opentelemetry/semantic-conventions';
3
7
  import { FileSystem } from '@effect/platform/FileSystem';
4
8
  import * as HttpClient from '@effect/platform/HttpClient';
5
9
  import * as HttpClientRequest from '@effect/platform/HttpClientRequest';
@@ -124,6 +128,27 @@ var HttpFilteringConfigSchema = z.object({
124
128
  include_urls: z.array(z.string()).optional()
125
129
  }).optional()
126
130
  });
131
+ var ExporterConfigSchema = z.object({
132
+ // Exporter type: 'otlp' | 'console' | 'none'
133
+ // - 'otlp': Export to OTLP endpoint (production)
134
+ // - 'console': Log spans to console (development)
135
+ // - 'none': No export (disable tracing)
136
+ type: z.enum(["otlp", "console", "none"]).default("otlp"),
137
+ // OTLP endpoint URL (for type: otlp)
138
+ // Defaults to OTEL_EXPORTER_OTLP_ENDPOINT env var or http://localhost:4318
139
+ endpoint: z.string().optional(),
140
+ // Span processor type
141
+ // - 'batch': Batch spans for export (production, lower overhead)
142
+ // - 'simple': Export immediately (development, no batching delay)
143
+ processor: z.enum(["batch", "simple"]).default("batch"),
144
+ // Batch processor settings (for processor: batch)
145
+ batch: z.object({
146
+ // Max time to wait before exporting (milliseconds)
147
+ scheduled_delay_millis: z.number().default(1e3),
148
+ // Max batch size
149
+ max_export_batch_size: z.number().default(100)
150
+ }).optional()
151
+ });
127
152
  var InstrumentationConfigSchema = z.object({
128
153
  version: z.string(),
129
154
  instrumentation: z.object({
@@ -137,10 +162,12 @@ var InstrumentationConfigSchema = z.object({
137
162
  // Enable/disable Effect tracing entirely
138
163
  // When false, EffectInstrumentationLive returns Layer.empty
139
164
  enabled: z.boolean().default(true),
140
- // Exporter mode:
165
+ // Exporter mode (legacy - use exporter.type instead):
141
166
  // - "unified": Use global TracerProvider from Node SDK (recommended, enables filtering)
142
167
  // - "standalone": Use Effect's own OTLP exporter (bypasses Node SDK filtering)
143
168
  exporter: z.enum(["unified", "standalone"]).default("unified"),
169
+ // Exporter configuration (for auto-instrumentation)
170
+ exporter_config: ExporterConfigSchema.optional(),
144
171
  auto_extract_metadata: z.boolean(),
145
172
  auto_isolation: AutoIsolationConfigSchema.optional(),
146
173
  // Auto-instrumentation: automatic tracing of all Effect fibers
@@ -656,6 +683,20 @@ var loadAutoTracingConfigSync = () => {
656
683
  };
657
684
  var AutoTracingConfigLive = Layer.effect(AutoTracingConfig, loadAutoTracingConfig());
658
685
  var AutoTracingConfigLayer = (config) => Layer.succeed(AutoTracingConfig, config);
686
+ var loadFullConfig = (options) => Effect.gen(function* () {
687
+ const config = yield* Effect.tryPromise({
688
+ try: () => loadConfigWithOptions(options),
689
+ catch: (error) => {
690
+ logger.log(`@atrim/auto-trace: Failed to load config: ${error}`);
691
+ return error;
692
+ }
693
+ }).pipe(Effect.catchAll(() => Effect.succeed(null)));
694
+ if (!config) {
695
+ logger.log("@atrim/auto-trace: No config found, using defaults");
696
+ return defaultConfig;
697
+ }
698
+ return config;
699
+ });
659
700
 
660
701
  // src/integrations/effect/auto/supervisor.ts
661
702
  var AutoTracingEnabled = FiberRef.unsafeMake(true);
@@ -668,14 +709,15 @@ var AutoTracingSupervisor = class extends Supervisor.AbstractSupervisor {
668
709
  __publicField(this, "fiberSpans", /* @__PURE__ */ new WeakMap());
669
710
  // WeakMap for fiber start times (for min_duration filtering)
670
711
  __publicField(this, "fiberStartTimes", /* @__PURE__ */ new WeakMap());
671
- // OpenTelemetry tracer
672
- __publicField(this, "tracer");
712
+ // OpenTelemetry tracer - lazily initialized
713
+ __publicField(this, "_tracer", null);
673
714
  // Compiled filter patterns
674
715
  __publicField(this, "includePatterns");
675
716
  __publicField(this, "excludePatterns");
676
717
  // Active fiber count (for max_concurrent limiting)
677
718
  __publicField(this, "activeFiberCount", 0);
678
- this.tracer = OtelApi.trace.getTracer("@atrim/auto-trace", "1.0.0");
719
+ // Root span for parent context (set by withAutoTracing)
720
+ __publicField(this, "_rootSpan", null);
679
721
  this.includePatterns = (config.filter?.include || []).map((p) => new RegExp(p));
680
722
  this.excludePatterns = (config.filter?.exclude || []).map((p) => new RegExp(p));
681
723
  logger.log("@atrim/auto-trace: Supervisor initialized");
@@ -683,6 +725,21 @@ var AutoTracingSupervisor = class extends Supervisor.AbstractSupervisor {
683
725
  logger.log(` Sampling rate: ${config.performance?.sampling_rate ?? 1}`);
684
726
  logger.log(` Infer from source: ${config.span_naming?.infer_from_source ?? true}`);
685
727
  }
728
+ /**
729
+ * Set the root span for parent context propagation
730
+ */
731
+ setRootSpan(span) {
732
+ this._rootSpan = span;
733
+ }
734
+ /**
735
+ * Get the tracer lazily - this allows time for the NodeSdk layer to register the global provider
736
+ */
737
+ get tracer() {
738
+ if (!this._tracer) {
739
+ this._tracer = OtelApi.trace.getTracer("@atrim/auto-trace", "1.0.0");
740
+ }
741
+ return this._tracer;
742
+ }
686
743
  /**
687
744
  * Returns the current value (void for this supervisor)
688
745
  */
@@ -707,28 +764,34 @@ var AutoTracingSupervisor = class extends Supervisor.AbstractSupervisor {
707
764
  return;
708
765
  }
709
766
  const nameOverride = FiberRefs.getOrDefault(fiberRefsValue, AutoTracingSpanName);
767
+ const sourceInfo = this.config.span_naming?.infer_from_source ? this.parseStackTrace() : void 0;
710
768
  let spanName;
711
769
  if (Option.isSome(nameOverride)) {
712
770
  spanName = nameOverride.value;
713
771
  } else {
714
- const sourceInfo = this.config.span_naming?.infer_from_source ? this.parseStackTrace() : void 0;
715
772
  spanName = inferSpanName(fiber.id().id, sourceInfo, this.config);
716
773
  }
717
774
  if (!this.shouldTrace(spanName)) {
718
775
  return;
719
776
  }
720
- let parentContext = OtelApi.context.active();
777
+ let parentContext = OtelApi.ROOT_CONTEXT;
778
+ let parentFiberId;
721
779
  if (Option.isSome(parent)) {
780
+ parentFiberId = parent.value.id().id;
722
781
  const parentSpan = this.fiberSpans.get(parent.value);
723
782
  if (parentSpan) {
724
- parentContext = OtelApi.trace.setSpan(parentContext, parentSpan);
783
+ parentContext = OtelApi.trace.setSpan(OtelApi.ROOT_CONTEXT, parentSpan);
784
+ } else if (this._rootSpan) {
785
+ parentContext = OtelApi.trace.setSpan(OtelApi.ROOT_CONTEXT, this._rootSpan);
725
786
  }
787
+ } else if (this._rootSpan) {
788
+ parentContext = OtelApi.trace.setSpan(OtelApi.ROOT_CONTEXT, this._rootSpan);
726
789
  }
727
790
  const span = this.tracer.startSpan(
728
791
  spanName,
729
792
  {
730
793
  kind: OtelApi.SpanKind.INTERNAL,
731
- attributes: this.getInitialAttributes(fiber)
794
+ attributes: this.getInitialAttributes(fiber, sourceInfo, parentFiberId)
732
795
  },
733
796
  parentContext
734
797
  );
@@ -791,13 +854,22 @@ var AutoTracingSupervisor = class extends Supervisor.AbstractSupervisor {
791
854
  /**
792
855
  * Get initial span attributes for a fiber
793
856
  */
794
- getInitialAttributes(fiber) {
857
+ getInitialAttributes(fiber, sourceInfo, parentFiberId) {
795
858
  const attrs = {
796
859
  "effect.auto_traced": true
797
860
  };
798
861
  if (this.config.metadata?.fiber_info !== false) {
799
862
  attrs["effect.fiber.id"] = fiber.id().id;
800
863
  }
864
+ if (this.config.metadata?.source_location !== false && sourceInfo) {
865
+ attrs["code.function"] = sourceInfo.function;
866
+ attrs["code.filepath"] = sourceInfo.file;
867
+ attrs["code.lineno"] = sourceInfo.line;
868
+ attrs["code.column"] = sourceInfo.column;
869
+ }
870
+ if (this.config.metadata?.parent_fiber !== false && parentFiberId !== void 0) {
871
+ attrs["effect.fiber.parent_id"] = parentFiberId;
872
+ }
801
873
  return attrs;
802
874
  }
803
875
  /**
@@ -866,10 +938,129 @@ var createAutoTracingLayer = (options) => {
866
938
  })
867
939
  );
868
940
  };
941
+ var withAutoTracing = (effect, config, mainSpanName) => {
942
+ if (!config.enabled) {
943
+ logger.log("@atrim/auto-trace: Auto-tracing disabled via config");
944
+ return effect;
945
+ }
946
+ const supervisor = createAutoTracingSupervisor(config);
947
+ const tracer = OtelApi.trace.getTracer("@atrim/auto-trace", "1.0.0");
948
+ const spanName = mainSpanName ?? inferSpanName(0, void 0, config);
949
+ const mainSpan = tracer.startSpan(spanName, {
950
+ kind: OtelApi.SpanKind.INTERNAL,
951
+ attributes: {
952
+ "effect.auto_traced": true,
953
+ "effect.fiber.id": 0,
954
+ // Main fiber
955
+ "effect.main_fiber": true
956
+ }
957
+ });
958
+ supervisor.setRootSpan(mainSpan);
959
+ return Effect.acquireUseRelease(
960
+ // Acquire: return the span (already started)
961
+ Effect.succeed(mainSpan),
962
+ // Use: run the supervised effect
963
+ () => Effect.supervised(supervisor)(effect),
964
+ // Release: end the span
965
+ (span, exit) => Effect.sync(() => {
966
+ if (Exit.isSuccess(exit)) {
967
+ span.setStatus({ code: OtelApi.SpanStatusCode.OK });
968
+ } else {
969
+ span.setStatus({
970
+ code: OtelApi.SpanStatusCode.ERROR,
971
+ message: "Effect failed"
972
+ });
973
+ }
974
+ span.end();
975
+ })
976
+ );
977
+ };
869
978
  var AutoTracingLive = createAutoTracingLayer();
870
979
  var withoutAutoTracing = (effect) => effect.pipe(Effect.locally(AutoTracingEnabled, false));
871
980
  var setSpanName = (name) => (effect) => effect.pipe(Effect.locally(AutoTracingSpanName, Option.some(name)));
981
+ var createExporterLayer = (exporterConfig, serviceName, serviceVersion) => {
982
+ const config = exporterConfig ?? {
983
+ type: "otlp",
984
+ processor: "batch",
985
+ batch: {
986
+ scheduled_delay_millis: 1e3,
987
+ max_export_batch_size: 100
988
+ }
989
+ };
990
+ if (config.type === "none") {
991
+ logger.log('@atrim/auto-trace: Exporter type is "none", no spans will be exported');
992
+ return Layer.empty;
993
+ }
994
+ const createSpanExporter = () => {
995
+ if (config.type === "console") {
996
+ logger.log("@atrim/auto-trace: Using ConsoleSpanExporter");
997
+ return new ConsoleSpanExporter();
998
+ }
999
+ const endpoint = config.endpoint || process.env.OTEL_EXPORTER_OTLP_ENDPOINT || "http://localhost:4318";
1000
+ logger.log(`@atrim/auto-trace: Using OTLPTraceExporter (${endpoint})`);
1001
+ return new OTLPTraceExporter({
1002
+ url: `${endpoint}/v1/traces`
1003
+ });
1004
+ };
1005
+ const createSpanProcessor = () => {
1006
+ const exporter = createSpanExporter();
1007
+ if (config.processor === "simple" || config.type === "console") {
1008
+ logger.log("@atrim/auto-trace: Using SimpleSpanProcessor");
1009
+ return new SimpleSpanProcessor(exporter);
1010
+ }
1011
+ const batchConfig = config.batch ?? {
1012
+ scheduled_delay_millis: 1e3,
1013
+ max_export_batch_size: 100
1014
+ };
1015
+ logger.log("@atrim/auto-trace: Using BatchSpanProcessor");
1016
+ return new BatchSpanProcessor(exporter, {
1017
+ scheduledDelayMillis: batchConfig.scheduled_delay_millis,
1018
+ maxExportBatchSize: batchConfig.max_export_batch_size
1019
+ });
1020
+ };
1021
+ return Layer.effectDiscard(
1022
+ Effect.sync(() => {
1023
+ const provider = new BasicTracerProvider({
1024
+ resource: resourceFromAttributes({
1025
+ [ATTR_SERVICE_NAME]: serviceName,
1026
+ [ATTR_SERVICE_VERSION]: serviceVersion
1027
+ }),
1028
+ spanProcessors: [createSpanProcessor()]
1029
+ });
1030
+ OtelApi.trace.setGlobalTracerProvider(provider);
1031
+ logger.log("@atrim/auto-trace: Global TracerProvider registered");
1032
+ return () => {
1033
+ provider.shutdown().catch((err) => {
1034
+ logger.log(`@atrim/auto-trace: Error shutting down provider: ${err}`);
1035
+ });
1036
+ };
1037
+ })
1038
+ );
1039
+ };
1040
+ var createFullAutoTracingLayer = () => {
1041
+ return Layer.unwrapEffect(
1042
+ Effect.gen(function* () {
1043
+ const config = yield* loadFullConfig();
1044
+ const serviceName = process.env.OTEL_SERVICE_NAME || "effect-service";
1045
+ const serviceVersion = process.env.npm_package_version || "1.0.0";
1046
+ const autoConfig = config.effect?.auto_instrumentation;
1047
+ if (!autoConfig?.enabled) {
1048
+ logger.log("@atrim/auto-trace: Auto-instrumentation disabled via config");
1049
+ return Layer.empty;
1050
+ }
1051
+ const exporterConfig = config.effect?.exporter_config;
1052
+ logger.log("@atrim/auto-trace: Full auto-instrumentation enabled");
1053
+ logger.log(` Service: ${serviceName}`);
1054
+ logger.log(` Exporter: ${exporterConfig?.type ?? "otlp"}`);
1055
+ const exporterLayer = createExporterLayer(exporterConfig, serviceName, serviceVersion);
1056
+ const supervisor = createAutoTracingSupervisor(autoConfig);
1057
+ const supervisorLayer = Supervisor.addSupervisor(supervisor);
1058
+ return Layer.mergeAll(exporterLayer, supervisorLayer);
1059
+ })
1060
+ );
1061
+ };
1062
+ var FullAutoTracingLive = createFullAutoTracingLayer();
872
1063
 
873
- export { AutoTracingConfig, AutoTracingConfigLayer, AutoTracingConfigLive, AutoTracingEnabled, AutoTracingLive, AutoTracingSpanName, AutoTracingSupervisor, createAutoTracingLayer, createAutoTracingSupervisor, defaultAutoTracingConfig, inferSpanName, loadAutoTracingConfig, loadAutoTracingConfigSync, sanitizeSpanName, setSpanName, withoutAutoTracing };
1064
+ export { AutoTracingConfig, AutoTracingConfigLayer, AutoTracingConfigLive, AutoTracingEnabled, AutoTracingLive, AutoTracingSpanName, AutoTracingSupervisor, FullAutoTracingLive, createAutoTracingLayer, createAutoTracingSupervisor, createFullAutoTracingLayer, defaultAutoTracingConfig, inferSpanName, loadAutoTracingConfig, loadAutoTracingConfigSync, sanitizeSpanName, setSpanName, withAutoTracing, withoutAutoTracing };
874
1065
  //# sourceMappingURL=index.js.map
875
1066
  //# sourceMappingURL=index.js.map