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