@atrim/instrument-node 0.5.0-14fdea7-20260108232035 → 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.
@@ -0,0 +1,991 @@
1
+ 'use strict';
2
+
3
+ var effect = require('effect');
4
+ var Tracer = require('@effect/opentelemetry/Tracer');
5
+ var Resource = require('@effect/opentelemetry/Resource');
6
+ var Otlp = require('@effect/opentelemetry/Otlp');
7
+ var platform = require('@effect/platform');
8
+ var api = require('@opentelemetry/api');
9
+ var semanticConventions = require('@opentelemetry/semantic-conventions');
10
+ var FileSystem = require('@effect/platform/FileSystem');
11
+ var HttpClient = require('@effect/platform/HttpClient');
12
+ var HttpClientRequest = require('@effect/platform/HttpClientRequest');
13
+ var yaml = require('yaml');
14
+ var zod = require('zod');
15
+
16
+ function _interopNamespace(e) {
17
+ if (e && e.__esModule) return e;
18
+ var n = Object.create(null);
19
+ if (e) {
20
+ Object.keys(e).forEach(function (k) {
21
+ if (k !== 'default') {
22
+ var d = Object.getOwnPropertyDescriptor(e, k);
23
+ Object.defineProperty(n, k, d.get ? d : {
24
+ enumerable: true,
25
+ get: function () { return e[k]; }
26
+ });
27
+ }
28
+ });
29
+ }
30
+ n.default = e;
31
+ return Object.freeze(n);
32
+ }
33
+
34
+ var Tracer__namespace = /*#__PURE__*/_interopNamespace(Tracer);
35
+ var Resource__namespace = /*#__PURE__*/_interopNamespace(Resource);
36
+ var Otlp__namespace = /*#__PURE__*/_interopNamespace(Otlp);
37
+ var HttpClient__namespace = /*#__PURE__*/_interopNamespace(HttpClient);
38
+ var HttpClientRequest__namespace = /*#__PURE__*/_interopNamespace(HttpClientRequest);
39
+
40
+ // src/integrations/effect/effect-tracer.ts
41
+ var __defProp = Object.defineProperty;
42
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
43
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
44
+ var PatternConfigSchema = zod.z.object({
45
+ pattern: zod.z.string(),
46
+ enabled: zod.z.boolean().optional(),
47
+ description: zod.z.string().optional()
48
+ });
49
+ var AutoIsolationConfigSchema = zod.z.object({
50
+ // Global enable/disable for auto-isolation
51
+ enabled: zod.z.boolean().default(false),
52
+ // Which operators to auto-isolate
53
+ operators: zod.z.object({
54
+ fiberset_run: zod.z.boolean().default(true),
55
+ effect_fork: zod.z.boolean().default(true),
56
+ effect_fork_daemon: zod.z.boolean().default(true),
57
+ effect_fork_in: zod.z.boolean().default(false)
58
+ }).default({}),
59
+ // Virtual parent tracking configuration
60
+ tracking: zod.z.object({
61
+ use_span_links: zod.z.boolean().default(true),
62
+ use_attributes: zod.z.boolean().default(true),
63
+ capture_logical_parent: zod.z.boolean().default(true)
64
+ }).default({}),
65
+ // Span categorization
66
+ attributes: zod.z.object({
67
+ category: zod.z.string().default("background_task"),
68
+ add_metadata: zod.z.boolean().default(true)
69
+ }).default({})
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
+ });
134
+ var HttpFilteringConfigSchema = zod.z.object({
135
+ // Patterns to ignore for outgoing HTTP requests (string patterns only in YAML)
136
+ ignore_outgoing_urls: zod.z.array(zod.z.string()).optional(),
137
+ // Patterns to ignore for incoming HTTP requests (string patterns only in YAML)
138
+ ignore_incoming_paths: zod.z.array(zod.z.string()).optional(),
139
+ // Require parent span for outgoing requests (prevents root spans for HTTP calls)
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()
178
+ });
179
+ var InstrumentationConfigSchema = zod.z.object({
180
+ version: zod.z.string(),
181
+ instrumentation: zod.z.object({
182
+ enabled: zod.z.boolean(),
183
+ description: zod.z.string().optional(),
184
+ logging: zod.z.enum(["on", "off", "minimal"]).optional().default("on"),
185
+ instrument_patterns: zod.z.array(PatternConfigSchema),
186
+ ignore_patterns: zod.z.array(PatternConfigSchema)
187
+ }),
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(),
198
+ auto_extract_metadata: zod.z.boolean(),
199
+ auto_isolation: AutoIsolationConfigSchema.optional(),
200
+ // Auto-instrumentation: automatic tracing of all Effect fibers
201
+ auto_instrumentation: AutoInstrumentationConfigSchema.optional()
202
+ }).optional(),
203
+ http: HttpFilteringConfigSchema.optional()
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
+ }
237
+ (class extends effect.Data.TaggedError("ConfigError") {
238
+ get message() {
239
+ return this.reason;
240
+ }
241
+ });
242
+ var ConfigUrlError = class extends effect.Data.TaggedError("ConfigUrlError") {
243
+ get message() {
244
+ return this.reason;
245
+ }
246
+ };
247
+ var ConfigValidationError = class extends effect.Data.TaggedError("ConfigValidationError") {
248
+ get message() {
249
+ return this.reason;
250
+ }
251
+ };
252
+ var ConfigFileError = class extends effect.Data.TaggedError("ConfigFileError") {
253
+ get message() {
254
+ return this.reason;
255
+ }
256
+ };
257
+ (class extends effect.Data.TaggedError("ServiceDetectionError") {
258
+ get message() {
259
+ return this.reason;
260
+ }
261
+ });
262
+ (class extends effect.Data.TaggedError("InitializationError") {
263
+ get message() {
264
+ return this.reason;
265
+ }
266
+ });
267
+ (class extends effect.Data.TaggedError("ExportError") {
268
+ get message() {
269
+ return this.reason;
270
+ }
271
+ });
272
+ (class extends effect.Data.TaggedError("ShutdownError") {
273
+ get message() {
274
+ return this.reason;
275
+ }
276
+ });
277
+ var SECURITY_DEFAULTS = {
278
+ maxConfigSize: 1e6,
279
+ // 1MB
280
+ requestTimeout: 5e3
281
+ // 5 seconds
282
+ };
283
+ var ConfigLoader = class extends effect.Context.Tag("ConfigLoader")() {
284
+ };
285
+ var parseYamlContent = (content, uri) => effect.Effect.gen(function* () {
286
+ const parsed = yield* effect.Effect.try({
287
+ try: () => yaml.parse(content),
288
+ catch: (error) => new ConfigValidationError({
289
+ reason: uri ? `Failed to parse YAML from ${uri}` : "Failed to parse YAML",
290
+ cause: error
291
+ })
292
+ });
293
+ return yield* effect.Effect.try({
294
+ try: () => InstrumentationConfigSchema.parse(parsed),
295
+ catch: (error) => new ConfigValidationError({
296
+ reason: uri ? `Invalid configuration schema from ${uri}` : "Invalid configuration schema",
297
+ cause: error
298
+ })
299
+ });
300
+ });
301
+ var loadFromFileWithFs = (fs, path, uri) => effect.Effect.gen(function* () {
302
+ const content = yield* fs.readFileString(path).pipe(
303
+ effect.Effect.mapError(
304
+ (error) => new ConfigFileError({
305
+ reason: `Failed to read config file at ${uri}`,
306
+ cause: error
307
+ })
308
+ )
309
+ );
310
+ if (content.length > SECURITY_DEFAULTS.maxConfigSize) {
311
+ return yield* effect.Effect.fail(
312
+ new ConfigFileError({
313
+ reason: `Config file exceeds maximum size of ${SECURITY_DEFAULTS.maxConfigSize} bytes`
314
+ })
315
+ );
316
+ }
317
+ return yield* parseYamlContent(content, uri);
318
+ });
319
+ var loadFromHttpWithClient = (client, url) => effect.Effect.scoped(
320
+ effect.Effect.gen(function* () {
321
+ if (url.startsWith("http://")) {
322
+ return yield* effect.Effect.fail(
323
+ new ConfigUrlError({
324
+ reason: "Insecure protocol: only HTTPS URLs are allowed"
325
+ })
326
+ );
327
+ }
328
+ const request = HttpClientRequest__namespace.get(url).pipe(
329
+ HttpClientRequest__namespace.setHeaders({
330
+ Accept: "application/yaml, text/yaml, application/x-yaml"
331
+ })
332
+ );
333
+ const response = yield* client.execute(request).pipe(
334
+ effect.Effect.timeout(`${SECURITY_DEFAULTS.requestTimeout} millis`),
335
+ effect.Effect.mapError((error) => {
336
+ if (error._tag === "TimeoutException") {
337
+ return new ConfigUrlError({
338
+ reason: `Config fetch timeout after ${SECURITY_DEFAULTS.requestTimeout}ms from ${url}`
339
+ });
340
+ }
341
+ return new ConfigUrlError({
342
+ reason: `Failed to load config from URL: ${url}`,
343
+ cause: error
344
+ });
345
+ })
346
+ );
347
+ if (response.status >= 400) {
348
+ return yield* effect.Effect.fail(
349
+ new ConfigUrlError({
350
+ reason: `HTTP ${response.status} from ${url}`
351
+ })
352
+ );
353
+ }
354
+ const text = yield* response.text.pipe(
355
+ effect.Effect.mapError(
356
+ (error) => new ConfigUrlError({
357
+ reason: `Failed to read response body from ${url}`,
358
+ cause: error
359
+ })
360
+ )
361
+ );
362
+ if (text.length > SECURITY_DEFAULTS.maxConfigSize) {
363
+ return yield* effect.Effect.fail(
364
+ new ConfigUrlError({
365
+ reason: `Config exceeds maximum size of ${SECURITY_DEFAULTS.maxConfigSize} bytes`
366
+ })
367
+ );
368
+ }
369
+ return yield* parseYamlContent(text, url);
370
+ })
371
+ );
372
+ var makeConfigLoader = effect.Effect.gen(function* () {
373
+ const fs = yield* effect.Effect.serviceOption(FileSystem.FileSystem);
374
+ const http = yield* HttpClient__namespace.HttpClient;
375
+ const loadFromUriUncached = (uri) => effect.Effect.gen(function* () {
376
+ if (uri.startsWith("file://")) {
377
+ const path = uri.slice(7);
378
+ if (fs._tag === "None") {
379
+ return yield* effect.Effect.fail(
380
+ new ConfigFileError({
381
+ reason: "FileSystem not available (browser environment?)",
382
+ cause: { uri }
383
+ })
384
+ );
385
+ }
386
+ return yield* loadFromFileWithFs(fs.value, path, uri);
387
+ }
388
+ if (uri.startsWith("http://") || uri.startsWith("https://")) {
389
+ return yield* loadFromHttpWithClient(http, uri);
390
+ }
391
+ if (fs._tag === "Some") {
392
+ return yield* loadFromFileWithFs(fs.value, uri, uri);
393
+ } else {
394
+ return yield* loadFromHttpWithClient(http, uri);
395
+ }
396
+ });
397
+ const loadFromUriCached = yield* effect.Effect.cachedFunction(loadFromUriUncached);
398
+ return ConfigLoader.of({
399
+ loadFromUri: loadFromUriCached,
400
+ loadFromInline: (content) => effect.Effect.gen(function* () {
401
+ if (typeof content === "string") {
402
+ return yield* parseYamlContent(content);
403
+ }
404
+ return yield* effect.Effect.try({
405
+ try: () => InstrumentationConfigSchema.parse(content),
406
+ catch: (error) => new ConfigValidationError({
407
+ reason: "Invalid configuration schema",
408
+ cause: error
409
+ })
410
+ });
411
+ })
412
+ });
413
+ });
414
+ effect.Layer.effect(ConfigLoader, makeConfigLoader);
415
+ var PatternMatcher = class {
416
+ constructor(config) {
417
+ __publicField(this, "ignorePatterns", []);
418
+ __publicField(this, "instrumentPatterns", []);
419
+ __publicField(this, "enabled", true);
420
+ this.enabled = config.instrumentation.enabled;
421
+ this.ignorePatterns = config.instrumentation.ignore_patterns.map((p) => this.compilePattern(p));
422
+ this.instrumentPatterns = config.instrumentation.instrument_patterns.filter((p) => p.enabled !== false).map((p) => this.compilePattern(p));
423
+ }
424
+ /**
425
+ * Compile a pattern configuration into a RegExp
426
+ */
427
+ compilePattern(pattern) {
428
+ try {
429
+ const compiled = {
430
+ regex: new RegExp(pattern.pattern),
431
+ enabled: pattern.enabled !== false
432
+ };
433
+ if (pattern.description !== void 0) {
434
+ compiled.description = pattern.description;
435
+ }
436
+ return compiled;
437
+ } catch (error) {
438
+ throw new Error(
439
+ `Failed to compile pattern "${pattern.pattern}": ${error instanceof Error ? error.message : String(error)}`
440
+ );
441
+ }
442
+ }
443
+ /**
444
+ * Check if a span should be instrumented
445
+ *
446
+ * Returns true if the span should be created, false otherwise.
447
+ *
448
+ * Logic:
449
+ * 1. If instrumentation disabled globally, return false
450
+ * 2. Check ignore patterns - if any match, return false
451
+ * 3. Check instrument patterns - if any match, return true
452
+ * 4. Default: return true (fail-open - create span if no patterns match)
453
+ */
454
+ shouldInstrument(spanName) {
455
+ if (!this.enabled) {
456
+ return false;
457
+ }
458
+ for (const pattern of this.ignorePatterns) {
459
+ if (pattern.regex.test(spanName)) {
460
+ return false;
461
+ }
462
+ }
463
+ for (const pattern of this.instrumentPatterns) {
464
+ if (pattern.enabled && pattern.regex.test(spanName)) {
465
+ return true;
466
+ }
467
+ }
468
+ return true;
469
+ }
470
+ /**
471
+ * Get statistics about pattern matching (for debugging/monitoring)
472
+ */
473
+ getStats() {
474
+ return {
475
+ enabled: this.enabled,
476
+ ignorePatternCount: this.ignorePatterns.length,
477
+ instrumentPatternCount: this.instrumentPatterns.filter((p) => p.enabled).length
478
+ };
479
+ }
480
+ };
481
+ function initializePatternMatcher(config) {
482
+ new PatternMatcher(config);
483
+ }
484
+ var Logger = class {
485
+ constructor() {
486
+ __publicField(this, "level", "on");
487
+ __publicField(this, "hasLoggedMinimal", false);
488
+ }
489
+ /**
490
+ * Set the logging level
491
+ */
492
+ setLevel(level) {
493
+ this.level = level;
494
+ this.hasLoggedMinimal = false;
495
+ }
496
+ /**
497
+ * Get the current logging level
498
+ */
499
+ getLevel() {
500
+ return this.level;
501
+ }
502
+ /**
503
+ * Log a minimal initialization message (only shown once in minimal mode)
504
+ */
505
+ minimal(message) {
506
+ if (this.level === "off") {
507
+ return;
508
+ }
509
+ if (this.level === "minimal" && !this.hasLoggedMinimal) {
510
+ console.log(message);
511
+ this.hasLoggedMinimal = true;
512
+ return;
513
+ }
514
+ if (this.level === "on") {
515
+ console.log(message);
516
+ }
517
+ }
518
+ /**
519
+ * Log an informational message
520
+ */
521
+ log(...args) {
522
+ if (this.level === "on") {
523
+ console.log(...args);
524
+ }
525
+ }
526
+ /**
527
+ * Log a warning message (shown in minimal mode)
528
+ */
529
+ warn(...args) {
530
+ if (this.level !== "off") {
531
+ console.warn(...args);
532
+ }
533
+ }
534
+ /**
535
+ * Log an error message (shown in minimal mode)
536
+ */
537
+ error(...args) {
538
+ if (this.level !== "off") {
539
+ console.error(...args);
540
+ }
541
+ }
542
+ /**
543
+ * Check if full logging is enabled
544
+ */
545
+ isEnabled() {
546
+ return this.level === "on";
547
+ }
548
+ /**
549
+ * Check if minimal logging is enabled
550
+ */
551
+ isMinimal() {
552
+ return this.level === "minimal";
553
+ }
554
+ /**
555
+ * Check if logging is completely disabled
556
+ */
557
+ isDisabled() {
558
+ return this.level === "off";
559
+ }
560
+ };
561
+ var logger = new Logger();
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}`);
571
+ }
572
+ const content = await response.text();
573
+ return parseAndValidateConfig(content);
574
+ }
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);
582
+ }
583
+ return loadFromFile(uri);
584
+ }
585
+ async function loadConfigFromInline(content) {
586
+ return parseAndValidateConfig(content);
587
+ }
588
+ async function loadConfigWithOptions(options = {}) {
589
+ if (options.config) {
590
+ return loadConfigFromInline(options.config);
591
+ }
592
+ const envConfigPath = process.env.ATRIM_INSTRUMENTATION_CONFIG;
593
+ if (envConfigPath) {
594
+ return loadConfig(envConfigPath);
595
+ }
596
+ if (options.configUrl) {
597
+ return loadConfig(options.configUrl);
598
+ }
599
+ if (options.configPath) {
600
+ return loadConfig(options.configPath);
601
+ }
602
+ const { existsSync } = await import('fs');
603
+ const { join } = await import('path');
604
+ const defaultPath = join(process.cwd(), "instrumentation.yaml");
605
+ if (existsSync(defaultPath)) {
606
+ return loadConfig(defaultPath);
607
+ }
608
+ return defaultConfig;
609
+ }
610
+
611
+ // src/integrations/effect/effect-tracer.ts
612
+ var SDK_NAME = "@effect/opentelemetry";
613
+ var ATTR_TELEMETRY_EXPORTER_MODE = "telemetry.exporter.mode";
614
+ function createEffectInstrumentation(options = {}) {
615
+ return effect.Layer.unwrapEffect(
616
+ effect.Effect.gen(function* () {
617
+ const config = yield* effect.Effect.tryPromise({
618
+ try: () => loadConfigWithOptions(options),
619
+ catch: (error) => ({
620
+ _tag: "ConfigError",
621
+ message: error instanceof Error ? error.message : String(error)
622
+ })
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
+ }
629
+ yield* effect.Effect.sync(() => {
630
+ const loggingLevel = config.instrumentation.logging || "on";
631
+ logger.setLevel(loggingLevel);
632
+ });
633
+ yield* effect.Effect.sync(() => initializePatternMatcher(config));
634
+ const serviceName = options.serviceName || process.env.OTEL_SERVICE_NAME || "effect-service";
635
+ const serviceVersion = options.serviceVersion || process.env.npm_package_version || "1.0.0";
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);
668
+ }
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
+ );
683
+ }
684
+ })
685
+ ).pipe(effect.Layer.orDie);
686
+ }
687
+ var EffectInstrumentationLive = effect.Effect.sync(() => {
688
+ const serviceName = process.env.OTEL_SERVICE_NAME || "effect-service";
689
+ const serviceVersion = process.env.npm_package_version || "1.0.0";
690
+ logger.minimal(`@atrim/instrumentation/effect: Effect tracing enabled (${serviceName})`);
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
+ );
707
+ }).pipe(effect.Layer.unwrapEffect);
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);
715
+ }
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);
725
+ }
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);
739
+ }
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);
751
+ }
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);
760
+ }
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);
769
+ }
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);
780
+ }
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);
787
+ }
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
+ };
846
+ }
847
+ var createLogicalParentLink = (parentSpan, useSpanLinks) => {
848
+ if (!useSpanLinks) {
849
+ return [];
850
+ }
851
+ return [
852
+ {
853
+ _tag: "SpanLink",
854
+ span: parentSpan,
855
+ attributes: {
856
+ "link.type": "logical_parent",
857
+ "atrim.relationship": "spawned_by",
858
+ description: "Logical parent (isolated to prevent context leakage)"
859
+ }
860
+ }
861
+ ];
862
+ };
863
+ var createLogicalParentAttributes = (parentSpan, useAttributes, category, useSpanLinks, customAttributes) => {
864
+ if (!useAttributes) {
865
+ return customAttributes;
866
+ }
867
+ return {
868
+ // Logical parent tracking (works in ALL tools)
869
+ "atrim.logical_parent.span_id": parentSpan.spanId,
870
+ "atrim.logical_parent.trace_id": parentSpan.traceId,
871
+ "atrim.logical_parent.name": parentSpan._tag === "Span" ? parentSpan.name : "external",
872
+ // Categorization and metadata
873
+ "atrim.fiberset.isolated": true,
874
+ "atrim.span.category": category,
875
+ "atrim.has_logical_parent": true,
876
+ "atrim.isolation.method": useSpanLinks ? "hybrid" : "attributes_only",
877
+ // User-provided attributes
878
+ ...customAttributes
879
+ };
880
+ };
881
+ var runIsolated = (set, effect$1, name, options) => {
882
+ const {
883
+ createRoot = true,
884
+ captureLogicalParent = true,
885
+ useSpanLinks = true,
886
+ useAttributes = true,
887
+ propagateInterruption,
888
+ attributes = {},
889
+ category = "background_task"
890
+ } = options ?? {};
891
+ if (!createRoot && !captureLogicalParent) {
892
+ return effect.FiberSet.run(set, effect$1, { propagateInterruption });
893
+ }
894
+ return effect.Effect.gen(function* () {
895
+ const maybeParent = yield* effect.Effect.serviceOption(effect.Tracer.ParentSpan);
896
+ if (maybeParent._tag === "None" || !captureLogicalParent) {
897
+ const isolated2 = effect$1.pipe(
898
+ effect.Effect.withSpan(name, {
899
+ root: createRoot,
900
+ attributes: {
901
+ "atrim.fiberset.isolated": createRoot,
902
+ "atrim.span.category": category,
903
+ "atrim.has_logical_parent": false,
904
+ ...attributes
905
+ }
906
+ })
907
+ );
908
+ return yield* effect.FiberSet.run(set, isolated2, { propagateInterruption });
909
+ }
910
+ const parent = maybeParent.value;
911
+ const links = createLogicalParentLink(parent, useSpanLinks);
912
+ const spanAttributes = createLogicalParentAttributes(
913
+ parent,
914
+ useAttributes,
915
+ category,
916
+ useSpanLinks,
917
+ attributes
918
+ );
919
+ const isolated = effect$1.pipe(
920
+ effect.Effect.withSpan(name, {
921
+ root: createRoot,
922
+ links: links.length > 0 ? links : void 0,
923
+ attributes: spanAttributes
924
+ })
925
+ );
926
+ return yield* effect.FiberSet.run(set, isolated, { propagateInterruption });
927
+ });
928
+ };
929
+ var runWithSpan = (set, name, effect, options) => {
930
+ return runIsolated(set, effect, name, {
931
+ createRoot: true,
932
+ captureLogicalParent: true,
933
+ useSpanLinks: true,
934
+ useAttributes: true,
935
+ ...options
936
+ });
937
+ };
938
+ var annotateSpawnedTasks = (tasks) => {
939
+ return effect.Effect.annotateCurrentSpan({
940
+ "atrim.fiberset.spawned_count": tasks.length,
941
+ "atrim.fiberset.task_names": tasks.map((t) => t.name).join(","),
942
+ "atrim.has_background_tasks": true,
943
+ "atrim.spawned_tasks": JSON.stringify(
944
+ tasks.map((t) => ({
945
+ name: t.name,
946
+ ...t.spanId && { span_id: t.spanId },
947
+ ...t.category && { category: t.category }
948
+ }))
949
+ )
950
+ });
951
+ };
952
+ var FiberSet = {
953
+ // Re-export all original FiberSet functions
954
+ make: effect.FiberSet.make,
955
+ add: effect.FiberSet.add,
956
+ unsafeAdd: effect.FiberSet.unsafeAdd,
957
+ run: effect.FiberSet.run,
958
+ clear: effect.FiberSet.clear,
959
+ join: effect.FiberSet.join,
960
+ awaitEmpty: effect.FiberSet.awaitEmpty,
961
+ size: effect.FiberSet.size,
962
+ runtime: effect.FiberSet.runtime,
963
+ runtimePromise: effect.FiberSet.runtimePromise,
964
+ makeRuntime: effect.FiberSet.makeRuntime,
965
+ makeRuntimePromise: effect.FiberSet.makeRuntimePromise,
966
+ isFiberSet: effect.FiberSet.isFiberSet,
967
+ // Add our isolation helpers
968
+ runIsolated,
969
+ runWithSpan
970
+ };
971
+
972
+ exports.EffectInstrumentationLive = EffectInstrumentationLive;
973
+ exports.FiberSet = FiberSet;
974
+ exports.annotateBatch = annotateBatch;
975
+ exports.annotateCache = annotateCache;
976
+ exports.annotateDataSize = annotateDataSize;
977
+ exports.annotateError = annotateError;
978
+ exports.annotateHttpRequest = annotateHttpRequest;
979
+ exports.annotateLLM = annotateLLM;
980
+ exports.annotatePriority = annotatePriority;
981
+ exports.annotateQuery = annotateQuery;
982
+ exports.annotateSpawnedTasks = annotateSpawnedTasks;
983
+ exports.annotateUser = annotateUser;
984
+ exports.autoEnrichSpan = autoEnrichSpan;
985
+ exports.createEffectInstrumentation = createEffectInstrumentation;
986
+ exports.extractEffectMetadata = extractEffectMetadata;
987
+ exports.runIsolated = runIsolated;
988
+ exports.runWithSpan = runWithSpan;
989
+ exports.withAutoEnrichedSpan = withAutoEnrichedSpan;
990
+ //# sourceMappingURL=index.cjs.map
991
+ //# sourceMappingURL=index.cjs.map