@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.
@@ -13,9 +13,9 @@ var zod = require('zod');
13
13
  var exporterTraceOtlpHttp = require('@opentelemetry/exporter-trace-otlp-http');
14
14
  var promises = require('fs/promises');
15
15
  var path = require('path');
16
- var platformNode = require('@effect/platform-node');
17
- var platform = require('@effect/platform');
16
+ var module$1 = require('module');
18
17
 
18
+ var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
19
19
  function _interopNamespace(e) {
20
20
  if (e && e.__esModule) return e;
21
21
  var n = Object.create(null);
@@ -107,11 +107,50 @@ var InstrumentationConfigSchema = zod.z.object({
107
107
  ignore_patterns: zod.z.array(PatternConfigSchema)
108
108
  }),
109
109
  effect: zod.z.object({
110
+ // Enable/disable Effect tracing entirely
111
+ // When false, EffectInstrumentationLive returns Layer.empty
112
+ enabled: zod.z.boolean().default(true),
113
+ // Exporter mode:
114
+ // - "unified": Use global TracerProvider from Node SDK (recommended, enables filtering)
115
+ // - "standalone": Use Effect's own OTLP exporter (bypasses Node SDK filtering)
116
+ exporter: zod.z.enum(["unified", "standalone"]).default("unified"),
110
117
  auto_extract_metadata: zod.z.boolean(),
111
118
  auto_isolation: AutoIsolationConfigSchema.optional()
112
119
  }).optional(),
113
120
  http: HttpFilteringConfigSchema.optional()
114
121
  });
122
+ var defaultConfig = {
123
+ version: "1.0",
124
+ instrumentation: {
125
+ enabled: true,
126
+ logging: "on",
127
+ description: "Default instrumentation configuration",
128
+ instrument_patterns: [
129
+ { pattern: "^app\\.", enabled: true, description: "Application operations" },
130
+ { pattern: "^http\\.server\\.", enabled: true, description: "HTTP server operations" },
131
+ { pattern: "^http\\.client\\.", enabled: true, description: "HTTP client operations" }
132
+ ],
133
+ ignore_patterns: [
134
+ { pattern: "^test\\.", description: "Test utilities" },
135
+ { pattern: "^internal\\.", description: "Internal operations" },
136
+ { pattern: "^health\\.", description: "Health checks" }
137
+ ]
138
+ },
139
+ effect: {
140
+ enabled: true,
141
+ exporter: "unified",
142
+ auto_extract_metadata: true
143
+ }
144
+ };
145
+ function parseAndValidateConfig(content) {
146
+ let parsed;
147
+ if (typeof content === "string") {
148
+ parsed = yaml.parse(content);
149
+ } else {
150
+ parsed = content;
151
+ }
152
+ return InstrumentationConfigSchema.parse(parsed);
153
+ }
115
154
  (class extends effect.Data.TaggedError("ConfigError") {
116
155
  get message() {
117
156
  return this.reason;
@@ -289,7 +328,7 @@ var makeConfigLoader = effect.Effect.gen(function* () {
289
328
  })
290
329
  });
291
330
  });
292
- var ConfigLoaderLive = effect.Layer.effect(ConfigLoader, makeConfigLoader);
331
+ effect.Layer.effect(ConfigLoader, makeConfigLoader);
293
332
  var PatternMatcher = class {
294
333
  constructor(config) {
295
334
  __publicField2(this, "ignorePatterns", []);
@@ -453,8 +492,14 @@ var PatternSpanProcessor = class {
453
492
  constructor(config, wrappedProcessor) {
454
493
  __publicField(this, "matcher");
455
494
  __publicField(this, "wrappedProcessor");
495
+ __publicField(this, "httpIgnorePatterns", []);
456
496
  this.matcher = new PatternMatcher(config);
457
497
  this.wrappedProcessor = wrappedProcessor;
498
+ if (config.http?.ignore_incoming_paths) {
499
+ this.httpIgnorePatterns = config.http.ignore_incoming_paths.map(
500
+ (pattern) => new RegExp(pattern)
501
+ );
502
+ }
458
503
  }
459
504
  /**
460
505
  * Called when a span is started
@@ -472,12 +517,40 @@ var PatternSpanProcessor = class {
472
517
  * Called when a span is ended
473
518
  *
474
519
  * This is where we make the final decision on whether to export the span.
520
+ * We check both span name patterns and HTTP path patterns.
475
521
  */
476
522
  onEnd(span) {
477
523
  const spanName = span.name;
478
- if (this.matcher.shouldInstrument(spanName)) {
479
- this.wrappedProcessor.onEnd(span);
524
+ if (!this.matcher.shouldInstrument(spanName)) {
525
+ return;
526
+ }
527
+ if (this.shouldIgnoreHttpSpan(span)) {
528
+ return;
480
529
  }
530
+ this.wrappedProcessor.onEnd(span);
531
+ }
532
+ /**
533
+ * Check if span should be ignored based on HTTP path attributes
534
+ *
535
+ * This checks the span's url.path, http.route, or http.target attributes
536
+ * against the configured http.ignore_incoming_paths patterns.
537
+ *
538
+ * This enables filtering of Effect HTTP spans (and any other HTTP spans)
539
+ * based on path patterns, which is essential for filtering out OTLP
540
+ * endpoint requests like /v1/traces, /v1/logs, /v1/metrics.
541
+ */
542
+ shouldIgnoreHttpSpan(span) {
543
+ if (this.httpIgnorePatterns.length === 0) {
544
+ return false;
545
+ }
546
+ const urlPath = span.attributes["url.path"];
547
+ const httpRoute = span.attributes["http.route"];
548
+ const httpTarget = span.attributes["http.target"];
549
+ const pathToCheck = urlPath || httpRoute || httpTarget;
550
+ if (!pathToCheck) {
551
+ return false;
552
+ }
553
+ return this.httpIgnorePatterns.some((pattern) => pattern.test(pathToCheck));
481
554
  }
482
555
  /**
483
556
  * Shutdown the processor
@@ -715,83 +788,55 @@ async function getServiceNameAsync() {
715
788
  async function getServiceVersionAsync() {
716
789
  return effect.Effect.runPromise(getServiceVersion);
717
790
  }
718
- var NodeConfigLoaderLive = ConfigLoaderLive.pipe(
719
- effect.Layer.provide(effect.Layer.mergeAll(platformNode.NodeContext.layer, platform.FetchHttpClient.layer))
720
- );
721
- var cachedLoaderPromise = null;
722
- function getCachedLoader() {
723
- if (!cachedLoaderPromise) {
724
- cachedLoaderPromise = effect.Effect.runPromise(
725
- effect.Effect.gen(function* () {
726
- return yield* ConfigLoader;
727
- }).pipe(effect.Effect.provide(NodeConfigLoaderLive))
728
- );
729
- }
730
- return cachedLoaderPromise;
791
+ async function loadFromFile(filePath) {
792
+ const { readFile: readFile2 } = await import('fs/promises');
793
+ const content = await readFile2(filePath, "utf-8");
794
+ return parseAndValidateConfig(content);
731
795
  }
732
- function _resetConfigLoaderCache() {
733
- cachedLoaderPromise = null;
796
+ async function loadFromUrl(url) {
797
+ const response = await fetch(url);
798
+ if (!response.ok) {
799
+ throw new Error(`Failed to fetch config from ${url}: ${response.statusText}`);
800
+ }
801
+ const content = await response.text();
802
+ return parseAndValidateConfig(content);
734
803
  }
735
- async function loadConfig(uri, options) {
736
- if (options?.cacheTimeout === 0) {
737
- const program = effect.Effect.gen(function* () {
738
- const loader2 = yield* ConfigLoader;
739
- return yield* loader2.loadFromUri(uri);
740
- });
741
- return effect.Effect.runPromise(program.pipe(effect.Effect.provide(NodeConfigLoaderLive)));
804
+ async function loadConfig(uri, _options) {
805
+ if (uri.startsWith("http://") || uri.startsWith("https://")) {
806
+ return loadFromUrl(uri);
807
+ }
808
+ if (uri.startsWith("file://")) {
809
+ const filePath = uri.slice(7);
810
+ return loadFromFile(filePath);
742
811
  }
743
- const loader = await getCachedLoader();
744
- return effect.Effect.runPromise(loader.loadFromUri(uri));
812
+ return loadFromFile(uri);
745
813
  }
746
814
  async function loadConfigFromInline(content) {
747
- const loader = await getCachedLoader();
748
- return effect.Effect.runPromise(loader.loadFromInline(content));
815
+ return parseAndValidateConfig(content);
749
816
  }
750
- function getDefaultConfig() {
751
- return {
752
- version: "1.0",
753
- instrumentation: {
754
- enabled: true,
755
- logging: "on",
756
- description: "Default instrumentation configuration",
757
- instrument_patterns: [
758
- { pattern: "^app\\.", enabled: true, description: "Application operations" },
759
- { pattern: "^http\\.server\\.", enabled: true, description: "HTTP server operations" },
760
- { pattern: "^http\\.client\\.", enabled: true, description: "HTTP client operations" }
761
- ],
762
- ignore_patterns: [
763
- { pattern: "^test\\.", description: "Test utilities" },
764
- { pattern: "^internal\\.", description: "Internal operations" },
765
- { pattern: "^health\\.", description: "Health checks" }
766
- ]
767
- },
768
- effect: {
769
- auto_extract_metadata: true
770
- }
771
- };
817
+ function _resetConfigLoaderCache() {
772
818
  }
773
819
  async function loadConfigWithOptions(options = {}) {
774
- const loadOptions = options.cacheTimeout !== void 0 ? { cacheTimeout: options.cacheTimeout } : void 0;
775
820
  if (options.config) {
776
821
  return loadConfigFromInline(options.config);
777
822
  }
778
823
  const envConfigPath = process.env.ATRIM_INSTRUMENTATION_CONFIG;
779
824
  if (envConfigPath) {
780
- return loadConfig(envConfigPath, loadOptions);
825
+ return loadConfig(envConfigPath);
781
826
  }
782
827
  if (options.configUrl) {
783
- return loadConfig(options.configUrl, loadOptions);
828
+ return loadConfig(options.configUrl);
784
829
  }
785
830
  if (options.configPath) {
786
- return loadConfig(options.configPath, loadOptions);
831
+ return loadConfig(options.configPath);
787
832
  }
788
833
  const { existsSync } = await import('fs');
789
834
  const { join: join2 } = await import('path');
790
835
  const defaultPath = join2(process.cwd(), "instrumentation.yaml");
791
836
  if (existsSync(defaultPath)) {
792
- return loadConfig(defaultPath, loadOptions);
837
+ return loadConfig(defaultPath);
793
838
  }
794
- return getDefaultConfig();
839
+ return defaultConfig;
795
840
  }
796
841
 
797
842
  // src/core/sdk-initializer.ts
@@ -1083,9 +1128,39 @@ function logInitialization(config, serviceName, serviceVersion, options, autoIns
1083
1128
  logger.log(` - OTLP endpoint: ${endpoint}`);
1084
1129
  logger.log("");
1085
1130
  }
1131
+ var require2 = module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
1132
+ function validateOpenTelemetryApi() {
1133
+ try {
1134
+ require2.resolve("@opentelemetry/api");
1135
+ } catch {
1136
+ throw new Error(
1137
+ "@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"
1138
+ );
1139
+ }
1140
+ }
1141
+ function validateEffectDependencies() {
1142
+ const packages = ["effect", "@effect/opentelemetry", "@effect/platform"];
1143
+ for (const pkg of packages) {
1144
+ try {
1145
+ require2.resolve(pkg);
1146
+ } catch {
1147
+ return false;
1148
+ }
1149
+ }
1150
+ return true;
1151
+ }
1152
+ var validateDependencies = effect.Effect.try({
1153
+ try: () => validateOpenTelemetryApi(),
1154
+ catch: (error) => new InitializationError2({
1155
+ reason: error instanceof Error ? error.message : "Dependency validation failed",
1156
+ cause: error
1157
+ })
1158
+ });
1159
+ effect.Effect.sync(() => validateEffectDependencies());
1086
1160
 
1087
1161
  // src/api.ts
1088
1162
  async function initializeInstrumentation(options = {}) {
1163
+ validateOpenTelemetryApi();
1089
1164
  const sdk = await initializeSdk(options);
1090
1165
  if (sdk) {
1091
1166
  const config = await loadConfigWithOptions(options);
@@ -1102,6 +1177,7 @@ async function initializePatternMatchingOnly(options = {}) {
1102
1177
  );
1103
1178
  }
1104
1179
  var initializeInstrumentationEffect = (options = {}) => effect.Effect.gen(function* () {
1180
+ yield* validateDependencies;
1105
1181
  const sdk = yield* effect.Effect.tryPromise({
1106
1182
  try: () => initializeSdk(options),
1107
1183
  catch: (error) => new InitializationError2({