@atrim/instrument-node 0.5.2-dev.ac2fbfe.20251221205322 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,15 +1,17 @@
1
1
  'use strict';
2
2
 
3
3
  var effect = require('effect');
4
+ var Tracer = require('@effect/opentelemetry/Tracer');
5
+ var Resource = require('@effect/opentelemetry/Resource');
4
6
  var Otlp = require('@effect/opentelemetry/Otlp');
5
7
  var platform = require('@effect/platform');
6
8
  var api = require('@opentelemetry/api');
9
+ var semanticConventions = require('@opentelemetry/semantic-conventions');
7
10
  var FileSystem = require('@effect/platform/FileSystem');
8
11
  var HttpClient = require('@effect/platform/HttpClient');
9
12
  var HttpClientRequest = require('@effect/platform/HttpClientRequest');
10
13
  var yaml = require('yaml');
11
14
  var zod = require('zod');
12
- var platformNode = require('@effect/platform-node');
13
15
 
14
16
  function _interopNamespace(e) {
15
17
  if (e && e.__esModule) return e;
@@ -29,16 +31,13 @@ function _interopNamespace(e) {
29
31
  return Object.freeze(n);
30
32
  }
31
33
 
34
+ var Tracer__namespace = /*#__PURE__*/_interopNamespace(Tracer);
35
+ var Resource__namespace = /*#__PURE__*/_interopNamespace(Resource);
32
36
  var Otlp__namespace = /*#__PURE__*/_interopNamespace(Otlp);
33
37
  var HttpClient__namespace = /*#__PURE__*/_interopNamespace(HttpClient);
34
38
  var HttpClientRequest__namespace = /*#__PURE__*/_interopNamespace(HttpClientRequest);
35
39
 
36
40
  // 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";
42
41
  var __defProp = Object.defineProperty;
43
42
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
44
43
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
@@ -100,11 +99,50 @@ var InstrumentationConfigSchema = zod.z.object({
100
99
  ignore_patterns: zod.z.array(PatternConfigSchema)
101
100
  }),
102
101
  effect: zod.z.object({
102
+ // Enable/disable Effect tracing entirely
103
+ // When false, EffectInstrumentationLive returns Layer.empty
104
+ enabled: zod.z.boolean().default(true),
105
+ // Exporter mode:
106
+ // - "unified": Use global TracerProvider from Node SDK (recommended, enables filtering)
107
+ // - "standalone": Use Effect's own OTLP exporter (bypasses Node SDK filtering)
108
+ exporter: zod.z.enum(["unified", "standalone"]).default("unified"),
103
109
  auto_extract_metadata: zod.z.boolean(),
104
110
  auto_isolation: AutoIsolationConfigSchema.optional()
105
111
  }).optional(),
106
112
  http: HttpFilteringConfigSchema.optional()
107
113
  });
114
+ var defaultConfig = {
115
+ version: "1.0",
116
+ instrumentation: {
117
+ enabled: true,
118
+ logging: "on",
119
+ description: "Default instrumentation configuration",
120
+ instrument_patterns: [
121
+ { pattern: "^app\\.", enabled: true, description: "Application operations" },
122
+ { pattern: "^http\\.server\\.", enabled: true, description: "HTTP server operations" },
123
+ { pattern: "^http\\.client\\.", enabled: true, description: "HTTP client operations" }
124
+ ],
125
+ ignore_patterns: [
126
+ { pattern: "^test\\.", description: "Test utilities" },
127
+ { pattern: "^internal\\.", description: "Internal operations" },
128
+ { pattern: "^health\\.", description: "Health checks" }
129
+ ]
130
+ },
131
+ effect: {
132
+ enabled: true,
133
+ exporter: "unified",
134
+ auto_extract_metadata: true
135
+ }
136
+ };
137
+ function parseAndValidateConfig(content) {
138
+ let parsed;
139
+ if (typeof content === "string") {
140
+ parsed = yaml.parse(content);
141
+ } else {
142
+ parsed = content;
143
+ }
144
+ return InstrumentationConfigSchema.parse(parsed);
145
+ }
108
146
  (class extends effect.Data.TaggedError("ConfigError") {
109
147
  get message() {
110
148
  return this.reason;
@@ -282,7 +320,7 @@ var makeConfigLoader = effect.Effect.gen(function* () {
282
320
  })
283
321
  });
284
322
  });
285
- var ConfigLoaderLive = effect.Layer.effect(ConfigLoader, makeConfigLoader);
323
+ effect.Layer.effect(ConfigLoader, makeConfigLoader);
286
324
  var PatternMatcher = class {
287
325
  constructor(config) {
288
326
  __publicField(this, "ignorePatterns", []);
@@ -430,84 +468,58 @@ var Logger = class {
430
468
  }
431
469
  };
432
470
  var logger = new Logger();
433
- var NodeConfigLoaderLive = ConfigLoaderLive.pipe(
434
- effect.Layer.provide(effect.Layer.mergeAll(platformNode.NodeContext.layer, platform.FetchHttpClient.layer))
435
- );
436
- var cachedLoaderPromise = null;
437
- function getCachedLoader() {
438
- if (!cachedLoaderPromise) {
439
- cachedLoaderPromise = effect.Effect.runPromise(
440
- effect.Effect.gen(function* () {
441
- return yield* ConfigLoader;
442
- }).pipe(effect.Effect.provide(NodeConfigLoaderLive))
443
- );
471
+ async function loadFromFile(filePath) {
472
+ const { readFile } = await import('fs/promises');
473
+ const content = await readFile(filePath, "utf-8");
474
+ return parseAndValidateConfig(content);
475
+ }
476
+ async function loadFromUrl(url) {
477
+ const response = await fetch(url);
478
+ if (!response.ok) {
479
+ throw new Error(`Failed to fetch config from ${url}: ${response.statusText}`);
444
480
  }
445
- return cachedLoaderPromise;
481
+ const content = await response.text();
482
+ return parseAndValidateConfig(content);
446
483
  }
447
- async function loadConfig(uri, options) {
448
- if (options?.cacheTimeout === 0) {
449
- const program = effect.Effect.gen(function* () {
450
- const loader2 = yield* ConfigLoader;
451
- return yield* loader2.loadFromUri(uri);
452
- });
453
- return effect.Effect.runPromise(program.pipe(effect.Effect.provide(NodeConfigLoaderLive)));
484
+ async function loadConfig(uri, _options) {
485
+ if (uri.startsWith("http://") || uri.startsWith("https://")) {
486
+ return loadFromUrl(uri);
454
487
  }
455
- const loader = await getCachedLoader();
456
- return effect.Effect.runPromise(loader.loadFromUri(uri));
488
+ if (uri.startsWith("file://")) {
489
+ const filePath = uri.slice(7);
490
+ return loadFromFile(filePath);
491
+ }
492
+ return loadFromFile(uri);
457
493
  }
458
494
  async function loadConfigFromInline(content) {
459
- const loader = await getCachedLoader();
460
- return effect.Effect.runPromise(loader.loadFromInline(content));
461
- }
462
- function getDefaultConfig() {
463
- return {
464
- version: "1.0",
465
- instrumentation: {
466
- enabled: true,
467
- logging: "on",
468
- description: "Default instrumentation configuration",
469
- instrument_patterns: [
470
- { pattern: "^app\\.", enabled: true, description: "Application operations" },
471
- { pattern: "^http\\.server\\.", enabled: true, description: "HTTP server operations" },
472
- { pattern: "^http\\.client\\.", enabled: true, description: "HTTP client operations" }
473
- ],
474
- ignore_patterns: [
475
- { pattern: "^test\\.", description: "Test utilities" },
476
- { pattern: "^internal\\.", description: "Internal operations" },
477
- { pattern: "^health\\.", description: "Health checks" }
478
- ]
479
- },
480
- effect: {
481
- auto_extract_metadata: true
482
- }
483
- };
495
+ return parseAndValidateConfig(content);
484
496
  }
485
497
  async function loadConfigWithOptions(options = {}) {
486
- const loadOptions = options.cacheTimeout !== void 0 ? { cacheTimeout: options.cacheTimeout } : void 0;
487
498
  if (options.config) {
488
499
  return loadConfigFromInline(options.config);
489
500
  }
490
501
  const envConfigPath = process.env.ATRIM_INSTRUMENTATION_CONFIG;
491
502
  if (envConfigPath) {
492
- return loadConfig(envConfigPath, loadOptions);
503
+ return loadConfig(envConfigPath);
493
504
  }
494
505
  if (options.configUrl) {
495
- return loadConfig(options.configUrl, loadOptions);
506
+ return loadConfig(options.configUrl);
496
507
  }
497
508
  if (options.configPath) {
498
- return loadConfig(options.configPath, loadOptions);
509
+ return loadConfig(options.configPath);
499
510
  }
500
511
  const { existsSync } = await import('fs');
501
512
  const { join } = await import('path');
502
513
  const defaultPath = join(process.cwd(), "instrumentation.yaml");
503
514
  if (existsSync(defaultPath)) {
504
- return loadConfig(defaultPath, loadOptions);
515
+ return loadConfig(defaultPath);
505
516
  }
506
- return getDefaultConfig();
517
+ return defaultConfig;
507
518
  }
508
519
 
509
520
  // src/integrations/effect/effect-tracer.ts
510
- var SDK_NAME = "@effect/opentelemetry-otlp";
521
+ var SDK_NAME = "@effect/opentelemetry";
522
+ var ATTR_TELEMETRY_EXPORTER_MODE = "telemetry.exporter.mode";
511
523
  function createEffectInstrumentation(options = {}) {
512
524
  return effect.Layer.unwrapEffect(
513
525
  effect.Effect.gen(function* () {
@@ -518,90 +530,89 @@ function createEffectInstrumentation(options = {}) {
518
530
  message: error instanceof Error ? error.message : String(error)
519
531
  })
520
532
  });
533
+ const effectEnabled = process.env.OTEL_EFFECT_ENABLED !== "false" && (config.effect?.enabled ?? true);
534
+ if (!effectEnabled) {
535
+ logger.log("@atrim/instrumentation/effect: Effect tracing disabled via config");
536
+ return effect.Layer.empty;
537
+ }
521
538
  yield* effect.Effect.sync(() => {
522
539
  const loggingLevel = config.instrumentation.logging || "on";
523
540
  logger.setLevel(loggingLevel);
524
541
  });
525
542
  yield* effect.Effect.sync(() => initializePatternMatcher(config));
526
- const otlpEndpoint = options.otlpEndpoint || process.env.OTEL_EXPORTER_OTLP_ENDPOINT || "http://localhost:4318";
527
543
  const serviceName = options.serviceName || process.env.OTEL_SERVICE_NAME || "effect-service";
528
544
  const serviceVersion = options.serviceVersion || process.env.npm_package_version || "1.0.0";
529
- const autoExtractMetadata = options.autoExtractMetadata ?? config.effect?.auto_extract_metadata ?? true;
530
- const continueExistingTraces = options.continueExistingTraces ?? true;
531
- logger.log("\u{1F50D} Effect OpenTelemetry instrumentation");
532
- logger.log(` \u{1F4E1} Endpoint: ${otlpEndpoint}`);
533
- logger.log(` \u{1F3F7}\uFE0F Service: ${serviceName}`);
534
- logger.log(` \u2705 Auto metadata extraction: ${autoExtractMetadata}`);
535
- logger.log(` \u2705 Continue existing traces: ${continueExistingTraces}`);
536
- const otlpLayer = Otlp__namespace.layer({
537
- baseUrl: otlpEndpoint,
538
- resource: {
539
- serviceName,
540
- serviceVersion,
541
- attributes: {
542
- "platform.component": "effect",
543
- "effect.auto_metadata": autoExtractMetadata,
544
- "effect.context_propagation": continueExistingTraces,
545
- [ATTR_TELEMETRY_SDK_LANGUAGE]: TELEMETRY_SDK_LANGUAGE_VALUE_NODEJS,
546
- [ATTR_TELEMETRY_SDK_NAME]: SDK_NAME
547
- }
548
- },
549
- // Bridge Effect context to OpenTelemetry global context
550
- // This is essential for context propagation to work properly
551
- tracerContext: (f, span) => {
552
- if (span._tag !== "Span") {
553
- return f();
545
+ const exporterMode = options.exporterMode ?? config.effect?.exporter ?? "unified";
546
+ const resourceAttributes = {
547
+ "platform.component": "effect",
548
+ [semanticConventions.ATTR_TELEMETRY_SDK_LANGUAGE]: semanticConventions.TELEMETRY_SDK_LANGUAGE_VALUE_NODEJS,
549
+ [semanticConventions.ATTR_TELEMETRY_SDK_NAME]: SDK_NAME,
550
+ [ATTR_TELEMETRY_EXPORTER_MODE]: exporterMode
551
+ };
552
+ if (exporterMode === "standalone") {
553
+ const otlpEndpoint = options.otlpEndpoint || process.env.OTEL_EXPORTER_OTLP_ENDPOINT || "http://localhost:4318";
554
+ logger.log("Effect OpenTelemetry instrumentation (standalone)");
555
+ logger.log(` Service: ${serviceName}`);
556
+ logger.log(` Endpoint: ${otlpEndpoint}`);
557
+ logger.log(" WARNING: Standalone mode bypasses Node SDK filtering");
558
+ return Otlp__namespace.layer({
559
+ baseUrl: otlpEndpoint,
560
+ resource: {
561
+ serviceName,
562
+ serviceVersion,
563
+ attributes: resourceAttributes
564
+ },
565
+ // Bridge Effect context to OpenTelemetry global context
566
+ tracerContext: (f, span) => {
567
+ if (span._tag !== "Span") {
568
+ return f();
569
+ }
570
+ const spanContext = {
571
+ traceId: span.traceId,
572
+ spanId: span.spanId,
573
+ traceFlags: span.sampled ? api.TraceFlags.SAMPLED : api.TraceFlags.NONE
574
+ };
575
+ const otelSpan = api.trace.wrapSpanContext(spanContext);
576
+ return api.context.with(api.trace.setSpan(api.context.active(), otelSpan), f);
554
577
  }
555
- const spanContext = {
556
- traceId: span.traceId,
557
- spanId: span.spanId,
558
- traceFlags: span.sampled ? api.TraceFlags.SAMPLED : api.TraceFlags.NONE
559
- };
560
- const otelSpan = api.trace.wrapSpanContext(spanContext);
561
- return api.context.with(api.trace.setSpan(api.context.active(), otelSpan), f);
562
- }
563
- }).pipe(effect.Layer.provide(platform.FetchHttpClient.layer));
564
- if (autoExtractMetadata) {
565
- return otlpLayer;
578
+ }).pipe(effect.Layer.provide(platform.FetchHttpClient.layer));
579
+ } else {
580
+ logger.log("Effect OpenTelemetry instrumentation (unified)");
581
+ logger.log(` Service: ${serviceName}`);
582
+ logger.log(" Using global TracerProvider for span export");
583
+ return Tracer__namespace.layerGlobal.pipe(
584
+ effect.Layer.provide(
585
+ Resource__namespace.layer({
586
+ serviceName,
587
+ serviceVersion,
588
+ attributes: resourceAttributes
589
+ })
590
+ )
591
+ );
566
592
  }
567
- return otlpLayer;
568
593
  })
569
594
  ).pipe(effect.Layer.orDie);
570
595
  }
571
596
  var EffectInstrumentationLive = effect.Effect.sync(() => {
572
- const endpoint = process.env.OTEL_EXPORTER_OTLP_ENDPOINT || "http://localhost:4318";
573
597
  const serviceName = process.env.OTEL_SERVICE_NAME || "effect-service";
574
598
  const serviceVersion = process.env.npm_package_version || "1.0.0";
575
599
  logger.minimal(`@atrim/instrumentation/effect: Effect tracing enabled (${serviceName})`);
576
- logger.log("\u{1F50D} Effect OpenTelemetry tracer");
577
- logger.log(` \u{1F4E1} Endpoint: ${endpoint}`);
578
- logger.log(` \u{1F3F7}\uFE0F Service: ${serviceName}`);
579
- return Otlp__namespace.layer({
580
- baseUrl: endpoint,
581
- resource: {
582
- serviceName,
583
- serviceVersion,
584
- attributes: {
585
- "platform.component": "effect",
586
- [ATTR_TELEMETRY_SDK_LANGUAGE]: TELEMETRY_SDK_LANGUAGE_VALUE_NODEJS,
587
- [ATTR_TELEMETRY_SDK_NAME]: SDK_NAME
588
- }
589
- },
590
- // CRITICAL: Bridge Effect context to OpenTelemetry global context
591
- // This allows NodeSDK auto-instrumentation to see Effect spans as parent spans
592
- tracerContext: (f, span) => {
593
- if (span._tag !== "Span") {
594
- return f();
595
- }
596
- const spanContext = {
597
- traceId: span.traceId,
598
- spanId: span.spanId,
599
- traceFlags: span.sampled ? api.TraceFlags.SAMPLED : api.TraceFlags.NONE
600
- };
601
- const otelSpan = api.trace.wrapSpanContext(spanContext);
602
- return api.context.with(api.trace.setSpan(api.context.active(), otelSpan), f);
603
- }
604
- }).pipe(effect.Layer.provide(platform.FetchHttpClient.layer));
600
+ logger.log("Effect OpenTelemetry tracer (unified)");
601
+ logger.log(` Service: ${serviceName}`);
602
+ return Tracer__namespace.layerGlobal.pipe(
603
+ effect.Layer.provide(
604
+ Resource__namespace.layer({
605
+ serviceName,
606
+ serviceVersion,
607
+ attributes: {
608
+ "platform.component": "effect",
609
+ [semanticConventions.ATTR_TELEMETRY_SDK_LANGUAGE]: semanticConventions.TELEMETRY_SDK_LANGUAGE_VALUE_NODEJS,
610
+ [semanticConventions.ATTR_TELEMETRY_SDK_NAME]: SDK_NAME,
611
+ [ATTR_TELEMETRY_EXPORTER_MODE]: "unified"
612
+ }
613
+ })
614
+ )
615
+ );
605
616
  }).pipe(effect.Layer.unwrapEffect);
606
617
  function annotateUser(userId, email, username) {
607
618
  const attributes = {