@atrim/instrument-node 0.5.2-dev.ac2fbfe.20251221205322 → 0.7.0-14fdea7-20260108225522

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, Effect, FiberSet as FiberSet$1 } from 'effect';
2
+ import * as Tracer from '@effect/opentelemetry/Tracer';
2
3
  import { InstrumentationConfig } from '@atrim/instrument-core';
3
4
  import * as effect_Runtime from 'effect/Runtime';
4
5
  import * as effect_FiberId from 'effect/FiberId';
@@ -6,9 +7,10 @@ import * as effect_Scope from 'effect/Scope';
6
7
  import { RuntimeFiber } from 'effect/Fiber';
7
8
 
8
9
  /**
9
- * Node.js configuration loader using Effect Platform
10
+ * Node.js configuration loader
10
11
  *
11
- * Provides FileSystem and HttpClient layers for the core ConfigLoader service
12
+ * Provides configuration loading using native Node.js APIs (fs, fetch)
13
+ * This module doesn't require Effect Platform, making it work without Effect installed.
12
14
  */
13
15
 
14
16
  /**
@@ -46,17 +48,12 @@ interface ConfigLoaderOptions {
46
48
  */
47
49
  interface EffectInstrumentationOptions extends ConfigLoaderOptions {
48
50
  /**
49
- * OTLP endpoint URL
50
- * @default process.env.OTEL_EXPORTER_OTLP_ENDPOINT || 'http://localhost:4318'
51
- */
52
- otlpEndpoint?: string;
53
- /**
54
- * Service name
51
+ * Service name for Effect spans
55
52
  * @default process.env.OTEL_SERVICE_NAME || 'effect-service'
56
53
  */
57
54
  serviceName?: string;
58
55
  /**
59
- * Service version
56
+ * Service version for Effect spans
60
57
  * @default process.env.npm_package_version || '1.0.0'
61
58
  */
62
59
  serviceVersion?: string;
@@ -66,20 +63,17 @@ interface EffectInstrumentationOptions extends ConfigLoaderOptions {
66
63
  */
67
64
  autoExtractMetadata?: boolean;
68
65
  /**
69
- * Whether to continue existing traces from NodeSDK auto-instrumentation
70
- *
71
- * When true (default):
72
- * - Effect spans become children of existing NodeSDK spans
73
- * - Example: HTTP request span → Effect business logic span
74
- * - Uses OpenTelemetry Context API for propagation
75
- *
76
- * When false:
77
- * - Effect operations always create new root spans
78
- * - Not recommended unless you have specific requirements
79
- *
80
- * @default true
66
+ * OTLP endpoint URL (only used when exporter mode is 'standalone')
67
+ * @default process.env.OTEL_EXPORTER_OTLP_ENDPOINT || 'http://localhost:4318'
68
+ */
69
+ otlpEndpoint?: string;
70
+ /**
71
+ * Exporter mode:
72
+ * - 'unified': Use global TracerProvider from Node SDK (recommended, enables filtering)
73
+ * - 'standalone': Use Effect's own OTLP exporter (bypasses Node SDK filtering)
74
+ * @default 'unified'
81
75
  */
82
- continueExistingTraces?: boolean;
76
+ exporterMode?: 'unified' | 'standalone';
83
77
  }
84
78
  /**
85
79
  * Create Effect instrumentation layer with custom options
@@ -118,11 +112,12 @@ declare function createEffectInstrumentation(options?: EffectInstrumentationOpti
118
112
  *
119
113
  * Uses the global OpenTelemetry tracer provider that was set up by
120
114
  * initializeInstrumentation(). This ensures all traces (Express, Effect, etc.)
121
- * go to the same OTLP endpoint.
115
+ * go through the same TracerProvider and PatternSpanProcessor.
122
116
  *
123
117
  * Context Propagation:
124
118
  * - Automatically continues traces from NodeSDK auto-instrumentation
125
119
  * - Effect spans become children of HTTP request spans
120
+ * - Respects http.ignore_incoming_paths and other filtering patterns
126
121
  * - No configuration needed
127
122
  *
128
123
  * @example
@@ -138,7 +133,7 @@ declare function createEffectInstrumentation(options?: EffectInstrumentationOpti
138
133
  * )
139
134
  * ```
140
135
  */
141
- declare const EffectInstrumentationLive: Layer.Layer<never, never, never>;
136
+ declare const EffectInstrumentationLive: Layer.Layer<Tracer.OtelTracer, never, never>;
142
137
 
143
138
  /**
144
139
  * Effect-specific span annotation helpers
@@ -1,4 +1,5 @@
1
1
  import { Layer, Effect, FiberSet as FiberSet$1 } from 'effect';
2
+ import * as Tracer from '@effect/opentelemetry/Tracer';
2
3
  import { InstrumentationConfig } from '@atrim/instrument-core';
3
4
  import * as effect_Runtime from 'effect/Runtime';
4
5
  import * as effect_FiberId from 'effect/FiberId';
@@ -6,9 +7,10 @@ import * as effect_Scope from 'effect/Scope';
6
7
  import { RuntimeFiber } from 'effect/Fiber';
7
8
 
8
9
  /**
9
- * Node.js configuration loader using Effect Platform
10
+ * Node.js configuration loader
10
11
  *
11
- * Provides FileSystem and HttpClient layers for the core ConfigLoader service
12
+ * Provides configuration loading using native Node.js APIs (fs, fetch)
13
+ * This module doesn't require Effect Platform, making it work without Effect installed.
12
14
  */
13
15
 
14
16
  /**
@@ -46,17 +48,12 @@ interface ConfigLoaderOptions {
46
48
  */
47
49
  interface EffectInstrumentationOptions extends ConfigLoaderOptions {
48
50
  /**
49
- * OTLP endpoint URL
50
- * @default process.env.OTEL_EXPORTER_OTLP_ENDPOINT || 'http://localhost:4318'
51
- */
52
- otlpEndpoint?: string;
53
- /**
54
- * Service name
51
+ * Service name for Effect spans
55
52
  * @default process.env.OTEL_SERVICE_NAME || 'effect-service'
56
53
  */
57
54
  serviceName?: string;
58
55
  /**
59
- * Service version
56
+ * Service version for Effect spans
60
57
  * @default process.env.npm_package_version || '1.0.0'
61
58
  */
62
59
  serviceVersion?: string;
@@ -66,20 +63,17 @@ interface EffectInstrumentationOptions extends ConfigLoaderOptions {
66
63
  */
67
64
  autoExtractMetadata?: boolean;
68
65
  /**
69
- * Whether to continue existing traces from NodeSDK auto-instrumentation
70
- *
71
- * When true (default):
72
- * - Effect spans become children of existing NodeSDK spans
73
- * - Example: HTTP request span → Effect business logic span
74
- * - Uses OpenTelemetry Context API for propagation
75
- *
76
- * When false:
77
- * - Effect operations always create new root spans
78
- * - Not recommended unless you have specific requirements
79
- *
80
- * @default true
66
+ * OTLP endpoint URL (only used when exporter mode is 'standalone')
67
+ * @default process.env.OTEL_EXPORTER_OTLP_ENDPOINT || 'http://localhost:4318'
68
+ */
69
+ otlpEndpoint?: string;
70
+ /**
71
+ * Exporter mode:
72
+ * - 'unified': Use global TracerProvider from Node SDK (recommended, enables filtering)
73
+ * - 'standalone': Use Effect's own OTLP exporter (bypasses Node SDK filtering)
74
+ * @default 'unified'
81
75
  */
82
- continueExistingTraces?: boolean;
76
+ exporterMode?: 'unified' | 'standalone';
83
77
  }
84
78
  /**
85
79
  * Create Effect instrumentation layer with custom options
@@ -118,11 +112,12 @@ declare function createEffectInstrumentation(options?: EffectInstrumentationOpti
118
112
  *
119
113
  * Uses the global OpenTelemetry tracer provider that was set up by
120
114
  * initializeInstrumentation(). This ensures all traces (Express, Effect, etc.)
121
- * go to the same OTLP endpoint.
115
+ * go through the same TracerProvider and PatternSpanProcessor.
122
116
  *
123
117
  * Context Propagation:
124
118
  * - Automatically continues traces from NodeSDK auto-instrumentation
125
119
  * - Effect spans become children of HTTP request spans
120
+ * - Respects http.ignore_incoming_paths and other filtering patterns
126
121
  * - No configuration needed
127
122
  *
128
123
  * @example
@@ -138,7 +133,7 @@ declare function createEffectInstrumentation(options?: EffectInstrumentationOpti
138
133
  * )
139
134
  * ```
140
135
  */
141
- declare const EffectInstrumentationLive: Layer.Layer<never, never, never>;
136
+ declare const EffectInstrumentationLive: Layer.Layer<Tracer.OtelTracer, never, never>;
142
137
 
143
138
  /**
144
139
  * Effect-specific span annotation helpers
@@ -1,20 +1,17 @@
1
- import { Data, Context, Effect, Layer, FiberSet as FiberSet$1, Fiber, Option, FiberId, Tracer } from 'effect';
1
+ import { Data, Context, Effect, Layer, FiberSet as FiberSet$1, Fiber, Option, FiberId, Tracer as Tracer$1 } from 'effect';
2
+ import * as Tracer from '@effect/opentelemetry/Tracer';
3
+ import * as Resource from '@effect/opentelemetry/Resource';
2
4
  import * as Otlp from '@effect/opentelemetry/Otlp';
3
5
  import { FetchHttpClient } from '@effect/platform';
4
6
  import { TraceFlags, trace, context } from '@opentelemetry/api';
7
+ import { TELEMETRY_SDK_LANGUAGE_VALUE_NODEJS, ATTR_TELEMETRY_SDK_NAME, ATTR_TELEMETRY_SDK_LANGUAGE } from '@opentelemetry/semantic-conventions';
5
8
  import { FileSystem } from '@effect/platform/FileSystem';
6
9
  import * as HttpClient from '@effect/platform/HttpClient';
7
10
  import * as HttpClientRequest from '@effect/platform/HttpClientRequest';
8
11
  import { parse } from 'yaml';
9
12
  import { z } from 'zod';
10
- import { NodeContext } from '@effect/platform-node';
11
13
 
12
14
  // src/integrations/effect/effect-tracer.ts
13
-
14
- // ../../node_modules/.pnpm/@opentelemetry+semantic-conventions@1.38.0/node_modules/@opentelemetry/semantic-conventions/build/esm/stable_attributes.js
15
- var ATTR_TELEMETRY_SDK_LANGUAGE = "telemetry.sdk.language";
16
- var TELEMETRY_SDK_LANGUAGE_VALUE_NODEJS = "nodejs";
17
- var ATTR_TELEMETRY_SDK_NAME = "telemetry.sdk.name";
18
15
  var __defProp = Object.defineProperty;
19
16
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
20
17
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
@@ -45,6 +42,69 @@ var AutoIsolationConfigSchema = z.object({
45
42
  add_metadata: z.boolean().default(true)
46
43
  }).default({})
47
44
  });
45
+ var SpanNamingRuleSchema = z.object({
46
+ // Match criteria (all specified criteria must match)
47
+ match: z.object({
48
+ // Regex pattern to match file path
49
+ file: z.string().optional(),
50
+ // Regex pattern to match function name
51
+ function: z.string().optional(),
52
+ // Regex pattern to match module name
53
+ module: z.string().optional()
54
+ }),
55
+ // Span name template with variables:
56
+ // {fiber_id} - Fiber ID
57
+ // {function} - Function name
58
+ // {module} - Module name
59
+ // {file} - File path
60
+ // {line} - Line number
61
+ // {operator} - Effect operator (gen, all, forEach, etc.)
62
+ // {match:field:N} - Captured regex group from match
63
+ name: z.string()
64
+ });
65
+ var AutoInstrumentationConfigSchema = z.object({
66
+ // Enable/disable auto-instrumentation
67
+ enabled: z.boolean().default(false),
68
+ // Tracing granularity
69
+ // - 'fiber': Trace at fiber creation (recommended, lower overhead)
70
+ // - 'operator': Trace each Effect operator (higher granularity, more overhead)
71
+ granularity: z.enum(["fiber", "operator"]).default("fiber"),
72
+ // Smart span naming configuration
73
+ span_naming: z.object({
74
+ // Default span name template when no rules match
75
+ default: z.string().default("effect.fiber.{fiber_id}"),
76
+ // Infer span names from source code (requires stack trace parsing)
77
+ // Adds ~50-100μs overhead per fiber
78
+ infer_from_source: z.boolean().default(true),
79
+ // Naming rules (first match wins)
80
+ rules: z.array(SpanNamingRuleSchema).default([])
81
+ }).default({}),
82
+ // Pattern-based filtering
83
+ filter: z.object({
84
+ // Only trace spans matching these patterns (empty = trace all)
85
+ include: z.array(z.string()).default([]),
86
+ // Never trace spans matching these patterns
87
+ exclude: z.array(z.string()).default([])
88
+ }).default({}),
89
+ // Performance controls
90
+ performance: z.object({
91
+ // Sample rate (0.0 - 1.0)
92
+ sampling_rate: z.number().min(0).max(1).default(1),
93
+ // Skip fibers shorter than this duration (e.g., "10ms", "100 millis")
94
+ min_duration: z.string().default("0ms"),
95
+ // Maximum concurrent traced fibers (0 = unlimited)
96
+ max_concurrent: z.number().default(0)
97
+ }).default({}),
98
+ // Automatic metadata extraction
99
+ metadata: z.object({
100
+ // Extract Effect fiber information
101
+ fiber_info: z.boolean().default(true),
102
+ // Extract source location (file:line)
103
+ source_location: z.boolean().default(true),
104
+ // Extract parent fiber information
105
+ parent_fiber: z.boolean().default(true)
106
+ }).default({})
107
+ });
48
108
  var HttpFilteringConfigSchema = z.object({
49
109
  // Patterns to ignore for outgoing HTTP requests (string patterns only in YAML)
50
110
  ignore_outgoing_urls: z.array(z.string()).optional(),
@@ -66,6 +126,30 @@ var HttpFilteringConfigSchema = z.object({
66
126
  include_urls: z.array(z.string()).optional()
67
127
  }).optional()
68
128
  });
129
+ var ExporterConfigSchema = z.object({
130
+ // Exporter type: 'otlp' | 'console' | 'none'
131
+ // - 'otlp': Export to OTLP endpoint (production)
132
+ // - 'console': Log spans to console (development)
133
+ // - 'none': No export (disable tracing)
134
+ type: z.enum(["otlp", "console", "none"]).default("otlp"),
135
+ // OTLP endpoint URL (for type: otlp)
136
+ // Defaults to OTEL_EXPORTER_OTLP_ENDPOINT env var or http://localhost:4318
137
+ endpoint: z.string().optional(),
138
+ // Custom headers to send with OTLP requests (for type: otlp)
139
+ // Useful for authentication (x-api-key, Authorization, etc.)
140
+ headers: z.record(z.string()).optional(),
141
+ // Span processor type
142
+ // - 'batch': Batch spans for export (production, lower overhead)
143
+ // - 'simple': Export immediately (development, no batching delay)
144
+ processor: z.enum(["batch", "simple"]).default("batch"),
145
+ // Batch processor settings (for processor: batch)
146
+ batch: z.object({
147
+ // Max time to wait before exporting (milliseconds)
148
+ scheduled_delay_millis: z.number().default(1e3),
149
+ // Max batch size
150
+ max_export_batch_size: z.number().default(100)
151
+ }).optional()
152
+ });
69
153
  var InstrumentationConfigSchema = z.object({
70
154
  version: z.string(),
71
155
  instrumentation: z.object({
@@ -76,11 +160,54 @@ var InstrumentationConfigSchema = z.object({
76
160
  ignore_patterns: z.array(PatternConfigSchema)
77
161
  }),
78
162
  effect: z.object({
163
+ // Enable/disable Effect tracing entirely
164
+ // When false, EffectInstrumentationLive returns Layer.empty
165
+ enabled: z.boolean().default(true),
166
+ // Exporter mode (legacy - use exporter.type instead):
167
+ // - "unified": Use global TracerProvider from Node SDK (recommended, enables filtering)
168
+ // - "standalone": Use Effect's own OTLP exporter (bypasses Node SDK filtering)
169
+ exporter: z.enum(["unified", "standalone"]).default("unified"),
170
+ // Exporter configuration (for auto-instrumentation)
171
+ exporter_config: ExporterConfigSchema.optional(),
79
172
  auto_extract_metadata: z.boolean(),
80
- auto_isolation: AutoIsolationConfigSchema.optional()
173
+ auto_isolation: AutoIsolationConfigSchema.optional(),
174
+ // Auto-instrumentation: automatic tracing of all Effect fibers
175
+ auto_instrumentation: AutoInstrumentationConfigSchema.optional()
81
176
  }).optional(),
82
177
  http: HttpFilteringConfigSchema.optional()
83
178
  });
179
+ var defaultConfig = {
180
+ version: "1.0",
181
+ instrumentation: {
182
+ enabled: true,
183
+ logging: "on",
184
+ description: "Default instrumentation configuration",
185
+ instrument_patterns: [
186
+ { pattern: "^app\\.", enabled: true, description: "Application operations" },
187
+ { pattern: "^http\\.server\\.", enabled: true, description: "HTTP server operations" },
188
+ { pattern: "^http\\.client\\.", enabled: true, description: "HTTP client operations" }
189
+ ],
190
+ ignore_patterns: [
191
+ { pattern: "^test\\.", description: "Test utilities" },
192
+ { pattern: "^internal\\.", description: "Internal operations" },
193
+ { pattern: "^health\\.", description: "Health checks" }
194
+ ]
195
+ },
196
+ effect: {
197
+ enabled: true,
198
+ exporter: "unified",
199
+ auto_extract_metadata: true
200
+ }
201
+ };
202
+ function parseAndValidateConfig(content) {
203
+ let parsed;
204
+ if (typeof content === "string") {
205
+ parsed = parse(content);
206
+ } else {
207
+ parsed = content;
208
+ }
209
+ return InstrumentationConfigSchema.parse(parsed);
210
+ }
84
211
  (class extends Data.TaggedError("ConfigError") {
85
212
  get message() {
86
213
  return this.reason;
@@ -258,7 +385,7 @@ var makeConfigLoader = Effect.gen(function* () {
258
385
  })
259
386
  });
260
387
  });
261
- var ConfigLoaderLive = Layer.effect(ConfigLoader, makeConfigLoader);
388
+ Layer.effect(ConfigLoader, makeConfigLoader);
262
389
  var PatternMatcher = class {
263
390
  constructor(config) {
264
391
  __publicField(this, "ignorePatterns", []);
@@ -406,84 +533,58 @@ var Logger = class {
406
533
  }
407
534
  };
408
535
  var logger = new Logger();
409
- var NodeConfigLoaderLive = ConfigLoaderLive.pipe(
410
- Layer.provide(Layer.mergeAll(NodeContext.layer, FetchHttpClient.layer))
411
- );
412
- var cachedLoaderPromise = null;
413
- function getCachedLoader() {
414
- if (!cachedLoaderPromise) {
415
- cachedLoaderPromise = Effect.runPromise(
416
- Effect.gen(function* () {
417
- return yield* ConfigLoader;
418
- }).pipe(Effect.provide(NodeConfigLoaderLive))
419
- );
536
+ async function loadFromFile(filePath) {
537
+ const { readFile } = await import('fs/promises');
538
+ const content = await readFile(filePath, "utf-8");
539
+ return parseAndValidateConfig(content);
540
+ }
541
+ async function loadFromUrl(url) {
542
+ const response = await fetch(url);
543
+ if (!response.ok) {
544
+ throw new Error(`Failed to fetch config from ${url}: ${response.statusText}`);
420
545
  }
421
- return cachedLoaderPromise;
546
+ const content = await response.text();
547
+ return parseAndValidateConfig(content);
422
548
  }
423
- async function loadConfig(uri, options) {
424
- if (options?.cacheTimeout === 0) {
425
- const program = Effect.gen(function* () {
426
- const loader2 = yield* ConfigLoader;
427
- return yield* loader2.loadFromUri(uri);
428
- });
429
- return Effect.runPromise(program.pipe(Effect.provide(NodeConfigLoaderLive)));
430
- }
431
- const loader = await getCachedLoader();
432
- return Effect.runPromise(loader.loadFromUri(uri));
549
+ async function loadConfig(uri, _options) {
550
+ if (uri.startsWith("http://") || uri.startsWith("https://")) {
551
+ return loadFromUrl(uri);
552
+ }
553
+ if (uri.startsWith("file://")) {
554
+ const filePath = uri.slice(7);
555
+ return loadFromFile(filePath);
556
+ }
557
+ return loadFromFile(uri);
433
558
  }
434
559
  async function loadConfigFromInline(content) {
435
- const loader = await getCachedLoader();
436
- return Effect.runPromise(loader.loadFromInline(content));
437
- }
438
- function getDefaultConfig() {
439
- return {
440
- version: "1.0",
441
- instrumentation: {
442
- enabled: true,
443
- logging: "on",
444
- description: "Default instrumentation configuration",
445
- instrument_patterns: [
446
- { pattern: "^app\\.", enabled: true, description: "Application operations" },
447
- { pattern: "^http\\.server\\.", enabled: true, description: "HTTP server operations" },
448
- { pattern: "^http\\.client\\.", enabled: true, description: "HTTP client operations" }
449
- ],
450
- ignore_patterns: [
451
- { pattern: "^test\\.", description: "Test utilities" },
452
- { pattern: "^internal\\.", description: "Internal operations" },
453
- { pattern: "^health\\.", description: "Health checks" }
454
- ]
455
- },
456
- effect: {
457
- auto_extract_metadata: true
458
- }
459
- };
560
+ return parseAndValidateConfig(content);
460
561
  }
461
562
  async function loadConfigWithOptions(options = {}) {
462
- const loadOptions = options.cacheTimeout !== void 0 ? { cacheTimeout: options.cacheTimeout } : void 0;
463
563
  if (options.config) {
464
564
  return loadConfigFromInline(options.config);
465
565
  }
466
566
  const envConfigPath = process.env.ATRIM_INSTRUMENTATION_CONFIG;
467
567
  if (envConfigPath) {
468
- return loadConfig(envConfigPath, loadOptions);
568
+ return loadConfig(envConfigPath);
469
569
  }
470
570
  if (options.configUrl) {
471
- return loadConfig(options.configUrl, loadOptions);
571
+ return loadConfig(options.configUrl);
472
572
  }
473
573
  if (options.configPath) {
474
- return loadConfig(options.configPath, loadOptions);
574
+ return loadConfig(options.configPath);
475
575
  }
476
576
  const { existsSync } = await import('fs');
477
577
  const { join } = await import('path');
478
578
  const defaultPath = join(process.cwd(), "instrumentation.yaml");
479
579
  if (existsSync(defaultPath)) {
480
- return loadConfig(defaultPath, loadOptions);
580
+ return loadConfig(defaultPath);
481
581
  }
482
- return getDefaultConfig();
582
+ return defaultConfig;
483
583
  }
484
584
 
485
585
  // src/integrations/effect/effect-tracer.ts
486
- var SDK_NAME = "@effect/opentelemetry-otlp";
586
+ var SDK_NAME = "@effect/opentelemetry";
587
+ var ATTR_TELEMETRY_EXPORTER_MODE = "telemetry.exporter.mode";
487
588
  function createEffectInstrumentation(options = {}) {
488
589
  return Layer.unwrapEffect(
489
590
  Effect.gen(function* () {
@@ -494,90 +595,89 @@ function createEffectInstrumentation(options = {}) {
494
595
  message: error instanceof Error ? error.message : String(error)
495
596
  })
496
597
  });
598
+ const effectEnabled = process.env.OTEL_EFFECT_ENABLED !== "false" && (config.effect?.enabled ?? true);
599
+ if (!effectEnabled) {
600
+ logger.log("@atrim/instrumentation/effect: Effect tracing disabled via config");
601
+ return Layer.empty;
602
+ }
497
603
  yield* Effect.sync(() => {
498
604
  const loggingLevel = config.instrumentation.logging || "on";
499
605
  logger.setLevel(loggingLevel);
500
606
  });
501
607
  yield* Effect.sync(() => initializePatternMatcher(config));
502
- const otlpEndpoint = options.otlpEndpoint || process.env.OTEL_EXPORTER_OTLP_ENDPOINT || "http://localhost:4318";
503
608
  const serviceName = options.serviceName || process.env.OTEL_SERVICE_NAME || "effect-service";
504
609
  const serviceVersion = options.serviceVersion || process.env.npm_package_version || "1.0.0";
505
- const autoExtractMetadata = options.autoExtractMetadata ?? config.effect?.auto_extract_metadata ?? true;
506
- const continueExistingTraces = options.continueExistingTraces ?? true;
507
- logger.log("\u{1F50D} Effect OpenTelemetry instrumentation");
508
- logger.log(` \u{1F4E1} Endpoint: ${otlpEndpoint}`);
509
- logger.log(` \u{1F3F7}\uFE0F Service: ${serviceName}`);
510
- logger.log(` \u2705 Auto metadata extraction: ${autoExtractMetadata}`);
511
- logger.log(` \u2705 Continue existing traces: ${continueExistingTraces}`);
512
- const otlpLayer = Otlp.layer({
513
- baseUrl: otlpEndpoint,
514
- resource: {
515
- serviceName,
516
- serviceVersion,
517
- attributes: {
518
- "platform.component": "effect",
519
- "effect.auto_metadata": autoExtractMetadata,
520
- "effect.context_propagation": continueExistingTraces,
521
- [ATTR_TELEMETRY_SDK_LANGUAGE]: TELEMETRY_SDK_LANGUAGE_VALUE_NODEJS,
522
- [ATTR_TELEMETRY_SDK_NAME]: SDK_NAME
523
- }
524
- },
525
- // Bridge Effect context to OpenTelemetry global context
526
- // This is essential for context propagation to work properly
527
- tracerContext: (f, span) => {
528
- if (span._tag !== "Span") {
529
- return f();
610
+ const exporterMode = options.exporterMode ?? config.effect?.exporter ?? "unified";
611
+ const resourceAttributes = {
612
+ "platform.component": "effect",
613
+ [ATTR_TELEMETRY_SDK_LANGUAGE]: TELEMETRY_SDK_LANGUAGE_VALUE_NODEJS,
614
+ [ATTR_TELEMETRY_SDK_NAME]: SDK_NAME,
615
+ [ATTR_TELEMETRY_EXPORTER_MODE]: exporterMode
616
+ };
617
+ if (exporterMode === "standalone") {
618
+ const otlpEndpoint = options.otlpEndpoint || process.env.OTEL_EXPORTER_OTLP_ENDPOINT || "http://localhost:4318";
619
+ logger.log("Effect OpenTelemetry instrumentation (standalone)");
620
+ logger.log(` Service: ${serviceName}`);
621
+ logger.log(` Endpoint: ${otlpEndpoint}`);
622
+ logger.log(" WARNING: Standalone mode bypasses Node SDK filtering");
623
+ return Otlp.layer({
624
+ baseUrl: otlpEndpoint,
625
+ resource: {
626
+ serviceName,
627
+ serviceVersion,
628
+ attributes: resourceAttributes
629
+ },
630
+ // Bridge Effect context to OpenTelemetry global context
631
+ tracerContext: (f, span) => {
632
+ if (span._tag !== "Span") {
633
+ return f();
634
+ }
635
+ const spanContext = {
636
+ traceId: span.traceId,
637
+ spanId: span.spanId,
638
+ traceFlags: span.sampled ? TraceFlags.SAMPLED : TraceFlags.NONE
639
+ };
640
+ const otelSpan = trace.wrapSpanContext(spanContext);
641
+ return context.with(trace.setSpan(context.active(), otelSpan), f);
530
642
  }
531
- const spanContext = {
532
- traceId: span.traceId,
533
- spanId: span.spanId,
534
- traceFlags: span.sampled ? TraceFlags.SAMPLED : TraceFlags.NONE
535
- };
536
- const otelSpan = trace.wrapSpanContext(spanContext);
537
- return context.with(trace.setSpan(context.active(), otelSpan), f);
538
- }
539
- }).pipe(Layer.provide(FetchHttpClient.layer));
540
- if (autoExtractMetadata) {
541
- return otlpLayer;
643
+ }).pipe(Layer.provide(FetchHttpClient.layer));
644
+ } else {
645
+ logger.log("Effect OpenTelemetry instrumentation (unified)");
646
+ logger.log(` Service: ${serviceName}`);
647
+ logger.log(" Using global TracerProvider for span export");
648
+ return Tracer.layerGlobal.pipe(
649
+ Layer.provide(
650
+ Resource.layer({
651
+ serviceName,
652
+ serviceVersion,
653
+ attributes: resourceAttributes
654
+ })
655
+ )
656
+ );
542
657
  }
543
- return otlpLayer;
544
658
  })
545
659
  ).pipe(Layer.orDie);
546
660
  }
547
661
  var EffectInstrumentationLive = Effect.sync(() => {
548
- const endpoint = process.env.OTEL_EXPORTER_OTLP_ENDPOINT || "http://localhost:4318";
549
662
  const serviceName = process.env.OTEL_SERVICE_NAME || "effect-service";
550
663
  const serviceVersion = process.env.npm_package_version || "1.0.0";
551
664
  logger.minimal(`@atrim/instrumentation/effect: Effect tracing enabled (${serviceName})`);
552
- logger.log("\u{1F50D} Effect OpenTelemetry tracer");
553
- logger.log(` \u{1F4E1} Endpoint: ${endpoint}`);
554
- logger.log(` \u{1F3F7}\uFE0F Service: ${serviceName}`);
555
- return Otlp.layer({
556
- baseUrl: endpoint,
557
- resource: {
558
- serviceName,
559
- serviceVersion,
560
- attributes: {
561
- "platform.component": "effect",
562
- [ATTR_TELEMETRY_SDK_LANGUAGE]: TELEMETRY_SDK_LANGUAGE_VALUE_NODEJS,
563
- [ATTR_TELEMETRY_SDK_NAME]: SDK_NAME
564
- }
565
- },
566
- // CRITICAL: Bridge Effect context to OpenTelemetry global context
567
- // This allows NodeSDK auto-instrumentation to see Effect spans as parent spans
568
- tracerContext: (f, span) => {
569
- if (span._tag !== "Span") {
570
- return f();
571
- }
572
- const spanContext = {
573
- traceId: span.traceId,
574
- spanId: span.spanId,
575
- traceFlags: span.sampled ? TraceFlags.SAMPLED : TraceFlags.NONE
576
- };
577
- const otelSpan = trace.wrapSpanContext(spanContext);
578
- return context.with(trace.setSpan(context.active(), otelSpan), f);
579
- }
580
- }).pipe(Layer.provide(FetchHttpClient.layer));
665
+ logger.log("Effect OpenTelemetry tracer (unified)");
666
+ logger.log(` Service: ${serviceName}`);
667
+ return Tracer.layerGlobal.pipe(
668
+ Layer.provide(
669
+ Resource.layer({
670
+ serviceName,
671
+ serviceVersion,
672
+ attributes: {
673
+ "platform.component": "effect",
674
+ [ATTR_TELEMETRY_SDK_LANGUAGE]: TELEMETRY_SDK_LANGUAGE_VALUE_NODEJS,
675
+ [ATTR_TELEMETRY_SDK_NAME]: SDK_NAME,
676
+ [ATTR_TELEMETRY_EXPORTER_MODE]: "unified"
677
+ }
678
+ })
679
+ )
680
+ );
581
681
  }).pipe(Layer.unwrapEffect);
582
682
  function annotateUser(userId, email, username) {
583
683
  const attributes = {
@@ -766,7 +866,7 @@ var runIsolated = (set, effect, name, options) => {
766
866
  return FiberSet$1.run(set, effect, { propagateInterruption });
767
867
  }
768
868
  return Effect.gen(function* () {
769
- const maybeParent = yield* Effect.serviceOption(Tracer.ParentSpan);
869
+ const maybeParent = yield* Effect.serviceOption(Tracer$1.ParentSpan);
770
870
  if (maybeParent._tag === "None" || !captureLogicalParent) {
771
871
  const isolated2 = effect.pipe(
772
872
  Effect.withSpan(name, {