@atrim/instrument-node 0.5.2-dev.ac2fbfe.20251221205322 → 0.7.0-b9eaf74-20260108193056

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.
@@ -46,35 +46,33 @@ declare function createOtlpExporter(options?: OtlpExporterOptions): OTLPTraceExp
46
46
  declare function getOtlpEndpoint(options?: OtlpExporterOptions): string;
47
47
 
48
48
  /**
49
- * Node.js configuration loader using Effect Platform
49
+ * Node.js configuration loader
50
50
  *
51
- * Provides FileSystem and HttpClient layers for the core ConfigLoader service
51
+ * Provides configuration loading using native Node.js APIs (fs, fetch)
52
+ * This module doesn't require Effect Platform, making it work without Effect installed.
52
53
  */
53
54
 
54
55
  /**
55
- * Reset the cached loader (for testing purposes)
56
- * @internal
57
- */
58
- declare function _resetConfigLoaderCache(): void;
59
- /**
60
- * Load configuration from URI (Promise-based convenience API)
61
- *
62
- * Automatically provides Node.js platform layers (FileSystem + HttpClient)
56
+ * Load configuration from URI (file path or URL)
63
57
  *
64
58
  * @param uri - Configuration URI (file://, http://, https://, or relative path)
65
- * @param options - Optional loading options (e.g., to disable caching)
66
59
  * @returns Promise that resolves to validated configuration
67
60
  */
68
- declare function loadConfig(uri: string, options?: {
61
+ declare function loadConfig(uri: string, _options?: {
69
62
  cacheTimeout?: number;
70
63
  }): Promise<InstrumentationConfig>;
71
64
  /**
72
- * Load configuration from inline content (Promise-based convenience API)
65
+ * Load configuration from inline content
73
66
  *
74
67
  * @param content - YAML string, JSON string, or plain object
75
68
  * @returns Promise that resolves to validated configuration
76
69
  */
77
70
  declare function loadConfigFromInline(content: string | unknown): Promise<InstrumentationConfig>;
71
+ /**
72
+ * Reset the config loader cache (no-op for native implementation)
73
+ * @internal
74
+ */
75
+ declare function _resetConfigLoaderCache(): void;
78
76
  /**
79
77
  * Legacy options interface for backward compatibility
80
78
  */
@@ -548,6 +546,10 @@ declare function getServiceVersionAsync(): Promise<string | undefined>;
548
546
  * This processor filters spans based on configured patterns before they are exported.
549
547
  * It wraps another processor (typically BatchSpanProcessor) and only forwards spans
550
548
  * that match the instrumentation patterns.
549
+ *
550
+ * Filtering is applied at two levels:
551
+ * 1. Span name patterns (instrument_patterns / ignore_patterns)
552
+ * 2. HTTP path patterns (http.ignore_incoming_paths) - for HTTP spans
551
553
  */
552
554
 
553
555
  /**
@@ -570,6 +572,7 @@ declare function getServiceVersionAsync(): Promise<string | undefined>;
570
572
  declare class PatternSpanProcessor implements SpanProcessor {
571
573
  private matcher;
572
574
  private wrappedProcessor;
575
+ private httpIgnorePatterns;
573
576
  constructor(config: InstrumentationConfig, wrappedProcessor: SpanProcessor);
574
577
  /**
575
578
  * Called when a span is started
@@ -582,8 +585,20 @@ declare class PatternSpanProcessor implements SpanProcessor {
582
585
  * Called when a span is ended
583
586
  *
584
587
  * This is where we make the final decision on whether to export the span.
588
+ * We check both span name patterns and HTTP path patterns.
585
589
  */
586
590
  onEnd(span: ReadableSpan): void;
591
+ /**
592
+ * Check if span should be ignored based on HTTP path attributes
593
+ *
594
+ * This checks the span's url.path, http.route, or http.target attributes
595
+ * against the configured http.ignore_incoming_paths patterns.
596
+ *
597
+ * This enables filtering of Effect HTTP spans (and any other HTTP spans)
598
+ * based on path patterns, which is essential for filtering out OTLP
599
+ * endpoint requests like /v1/traces, /v1/logs, /v1/metrics.
600
+ */
601
+ private shouldIgnoreHttpSpan;
587
602
  /**
588
603
  * Shutdown the processor
589
604
  */
@@ -46,35 +46,33 @@ declare function createOtlpExporter(options?: OtlpExporterOptions): OTLPTraceExp
46
46
  declare function getOtlpEndpoint(options?: OtlpExporterOptions): string;
47
47
 
48
48
  /**
49
- * Node.js configuration loader using Effect Platform
49
+ * Node.js configuration loader
50
50
  *
51
- * Provides FileSystem and HttpClient layers for the core ConfigLoader service
51
+ * Provides configuration loading using native Node.js APIs (fs, fetch)
52
+ * This module doesn't require Effect Platform, making it work without Effect installed.
52
53
  */
53
54
 
54
55
  /**
55
- * Reset the cached loader (for testing purposes)
56
- * @internal
57
- */
58
- declare function _resetConfigLoaderCache(): void;
59
- /**
60
- * Load configuration from URI (Promise-based convenience API)
61
- *
62
- * Automatically provides Node.js platform layers (FileSystem + HttpClient)
56
+ * Load configuration from URI (file path or URL)
63
57
  *
64
58
  * @param uri - Configuration URI (file://, http://, https://, or relative path)
65
- * @param options - Optional loading options (e.g., to disable caching)
66
59
  * @returns Promise that resolves to validated configuration
67
60
  */
68
- declare function loadConfig(uri: string, options?: {
61
+ declare function loadConfig(uri: string, _options?: {
69
62
  cacheTimeout?: number;
70
63
  }): Promise<InstrumentationConfig>;
71
64
  /**
72
- * Load configuration from inline content (Promise-based convenience API)
65
+ * Load configuration from inline content
73
66
  *
74
67
  * @param content - YAML string, JSON string, or plain object
75
68
  * @returns Promise that resolves to validated configuration
76
69
  */
77
70
  declare function loadConfigFromInline(content: string | unknown): Promise<InstrumentationConfig>;
71
+ /**
72
+ * Reset the config loader cache (no-op for native implementation)
73
+ * @internal
74
+ */
75
+ declare function _resetConfigLoaderCache(): void;
78
76
  /**
79
77
  * Legacy options interface for backward compatibility
80
78
  */
@@ -548,6 +546,10 @@ declare function getServiceVersionAsync(): Promise<string | undefined>;
548
546
  * This processor filters spans based on configured patterns before they are exported.
549
547
  * It wraps another processor (typically BatchSpanProcessor) and only forwards spans
550
548
  * that match the instrumentation patterns.
549
+ *
550
+ * Filtering is applied at two levels:
551
+ * 1. Span name patterns (instrument_patterns / ignore_patterns)
552
+ * 2. HTTP path patterns (http.ignore_incoming_paths) - for HTTP spans
551
553
  */
552
554
 
553
555
  /**
@@ -570,6 +572,7 @@ declare function getServiceVersionAsync(): Promise<string | undefined>;
570
572
  declare class PatternSpanProcessor implements SpanProcessor {
571
573
  private matcher;
572
574
  private wrappedProcessor;
575
+ private httpIgnorePatterns;
573
576
  constructor(config: InstrumentationConfig, wrappedProcessor: SpanProcessor);
574
577
  /**
575
578
  * Called when a span is started
@@ -582,8 +585,20 @@ declare class PatternSpanProcessor implements SpanProcessor {
582
585
  * Called when a span is ended
583
586
  *
584
587
  * This is where we make the final decision on whether to export the span.
588
+ * We check both span name patterns and HTTP path patterns.
585
589
  */
586
590
  onEnd(span: ReadableSpan): void;
591
+ /**
592
+ * Check if span should be ignored based on HTTP path attributes
593
+ *
594
+ * This checks the span's url.path, http.route, or http.target attributes
595
+ * against the configured http.ignore_incoming_paths patterns.
596
+ *
597
+ * This enables filtering of Effect HTTP spans (and any other HTTP spans)
598
+ * based on path patterns, which is essential for filtering out OTLP
599
+ * endpoint requests like /v1/traces, /v1/logs, /v1/metrics.
600
+ */
601
+ private shouldIgnoreHttpSpan;
587
602
  /**
588
603
  * Shutdown the processor
589
604
  */
@@ -11,8 +11,7 @@ import { z } from 'zod';
11
11
  import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
12
12
  import { readFile } from 'fs/promises';
13
13
  import { join } from 'path';
14
- import { NodeContext } from '@effect/platform-node';
15
- import { FetchHttpClient } from '@effect/platform';
14
+ import { createRequire } from 'module';
16
15
 
17
16
  var __defProp = Object.defineProperty;
18
17
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
@@ -53,6 +52,69 @@ var AutoIsolationConfigSchema = z.object({
53
52
  add_metadata: z.boolean().default(true)
54
53
  }).default({})
55
54
  });
55
+ var SpanNamingRuleSchema = z.object({
56
+ // Match criteria (all specified criteria must match)
57
+ match: z.object({
58
+ // Regex pattern to match file path
59
+ file: z.string().optional(),
60
+ // Regex pattern to match function name
61
+ function: z.string().optional(),
62
+ // Regex pattern to match module name
63
+ module: z.string().optional()
64
+ }),
65
+ // Span name template with variables:
66
+ // {fiber_id} - Fiber ID
67
+ // {function} - Function name
68
+ // {module} - Module name
69
+ // {file} - File path
70
+ // {line} - Line number
71
+ // {operator} - Effect operator (gen, all, forEach, etc.)
72
+ // {match:field:N} - Captured regex group from match
73
+ name: z.string()
74
+ });
75
+ var AutoInstrumentationConfigSchema = z.object({
76
+ // Enable/disable auto-instrumentation
77
+ enabled: z.boolean().default(false),
78
+ // Tracing granularity
79
+ // - 'fiber': Trace at fiber creation (recommended, lower overhead)
80
+ // - 'operator': Trace each Effect operator (higher granularity, more overhead)
81
+ granularity: z.enum(["fiber", "operator"]).default("fiber"),
82
+ // Smart span naming configuration
83
+ span_naming: z.object({
84
+ // Default span name template when no rules match
85
+ default: z.string().default("effect.fiber.{fiber_id}"),
86
+ // Infer span names from source code (requires stack trace parsing)
87
+ // Adds ~50-100μs overhead per fiber
88
+ infer_from_source: z.boolean().default(true),
89
+ // Naming rules (first match wins)
90
+ rules: z.array(SpanNamingRuleSchema).default([])
91
+ }).default({}),
92
+ // Pattern-based filtering
93
+ filter: z.object({
94
+ // Only trace spans matching these patterns (empty = trace all)
95
+ include: z.array(z.string()).default([]),
96
+ // Never trace spans matching these patterns
97
+ exclude: z.array(z.string()).default([])
98
+ }).default({}),
99
+ // Performance controls
100
+ performance: z.object({
101
+ // Sample rate (0.0 - 1.0)
102
+ sampling_rate: z.number().min(0).max(1).default(1),
103
+ // Skip fibers shorter than this duration (e.g., "10ms", "100 millis")
104
+ min_duration: z.string().default("0ms"),
105
+ // Maximum concurrent traced fibers (0 = unlimited)
106
+ max_concurrent: z.number().default(0)
107
+ }).default({}),
108
+ // Automatic metadata extraction
109
+ metadata: z.object({
110
+ // Extract Effect fiber information
111
+ fiber_info: z.boolean().default(true),
112
+ // Extract source location (file:line)
113
+ source_location: z.boolean().default(true),
114
+ // Extract parent fiber information
115
+ parent_fiber: z.boolean().default(true)
116
+ }).default({})
117
+ });
56
118
  var HttpFilteringConfigSchema = z.object({
57
119
  // Patterns to ignore for outgoing HTTP requests (string patterns only in YAML)
58
120
  ignore_outgoing_urls: z.array(z.string()).optional(),
@@ -84,11 +146,52 @@ var InstrumentationConfigSchema = z.object({
84
146
  ignore_patterns: z.array(PatternConfigSchema)
85
147
  }),
86
148
  effect: z.object({
149
+ // Enable/disable Effect tracing entirely
150
+ // When false, EffectInstrumentationLive returns Layer.empty
151
+ enabled: z.boolean().default(true),
152
+ // Exporter mode:
153
+ // - "unified": Use global TracerProvider from Node SDK (recommended, enables filtering)
154
+ // - "standalone": Use Effect's own OTLP exporter (bypasses Node SDK filtering)
155
+ exporter: z.enum(["unified", "standalone"]).default("unified"),
87
156
  auto_extract_metadata: z.boolean(),
88
- auto_isolation: AutoIsolationConfigSchema.optional()
157
+ auto_isolation: AutoIsolationConfigSchema.optional(),
158
+ // Auto-instrumentation: automatic tracing of all Effect fibers
159
+ auto_instrumentation: AutoInstrumentationConfigSchema.optional()
89
160
  }).optional(),
90
161
  http: HttpFilteringConfigSchema.optional()
91
162
  });
163
+ var defaultConfig = {
164
+ version: "1.0",
165
+ instrumentation: {
166
+ enabled: true,
167
+ logging: "on",
168
+ description: "Default instrumentation configuration",
169
+ instrument_patterns: [
170
+ { pattern: "^app\\.", enabled: true, description: "Application operations" },
171
+ { pattern: "^http\\.server\\.", enabled: true, description: "HTTP server operations" },
172
+ { pattern: "^http\\.client\\.", enabled: true, description: "HTTP client operations" }
173
+ ],
174
+ ignore_patterns: [
175
+ { pattern: "^test\\.", description: "Test utilities" },
176
+ { pattern: "^internal\\.", description: "Internal operations" },
177
+ { pattern: "^health\\.", description: "Health checks" }
178
+ ]
179
+ },
180
+ effect: {
181
+ enabled: true,
182
+ exporter: "unified",
183
+ auto_extract_metadata: true
184
+ }
185
+ };
186
+ function parseAndValidateConfig(content) {
187
+ let parsed;
188
+ if (typeof content === "string") {
189
+ parsed = parse(content);
190
+ } else {
191
+ parsed = content;
192
+ }
193
+ return InstrumentationConfigSchema.parse(parsed);
194
+ }
92
195
  (class extends Data.TaggedError("ConfigError") {
93
196
  get message() {
94
197
  return this.reason;
@@ -266,7 +369,7 @@ var makeConfigLoader = Effect.gen(function* () {
266
369
  })
267
370
  });
268
371
  });
269
- var ConfigLoaderLive = Layer.effect(ConfigLoader, makeConfigLoader);
372
+ Layer.effect(ConfigLoader, makeConfigLoader);
270
373
  var PatternMatcher = class {
271
374
  constructor(config) {
272
375
  __publicField2(this, "ignorePatterns", []);
@@ -430,8 +533,14 @@ var PatternSpanProcessor = class {
430
533
  constructor(config, wrappedProcessor) {
431
534
  __publicField(this, "matcher");
432
535
  __publicField(this, "wrappedProcessor");
536
+ __publicField(this, "httpIgnorePatterns", []);
433
537
  this.matcher = new PatternMatcher(config);
434
538
  this.wrappedProcessor = wrappedProcessor;
539
+ if (config.http?.ignore_incoming_paths) {
540
+ this.httpIgnorePatterns = config.http.ignore_incoming_paths.map(
541
+ (pattern) => new RegExp(pattern)
542
+ );
543
+ }
435
544
  }
436
545
  /**
437
546
  * Called when a span is started
@@ -449,12 +558,40 @@ var PatternSpanProcessor = class {
449
558
  * Called when a span is ended
450
559
  *
451
560
  * This is where we make the final decision on whether to export the span.
561
+ * We check both span name patterns and HTTP path patterns.
452
562
  */
453
563
  onEnd(span) {
454
564
  const spanName = span.name;
455
- if (this.matcher.shouldInstrument(spanName)) {
456
- this.wrappedProcessor.onEnd(span);
565
+ if (!this.matcher.shouldInstrument(spanName)) {
566
+ return;
567
+ }
568
+ if (this.shouldIgnoreHttpSpan(span)) {
569
+ return;
570
+ }
571
+ this.wrappedProcessor.onEnd(span);
572
+ }
573
+ /**
574
+ * Check if span should be ignored based on HTTP path attributes
575
+ *
576
+ * This checks the span's url.path, http.route, or http.target attributes
577
+ * against the configured http.ignore_incoming_paths patterns.
578
+ *
579
+ * This enables filtering of Effect HTTP spans (and any other HTTP spans)
580
+ * based on path patterns, which is essential for filtering out OTLP
581
+ * endpoint requests like /v1/traces, /v1/logs, /v1/metrics.
582
+ */
583
+ shouldIgnoreHttpSpan(span) {
584
+ if (this.httpIgnorePatterns.length === 0) {
585
+ return false;
457
586
  }
587
+ const urlPath = span.attributes["url.path"];
588
+ const httpRoute = span.attributes["http.route"];
589
+ const httpTarget = span.attributes["http.target"];
590
+ const pathToCheck = urlPath || httpRoute || httpTarget;
591
+ if (!pathToCheck) {
592
+ return false;
593
+ }
594
+ return this.httpIgnorePatterns.some((pattern) => pattern.test(pathToCheck));
458
595
  }
459
596
  /**
460
597
  * Shutdown the processor
@@ -692,83 +829,55 @@ async function getServiceNameAsync() {
692
829
  async function getServiceVersionAsync() {
693
830
  return Effect.runPromise(getServiceVersion);
694
831
  }
695
- var NodeConfigLoaderLive = ConfigLoaderLive.pipe(
696
- Layer.provide(Layer.mergeAll(NodeContext.layer, FetchHttpClient.layer))
697
- );
698
- var cachedLoaderPromise = null;
699
- function getCachedLoader() {
700
- if (!cachedLoaderPromise) {
701
- cachedLoaderPromise = Effect.runPromise(
702
- Effect.gen(function* () {
703
- return yield* ConfigLoader;
704
- }).pipe(Effect.provide(NodeConfigLoaderLive))
705
- );
706
- }
707
- return cachedLoaderPromise;
832
+ async function loadFromFile(filePath) {
833
+ const { readFile: readFile2 } = await import('fs/promises');
834
+ const content = await readFile2(filePath, "utf-8");
835
+ return parseAndValidateConfig(content);
708
836
  }
709
- function _resetConfigLoaderCache() {
710
- cachedLoaderPromise = null;
837
+ async function loadFromUrl(url) {
838
+ const response = await fetch(url);
839
+ if (!response.ok) {
840
+ throw new Error(`Failed to fetch config from ${url}: ${response.statusText}`);
841
+ }
842
+ const content = await response.text();
843
+ return parseAndValidateConfig(content);
711
844
  }
712
- async function loadConfig(uri, options) {
713
- if (options?.cacheTimeout === 0) {
714
- const program = Effect.gen(function* () {
715
- const loader2 = yield* ConfigLoader;
716
- return yield* loader2.loadFromUri(uri);
717
- });
718
- return Effect.runPromise(program.pipe(Effect.provide(NodeConfigLoaderLive)));
845
+ async function loadConfig(uri, _options) {
846
+ if (uri.startsWith("http://") || uri.startsWith("https://")) {
847
+ return loadFromUrl(uri);
848
+ }
849
+ if (uri.startsWith("file://")) {
850
+ const filePath = uri.slice(7);
851
+ return loadFromFile(filePath);
719
852
  }
720
- const loader = await getCachedLoader();
721
- return Effect.runPromise(loader.loadFromUri(uri));
853
+ return loadFromFile(uri);
722
854
  }
723
855
  async function loadConfigFromInline(content) {
724
- const loader = await getCachedLoader();
725
- return Effect.runPromise(loader.loadFromInline(content));
856
+ return parseAndValidateConfig(content);
726
857
  }
727
- function getDefaultConfig() {
728
- return {
729
- version: "1.0",
730
- instrumentation: {
731
- enabled: true,
732
- logging: "on",
733
- description: "Default instrumentation configuration",
734
- instrument_patterns: [
735
- { pattern: "^app\\.", enabled: true, description: "Application operations" },
736
- { pattern: "^http\\.server\\.", enabled: true, description: "HTTP server operations" },
737
- { pattern: "^http\\.client\\.", enabled: true, description: "HTTP client operations" }
738
- ],
739
- ignore_patterns: [
740
- { pattern: "^test\\.", description: "Test utilities" },
741
- { pattern: "^internal\\.", description: "Internal operations" },
742
- { pattern: "^health\\.", description: "Health checks" }
743
- ]
744
- },
745
- effect: {
746
- auto_extract_metadata: true
747
- }
748
- };
858
+ function _resetConfigLoaderCache() {
749
859
  }
750
860
  async function loadConfigWithOptions(options = {}) {
751
- const loadOptions = options.cacheTimeout !== void 0 ? { cacheTimeout: options.cacheTimeout } : void 0;
752
861
  if (options.config) {
753
862
  return loadConfigFromInline(options.config);
754
863
  }
755
864
  const envConfigPath = process.env.ATRIM_INSTRUMENTATION_CONFIG;
756
865
  if (envConfigPath) {
757
- return loadConfig(envConfigPath, loadOptions);
866
+ return loadConfig(envConfigPath);
758
867
  }
759
868
  if (options.configUrl) {
760
- return loadConfig(options.configUrl, loadOptions);
869
+ return loadConfig(options.configUrl);
761
870
  }
762
871
  if (options.configPath) {
763
- return loadConfig(options.configPath, loadOptions);
872
+ return loadConfig(options.configPath);
764
873
  }
765
874
  const { existsSync } = await import('fs');
766
875
  const { join: join2 } = await import('path');
767
876
  const defaultPath = join2(process.cwd(), "instrumentation.yaml");
768
877
  if (existsSync(defaultPath)) {
769
- return loadConfig(defaultPath, loadOptions);
878
+ return loadConfig(defaultPath);
770
879
  }
771
- return getDefaultConfig();
880
+ return defaultConfig;
772
881
  }
773
882
 
774
883
  // src/core/sdk-initializer.ts
@@ -1060,9 +1169,39 @@ function logInitialization(config, serviceName, serviceVersion, options, autoIns
1060
1169
  logger.log(` - OTLP endpoint: ${endpoint}`);
1061
1170
  logger.log("");
1062
1171
  }
1172
+ var require2 = createRequire(import.meta.url);
1173
+ function validateOpenTelemetryApi() {
1174
+ try {
1175
+ require2.resolve("@opentelemetry/api");
1176
+ } catch {
1177
+ throw new Error(
1178
+ "@atrim/instrument-node requires @opentelemetry/api as a peer dependency.\n\nInstall it with:\n npm install @opentelemetry/api\n\nOr with your preferred package manager:\n pnpm add @opentelemetry/api\n yarn add @opentelemetry/api\n bun add @opentelemetry/api"
1179
+ );
1180
+ }
1181
+ }
1182
+ function validateEffectDependencies() {
1183
+ const packages = ["effect", "@effect/opentelemetry", "@effect/platform"];
1184
+ for (const pkg of packages) {
1185
+ try {
1186
+ require2.resolve(pkg);
1187
+ } catch {
1188
+ return false;
1189
+ }
1190
+ }
1191
+ return true;
1192
+ }
1193
+ var validateDependencies = Effect.try({
1194
+ try: () => validateOpenTelemetryApi(),
1195
+ catch: (error) => new InitializationError2({
1196
+ reason: error instanceof Error ? error.message : "Dependency validation failed",
1197
+ cause: error
1198
+ })
1199
+ });
1200
+ Effect.sync(() => validateEffectDependencies());
1063
1201
 
1064
1202
  // src/api.ts
1065
1203
  async function initializeInstrumentation(options = {}) {
1204
+ validateOpenTelemetryApi();
1066
1205
  const sdk = await initializeSdk(options);
1067
1206
  if (sdk) {
1068
1207
  const config = await loadConfigWithOptions(options);
@@ -1079,6 +1218,7 @@ async function initializePatternMatchingOnly(options = {}) {
1079
1218
  );
1080
1219
  }
1081
1220
  var initializeInstrumentationEffect = (options = {}) => Effect.gen(function* () {
1221
+ yield* validateDependencies;
1082
1222
  const sdk = yield* Effect.tryPromise({
1083
1223
  try: () => initializeSdk(options),
1084
1224
  catch: (error) => new InitializationError2({