@atrim/instrument-node 0.4.0 → 0.5.0-3a3dd2c-20251124202015

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,20 @@
1
- import { Data, Effect, Cache, Layer, FiberSet as FiberSet$1, Duration, Schedule, Tracer } from 'effect';
1
+ import { Data, Context, Effect, Layer, FiberSet as FiberSet$1, Fiber, Option, FiberId, Tracer } from 'effect';
2
2
  import * as Otlp from '@effect/opentelemetry/Otlp';
3
3
  import { FetchHttpClient } from '@effect/platform';
4
4
  import { TraceFlags, trace, context } from '@opentelemetry/api';
5
- import { existsSync, readFileSync } from 'fs';
6
- import { join } from 'path';
5
+ import { FileSystem } from '@effect/platform/FileSystem';
6
+ import * as HttpClient from '@effect/platform/HttpClient';
7
+ import * as HttpClientRequest from '@effect/platform/HttpClientRequest';
7
8
  import { parse } from 'yaml';
8
9
  import { z } from 'zod';
10
+ import { NodeContext } from '@effect/platform-node';
9
11
 
10
12
  // 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";
11
18
  var __defProp = Object.defineProperty;
12
19
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
13
20
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
@@ -38,13 +45,69 @@ var AutoIsolationConfigSchema = z.object({
38
45
  add_metadata: z.boolean().default(true)
39
46
  }).default({})
40
47
  });
48
+ var SpanNamingRuleSchema = z.object({
49
+ // Match conditions (all specified conditions must match)
50
+ match: z.object({
51
+ // Match by file path pattern (regex)
52
+ file: z.string().optional(),
53
+ // Match by function name pattern (regex)
54
+ function: z.string().optional(),
55
+ // Match by operator type (gen, all, forEach, etc.)
56
+ operator: z.string().optional(),
57
+ // Match by call stack pattern (regex)
58
+ stack: z.string().optional(),
59
+ // Match by fiber annotation key
60
+ annotation: z.string().optional()
61
+ }),
62
+ // Name template with substitution variables
63
+ // Available: {operator}, {function}, {module}, {file}, {line}, {class}, {match:N}
64
+ name: z.string()
65
+ });
66
+ var AutoTracingConfigSchema = z.object({
67
+ // Global enable/disable for auto-tracing
68
+ enabled: z.boolean().default(true),
69
+ // Span naming configuration
70
+ span_naming: z.object({
71
+ // Default name template when no rules match
72
+ default: z.string().default("effect.{operator}"),
73
+ // Custom naming rules (applied in order, first match wins)
74
+ rules: z.array(SpanNamingRuleSchema).default([])
75
+ }).default({}),
76
+ // Pattern filtering
77
+ filter_patterns: z.object({
78
+ // Only trace spans matching these patterns
79
+ include: z.array(z.string()).default([]),
80
+ // Exclude spans matching these patterns (takes precedence)
81
+ exclude: z.array(z.string()).default([])
82
+ }).default({}),
83
+ // Sampling configuration
84
+ sampling: z.object({
85
+ // Sampling rate (0.0 to 1.0)
86
+ rate: z.number().min(0).max(1).default(1),
87
+ // Only trace effects with duration > this value
88
+ min_duration: z.string().default("0 millis")
89
+ }).default({})
90
+ });
41
91
  var HttpFilteringConfigSchema = z.object({
42
92
  // Patterns to ignore for outgoing HTTP requests (string patterns only in YAML)
43
93
  ignore_outgoing_urls: z.array(z.string()).optional(),
44
94
  // Patterns to ignore for incoming HTTP requests (string patterns only in YAML)
45
95
  ignore_incoming_paths: z.array(z.string()).optional(),
46
96
  // Require parent span for outgoing requests (prevents root spans for HTTP calls)
47
- require_parent_for_outgoing_spans: z.boolean().optional()
97
+ require_parent_for_outgoing_spans: z.boolean().optional(),
98
+ // Trace context propagation configuration
99
+ // Controls which cross-origin requests receive W3C Trace Context headers (traceparent, tracestate)
100
+ propagate_trace_context: z.object({
101
+ // Strategy for trace propagation
102
+ // - "all": Propagate to all cross-origin requests (may cause CORS errors)
103
+ // - "none": Never propagate trace headers
104
+ // - "same-origin": Only propagate to same-origin requests (default, safe)
105
+ // - "patterns": Propagate based on include_urls patterns
106
+ strategy: z.enum(["all", "none", "same-origin", "patterns"]).default("same-origin"),
107
+ // URL patterns to include when strategy is "patterns"
108
+ // Supports regex patterns (e.g., "^https://api\\.myapp\\.com")
109
+ include_urls: z.array(z.string()).optional()
110
+ }).optional()
48
111
  });
49
112
  var InstrumentationConfigSchema = z.object({
50
113
  version: z.string(),
@@ -57,238 +120,189 @@ var InstrumentationConfigSchema = z.object({
57
120
  }),
58
121
  effect: z.object({
59
122
  auto_extract_metadata: z.boolean(),
60
- auto_isolation: AutoIsolationConfigSchema.optional()
123
+ auto_isolation: AutoIsolationConfigSchema.optional(),
124
+ auto_tracing: AutoTracingConfigSchema.optional()
61
125
  }).optional(),
62
126
  http: HttpFilteringConfigSchema.optional()
63
127
  });
64
128
  (class extends Data.TaggedError("ConfigError") {
129
+ get message() {
130
+ return this.reason;
131
+ }
65
132
  });
66
133
  var ConfigUrlError = class extends Data.TaggedError("ConfigUrlError") {
134
+ get message() {
135
+ return this.reason;
136
+ }
67
137
  };
68
138
  var ConfigValidationError = class extends Data.TaggedError("ConfigValidationError") {
139
+ get message() {
140
+ return this.reason;
141
+ }
69
142
  };
70
143
  var ConfigFileError = class extends Data.TaggedError("ConfigFileError") {
144
+ get message() {
145
+ return this.reason;
146
+ }
71
147
  };
72
148
  (class extends Data.TaggedError("ServiceDetectionError") {
149
+ get message() {
150
+ return this.reason;
151
+ }
73
152
  });
74
153
  (class extends Data.TaggedError("InitializationError") {
154
+ get message() {
155
+ return this.reason;
156
+ }
75
157
  });
76
158
  (class extends Data.TaggedError("ExportError") {
159
+ get message() {
160
+ return this.reason;
161
+ }
77
162
  });
78
163
  (class extends Data.TaggedError("ShutdownError") {
164
+ get message() {
165
+ return this.reason;
166
+ }
79
167
  });
80
168
  var SECURITY_DEFAULTS = {
81
169
  maxConfigSize: 1e6,
82
170
  // 1MB
83
- requestTimeout: 5e3,
84
- allowedProtocols: ["https:"],
85
- // Only HTTPS for remote configs
86
- cacheTimeout: 3e5
87
- // 5 minutes
171
+ requestTimeout: 5e3
172
+ // 5 seconds
88
173
  };
89
- function getDefaultConfig() {
90
- return {
91
- version: "1.0",
92
- instrumentation: {
93
- enabled: true,
94
- logging: "on",
95
- description: "Default instrumentation configuration",
96
- instrument_patterns: [
97
- { pattern: "^app\\.", enabled: true, description: "Application operations" },
98
- { pattern: "^http\\.server\\.", enabled: true, description: "HTTP server operations" },
99
- { pattern: "^http\\.client\\.", enabled: true, description: "HTTP client operations" }
100
- ],
101
- ignore_patterns: [
102
- { pattern: "^test\\.", description: "Test utilities" },
103
- { pattern: "^internal\\.", description: "Internal operations" },
104
- { pattern: "^health\\.", description: "Health checks" }
105
- ]
106
- },
107
- effect: {
108
- auto_extract_metadata: true
109
- }
110
- };
111
- }
112
- var validateConfigEffect = (rawConfig) => Effect.try({
113
- try: () => InstrumentationConfigSchema.parse(rawConfig),
114
- catch: (error) => new ConfigValidationError({
115
- reason: "Invalid configuration schema",
116
- cause: error
117
- })
118
- });
119
- var loadConfigFromFileEffect = (filePath) => Effect.gen(function* () {
120
- const fileContents = yield* Effect.try({
121
- try: () => readFileSync(filePath, "utf8"),
122
- catch: (error) => new ConfigFileError({
123
- reason: `Failed to read config file at ${filePath}`,
174
+ var ConfigLoader = class extends Context.Tag("ConfigLoader")() {
175
+ };
176
+ var parseYamlContent = (content, uri) => Effect.gen(function* () {
177
+ const parsed = yield* Effect.try({
178
+ try: () => parse(content),
179
+ catch: (error) => new ConfigValidationError({
180
+ reason: uri ? `Failed to parse YAML from ${uri}` : "Failed to parse YAML",
181
+ cause: error
182
+ })
183
+ });
184
+ return yield* Effect.try({
185
+ try: () => InstrumentationConfigSchema.parse(parsed),
186
+ catch: (error) => new ConfigValidationError({
187
+ reason: uri ? `Invalid configuration schema from ${uri}` : "Invalid configuration schema",
124
188
  cause: error
125
189
  })
126
190
  });
127
- if (fileContents.length > SECURITY_DEFAULTS.maxConfigSize) {
191
+ });
192
+ var loadFromFileWithFs = (fs, path, uri) => Effect.gen(function* () {
193
+ const content = yield* fs.readFileString(path).pipe(
194
+ Effect.mapError(
195
+ (error) => new ConfigFileError({
196
+ reason: `Failed to read config file at ${uri}`,
197
+ cause: error
198
+ })
199
+ )
200
+ );
201
+ if (content.length > SECURITY_DEFAULTS.maxConfigSize) {
128
202
  return yield* Effect.fail(
129
203
  new ConfigFileError({
130
204
  reason: `Config file exceeds maximum size of ${SECURITY_DEFAULTS.maxConfigSize} bytes`
131
205
  })
132
206
  );
133
207
  }
134
- let rawConfig;
135
- try {
136
- rawConfig = parse(fileContents);
137
- } catch (error) {
138
- return yield* Effect.fail(
139
- new ConfigValidationError({
140
- reason: "Invalid YAML syntax",
141
- cause: error
142
- })
143
- );
144
- }
145
- return yield* validateConfigEffect(rawConfig);
208
+ return yield* parseYamlContent(content, uri);
146
209
  });
147
- var fetchAndParseConfig = (url) => Effect.gen(function* () {
148
- let urlObj;
149
- try {
150
- urlObj = new URL(url);
151
- } catch (error) {
152
- return yield* Effect.fail(
153
- new ConfigUrlError({
154
- reason: `Invalid URL: ${url}`,
155
- cause: error
210
+ var loadFromHttpWithClient = (client, url) => Effect.scoped(
211
+ Effect.gen(function* () {
212
+ if (url.startsWith("http://")) {
213
+ return yield* Effect.fail(
214
+ new ConfigUrlError({
215
+ reason: "Insecure protocol: only HTTPS URLs are allowed"
216
+ })
217
+ );
218
+ }
219
+ const request = HttpClientRequest.get(url).pipe(
220
+ HttpClientRequest.setHeaders({
221
+ Accept: "application/yaml, text/yaml, application/x-yaml"
156
222
  })
157
223
  );
158
- }
159
- if (!SECURITY_DEFAULTS.allowedProtocols.includes(urlObj.protocol)) {
160
- return yield* Effect.fail(
161
- new ConfigUrlError({
162
- reason: `Insecure protocol ${urlObj.protocol}. Only ${SECURITY_DEFAULTS.allowedProtocols.join(", ")} are allowed`
224
+ const response = yield* client.execute(request).pipe(
225
+ Effect.timeout(`${SECURITY_DEFAULTS.requestTimeout} millis`),
226
+ Effect.mapError((error) => {
227
+ if (error._tag === "TimeoutException") {
228
+ return new ConfigUrlError({
229
+ reason: `Config fetch timeout after ${SECURITY_DEFAULTS.requestTimeout}ms from ${url}`
230
+ });
231
+ }
232
+ return new ConfigUrlError({
233
+ reason: `Failed to load config from URL: ${url}`,
234
+ cause: error
235
+ });
163
236
  })
164
237
  );
165
- }
166
- const response = yield* Effect.tryPromise({
167
- try: () => fetch(url, {
168
- redirect: "follow",
169
- headers: {
170
- Accept: "application/yaml, text/yaml, text/x-yaml"
171
- }
172
- }),
173
- catch: (error) => new ConfigUrlError({
174
- reason: `Failed to load config from URL ${url}`,
175
- cause: error
176
- })
177
- }).pipe(
178
- Effect.timeout(Duration.millis(SECURITY_DEFAULTS.requestTimeout)),
179
- Effect.retry({
180
- times: 3,
181
- schedule: Schedule.exponential(Duration.millis(100))
182
- }),
183
- Effect.catchAll((error) => {
184
- if (error._tag === "TimeoutException") {
185
- return Effect.fail(
186
- new ConfigUrlError({
187
- reason: `Config fetch timeout after ${SECURITY_DEFAULTS.requestTimeout}ms`
238
+ if (response.status >= 400) {
239
+ return yield* Effect.fail(
240
+ new ConfigUrlError({
241
+ reason: `HTTP ${response.status} from ${url}`
242
+ })
243
+ );
244
+ }
245
+ const text = yield* response.text.pipe(
246
+ Effect.mapError(
247
+ (error) => new ConfigUrlError({
248
+ reason: `Failed to read response body from ${url}`,
249
+ cause: error
250
+ })
251
+ )
252
+ );
253
+ if (text.length > SECURITY_DEFAULTS.maxConfigSize) {
254
+ return yield* Effect.fail(
255
+ new ConfigUrlError({
256
+ reason: `Config exceeds maximum size of ${SECURITY_DEFAULTS.maxConfigSize} bytes`
257
+ })
258
+ );
259
+ }
260
+ return yield* parseYamlContent(text, url);
261
+ })
262
+ );
263
+ var makeConfigLoader = Effect.gen(function* () {
264
+ const fs = yield* Effect.serviceOption(FileSystem);
265
+ const http = yield* HttpClient.HttpClient;
266
+ const loadFromUriUncached = (uri) => Effect.gen(function* () {
267
+ if (uri.startsWith("file://")) {
268
+ const path = uri.slice(7);
269
+ if (fs._tag === "None") {
270
+ return yield* Effect.fail(
271
+ new ConfigFileError({
272
+ reason: "FileSystem not available (browser environment?)",
273
+ cause: { uri }
188
274
  })
189
275
  );
190
276
  }
191
- return Effect.fail(error);
192
- })
193
- );
194
- if (!response.ok) {
195
- return yield* Effect.fail(
196
- new ConfigUrlError({
197
- reason: `HTTP ${response.status}: ${response.statusText}`
198
- })
199
- );
200
- }
201
- const contentLength = response.headers.get("content-length");
202
- if (contentLength && parseInt(contentLength) > SECURITY_DEFAULTS.maxConfigSize) {
203
- return yield* Effect.fail(
204
- new ConfigUrlError({
205
- reason: `Config exceeds maximum size of ${SECURITY_DEFAULTS.maxConfigSize} bytes`
206
- })
207
- );
208
- }
209
- const text = yield* Effect.tryPromise({
210
- try: () => response.text(),
211
- catch: (error) => new ConfigUrlError({
212
- reason: "Failed to read response body",
213
- cause: error
277
+ return yield* loadFromFileWithFs(fs.value, path, uri);
278
+ }
279
+ if (uri.startsWith("http://") || uri.startsWith("https://")) {
280
+ return yield* loadFromHttpWithClient(http, uri);
281
+ }
282
+ if (fs._tag === "Some") {
283
+ return yield* loadFromFileWithFs(fs.value, uri, uri);
284
+ } else {
285
+ return yield* loadFromHttpWithClient(http, uri);
286
+ }
287
+ });
288
+ const loadFromUriCached = yield* Effect.cachedFunction(loadFromUriUncached);
289
+ return ConfigLoader.of({
290
+ loadFromUri: loadFromUriCached,
291
+ loadFromInline: (content) => Effect.gen(function* () {
292
+ if (typeof content === "string") {
293
+ return yield* parseYamlContent(content);
294
+ }
295
+ return yield* Effect.try({
296
+ try: () => InstrumentationConfigSchema.parse(content),
297
+ catch: (error) => new ConfigValidationError({
298
+ reason: "Invalid configuration schema",
299
+ cause: error
300
+ })
301
+ });
214
302
  })
215
303
  });
216
- if (text.length > SECURITY_DEFAULTS.maxConfigSize) {
217
- return yield* Effect.fail(
218
- new ConfigUrlError({
219
- reason: `Config exceeds maximum size of ${SECURITY_DEFAULTS.maxConfigSize} bytes`
220
- })
221
- );
222
- }
223
- let rawConfig;
224
- try {
225
- rawConfig = parse(text);
226
- } catch (error) {
227
- return yield* Effect.fail(
228
- new ConfigValidationError({
229
- reason: "Invalid YAML syntax",
230
- cause: error
231
- })
232
- );
233
- }
234
- return yield* validateConfigEffect(rawConfig);
235
- });
236
- var makeConfigCache = () => Cache.make({
237
- capacity: 100,
238
- timeToLive: Duration.minutes(5),
239
- lookup: (url) => fetchAndParseConfig(url)
240
- });
241
- var cacheInstance = null;
242
- var getCache = Effect.gen(function* () {
243
- if (!cacheInstance) {
244
- cacheInstance = yield* makeConfigCache();
245
- }
246
- return cacheInstance;
247
304
  });
248
- var loadConfigFromUrlEffect = (url, cacheTimeout = SECURITY_DEFAULTS.cacheTimeout) => Effect.gen(function* () {
249
- if (cacheTimeout === 0) {
250
- return yield* fetchAndParseConfig(url);
251
- }
252
- const cache = yield* getCache;
253
- return yield* cache.get(url);
254
- });
255
- var loadConfigEffect = (options = {}) => Effect.gen(function* () {
256
- if (options.config) {
257
- return yield* validateConfigEffect(options.config);
258
- }
259
- const envConfigPath = process.env.ATRIM_INSTRUMENTATION_CONFIG;
260
- if (envConfigPath) {
261
- if (envConfigPath.startsWith("http://") || envConfigPath.startsWith("https://")) {
262
- return yield* loadConfigFromUrlEffect(envConfigPath, options.cacheTimeout);
263
- }
264
- return yield* loadConfigFromFileEffect(envConfigPath);
265
- }
266
- if (options.configUrl) {
267
- return yield* loadConfigFromUrlEffect(options.configUrl, options.cacheTimeout);
268
- }
269
- if (options.configPath) {
270
- return yield* loadConfigFromFileEffect(options.configPath);
271
- }
272
- const defaultPath = join(process.cwd(), "instrumentation.yaml");
273
- const exists = yield* Effect.sync(() => existsSync(defaultPath));
274
- if (exists) {
275
- return yield* loadConfigFromFileEffect(defaultPath);
276
- }
277
- return getDefaultConfig();
278
- });
279
- async function loadConfig(options = {}) {
280
- return Effect.runPromise(
281
- loadConfigEffect(options).pipe(
282
- // Convert typed errors to regular Error with reason message for backward compatibility
283
- Effect.mapError((error) => {
284
- const message = error.reason;
285
- const newError = new Error(message);
286
- newError.cause = error.cause;
287
- return newError;
288
- })
289
- )
290
- );
291
- }
305
+ var ConfigLoaderLive = Layer.effect(ConfigLoader, makeConfigLoader);
292
306
  var PatternMatcher = class {
293
307
  constructor(config) {
294
308
  __publicField(this, "ignorePatterns", []);
@@ -436,13 +450,89 @@ var Logger = class {
436
450
  }
437
451
  };
438
452
  var logger = new Logger();
453
+ var NodeConfigLoaderLive = ConfigLoaderLive.pipe(
454
+ Layer.provide(Layer.mergeAll(NodeContext.layer, FetchHttpClient.layer))
455
+ );
456
+ var cachedLoaderPromise = null;
457
+ function getCachedLoader() {
458
+ if (!cachedLoaderPromise) {
459
+ cachedLoaderPromise = Effect.runPromise(
460
+ Effect.gen(function* () {
461
+ return yield* ConfigLoader;
462
+ }).pipe(Effect.provide(NodeConfigLoaderLive))
463
+ );
464
+ }
465
+ return cachedLoaderPromise;
466
+ }
467
+ async function loadConfig(uri, options) {
468
+ if (options?.cacheTimeout === 0) {
469
+ const program = Effect.gen(function* () {
470
+ const loader2 = yield* ConfigLoader;
471
+ return yield* loader2.loadFromUri(uri);
472
+ });
473
+ return Effect.runPromise(program.pipe(Effect.provide(NodeConfigLoaderLive)));
474
+ }
475
+ const loader = await getCachedLoader();
476
+ return Effect.runPromise(loader.loadFromUri(uri));
477
+ }
478
+ async function loadConfigFromInline(content) {
479
+ const loader = await getCachedLoader();
480
+ return Effect.runPromise(loader.loadFromInline(content));
481
+ }
482
+ function getDefaultConfig() {
483
+ return {
484
+ version: "1.0",
485
+ instrumentation: {
486
+ enabled: true,
487
+ logging: "on",
488
+ description: "Default instrumentation configuration",
489
+ instrument_patterns: [
490
+ { pattern: "^app\\.", enabled: true, description: "Application operations" },
491
+ { pattern: "^http\\.server\\.", enabled: true, description: "HTTP server operations" },
492
+ { pattern: "^http\\.client\\.", enabled: true, description: "HTTP client operations" }
493
+ ],
494
+ ignore_patterns: [
495
+ { pattern: "^test\\.", description: "Test utilities" },
496
+ { pattern: "^internal\\.", description: "Internal operations" },
497
+ { pattern: "^health\\.", description: "Health checks" }
498
+ ]
499
+ },
500
+ effect: {
501
+ auto_extract_metadata: true
502
+ }
503
+ };
504
+ }
505
+ async function loadConfigWithOptions(options = {}) {
506
+ const loadOptions = options.cacheTimeout !== void 0 ? { cacheTimeout: options.cacheTimeout } : void 0;
507
+ if (options.config) {
508
+ return loadConfigFromInline(options.config);
509
+ }
510
+ const envConfigPath = process.env.ATRIM_INSTRUMENTATION_CONFIG;
511
+ if (envConfigPath) {
512
+ return loadConfig(envConfigPath, loadOptions);
513
+ }
514
+ if (options.configUrl) {
515
+ return loadConfig(options.configUrl, loadOptions);
516
+ }
517
+ if (options.configPath) {
518
+ return loadConfig(options.configPath, loadOptions);
519
+ }
520
+ const { existsSync } = await import('fs');
521
+ const { join } = await import('path');
522
+ const defaultPath = join(process.cwd(), "instrumentation.yaml");
523
+ if (existsSync(defaultPath)) {
524
+ return loadConfig(defaultPath, loadOptions);
525
+ }
526
+ return getDefaultConfig();
527
+ }
439
528
 
440
529
  // src/integrations/effect/effect-tracer.ts
530
+ var SDK_NAME = "@effect/opentelemetry-otlp";
441
531
  function createEffectInstrumentation(options = {}) {
442
532
  return Layer.unwrapEffect(
443
533
  Effect.gen(function* () {
444
534
  const config = yield* Effect.tryPromise({
445
- try: () => loadConfig(options),
535
+ try: () => loadConfigWithOptions(options),
446
536
  catch: (error) => ({
447
537
  _tag: "ConfigError",
448
538
  message: error instanceof Error ? error.message : String(error)
@@ -471,7 +561,9 @@ function createEffectInstrumentation(options = {}) {
471
561
  attributes: {
472
562
  "platform.component": "effect",
473
563
  "effect.auto_metadata": autoExtractMetadata,
474
- "effect.context_propagation": continueExistingTraces
564
+ "effect.context_propagation": continueExistingTraces,
565
+ [ATTR_TELEMETRY_SDK_LANGUAGE]: TELEMETRY_SDK_LANGUAGE_VALUE_NODEJS,
566
+ [ATTR_TELEMETRY_SDK_NAME]: SDK_NAME
475
567
  }
476
568
  },
477
569
  // Bridge Effect context to OpenTelemetry global context
@@ -510,7 +602,9 @@ var EffectInstrumentationLive = Effect.sync(() => {
510
602
  serviceName,
511
603
  serviceVersion,
512
604
  attributes: {
513
- "platform.component": "effect"
605
+ "platform.component": "effect",
606
+ [ATTR_TELEMETRY_SDK_LANGUAGE]: TELEMETRY_SDK_LANGUAGE_VALUE_NODEJS,
607
+ [ATTR_TELEMETRY_SDK_NAME]: SDK_NAME
514
608
  }
515
609
  },
516
610
  // CRITICAL: Bridge Effect context to OpenTelemetry global context
@@ -529,25 +623,144 @@ var EffectInstrumentationLive = Effect.sync(() => {
529
623
  }
530
624
  }).pipe(Layer.provide(FetchHttpClient.layer));
531
625
  }).pipe(Layer.unwrapEffect);
532
-
533
- // src/integrations/effect/effect-helpers.ts
534
- function annotateUser(_userId, _email) {
626
+ function annotateUser(userId, email, username) {
627
+ const attributes = {
628
+ "user.id": userId
629
+ };
630
+ if (email) attributes["user.email"] = email;
631
+ if (username) attributes["user.name"] = username;
632
+ return Effect.annotateCurrentSpan(attributes);
535
633
  }
536
- function annotateDataSize(_bytes, _count) {
634
+ function annotateDataSize(bytes, items, compressionRatio) {
635
+ const attributes = {
636
+ "data.size.bytes": bytes,
637
+ "data.size.items": items
638
+ };
639
+ if (compressionRatio !== void 0) {
640
+ attributes["data.compression.ratio"] = compressionRatio;
641
+ }
642
+ return Effect.annotateCurrentSpan(attributes);
537
643
  }
538
- function annotateBatch(_size, _batchSize) {
644
+ function annotateBatch(totalItems, batchSize, successCount, failureCount) {
645
+ const attributes = {
646
+ "batch.size": batchSize,
647
+ "batch.total_items": totalItems,
648
+ "batch.count": Math.ceil(totalItems / batchSize)
649
+ };
650
+ if (successCount !== void 0) {
651
+ attributes["batch.success_count"] = successCount;
652
+ }
653
+ if (failureCount !== void 0) {
654
+ attributes["batch.failure_count"] = failureCount;
655
+ }
656
+ return Effect.annotateCurrentSpan(attributes);
657
+ }
658
+ function annotateLLM(model, provider, tokens) {
659
+ const attributes = {
660
+ "llm.model": model,
661
+ "llm.provider": provider
662
+ };
663
+ if (tokens) {
664
+ if (tokens.prompt !== void 0) attributes["llm.tokens.prompt"] = tokens.prompt;
665
+ if (tokens.completion !== void 0) attributes["llm.tokens.completion"] = tokens.completion;
666
+ if (tokens.total !== void 0) attributes["llm.tokens.total"] = tokens.total;
667
+ }
668
+ return Effect.annotateCurrentSpan(attributes);
669
+ }
670
+ function annotateQuery(query, duration, rowCount, database) {
671
+ const attributes = {
672
+ "db.statement": query.length > 1e3 ? query.substring(0, 1e3) + "..." : query
673
+ };
674
+ if (duration !== void 0) attributes["db.duration.ms"] = duration;
675
+ if (rowCount !== void 0) attributes["db.row_count"] = rowCount;
676
+ if (database) attributes["db.name"] = database;
677
+ return Effect.annotateCurrentSpan(attributes);
678
+ }
679
+ function annotateHttpRequest(method, url, statusCode, contentLength) {
680
+ const attributes = {
681
+ "http.method": method,
682
+ "http.url": url
683
+ };
684
+ if (statusCode !== void 0) attributes["http.status_code"] = statusCode;
685
+ if (contentLength !== void 0) attributes["http.response.content_length"] = contentLength;
686
+ return Effect.annotateCurrentSpan(attributes);
539
687
  }
540
- function annotateLLM(_model, _operation, _inputTokens, _outputTokens) {
688
+ function annotateError(error, recoverable, errorType) {
689
+ const errorMessage = typeof error === "string" ? error : error.message;
690
+ const errorStack = typeof error === "string" ? void 0 : error.stack;
691
+ const attributes = {
692
+ "error.message": errorMessage,
693
+ "error.recoverable": recoverable
694
+ };
695
+ if (errorType) attributes["error.type"] = errorType;
696
+ if (errorStack) attributes["error.stack"] = errorStack;
697
+ return Effect.annotateCurrentSpan(attributes);
541
698
  }
542
- function annotateQuery(_query, _database) {
699
+ function annotatePriority(priority, reason) {
700
+ const attributes = {
701
+ "operation.priority": priority
702
+ };
703
+ if (reason) attributes["operation.priority.reason"] = reason;
704
+ return Effect.annotateCurrentSpan(attributes);
543
705
  }
544
- function annotateHttpRequest(_method, _url, _statusCode) {
706
+ function annotateCache(hit, key, ttl) {
707
+ const attributes = {
708
+ "cache.hit": hit,
709
+ "cache.key": key
710
+ };
711
+ if (ttl !== void 0) attributes["cache.ttl.seconds"] = ttl;
712
+ return Effect.annotateCurrentSpan(attributes);
545
713
  }
546
- function annotateError(_error, _context) {
714
+ function extractEffectMetadata() {
715
+ return Effect.gen(function* () {
716
+ const metadata = {};
717
+ const currentFiber = Fiber.getCurrentFiber();
718
+ if (Option.isSome(currentFiber)) {
719
+ const fiber = currentFiber.value;
720
+ const fiberId = fiber.id();
721
+ metadata["effect.fiber.id"] = FiberId.threadName(fiberId);
722
+ const status = yield* Fiber.status(fiber);
723
+ if (status._tag) {
724
+ metadata["effect.fiber.status"] = status._tag;
725
+ }
726
+ }
727
+ const parentSpanResult = yield* Effect.currentSpan.pipe(
728
+ Effect.option
729
+ // Convert NoSuchElementException to Option
730
+ );
731
+ if (Option.isSome(parentSpanResult)) {
732
+ const parentSpan = parentSpanResult.value;
733
+ metadata["effect.operation.nested"] = true;
734
+ metadata["effect.operation.root"] = false;
735
+ if (parentSpan.spanId) {
736
+ metadata["effect.parent.span.id"] = parentSpan.spanId;
737
+ }
738
+ if (parentSpan.name) {
739
+ metadata["effect.parent.span.name"] = parentSpan.name;
740
+ }
741
+ if (parentSpan.traceId) {
742
+ metadata["effect.parent.trace.id"] = parentSpan.traceId;
743
+ }
744
+ } else {
745
+ metadata["effect.operation.nested"] = false;
746
+ metadata["effect.operation.root"] = true;
747
+ }
748
+ return metadata;
749
+ });
547
750
  }
548
- function annotatePriority(_priority) {
751
+ function autoEnrichSpan() {
752
+ return Effect.gen(function* () {
753
+ const metadata = yield* extractEffectMetadata();
754
+ yield* Effect.annotateCurrentSpan(metadata);
755
+ });
549
756
  }
550
- function annotateCache(_operation, _hit) {
757
+ function withAutoEnrichedSpan(spanName, options) {
758
+ return (self) => {
759
+ return Effect.gen(function* () {
760
+ yield* autoEnrichSpan();
761
+ return yield* self;
762
+ }).pipe(Effect.withSpan(spanName, options));
763
+ };
551
764
  }
552
765
  var createLogicalParentLink = (parentSpan, useSpanLinks) => {
553
766
  if (!useSpanLinks) {
@@ -674,6 +887,6 @@ var FiberSet = {
674
887
  runWithSpan
675
888
  };
676
889
 
677
- export { EffectInstrumentationLive, FiberSet, annotateBatch, annotateCache, annotateDataSize, annotateError, annotateHttpRequest, annotateLLM, annotatePriority, annotateQuery, annotateSpawnedTasks, annotateUser, createEffectInstrumentation, runIsolated, runWithSpan };
890
+ export { EffectInstrumentationLive, FiberSet, annotateBatch, annotateCache, annotateDataSize, annotateError, annotateHttpRequest, annotateLLM, annotatePriority, annotateQuery, annotateSpawnedTasks, annotateUser, autoEnrichSpan, createEffectInstrumentation, extractEffectMetadata, runIsolated, runWithSpan, withAutoEnrichedSpan };
678
891
  //# sourceMappingURL=index.js.map
679
892
  //# sourceMappingURL=index.js.map