@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,15 +1,17 @@
1
1
  'use strict';
2
2
 
3
3
  var effect = require('effect');
4
+ var Tracer = require('@effect/opentelemetry/Tracer');
5
+ var Resource = require('@effect/opentelemetry/Resource');
4
6
  var Otlp = require('@effect/opentelemetry/Otlp');
5
7
  var platform = require('@effect/platform');
6
8
  var api = require('@opentelemetry/api');
9
+ var semanticConventions = require('@opentelemetry/semantic-conventions');
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 platformNode = require('@effect/platform-node');
13
15
 
14
16
  function _interopNamespace(e) {
15
17
  if (e && e.__esModule) return e;
@@ -29,16 +31,13 @@ function _interopNamespace(e) {
29
31
  return Object.freeze(n);
30
32
  }
31
33
 
34
+ var Tracer__namespace = /*#__PURE__*/_interopNamespace(Tracer);
35
+ var Resource__namespace = /*#__PURE__*/_interopNamespace(Resource);
32
36
  var Otlp__namespace = /*#__PURE__*/_interopNamespace(Otlp);
33
37
  var HttpClient__namespace = /*#__PURE__*/_interopNamespace(HttpClient);
34
38
  var HttpClientRequest__namespace = /*#__PURE__*/_interopNamespace(HttpClientRequest);
35
39
 
36
40
  // src/integrations/effect/effect-tracer.ts
37
-
38
- // ../../node_modules/.pnpm/@opentelemetry+semantic-conventions@1.38.0/node_modules/@opentelemetry/semantic-conventions/build/esm/stable_attributes.js
39
- var ATTR_TELEMETRY_SDK_LANGUAGE = "telemetry.sdk.language";
40
- var TELEMETRY_SDK_LANGUAGE_VALUE_NODEJS = "nodejs";
41
- var ATTR_TELEMETRY_SDK_NAME = "telemetry.sdk.name";
42
41
  var __defProp = Object.defineProperty;
43
42
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
44
43
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
@@ -69,6 +68,69 @@ var AutoIsolationConfigSchema = zod.z.object({
69
68
  add_metadata: zod.z.boolean().default(true)
70
69
  }).default({})
71
70
  });
71
+ var SpanNamingRuleSchema = zod.z.object({
72
+ // Match criteria (all specified criteria must match)
73
+ match: zod.z.object({
74
+ // Regex pattern to match file path
75
+ file: zod.z.string().optional(),
76
+ // Regex pattern to match function name
77
+ function: zod.z.string().optional(),
78
+ // Regex pattern to match module name
79
+ module: zod.z.string().optional()
80
+ }),
81
+ // Span name template with variables:
82
+ // {fiber_id} - Fiber ID
83
+ // {function} - Function name
84
+ // {module} - Module name
85
+ // {file} - File path
86
+ // {line} - Line number
87
+ // {operator} - Effect operator (gen, all, forEach, etc.)
88
+ // {match:field:N} - Captured regex group from match
89
+ name: zod.z.string()
90
+ });
91
+ var AutoInstrumentationConfigSchema = zod.z.object({
92
+ // Enable/disable auto-instrumentation
93
+ enabled: zod.z.boolean().default(false),
94
+ // Tracing granularity
95
+ // - 'fiber': Trace at fiber creation (recommended, lower overhead)
96
+ // - 'operator': Trace each Effect operator (higher granularity, more overhead)
97
+ granularity: zod.z.enum(["fiber", "operator"]).default("fiber"),
98
+ // Smart span naming configuration
99
+ span_naming: zod.z.object({
100
+ // Default span name template when no rules match
101
+ default: zod.z.string().default("effect.fiber.{fiber_id}"),
102
+ // Infer span names from source code (requires stack trace parsing)
103
+ // Adds ~50-100μs overhead per fiber
104
+ infer_from_source: zod.z.boolean().default(true),
105
+ // Naming rules (first match wins)
106
+ rules: zod.z.array(SpanNamingRuleSchema).default([])
107
+ }).default({}),
108
+ // Pattern-based filtering
109
+ filter: zod.z.object({
110
+ // Only trace spans matching these patterns (empty = trace all)
111
+ include: zod.z.array(zod.z.string()).default([]),
112
+ // Never trace spans matching these patterns
113
+ exclude: zod.z.array(zod.z.string()).default([])
114
+ }).default({}),
115
+ // Performance controls
116
+ performance: zod.z.object({
117
+ // Sample rate (0.0 - 1.0)
118
+ sampling_rate: zod.z.number().min(0).max(1).default(1),
119
+ // Skip fibers shorter than this duration (e.g., "10ms", "100 millis")
120
+ min_duration: zod.z.string().default("0ms"),
121
+ // Maximum concurrent traced fibers (0 = unlimited)
122
+ max_concurrent: zod.z.number().default(0)
123
+ }).default({}),
124
+ // Automatic metadata extraction
125
+ metadata: zod.z.object({
126
+ // Extract Effect fiber information
127
+ fiber_info: zod.z.boolean().default(true),
128
+ // Extract source location (file:line)
129
+ source_location: zod.z.boolean().default(true),
130
+ // Extract parent fiber information
131
+ parent_fiber: zod.z.boolean().default(true)
132
+ }).default({})
133
+ });
72
134
  var HttpFilteringConfigSchema = zod.z.object({
73
135
  // Patterns to ignore for outgoing HTTP requests (string patterns only in YAML)
74
136
  ignore_outgoing_urls: zod.z.array(zod.z.string()).optional(),
@@ -90,6 +152,30 @@ var HttpFilteringConfigSchema = zod.z.object({
90
152
  include_urls: zod.z.array(zod.z.string()).optional()
91
153
  }).optional()
92
154
  });
155
+ var ExporterConfigSchema = zod.z.object({
156
+ // Exporter type: 'otlp' | 'console' | 'none'
157
+ // - 'otlp': Export to OTLP endpoint (production)
158
+ // - 'console': Log spans to console (development)
159
+ // - 'none': No export (disable tracing)
160
+ type: zod.z.enum(["otlp", "console", "none"]).default("otlp"),
161
+ // OTLP endpoint URL (for type: otlp)
162
+ // Defaults to OTEL_EXPORTER_OTLP_ENDPOINT env var or http://localhost:4318
163
+ endpoint: zod.z.string().optional(),
164
+ // Custom headers to send with OTLP requests (for type: otlp)
165
+ // Useful for authentication (x-api-key, Authorization, etc.)
166
+ headers: zod.z.record(zod.z.string()).optional(),
167
+ // Span processor type
168
+ // - 'batch': Batch spans for export (production, lower overhead)
169
+ // - 'simple': Export immediately (development, no batching delay)
170
+ processor: zod.z.enum(["batch", "simple"]).default("batch"),
171
+ // Batch processor settings (for processor: batch)
172
+ batch: zod.z.object({
173
+ // Max time to wait before exporting (milliseconds)
174
+ scheduled_delay_millis: zod.z.number().default(1e3),
175
+ // Max batch size
176
+ max_export_batch_size: zod.z.number().default(100)
177
+ }).optional()
178
+ });
93
179
  var InstrumentationConfigSchema = zod.z.object({
94
180
  version: zod.z.string(),
95
181
  instrumentation: zod.z.object({
@@ -100,11 +186,54 @@ var InstrumentationConfigSchema = zod.z.object({
100
186
  ignore_patterns: zod.z.array(PatternConfigSchema)
101
187
  }),
102
188
  effect: zod.z.object({
189
+ // Enable/disable Effect tracing entirely
190
+ // When false, EffectInstrumentationLive returns Layer.empty
191
+ enabled: zod.z.boolean().default(true),
192
+ // Exporter mode (legacy - use exporter.type instead):
193
+ // - "unified": Use global TracerProvider from Node SDK (recommended, enables filtering)
194
+ // - "standalone": Use Effect's own OTLP exporter (bypasses Node SDK filtering)
195
+ exporter: zod.z.enum(["unified", "standalone"]).default("unified"),
196
+ // Exporter configuration (for auto-instrumentation)
197
+ exporter_config: ExporterConfigSchema.optional(),
103
198
  auto_extract_metadata: zod.z.boolean(),
104
- auto_isolation: AutoIsolationConfigSchema.optional()
199
+ auto_isolation: AutoIsolationConfigSchema.optional(),
200
+ // Auto-instrumentation: automatic tracing of all Effect fibers
201
+ auto_instrumentation: AutoInstrumentationConfigSchema.optional()
105
202
  }).optional(),
106
203
  http: HttpFilteringConfigSchema.optional()
107
204
  });
205
+ var defaultConfig = {
206
+ version: "1.0",
207
+ instrumentation: {
208
+ enabled: true,
209
+ logging: "on",
210
+ description: "Default instrumentation configuration",
211
+ instrument_patterns: [
212
+ { pattern: "^app\\.", enabled: true, description: "Application operations" },
213
+ { pattern: "^http\\.server\\.", enabled: true, description: "HTTP server operations" },
214
+ { pattern: "^http\\.client\\.", enabled: true, description: "HTTP client operations" }
215
+ ],
216
+ ignore_patterns: [
217
+ { pattern: "^test\\.", description: "Test utilities" },
218
+ { pattern: "^internal\\.", description: "Internal operations" },
219
+ { pattern: "^health\\.", description: "Health checks" }
220
+ ]
221
+ },
222
+ effect: {
223
+ enabled: true,
224
+ exporter: "unified",
225
+ auto_extract_metadata: true
226
+ }
227
+ };
228
+ function parseAndValidateConfig(content) {
229
+ let parsed;
230
+ if (typeof content === "string") {
231
+ parsed = yaml.parse(content);
232
+ } else {
233
+ parsed = content;
234
+ }
235
+ return InstrumentationConfigSchema.parse(parsed);
236
+ }
108
237
  (class extends effect.Data.TaggedError("ConfigError") {
109
238
  get message() {
110
239
  return this.reason;
@@ -282,7 +411,7 @@ var makeConfigLoader = effect.Effect.gen(function* () {
282
411
  })
283
412
  });
284
413
  });
285
- var ConfigLoaderLive = effect.Layer.effect(ConfigLoader, makeConfigLoader);
414
+ effect.Layer.effect(ConfigLoader, makeConfigLoader);
286
415
  var PatternMatcher = class {
287
416
  constructor(config) {
288
417
  __publicField(this, "ignorePatterns", []);
@@ -430,84 +559,58 @@ var Logger = class {
430
559
  }
431
560
  };
432
561
  var logger = new Logger();
433
- var NodeConfigLoaderLive = ConfigLoaderLive.pipe(
434
- effect.Layer.provide(effect.Layer.mergeAll(platformNode.NodeContext.layer, platform.FetchHttpClient.layer))
435
- );
436
- var cachedLoaderPromise = null;
437
- function getCachedLoader() {
438
- if (!cachedLoaderPromise) {
439
- cachedLoaderPromise = effect.Effect.runPromise(
440
- effect.Effect.gen(function* () {
441
- return yield* ConfigLoader;
442
- }).pipe(effect.Effect.provide(NodeConfigLoaderLive))
443
- );
562
+ async function loadFromFile(filePath) {
563
+ const { readFile } = await import('fs/promises');
564
+ const content = await readFile(filePath, "utf-8");
565
+ return parseAndValidateConfig(content);
566
+ }
567
+ async function loadFromUrl(url) {
568
+ const response = await fetch(url);
569
+ if (!response.ok) {
570
+ throw new Error(`Failed to fetch config from ${url}: ${response.statusText}`);
444
571
  }
445
- return cachedLoaderPromise;
572
+ const content = await response.text();
573
+ return parseAndValidateConfig(content);
446
574
  }
447
- async function loadConfig(uri, options) {
448
- if (options?.cacheTimeout === 0) {
449
- const program = effect.Effect.gen(function* () {
450
- const loader2 = yield* ConfigLoader;
451
- return yield* loader2.loadFromUri(uri);
452
- });
453
- return effect.Effect.runPromise(program.pipe(effect.Effect.provide(NodeConfigLoaderLive)));
575
+ async function loadConfig(uri, _options) {
576
+ if (uri.startsWith("http://") || uri.startsWith("https://")) {
577
+ return loadFromUrl(uri);
454
578
  }
455
- const loader = await getCachedLoader();
456
- return effect.Effect.runPromise(loader.loadFromUri(uri));
579
+ if (uri.startsWith("file://")) {
580
+ const filePath = uri.slice(7);
581
+ return loadFromFile(filePath);
582
+ }
583
+ return loadFromFile(uri);
457
584
  }
458
585
  async function loadConfigFromInline(content) {
459
- const loader = await getCachedLoader();
460
- return effect.Effect.runPromise(loader.loadFromInline(content));
461
- }
462
- function getDefaultConfig() {
463
- return {
464
- version: "1.0",
465
- instrumentation: {
466
- enabled: true,
467
- logging: "on",
468
- description: "Default instrumentation configuration",
469
- instrument_patterns: [
470
- { pattern: "^app\\.", enabled: true, description: "Application operations" },
471
- { pattern: "^http\\.server\\.", enabled: true, description: "HTTP server operations" },
472
- { pattern: "^http\\.client\\.", enabled: true, description: "HTTP client operations" }
473
- ],
474
- ignore_patterns: [
475
- { pattern: "^test\\.", description: "Test utilities" },
476
- { pattern: "^internal\\.", description: "Internal operations" },
477
- { pattern: "^health\\.", description: "Health checks" }
478
- ]
479
- },
480
- effect: {
481
- auto_extract_metadata: true
482
- }
483
- };
586
+ return parseAndValidateConfig(content);
484
587
  }
485
588
  async function loadConfigWithOptions(options = {}) {
486
- const loadOptions = options.cacheTimeout !== void 0 ? { cacheTimeout: options.cacheTimeout } : void 0;
487
589
  if (options.config) {
488
590
  return loadConfigFromInline(options.config);
489
591
  }
490
592
  const envConfigPath = process.env.ATRIM_INSTRUMENTATION_CONFIG;
491
593
  if (envConfigPath) {
492
- return loadConfig(envConfigPath, loadOptions);
594
+ return loadConfig(envConfigPath);
493
595
  }
494
596
  if (options.configUrl) {
495
- return loadConfig(options.configUrl, loadOptions);
597
+ return loadConfig(options.configUrl);
496
598
  }
497
599
  if (options.configPath) {
498
- return loadConfig(options.configPath, loadOptions);
600
+ return loadConfig(options.configPath);
499
601
  }
500
602
  const { existsSync } = await import('fs');
501
603
  const { join } = await import('path');
502
604
  const defaultPath = join(process.cwd(), "instrumentation.yaml");
503
605
  if (existsSync(defaultPath)) {
504
- return loadConfig(defaultPath, loadOptions);
606
+ return loadConfig(defaultPath);
505
607
  }
506
- return getDefaultConfig();
608
+ return defaultConfig;
507
609
  }
508
610
 
509
611
  // src/integrations/effect/effect-tracer.ts
510
- var SDK_NAME = "@effect/opentelemetry-otlp";
612
+ var SDK_NAME = "@effect/opentelemetry";
613
+ var ATTR_TELEMETRY_EXPORTER_MODE = "telemetry.exporter.mode";
511
614
  function createEffectInstrumentation(options = {}) {
512
615
  return effect.Layer.unwrapEffect(
513
616
  effect.Effect.gen(function* () {
@@ -518,90 +621,89 @@ function createEffectInstrumentation(options = {}) {
518
621
  message: error instanceof Error ? error.message : String(error)
519
622
  })
520
623
  });
624
+ const effectEnabled = process.env.OTEL_EFFECT_ENABLED !== "false" && (config.effect?.enabled ?? true);
625
+ if (!effectEnabled) {
626
+ logger.log("@atrim/instrumentation/effect: Effect tracing disabled via config");
627
+ return effect.Layer.empty;
628
+ }
521
629
  yield* effect.Effect.sync(() => {
522
630
  const loggingLevel = config.instrumentation.logging || "on";
523
631
  logger.setLevel(loggingLevel);
524
632
  });
525
633
  yield* effect.Effect.sync(() => initializePatternMatcher(config));
526
- const otlpEndpoint = options.otlpEndpoint || process.env.OTEL_EXPORTER_OTLP_ENDPOINT || "http://localhost:4318";
527
634
  const serviceName = options.serviceName || process.env.OTEL_SERVICE_NAME || "effect-service";
528
635
  const serviceVersion = options.serviceVersion || process.env.npm_package_version || "1.0.0";
529
- const autoExtractMetadata = options.autoExtractMetadata ?? config.effect?.auto_extract_metadata ?? true;
530
- const continueExistingTraces = options.continueExistingTraces ?? true;
531
- logger.log("\u{1F50D} Effect OpenTelemetry instrumentation");
532
- logger.log(` \u{1F4E1} Endpoint: ${otlpEndpoint}`);
533
- logger.log(` \u{1F3F7}\uFE0F Service: ${serviceName}`);
534
- logger.log(` \u2705 Auto metadata extraction: ${autoExtractMetadata}`);
535
- logger.log(` \u2705 Continue existing traces: ${continueExistingTraces}`);
536
- const otlpLayer = Otlp__namespace.layer({
537
- baseUrl: otlpEndpoint,
538
- resource: {
539
- serviceName,
540
- serviceVersion,
541
- attributes: {
542
- "platform.component": "effect",
543
- "effect.auto_metadata": autoExtractMetadata,
544
- "effect.context_propagation": continueExistingTraces,
545
- [ATTR_TELEMETRY_SDK_LANGUAGE]: TELEMETRY_SDK_LANGUAGE_VALUE_NODEJS,
546
- [ATTR_TELEMETRY_SDK_NAME]: SDK_NAME
547
- }
548
- },
549
- // Bridge Effect context to OpenTelemetry global context
550
- // This is essential for context propagation to work properly
551
- tracerContext: (f, span) => {
552
- if (span._tag !== "Span") {
553
- return f();
636
+ const exporterMode = options.exporterMode ?? config.effect?.exporter ?? "unified";
637
+ const resourceAttributes = {
638
+ "platform.component": "effect",
639
+ [semanticConventions.ATTR_TELEMETRY_SDK_LANGUAGE]: semanticConventions.TELEMETRY_SDK_LANGUAGE_VALUE_NODEJS,
640
+ [semanticConventions.ATTR_TELEMETRY_SDK_NAME]: SDK_NAME,
641
+ [ATTR_TELEMETRY_EXPORTER_MODE]: exporterMode
642
+ };
643
+ if (exporterMode === "standalone") {
644
+ const otlpEndpoint = options.otlpEndpoint || process.env.OTEL_EXPORTER_OTLP_ENDPOINT || "http://localhost:4318";
645
+ logger.log("Effect OpenTelemetry instrumentation (standalone)");
646
+ logger.log(` Service: ${serviceName}`);
647
+ logger.log(` Endpoint: ${otlpEndpoint}`);
648
+ logger.log(" WARNING: Standalone mode bypasses Node SDK filtering");
649
+ return Otlp__namespace.layer({
650
+ baseUrl: otlpEndpoint,
651
+ resource: {
652
+ serviceName,
653
+ serviceVersion,
654
+ attributes: resourceAttributes
655
+ },
656
+ // Bridge Effect context to OpenTelemetry global context
657
+ tracerContext: (f, span) => {
658
+ if (span._tag !== "Span") {
659
+ return f();
660
+ }
661
+ const spanContext = {
662
+ traceId: span.traceId,
663
+ spanId: span.spanId,
664
+ traceFlags: span.sampled ? api.TraceFlags.SAMPLED : api.TraceFlags.NONE
665
+ };
666
+ const otelSpan = api.trace.wrapSpanContext(spanContext);
667
+ return api.context.with(api.trace.setSpan(api.context.active(), otelSpan), f);
554
668
  }
555
- const spanContext = {
556
- traceId: span.traceId,
557
- spanId: span.spanId,
558
- traceFlags: span.sampled ? api.TraceFlags.SAMPLED : api.TraceFlags.NONE
559
- };
560
- const otelSpan = api.trace.wrapSpanContext(spanContext);
561
- return api.context.with(api.trace.setSpan(api.context.active(), otelSpan), f);
562
- }
563
- }).pipe(effect.Layer.provide(platform.FetchHttpClient.layer));
564
- if (autoExtractMetadata) {
565
- return otlpLayer;
669
+ }).pipe(effect.Layer.provide(platform.FetchHttpClient.layer));
670
+ } else {
671
+ logger.log("Effect OpenTelemetry instrumentation (unified)");
672
+ logger.log(` Service: ${serviceName}`);
673
+ logger.log(" Using global TracerProvider for span export");
674
+ return Tracer__namespace.layerGlobal.pipe(
675
+ effect.Layer.provide(
676
+ Resource__namespace.layer({
677
+ serviceName,
678
+ serviceVersion,
679
+ attributes: resourceAttributes
680
+ })
681
+ )
682
+ );
566
683
  }
567
- return otlpLayer;
568
684
  })
569
685
  ).pipe(effect.Layer.orDie);
570
686
  }
571
687
  var EffectInstrumentationLive = effect.Effect.sync(() => {
572
- const endpoint = process.env.OTEL_EXPORTER_OTLP_ENDPOINT || "http://localhost:4318";
573
688
  const serviceName = process.env.OTEL_SERVICE_NAME || "effect-service";
574
689
  const serviceVersion = process.env.npm_package_version || "1.0.0";
575
690
  logger.minimal(`@atrim/instrumentation/effect: Effect tracing enabled (${serviceName})`);
576
- logger.log("\u{1F50D} Effect OpenTelemetry tracer");
577
- logger.log(` \u{1F4E1} Endpoint: ${endpoint}`);
578
- logger.log(` \u{1F3F7}\uFE0F Service: ${serviceName}`);
579
- return Otlp__namespace.layer({
580
- baseUrl: endpoint,
581
- resource: {
582
- serviceName,
583
- serviceVersion,
584
- attributes: {
585
- "platform.component": "effect",
586
- [ATTR_TELEMETRY_SDK_LANGUAGE]: TELEMETRY_SDK_LANGUAGE_VALUE_NODEJS,
587
- [ATTR_TELEMETRY_SDK_NAME]: SDK_NAME
588
- }
589
- },
590
- // CRITICAL: Bridge Effect context to OpenTelemetry global context
591
- // This allows NodeSDK auto-instrumentation to see Effect spans as parent spans
592
- tracerContext: (f, span) => {
593
- if (span._tag !== "Span") {
594
- return f();
595
- }
596
- const spanContext = {
597
- traceId: span.traceId,
598
- spanId: span.spanId,
599
- traceFlags: span.sampled ? api.TraceFlags.SAMPLED : api.TraceFlags.NONE
600
- };
601
- const otelSpan = api.trace.wrapSpanContext(spanContext);
602
- return api.context.with(api.trace.setSpan(api.context.active(), otelSpan), f);
603
- }
604
- }).pipe(effect.Layer.provide(platform.FetchHttpClient.layer));
691
+ logger.log("Effect OpenTelemetry tracer (unified)");
692
+ logger.log(` Service: ${serviceName}`);
693
+ return Tracer__namespace.layerGlobal.pipe(
694
+ effect.Layer.provide(
695
+ Resource__namespace.layer({
696
+ serviceName,
697
+ serviceVersion,
698
+ attributes: {
699
+ "platform.component": "effect",
700
+ [semanticConventions.ATTR_TELEMETRY_SDK_LANGUAGE]: semanticConventions.TELEMETRY_SDK_LANGUAGE_VALUE_NODEJS,
701
+ [semanticConventions.ATTR_TELEMETRY_SDK_NAME]: SDK_NAME,
702
+ [ATTR_TELEMETRY_EXPORTER_MODE]: "unified"
703
+ }
704
+ })
705
+ )
706
+ );
605
707
  }).pipe(effect.Layer.unwrapEffect);
606
708
  function annotateUser(userId, email, username) {
607
709
  const attributes = {