@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,13 +1,15 @@
1
- import { Data, Context, Effect, Layer, FiberSet as FiberSet$1, 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
15
  var __defProp = Object.defineProperty;
@@ -40,13 +42,113 @@ var AutoIsolationConfigSchema = z.object({
40
42
  add_metadata: z.boolean().default(true)
41
43
  }).default({})
42
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
+ });
43
108
  var HttpFilteringConfigSchema = z.object({
44
109
  // Patterns to ignore for outgoing HTTP requests (string patterns only in YAML)
45
110
  ignore_outgoing_urls: z.array(z.string()).optional(),
46
111
  // Patterns to ignore for incoming HTTP requests (string patterns only in YAML)
47
112
  ignore_incoming_paths: z.array(z.string()).optional(),
48
113
  // Require parent span for outgoing requests (prevents root spans for HTTP calls)
49
- require_parent_for_outgoing_spans: z.boolean().optional()
114
+ require_parent_for_outgoing_spans: z.boolean().optional(),
115
+ // Trace context propagation configuration
116
+ // Controls which cross-origin requests receive W3C Trace Context headers (traceparent, tracestate)
117
+ propagate_trace_context: z.object({
118
+ // Strategy for trace propagation
119
+ // - "all": Propagate to all cross-origin requests (may cause CORS errors)
120
+ // - "none": Never propagate trace headers
121
+ // - "same-origin": Only propagate to same-origin requests (default, safe)
122
+ // - "patterns": Propagate based on include_urls patterns
123
+ strategy: z.enum(["all", "none", "same-origin", "patterns"]).default("same-origin"),
124
+ // URL patterns to include when strategy is "patterns"
125
+ // Supports regex patterns (e.g., "^https://api\\.myapp\\.com")
126
+ include_urls: z.array(z.string()).optional()
127
+ }).optional()
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()
50
152
  });
51
153
  var InstrumentationConfigSchema = z.object({
52
154
  version: z.string(),
@@ -58,11 +160,54 @@ var InstrumentationConfigSchema = z.object({
58
160
  ignore_patterns: z.array(PatternConfigSchema)
59
161
  }),
60
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(),
61
172
  auto_extract_metadata: z.boolean(),
62
- auto_isolation: AutoIsolationConfigSchema.optional()
173
+ auto_isolation: AutoIsolationConfigSchema.optional(),
174
+ // Auto-instrumentation: automatic tracing of all Effect fibers
175
+ auto_instrumentation: AutoInstrumentationConfigSchema.optional()
63
176
  }).optional(),
64
177
  http: HttpFilteringConfigSchema.optional()
65
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
+ }
66
211
  (class extends Data.TaggedError("ConfigError") {
67
212
  get message() {
68
213
  return this.reason;
@@ -240,7 +385,7 @@ var makeConfigLoader = Effect.gen(function* () {
240
385
  })
241
386
  });
242
387
  });
243
- var ConfigLoaderLive = Layer.effect(ConfigLoader, makeConfigLoader);
388
+ Layer.effect(ConfigLoader, makeConfigLoader);
244
389
  var PatternMatcher = class {
245
390
  constructor(config) {
246
391
  __publicField(this, "ignorePatterns", []);
@@ -388,83 +533,58 @@ var Logger = class {
388
533
  }
389
534
  };
390
535
  var logger = new Logger();
391
- var NodeConfigLoaderLive = ConfigLoaderLive.pipe(
392
- Layer.provide(Layer.mergeAll(NodeContext.layer, FetchHttpClient.layer))
393
- );
394
- var cachedLoaderPromise = null;
395
- function getCachedLoader() {
396
- if (!cachedLoaderPromise) {
397
- cachedLoaderPromise = Effect.runPromise(
398
- Effect.gen(function* () {
399
- return yield* ConfigLoader;
400
- }).pipe(Effect.provide(NodeConfigLoaderLive))
401
- );
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}`);
402
545
  }
403
- return cachedLoaderPromise;
546
+ const content = await response.text();
547
+ return parseAndValidateConfig(content);
404
548
  }
405
- async function loadConfig(uri, options) {
406
- if (options?.cacheTimeout === 0) {
407
- const program = Effect.gen(function* () {
408
- const loader2 = yield* ConfigLoader;
409
- return yield* loader2.loadFromUri(uri);
410
- });
411
- return Effect.runPromise(program.pipe(Effect.provide(NodeConfigLoaderLive)));
412
- }
413
- const loader = await getCachedLoader();
414
- 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);
415
558
  }
416
559
  async function loadConfigFromInline(content) {
417
- const loader = await getCachedLoader();
418
- return Effect.runPromise(loader.loadFromInline(content));
419
- }
420
- function getDefaultConfig() {
421
- return {
422
- version: "1.0",
423
- instrumentation: {
424
- enabled: true,
425
- logging: "on",
426
- description: "Default instrumentation configuration",
427
- instrument_patterns: [
428
- { pattern: "^app\\.", enabled: true, description: "Application operations" },
429
- { pattern: "^http\\.server\\.", enabled: true, description: "HTTP server operations" },
430
- { pattern: "^http\\.client\\.", enabled: true, description: "HTTP client operations" }
431
- ],
432
- ignore_patterns: [
433
- { pattern: "^test\\.", description: "Test utilities" },
434
- { pattern: "^internal\\.", description: "Internal operations" },
435
- { pattern: "^health\\.", description: "Health checks" }
436
- ]
437
- },
438
- effect: {
439
- auto_extract_metadata: true
440
- }
441
- };
560
+ return parseAndValidateConfig(content);
442
561
  }
443
562
  async function loadConfigWithOptions(options = {}) {
444
- const loadOptions = options.cacheTimeout !== void 0 ? { cacheTimeout: options.cacheTimeout } : void 0;
445
563
  if (options.config) {
446
564
  return loadConfigFromInline(options.config);
447
565
  }
448
566
  const envConfigPath = process.env.ATRIM_INSTRUMENTATION_CONFIG;
449
567
  if (envConfigPath) {
450
- return loadConfig(envConfigPath, loadOptions);
568
+ return loadConfig(envConfigPath);
451
569
  }
452
570
  if (options.configUrl) {
453
- return loadConfig(options.configUrl, loadOptions);
571
+ return loadConfig(options.configUrl);
454
572
  }
455
573
  if (options.configPath) {
456
- return loadConfig(options.configPath, loadOptions);
574
+ return loadConfig(options.configPath);
457
575
  }
458
576
  const { existsSync } = await import('fs');
459
577
  const { join } = await import('path');
460
578
  const defaultPath = join(process.cwd(), "instrumentation.yaml");
461
579
  if (existsSync(defaultPath)) {
462
- return loadConfig(defaultPath, loadOptions);
580
+ return loadConfig(defaultPath);
463
581
  }
464
- return getDefaultConfig();
582
+ return defaultConfig;
465
583
  }
466
584
 
467
585
  // src/integrations/effect/effect-tracer.ts
586
+ var SDK_NAME = "@effect/opentelemetry";
587
+ var ATTR_TELEMETRY_EXPORTER_MODE = "telemetry.exporter.mode";
468
588
  function createEffectInstrumentation(options = {}) {
469
589
  return Layer.unwrapEffect(
470
590
  Effect.gen(function* () {
@@ -475,106 +595,228 @@ function createEffectInstrumentation(options = {}) {
475
595
  message: error instanceof Error ? error.message : String(error)
476
596
  })
477
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
+ }
478
603
  yield* Effect.sync(() => {
479
604
  const loggingLevel = config.instrumentation.logging || "on";
480
605
  logger.setLevel(loggingLevel);
481
606
  });
482
607
  yield* Effect.sync(() => initializePatternMatcher(config));
483
- const otlpEndpoint = options.otlpEndpoint || process.env.OTEL_EXPORTER_OTLP_ENDPOINT || "http://localhost:4318";
484
608
  const serviceName = options.serviceName || process.env.OTEL_SERVICE_NAME || "effect-service";
485
609
  const serviceVersion = options.serviceVersion || process.env.npm_package_version || "1.0.0";
486
- const autoExtractMetadata = options.autoExtractMetadata ?? config.effect?.auto_extract_metadata ?? true;
487
- const continueExistingTraces = options.continueExistingTraces ?? true;
488
- logger.log("\u{1F50D} Effect OpenTelemetry instrumentation");
489
- logger.log(` \u{1F4E1} Endpoint: ${otlpEndpoint}`);
490
- logger.log(` \u{1F3F7}\uFE0F Service: ${serviceName}`);
491
- logger.log(` \u2705 Auto metadata extraction: ${autoExtractMetadata}`);
492
- logger.log(` \u2705 Continue existing traces: ${continueExistingTraces}`);
493
- const otlpLayer = Otlp.layer({
494
- baseUrl: otlpEndpoint,
495
- resource: {
496
- serviceName,
497
- serviceVersion,
498
- attributes: {
499
- "platform.component": "effect",
500
- "effect.auto_metadata": autoExtractMetadata,
501
- "effect.context_propagation": continueExistingTraces
502
- }
503
- },
504
- // Bridge Effect context to OpenTelemetry global context
505
- // This is essential for context propagation to work properly
506
- tracerContext: (f, span) => {
507
- if (span._tag !== "Span") {
508
- 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);
509
642
  }
510
- const spanContext = {
511
- traceId: span.traceId,
512
- spanId: span.spanId,
513
- traceFlags: span.sampled ? TraceFlags.SAMPLED : TraceFlags.NONE
514
- };
515
- const otelSpan = trace.wrapSpanContext(spanContext);
516
- return context.with(trace.setSpan(context.active(), otelSpan), f);
517
- }
518
- }).pipe(Layer.provide(FetchHttpClient.layer));
519
- if (autoExtractMetadata) {
520
- 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
+ );
521
657
  }
522
- return otlpLayer;
523
658
  })
524
659
  ).pipe(Layer.orDie);
525
660
  }
526
661
  var EffectInstrumentationLive = Effect.sync(() => {
527
- const endpoint = process.env.OTEL_EXPORTER_OTLP_ENDPOINT || "http://localhost:4318";
528
662
  const serviceName = process.env.OTEL_SERVICE_NAME || "effect-service";
529
663
  const serviceVersion = process.env.npm_package_version || "1.0.0";
530
664
  logger.minimal(`@atrim/instrumentation/effect: Effect tracing enabled (${serviceName})`);
531
- logger.log("\u{1F50D} Effect OpenTelemetry tracer");
532
- logger.log(` \u{1F4E1} Endpoint: ${endpoint}`);
533
- logger.log(` \u{1F3F7}\uFE0F Service: ${serviceName}`);
534
- return Otlp.layer({
535
- baseUrl: endpoint,
536
- resource: {
537
- serviceName,
538
- serviceVersion,
539
- attributes: {
540
- "platform.component": "effect"
541
- }
542
- },
543
- // CRITICAL: Bridge Effect context to OpenTelemetry global context
544
- // This allows NodeSDK auto-instrumentation to see Effect spans as parent spans
545
- tracerContext: (f, span) => {
546
- if (span._tag !== "Span") {
547
- return f();
548
- }
549
- const spanContext = {
550
- traceId: span.traceId,
551
- spanId: span.spanId,
552
- traceFlags: span.sampled ? TraceFlags.SAMPLED : TraceFlags.NONE
553
- };
554
- const otelSpan = trace.wrapSpanContext(spanContext);
555
- return context.with(trace.setSpan(context.active(), otelSpan), f);
556
- }
557
- }).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
+ );
558
681
  }).pipe(Layer.unwrapEffect);
559
-
560
- // src/integrations/effect/effect-helpers.ts
561
- function annotateUser(_userId, _email) {
682
+ function annotateUser(userId, email, username) {
683
+ const attributes = {
684
+ "user.id": userId
685
+ };
686
+ if (email) attributes["user.email"] = email;
687
+ if (username) attributes["user.name"] = username;
688
+ return Effect.annotateCurrentSpan(attributes);
562
689
  }
563
- function annotateDataSize(_bytes, _count) {
690
+ function annotateDataSize(bytes, items, compressionRatio) {
691
+ const attributes = {
692
+ "data.size.bytes": bytes,
693
+ "data.size.items": items
694
+ };
695
+ if (compressionRatio !== void 0) {
696
+ attributes["data.compression.ratio"] = compressionRatio;
697
+ }
698
+ return Effect.annotateCurrentSpan(attributes);
564
699
  }
565
- function annotateBatch(_size, _batchSize) {
700
+ function annotateBatch(totalItems, batchSize, successCount, failureCount) {
701
+ const attributes = {
702
+ "batch.size": batchSize,
703
+ "batch.total_items": totalItems,
704
+ "batch.count": Math.ceil(totalItems / batchSize)
705
+ };
706
+ if (successCount !== void 0) {
707
+ attributes["batch.success_count"] = successCount;
708
+ }
709
+ if (failureCount !== void 0) {
710
+ attributes["batch.failure_count"] = failureCount;
711
+ }
712
+ return Effect.annotateCurrentSpan(attributes);
566
713
  }
567
- function annotateLLM(_model, _operation, _inputTokens, _outputTokens) {
714
+ function annotateLLM(model, provider, tokens) {
715
+ const attributes = {
716
+ "llm.model": model,
717
+ "llm.provider": provider
718
+ };
719
+ if (tokens) {
720
+ if (tokens.prompt !== void 0) attributes["llm.tokens.prompt"] = tokens.prompt;
721
+ if (tokens.completion !== void 0) attributes["llm.tokens.completion"] = tokens.completion;
722
+ if (tokens.total !== void 0) attributes["llm.tokens.total"] = tokens.total;
723
+ }
724
+ return Effect.annotateCurrentSpan(attributes);
568
725
  }
569
- function annotateQuery(_query, _database) {
726
+ function annotateQuery(query, duration, rowCount, database) {
727
+ const attributes = {
728
+ "db.statement": query.length > 1e3 ? query.substring(0, 1e3) + "..." : query
729
+ };
730
+ if (duration !== void 0) attributes["db.duration.ms"] = duration;
731
+ if (rowCount !== void 0) attributes["db.row_count"] = rowCount;
732
+ if (database) attributes["db.name"] = database;
733
+ return Effect.annotateCurrentSpan(attributes);
570
734
  }
571
- function annotateHttpRequest(_method, _url, _statusCode) {
735
+ function annotateHttpRequest(method, url, statusCode, contentLength) {
736
+ const attributes = {
737
+ "http.method": method,
738
+ "http.url": url
739
+ };
740
+ if (statusCode !== void 0) attributes["http.status_code"] = statusCode;
741
+ if (contentLength !== void 0) attributes["http.response.content_length"] = contentLength;
742
+ return Effect.annotateCurrentSpan(attributes);
572
743
  }
573
- function annotateError(_error, _context) {
744
+ function annotateError(error, recoverable, errorType) {
745
+ const errorMessage = typeof error === "string" ? error : error.message;
746
+ const errorStack = typeof error === "string" ? void 0 : error.stack;
747
+ const attributes = {
748
+ "error.message": errorMessage,
749
+ "error.recoverable": recoverable
750
+ };
751
+ if (errorType) attributes["error.type"] = errorType;
752
+ if (errorStack) attributes["error.stack"] = errorStack;
753
+ return Effect.annotateCurrentSpan(attributes);
574
754
  }
575
- function annotatePriority(_priority) {
755
+ function annotatePriority(priority, reason) {
756
+ const attributes = {
757
+ "operation.priority": priority
758
+ };
759
+ if (reason) attributes["operation.priority.reason"] = reason;
760
+ return Effect.annotateCurrentSpan(attributes);
576
761
  }
577
- function annotateCache(_operation, _hit) {
762
+ function annotateCache(hit, key, ttl) {
763
+ const attributes = {
764
+ "cache.hit": hit,
765
+ "cache.key": key
766
+ };
767
+ if (ttl !== void 0) attributes["cache.ttl.seconds"] = ttl;
768
+ return Effect.annotateCurrentSpan(attributes);
769
+ }
770
+ function extractEffectMetadata() {
771
+ return Effect.gen(function* () {
772
+ const metadata = {};
773
+ const currentFiber = Fiber.getCurrentFiber();
774
+ if (Option.isSome(currentFiber)) {
775
+ const fiber = currentFiber.value;
776
+ const fiberId = fiber.id();
777
+ metadata["effect.fiber.id"] = FiberId.threadName(fiberId);
778
+ const status = yield* Fiber.status(fiber);
779
+ if (status._tag) {
780
+ metadata["effect.fiber.status"] = status._tag;
781
+ }
782
+ }
783
+ const parentSpanResult = yield* Effect.currentSpan.pipe(
784
+ Effect.option
785
+ // Convert NoSuchElementException to Option
786
+ );
787
+ if (Option.isSome(parentSpanResult)) {
788
+ const parentSpan = parentSpanResult.value;
789
+ metadata["effect.operation.nested"] = true;
790
+ metadata["effect.operation.root"] = false;
791
+ if (parentSpan.spanId) {
792
+ metadata["effect.parent.span.id"] = parentSpan.spanId;
793
+ }
794
+ if (parentSpan.name) {
795
+ metadata["effect.parent.span.name"] = parentSpan.name;
796
+ }
797
+ if (parentSpan.traceId) {
798
+ metadata["effect.parent.trace.id"] = parentSpan.traceId;
799
+ }
800
+ } else {
801
+ metadata["effect.operation.nested"] = false;
802
+ metadata["effect.operation.root"] = true;
803
+ }
804
+ return metadata;
805
+ });
806
+ }
807
+ function autoEnrichSpan() {
808
+ return Effect.gen(function* () {
809
+ const metadata = yield* extractEffectMetadata();
810
+ yield* Effect.annotateCurrentSpan(metadata);
811
+ });
812
+ }
813
+ function withAutoEnrichedSpan(spanName, options) {
814
+ return (self) => {
815
+ return Effect.gen(function* () {
816
+ yield* autoEnrichSpan();
817
+ return yield* self;
818
+ }).pipe(Effect.withSpan(spanName, options));
819
+ };
578
820
  }
579
821
  var createLogicalParentLink = (parentSpan, useSpanLinks) => {
580
822
  if (!useSpanLinks) {
@@ -624,7 +866,7 @@ var runIsolated = (set, effect, name, options) => {
624
866
  return FiberSet$1.run(set, effect, { propagateInterruption });
625
867
  }
626
868
  return Effect.gen(function* () {
627
- const maybeParent = yield* Effect.serviceOption(Tracer.ParentSpan);
869
+ const maybeParent = yield* Effect.serviceOption(Tracer$1.ParentSpan);
628
870
  if (maybeParent._tag === "None" || !captureLogicalParent) {
629
871
  const isolated2 = effect.pipe(
630
872
  Effect.withSpan(name, {
@@ -701,6 +943,6 @@ var FiberSet = {
701
943
  runWithSpan
702
944
  };
703
945
 
704
- export { EffectInstrumentationLive, FiberSet, annotateBatch, annotateCache, annotateDataSize, annotateError, annotateHttpRequest, annotateLLM, annotatePriority, annotateQuery, annotateSpawnedTasks, annotateUser, createEffectInstrumentation, runIsolated, runWithSpan };
946
+ export { EffectInstrumentationLive, FiberSet, annotateBatch, annotateCache, annotateDataSize, annotateError, annotateHttpRequest, annotateLLM, annotatePriority, annotateQuery, annotateSpawnedTasks, annotateUser, autoEnrichSpan, createEffectInstrumentation, extractEffectMetadata, runIsolated, runWithSpan, withAutoEnrichedSpan };
705
947
  //# sourceMappingURL=index.js.map
706
948
  //# sourceMappingURL=index.js.map