@atrim/instrument-node 0.4.1 → 0.5.0-14fdea7-20260109020503

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,6 +31,8 @@ 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);
@@ -64,13 +68,113 @@ var AutoIsolationConfigSchema = zod.z.object({
64
68
  add_metadata: zod.z.boolean().default(true)
65
69
  }).default({})
66
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
+ });
67
134
  var HttpFilteringConfigSchema = zod.z.object({
68
135
  // Patterns to ignore for outgoing HTTP requests (string patterns only in YAML)
69
136
  ignore_outgoing_urls: zod.z.array(zod.z.string()).optional(),
70
137
  // Patterns to ignore for incoming HTTP requests (string patterns only in YAML)
71
138
  ignore_incoming_paths: zod.z.array(zod.z.string()).optional(),
72
139
  // Require parent span for outgoing requests (prevents root spans for HTTP calls)
73
- require_parent_for_outgoing_spans: zod.z.boolean().optional()
140
+ require_parent_for_outgoing_spans: zod.z.boolean().optional(),
141
+ // Trace context propagation configuration
142
+ // Controls which cross-origin requests receive W3C Trace Context headers (traceparent, tracestate)
143
+ propagate_trace_context: zod.z.object({
144
+ // Strategy for trace propagation
145
+ // - "all": Propagate to all cross-origin requests (may cause CORS errors)
146
+ // - "none": Never propagate trace headers
147
+ // - "same-origin": Only propagate to same-origin requests (default, safe)
148
+ // - "patterns": Propagate based on include_urls patterns
149
+ strategy: zod.z.enum(["all", "none", "same-origin", "patterns"]).default("same-origin"),
150
+ // URL patterns to include when strategy is "patterns"
151
+ // Supports regex patterns (e.g., "^https://api\\.myapp\\.com")
152
+ include_urls: zod.z.array(zod.z.string()).optional()
153
+ }).optional()
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()
74
178
  });
75
179
  var InstrumentationConfigSchema = zod.z.object({
76
180
  version: zod.z.string(),
@@ -82,11 +186,54 @@ var InstrumentationConfigSchema = zod.z.object({
82
186
  ignore_patterns: zod.z.array(PatternConfigSchema)
83
187
  }),
84
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(),
85
198
  auto_extract_metadata: zod.z.boolean(),
86
- auto_isolation: AutoIsolationConfigSchema.optional()
199
+ auto_isolation: AutoIsolationConfigSchema.optional(),
200
+ // Auto-instrumentation: automatic tracing of all Effect fibers
201
+ auto_instrumentation: AutoInstrumentationConfigSchema.optional()
87
202
  }).optional(),
88
203
  http: HttpFilteringConfigSchema.optional()
89
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
+ }
90
237
  (class extends effect.Data.TaggedError("ConfigError") {
91
238
  get message() {
92
239
  return this.reason;
@@ -264,7 +411,7 @@ var makeConfigLoader = effect.Effect.gen(function* () {
264
411
  })
265
412
  });
266
413
  });
267
- var ConfigLoaderLive = effect.Layer.effect(ConfigLoader, makeConfigLoader);
414
+ effect.Layer.effect(ConfigLoader, makeConfigLoader);
268
415
  var PatternMatcher = class {
269
416
  constructor(config) {
270
417
  __publicField(this, "ignorePatterns", []);
@@ -412,83 +559,58 @@ var Logger = class {
412
559
  }
413
560
  };
414
561
  var logger = new Logger();
415
- var NodeConfigLoaderLive = ConfigLoaderLive.pipe(
416
- effect.Layer.provide(effect.Layer.mergeAll(platformNode.NodeContext.layer, platform.FetchHttpClient.layer))
417
- );
418
- var cachedLoaderPromise = null;
419
- function getCachedLoader() {
420
- if (!cachedLoaderPromise) {
421
- cachedLoaderPromise = effect.Effect.runPromise(
422
- effect.Effect.gen(function* () {
423
- return yield* ConfigLoader;
424
- }).pipe(effect.Effect.provide(NodeConfigLoaderLive))
425
- );
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}`);
426
571
  }
427
- return cachedLoaderPromise;
572
+ const content = await response.text();
573
+ return parseAndValidateConfig(content);
428
574
  }
429
- async function loadConfig(uri, options) {
430
- if (options?.cacheTimeout === 0) {
431
- const program = effect.Effect.gen(function* () {
432
- const loader2 = yield* ConfigLoader;
433
- return yield* loader2.loadFromUri(uri);
434
- });
435
- 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);
578
+ }
579
+ if (uri.startsWith("file://")) {
580
+ const filePath = uri.slice(7);
581
+ return loadFromFile(filePath);
436
582
  }
437
- const loader = await getCachedLoader();
438
- return effect.Effect.runPromise(loader.loadFromUri(uri));
583
+ return loadFromFile(uri);
439
584
  }
440
585
  async function loadConfigFromInline(content) {
441
- const loader = await getCachedLoader();
442
- return effect.Effect.runPromise(loader.loadFromInline(content));
443
- }
444
- function getDefaultConfig() {
445
- return {
446
- version: "1.0",
447
- instrumentation: {
448
- enabled: true,
449
- logging: "on",
450
- description: "Default instrumentation configuration",
451
- instrument_patterns: [
452
- { pattern: "^app\\.", enabled: true, description: "Application operations" },
453
- { pattern: "^http\\.server\\.", enabled: true, description: "HTTP server operations" },
454
- { pattern: "^http\\.client\\.", enabled: true, description: "HTTP client operations" }
455
- ],
456
- ignore_patterns: [
457
- { pattern: "^test\\.", description: "Test utilities" },
458
- { pattern: "^internal\\.", description: "Internal operations" },
459
- { pattern: "^health\\.", description: "Health checks" }
460
- ]
461
- },
462
- effect: {
463
- auto_extract_metadata: true
464
- }
465
- };
586
+ return parseAndValidateConfig(content);
466
587
  }
467
588
  async function loadConfigWithOptions(options = {}) {
468
- const loadOptions = options.cacheTimeout !== void 0 ? { cacheTimeout: options.cacheTimeout } : void 0;
469
589
  if (options.config) {
470
590
  return loadConfigFromInline(options.config);
471
591
  }
472
592
  const envConfigPath = process.env.ATRIM_INSTRUMENTATION_CONFIG;
473
593
  if (envConfigPath) {
474
- return loadConfig(envConfigPath, loadOptions);
594
+ return loadConfig(envConfigPath);
475
595
  }
476
596
  if (options.configUrl) {
477
- return loadConfig(options.configUrl, loadOptions);
597
+ return loadConfig(options.configUrl);
478
598
  }
479
599
  if (options.configPath) {
480
- return loadConfig(options.configPath, loadOptions);
600
+ return loadConfig(options.configPath);
481
601
  }
482
602
  const { existsSync } = await import('fs');
483
603
  const { join } = await import('path');
484
604
  const defaultPath = join(process.cwd(), "instrumentation.yaml");
485
605
  if (existsSync(defaultPath)) {
486
- return loadConfig(defaultPath, loadOptions);
606
+ return loadConfig(defaultPath);
487
607
  }
488
- return getDefaultConfig();
608
+ return defaultConfig;
489
609
  }
490
610
 
491
611
  // src/integrations/effect/effect-tracer.ts
612
+ var SDK_NAME = "@effect/opentelemetry";
613
+ var ATTR_TELEMETRY_EXPORTER_MODE = "telemetry.exporter.mode";
492
614
  function createEffectInstrumentation(options = {}) {
493
615
  return effect.Layer.unwrapEffect(
494
616
  effect.Effect.gen(function* () {
@@ -499,106 +621,228 @@ function createEffectInstrumentation(options = {}) {
499
621
  message: error instanceof Error ? error.message : String(error)
500
622
  })
501
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
+ }
502
629
  yield* effect.Effect.sync(() => {
503
630
  const loggingLevel = config.instrumentation.logging || "on";
504
631
  logger.setLevel(loggingLevel);
505
632
  });
506
633
  yield* effect.Effect.sync(() => initializePatternMatcher(config));
507
- const otlpEndpoint = options.otlpEndpoint || process.env.OTEL_EXPORTER_OTLP_ENDPOINT || "http://localhost:4318";
508
634
  const serviceName = options.serviceName || process.env.OTEL_SERVICE_NAME || "effect-service";
509
635
  const serviceVersion = options.serviceVersion || process.env.npm_package_version || "1.0.0";
510
- const autoExtractMetadata = options.autoExtractMetadata ?? config.effect?.auto_extract_metadata ?? true;
511
- const continueExistingTraces = options.continueExistingTraces ?? true;
512
- logger.log("\u{1F50D} Effect OpenTelemetry instrumentation");
513
- logger.log(` \u{1F4E1} Endpoint: ${otlpEndpoint}`);
514
- logger.log(` \u{1F3F7}\uFE0F Service: ${serviceName}`);
515
- logger.log(` \u2705 Auto metadata extraction: ${autoExtractMetadata}`);
516
- logger.log(` \u2705 Continue existing traces: ${continueExistingTraces}`);
517
- const otlpLayer = Otlp__namespace.layer({
518
- baseUrl: otlpEndpoint,
519
- resource: {
520
- serviceName,
521
- serviceVersion,
522
- attributes: {
523
- "platform.component": "effect",
524
- "effect.auto_metadata": autoExtractMetadata,
525
- "effect.context_propagation": continueExistingTraces
526
- }
527
- },
528
- // Bridge Effect context to OpenTelemetry global context
529
- // This is essential for context propagation to work properly
530
- tracerContext: (f, span) => {
531
- if (span._tag !== "Span") {
532
- 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);
533
668
  }
534
- const spanContext = {
535
- traceId: span.traceId,
536
- spanId: span.spanId,
537
- traceFlags: span.sampled ? api.TraceFlags.SAMPLED : api.TraceFlags.NONE
538
- };
539
- const otelSpan = api.trace.wrapSpanContext(spanContext);
540
- return api.context.with(api.trace.setSpan(api.context.active(), otelSpan), f);
541
- }
542
- }).pipe(effect.Layer.provide(platform.FetchHttpClient.layer));
543
- if (autoExtractMetadata) {
544
- 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
+ );
545
683
  }
546
- return otlpLayer;
547
684
  })
548
685
  ).pipe(effect.Layer.orDie);
549
686
  }
550
687
  var EffectInstrumentationLive = effect.Effect.sync(() => {
551
- const endpoint = process.env.OTEL_EXPORTER_OTLP_ENDPOINT || "http://localhost:4318";
552
688
  const serviceName = process.env.OTEL_SERVICE_NAME || "effect-service";
553
689
  const serviceVersion = process.env.npm_package_version || "1.0.0";
554
690
  logger.minimal(`@atrim/instrumentation/effect: Effect tracing enabled (${serviceName})`);
555
- logger.log("\u{1F50D} Effect OpenTelemetry tracer");
556
- logger.log(` \u{1F4E1} Endpoint: ${endpoint}`);
557
- logger.log(` \u{1F3F7}\uFE0F Service: ${serviceName}`);
558
- return Otlp__namespace.layer({
559
- baseUrl: endpoint,
560
- resource: {
561
- serviceName,
562
- serviceVersion,
563
- attributes: {
564
- "platform.component": "effect"
565
- }
566
- },
567
- // CRITICAL: Bridge Effect context to OpenTelemetry global context
568
- // This allows NodeSDK auto-instrumentation to see Effect spans as parent spans
569
- tracerContext: (f, span) => {
570
- if (span._tag !== "Span") {
571
- return f();
572
- }
573
- const spanContext = {
574
- traceId: span.traceId,
575
- spanId: span.spanId,
576
- traceFlags: span.sampled ? api.TraceFlags.SAMPLED : api.TraceFlags.NONE
577
- };
578
- const otelSpan = api.trace.wrapSpanContext(spanContext);
579
- return api.context.with(api.trace.setSpan(api.context.active(), otelSpan), f);
580
- }
581
- }).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
+ );
582
707
  }).pipe(effect.Layer.unwrapEffect);
583
-
584
- // src/integrations/effect/effect-helpers.ts
585
- function annotateUser(_userId, _email) {
708
+ function annotateUser(userId, email, username) {
709
+ const attributes = {
710
+ "user.id": userId
711
+ };
712
+ if (email) attributes["user.email"] = email;
713
+ if (username) attributes["user.name"] = username;
714
+ return effect.Effect.annotateCurrentSpan(attributes);
586
715
  }
587
- function annotateDataSize(_bytes, _count) {
716
+ function annotateDataSize(bytes, items, compressionRatio) {
717
+ const attributes = {
718
+ "data.size.bytes": bytes,
719
+ "data.size.items": items
720
+ };
721
+ if (compressionRatio !== void 0) {
722
+ attributes["data.compression.ratio"] = compressionRatio;
723
+ }
724
+ return effect.Effect.annotateCurrentSpan(attributes);
588
725
  }
589
- function annotateBatch(_size, _batchSize) {
726
+ function annotateBatch(totalItems, batchSize, successCount, failureCount) {
727
+ const attributes = {
728
+ "batch.size": batchSize,
729
+ "batch.total_items": totalItems,
730
+ "batch.count": Math.ceil(totalItems / batchSize)
731
+ };
732
+ if (successCount !== void 0) {
733
+ attributes["batch.success_count"] = successCount;
734
+ }
735
+ if (failureCount !== void 0) {
736
+ attributes["batch.failure_count"] = failureCount;
737
+ }
738
+ return effect.Effect.annotateCurrentSpan(attributes);
590
739
  }
591
- function annotateLLM(_model, _operation, _inputTokens, _outputTokens) {
740
+ function annotateLLM(model, provider, tokens) {
741
+ const attributes = {
742
+ "llm.model": model,
743
+ "llm.provider": provider
744
+ };
745
+ if (tokens) {
746
+ if (tokens.prompt !== void 0) attributes["llm.tokens.prompt"] = tokens.prompt;
747
+ if (tokens.completion !== void 0) attributes["llm.tokens.completion"] = tokens.completion;
748
+ if (tokens.total !== void 0) attributes["llm.tokens.total"] = tokens.total;
749
+ }
750
+ return effect.Effect.annotateCurrentSpan(attributes);
592
751
  }
593
- function annotateQuery(_query, _database) {
752
+ function annotateQuery(query, duration, rowCount, database) {
753
+ const attributes = {
754
+ "db.statement": query.length > 1e3 ? query.substring(0, 1e3) + "..." : query
755
+ };
756
+ if (duration !== void 0) attributes["db.duration.ms"] = duration;
757
+ if (rowCount !== void 0) attributes["db.row_count"] = rowCount;
758
+ if (database) attributes["db.name"] = database;
759
+ return effect.Effect.annotateCurrentSpan(attributes);
594
760
  }
595
- function annotateHttpRequest(_method, _url, _statusCode) {
761
+ function annotateHttpRequest(method, url, statusCode, contentLength) {
762
+ const attributes = {
763
+ "http.method": method,
764
+ "http.url": url
765
+ };
766
+ if (statusCode !== void 0) attributes["http.status_code"] = statusCode;
767
+ if (contentLength !== void 0) attributes["http.response.content_length"] = contentLength;
768
+ return effect.Effect.annotateCurrentSpan(attributes);
596
769
  }
597
- function annotateError(_error, _context) {
770
+ function annotateError(error, recoverable, errorType) {
771
+ const errorMessage = typeof error === "string" ? error : error.message;
772
+ const errorStack = typeof error === "string" ? void 0 : error.stack;
773
+ const attributes = {
774
+ "error.message": errorMessage,
775
+ "error.recoverable": recoverable
776
+ };
777
+ if (errorType) attributes["error.type"] = errorType;
778
+ if (errorStack) attributes["error.stack"] = errorStack;
779
+ return effect.Effect.annotateCurrentSpan(attributes);
598
780
  }
599
- function annotatePriority(_priority) {
781
+ function annotatePriority(priority, reason) {
782
+ const attributes = {
783
+ "operation.priority": priority
784
+ };
785
+ if (reason) attributes["operation.priority.reason"] = reason;
786
+ return effect.Effect.annotateCurrentSpan(attributes);
600
787
  }
601
- function annotateCache(_operation, _hit) {
788
+ function annotateCache(hit, key, ttl) {
789
+ const attributes = {
790
+ "cache.hit": hit,
791
+ "cache.key": key
792
+ };
793
+ if (ttl !== void 0) attributes["cache.ttl.seconds"] = ttl;
794
+ return effect.Effect.annotateCurrentSpan(attributes);
795
+ }
796
+ function extractEffectMetadata() {
797
+ return effect.Effect.gen(function* () {
798
+ const metadata = {};
799
+ const currentFiber = effect.Fiber.getCurrentFiber();
800
+ if (effect.Option.isSome(currentFiber)) {
801
+ const fiber = currentFiber.value;
802
+ const fiberId = fiber.id();
803
+ metadata["effect.fiber.id"] = effect.FiberId.threadName(fiberId);
804
+ const status = yield* effect.Fiber.status(fiber);
805
+ if (status._tag) {
806
+ metadata["effect.fiber.status"] = status._tag;
807
+ }
808
+ }
809
+ const parentSpanResult = yield* effect.Effect.currentSpan.pipe(
810
+ effect.Effect.option
811
+ // Convert NoSuchElementException to Option
812
+ );
813
+ if (effect.Option.isSome(parentSpanResult)) {
814
+ const parentSpan = parentSpanResult.value;
815
+ metadata["effect.operation.nested"] = true;
816
+ metadata["effect.operation.root"] = false;
817
+ if (parentSpan.spanId) {
818
+ metadata["effect.parent.span.id"] = parentSpan.spanId;
819
+ }
820
+ if (parentSpan.name) {
821
+ metadata["effect.parent.span.name"] = parentSpan.name;
822
+ }
823
+ if (parentSpan.traceId) {
824
+ metadata["effect.parent.trace.id"] = parentSpan.traceId;
825
+ }
826
+ } else {
827
+ metadata["effect.operation.nested"] = false;
828
+ metadata["effect.operation.root"] = true;
829
+ }
830
+ return metadata;
831
+ });
832
+ }
833
+ function autoEnrichSpan() {
834
+ return effect.Effect.gen(function* () {
835
+ const metadata = yield* extractEffectMetadata();
836
+ yield* effect.Effect.annotateCurrentSpan(metadata);
837
+ });
838
+ }
839
+ function withAutoEnrichedSpan(spanName, options) {
840
+ return (self) => {
841
+ return effect.Effect.gen(function* () {
842
+ yield* autoEnrichSpan();
843
+ return yield* self;
844
+ }).pipe(effect.Effect.withSpan(spanName, options));
845
+ };
602
846
  }
603
847
  var createLogicalParentLink = (parentSpan, useSpanLinks) => {
604
848
  if (!useSpanLinks) {
@@ -737,8 +981,11 @@ exports.annotatePriority = annotatePriority;
737
981
  exports.annotateQuery = annotateQuery;
738
982
  exports.annotateSpawnedTasks = annotateSpawnedTasks;
739
983
  exports.annotateUser = annotateUser;
984
+ exports.autoEnrichSpan = autoEnrichSpan;
740
985
  exports.createEffectInstrumentation = createEffectInstrumentation;
986
+ exports.extractEffectMetadata = extractEffectMetadata;
741
987
  exports.runIsolated = runIsolated;
742
988
  exports.runWithSpan = runWithSpan;
989
+ exports.withAutoEnrichedSpan = withAutoEnrichedSpan;
743
990
  //# sourceMappingURL=index.cjs.map
744
991
  //# sourceMappingURL=index.cjs.map