@atrim/instrument-node 0.4.0-c3ef89c-20251118193817 → 0.4.1

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,10 +1,13 @@
1
- import { Data, Effect, Layer } from 'effect';
1
+ import { Data, Context, Effect, Layer } from 'effect';
2
2
  import { NodeSDK } from '@opentelemetry/sdk-node';
3
3
  import { SimpleSpanProcessor, BatchSpanProcessor } from '@opentelemetry/sdk-trace-base';
4
4
  import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
5
5
  import { trace } from '@opentelemetry/api';
6
- import { ConfigLoaderLive, PatternMatcher, ConfigLoader, initializePatternMatcher, logger } from '@atrim/instrument-core';
7
- export { PatternMatcher, getPatternMatcher, shouldInstrumentSpan } from '@atrim/instrument-core';
6
+ import { FileSystem } from '@effect/platform/FileSystem';
7
+ import * as HttpClient from '@effect/platform/HttpClient';
8
+ import * as HttpClientRequest from '@effect/platform/HttpClientRequest';
9
+ import { parse } from 'yaml';
10
+ import { z } from 'zod';
8
11
  import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
9
12
  import { readFile } from 'fs/promises';
10
13
  import { join } from 'path';
@@ -20,6 +23,396 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
20
23
  throw Error('Dynamic require of "' + x + '" is not supported');
21
24
  });
22
25
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
26
+ var __defProp2 = Object.defineProperty;
27
+ var __defNormalProp2 = (obj, key, value) => key in obj ? __defProp2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
28
+ var __publicField2 = (obj, key, value) => __defNormalProp2(obj, typeof key !== "symbol" ? key + "" : key, value);
29
+ var PatternConfigSchema = z.object({
30
+ pattern: z.string(),
31
+ enabled: z.boolean().optional(),
32
+ description: z.string().optional()
33
+ });
34
+ var AutoIsolationConfigSchema = z.object({
35
+ // Global enable/disable for auto-isolation
36
+ enabled: z.boolean().default(false),
37
+ // Which operators to auto-isolate
38
+ operators: z.object({
39
+ fiberset_run: z.boolean().default(true),
40
+ effect_fork: z.boolean().default(true),
41
+ effect_fork_daemon: z.boolean().default(true),
42
+ effect_fork_in: z.boolean().default(false)
43
+ }).default({}),
44
+ // Virtual parent tracking configuration
45
+ tracking: z.object({
46
+ use_span_links: z.boolean().default(true),
47
+ use_attributes: z.boolean().default(true),
48
+ capture_logical_parent: z.boolean().default(true)
49
+ }).default({}),
50
+ // Span categorization
51
+ attributes: z.object({
52
+ category: z.string().default("background_task"),
53
+ add_metadata: z.boolean().default(true)
54
+ }).default({})
55
+ });
56
+ var HttpFilteringConfigSchema = z.object({
57
+ // Patterns to ignore for outgoing HTTP requests (string patterns only in YAML)
58
+ ignore_outgoing_urls: z.array(z.string()).optional(),
59
+ // Patterns to ignore for incoming HTTP requests (string patterns only in YAML)
60
+ ignore_incoming_paths: z.array(z.string()).optional(),
61
+ // Require parent span for outgoing requests (prevents root spans for HTTP calls)
62
+ require_parent_for_outgoing_spans: z.boolean().optional()
63
+ });
64
+ var InstrumentationConfigSchema = z.object({
65
+ version: z.string(),
66
+ instrumentation: z.object({
67
+ enabled: z.boolean(),
68
+ description: z.string().optional(),
69
+ logging: z.enum(["on", "off", "minimal"]).optional().default("on"),
70
+ instrument_patterns: z.array(PatternConfigSchema),
71
+ ignore_patterns: z.array(PatternConfigSchema)
72
+ }),
73
+ effect: z.object({
74
+ auto_extract_metadata: z.boolean(),
75
+ auto_isolation: AutoIsolationConfigSchema.optional()
76
+ }).optional(),
77
+ http: HttpFilteringConfigSchema.optional()
78
+ });
79
+ (class extends Data.TaggedError("ConfigError") {
80
+ get message() {
81
+ return this.reason;
82
+ }
83
+ });
84
+ var ConfigUrlError = class extends Data.TaggedError("ConfigUrlError") {
85
+ get message() {
86
+ return this.reason;
87
+ }
88
+ };
89
+ var ConfigValidationError = class extends Data.TaggedError("ConfigValidationError") {
90
+ get message() {
91
+ return this.reason;
92
+ }
93
+ };
94
+ var ConfigFileError = class extends Data.TaggedError("ConfigFileError") {
95
+ get message() {
96
+ return this.reason;
97
+ }
98
+ };
99
+ (class extends Data.TaggedError("ServiceDetectionError") {
100
+ get message() {
101
+ return this.reason;
102
+ }
103
+ });
104
+ (class extends Data.TaggedError("InitializationError") {
105
+ get message() {
106
+ return this.reason;
107
+ }
108
+ });
109
+ (class extends Data.TaggedError("ExportError") {
110
+ get message() {
111
+ return this.reason;
112
+ }
113
+ });
114
+ (class extends Data.TaggedError("ShutdownError") {
115
+ get message() {
116
+ return this.reason;
117
+ }
118
+ });
119
+ var SECURITY_DEFAULTS = {
120
+ maxConfigSize: 1e6,
121
+ // 1MB
122
+ requestTimeout: 5e3
123
+ // 5 seconds
124
+ };
125
+ var ConfigLoader = class extends Context.Tag("ConfigLoader")() {
126
+ };
127
+ var parseYamlContent = (content, uri) => Effect.gen(function* () {
128
+ const parsed = yield* Effect.try({
129
+ try: () => parse(content),
130
+ catch: (error) => new ConfigValidationError({
131
+ reason: uri ? `Failed to parse YAML from ${uri}` : "Failed to parse YAML",
132
+ cause: error
133
+ })
134
+ });
135
+ return yield* Effect.try({
136
+ try: () => InstrumentationConfigSchema.parse(parsed),
137
+ catch: (error) => new ConfigValidationError({
138
+ reason: uri ? `Invalid configuration schema from ${uri}` : "Invalid configuration schema",
139
+ cause: error
140
+ })
141
+ });
142
+ });
143
+ var loadFromFileWithFs = (fs, path, uri) => Effect.gen(function* () {
144
+ const content = yield* fs.readFileString(path).pipe(
145
+ Effect.mapError(
146
+ (error) => new ConfigFileError({
147
+ reason: `Failed to read config file at ${uri}`,
148
+ cause: error
149
+ })
150
+ )
151
+ );
152
+ if (content.length > SECURITY_DEFAULTS.maxConfigSize) {
153
+ return yield* Effect.fail(
154
+ new ConfigFileError({
155
+ reason: `Config file exceeds maximum size of ${SECURITY_DEFAULTS.maxConfigSize} bytes`
156
+ })
157
+ );
158
+ }
159
+ return yield* parseYamlContent(content, uri);
160
+ });
161
+ var loadFromHttpWithClient = (client, url) => Effect.scoped(
162
+ Effect.gen(function* () {
163
+ if (url.startsWith("http://")) {
164
+ return yield* Effect.fail(
165
+ new ConfigUrlError({
166
+ reason: "Insecure protocol: only HTTPS URLs are allowed"
167
+ })
168
+ );
169
+ }
170
+ const request = HttpClientRequest.get(url).pipe(
171
+ HttpClientRequest.setHeaders({
172
+ Accept: "application/yaml, text/yaml, application/x-yaml"
173
+ })
174
+ );
175
+ const response = yield* client.execute(request).pipe(
176
+ Effect.timeout(`${SECURITY_DEFAULTS.requestTimeout} millis`),
177
+ Effect.mapError((error) => {
178
+ if (error._tag === "TimeoutException") {
179
+ return new ConfigUrlError({
180
+ reason: `Config fetch timeout after ${SECURITY_DEFAULTS.requestTimeout}ms from ${url}`
181
+ });
182
+ }
183
+ return new ConfigUrlError({
184
+ reason: `Failed to load config from URL: ${url}`,
185
+ cause: error
186
+ });
187
+ })
188
+ );
189
+ if (response.status >= 400) {
190
+ return yield* Effect.fail(
191
+ new ConfigUrlError({
192
+ reason: `HTTP ${response.status} from ${url}`
193
+ })
194
+ );
195
+ }
196
+ const text = yield* response.text.pipe(
197
+ Effect.mapError(
198
+ (error) => new ConfigUrlError({
199
+ reason: `Failed to read response body from ${url}`,
200
+ cause: error
201
+ })
202
+ )
203
+ );
204
+ if (text.length > SECURITY_DEFAULTS.maxConfigSize) {
205
+ return yield* Effect.fail(
206
+ new ConfigUrlError({
207
+ reason: `Config exceeds maximum size of ${SECURITY_DEFAULTS.maxConfigSize} bytes`
208
+ })
209
+ );
210
+ }
211
+ return yield* parseYamlContent(text, url);
212
+ })
213
+ );
214
+ var makeConfigLoader = Effect.gen(function* () {
215
+ const fs = yield* Effect.serviceOption(FileSystem);
216
+ const http = yield* HttpClient.HttpClient;
217
+ const loadFromUriUncached = (uri) => Effect.gen(function* () {
218
+ if (uri.startsWith("file://")) {
219
+ const path = uri.slice(7);
220
+ if (fs._tag === "None") {
221
+ return yield* Effect.fail(
222
+ new ConfigFileError({
223
+ reason: "FileSystem not available (browser environment?)",
224
+ cause: { uri }
225
+ })
226
+ );
227
+ }
228
+ return yield* loadFromFileWithFs(fs.value, path, uri);
229
+ }
230
+ if (uri.startsWith("http://") || uri.startsWith("https://")) {
231
+ return yield* loadFromHttpWithClient(http, uri);
232
+ }
233
+ if (fs._tag === "Some") {
234
+ return yield* loadFromFileWithFs(fs.value, uri, uri);
235
+ } else {
236
+ return yield* loadFromHttpWithClient(http, uri);
237
+ }
238
+ });
239
+ const loadFromUriCached = yield* Effect.cachedFunction(loadFromUriUncached);
240
+ return ConfigLoader.of({
241
+ loadFromUri: loadFromUriCached,
242
+ loadFromInline: (content) => Effect.gen(function* () {
243
+ if (typeof content === "string") {
244
+ return yield* parseYamlContent(content);
245
+ }
246
+ return yield* Effect.try({
247
+ try: () => InstrumentationConfigSchema.parse(content),
248
+ catch: (error) => new ConfigValidationError({
249
+ reason: "Invalid configuration schema",
250
+ cause: error
251
+ })
252
+ });
253
+ })
254
+ });
255
+ });
256
+ var ConfigLoaderLive = Layer.effect(ConfigLoader, makeConfigLoader);
257
+ var PatternMatcher = class {
258
+ constructor(config) {
259
+ __publicField2(this, "ignorePatterns", []);
260
+ __publicField2(this, "instrumentPatterns", []);
261
+ __publicField2(this, "enabled", true);
262
+ this.enabled = config.instrumentation.enabled;
263
+ this.ignorePatterns = config.instrumentation.ignore_patterns.map((p) => this.compilePattern(p));
264
+ this.instrumentPatterns = config.instrumentation.instrument_patterns.filter((p) => p.enabled !== false).map((p) => this.compilePattern(p));
265
+ }
266
+ /**
267
+ * Compile a pattern configuration into a RegExp
268
+ */
269
+ compilePattern(pattern) {
270
+ try {
271
+ const compiled = {
272
+ regex: new RegExp(pattern.pattern),
273
+ enabled: pattern.enabled !== false
274
+ };
275
+ if (pattern.description !== void 0) {
276
+ compiled.description = pattern.description;
277
+ }
278
+ return compiled;
279
+ } catch (error) {
280
+ throw new Error(
281
+ `Failed to compile pattern "${pattern.pattern}": ${error instanceof Error ? error.message : String(error)}`
282
+ );
283
+ }
284
+ }
285
+ /**
286
+ * Check if a span should be instrumented
287
+ *
288
+ * Returns true if the span should be created, false otherwise.
289
+ *
290
+ * Logic:
291
+ * 1. If instrumentation disabled globally, return false
292
+ * 2. Check ignore patterns - if any match, return false
293
+ * 3. Check instrument patterns - if any match, return true
294
+ * 4. Default: return true (fail-open - create span if no patterns match)
295
+ */
296
+ shouldInstrument(spanName) {
297
+ if (!this.enabled) {
298
+ return false;
299
+ }
300
+ for (const pattern of this.ignorePatterns) {
301
+ if (pattern.regex.test(spanName)) {
302
+ return false;
303
+ }
304
+ }
305
+ for (const pattern of this.instrumentPatterns) {
306
+ if (pattern.enabled && pattern.regex.test(spanName)) {
307
+ return true;
308
+ }
309
+ }
310
+ return true;
311
+ }
312
+ /**
313
+ * Get statistics about pattern matching (for debugging/monitoring)
314
+ */
315
+ getStats() {
316
+ return {
317
+ enabled: this.enabled,
318
+ ignorePatternCount: this.ignorePatterns.length,
319
+ instrumentPatternCount: this.instrumentPatterns.filter((p) => p.enabled).length
320
+ };
321
+ }
322
+ };
323
+ var globalMatcher = null;
324
+ function initializePatternMatcher(config) {
325
+ globalMatcher = new PatternMatcher(config);
326
+ }
327
+ function shouldInstrumentSpan(spanName) {
328
+ if (!globalMatcher) {
329
+ return true;
330
+ }
331
+ return globalMatcher.shouldInstrument(spanName);
332
+ }
333
+ function getPatternMatcher() {
334
+ return globalMatcher;
335
+ }
336
+ var Logger = class {
337
+ constructor() {
338
+ __publicField2(this, "level", "on");
339
+ __publicField2(this, "hasLoggedMinimal", false);
340
+ }
341
+ /**
342
+ * Set the logging level
343
+ */
344
+ setLevel(level) {
345
+ this.level = level;
346
+ this.hasLoggedMinimal = false;
347
+ }
348
+ /**
349
+ * Get the current logging level
350
+ */
351
+ getLevel() {
352
+ return this.level;
353
+ }
354
+ /**
355
+ * Log a minimal initialization message (only shown once in minimal mode)
356
+ */
357
+ minimal(message) {
358
+ if (this.level === "off") {
359
+ return;
360
+ }
361
+ if (this.level === "minimal" && !this.hasLoggedMinimal) {
362
+ console.log(message);
363
+ this.hasLoggedMinimal = true;
364
+ return;
365
+ }
366
+ if (this.level === "on") {
367
+ console.log(message);
368
+ }
369
+ }
370
+ /**
371
+ * Log an informational message
372
+ */
373
+ log(...args) {
374
+ if (this.level === "on") {
375
+ console.log(...args);
376
+ }
377
+ }
378
+ /**
379
+ * Log a warning message (shown in minimal mode)
380
+ */
381
+ warn(...args) {
382
+ if (this.level !== "off") {
383
+ console.warn(...args);
384
+ }
385
+ }
386
+ /**
387
+ * Log an error message (shown in minimal mode)
388
+ */
389
+ error(...args) {
390
+ if (this.level !== "off") {
391
+ console.error(...args);
392
+ }
393
+ }
394
+ /**
395
+ * Check if full logging is enabled
396
+ */
397
+ isEnabled() {
398
+ return this.level === "on";
399
+ }
400
+ /**
401
+ * Check if minimal logging is enabled
402
+ */
403
+ isMinimal() {
404
+ return this.level === "minimal";
405
+ }
406
+ /**
407
+ * Check if logging is completely disabled
408
+ */
409
+ isDisabled() {
410
+ return this.level === "off";
411
+ }
412
+ };
413
+ var logger = new Logger();
414
+
415
+ // src/core/span-processor.ts
23
416
  var PatternSpanProcessor = class {
24
417
  constructor(config, wrappedProcessor) {
25
418
  __publicField(this, "matcher");
@@ -97,6 +490,8 @@ function getOtlpEndpoint(options = {}) {
97
490
  const endpoint = options.endpoint || process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT || process.env.OTEL_EXPORTER_OTLP_ENDPOINT || DEFAULT_OTLP_ENDPOINT;
98
491
  return normalizeEndpoint(endpoint);
99
492
  }
493
+
494
+ // src/core/safe-exporter.ts
100
495
  var SafeSpanExporter = class {
101
496
  // Log errors max once per minute
102
497
  constructor(exporter) {
@@ -196,21 +591,21 @@ var SafeSpanExporter = class {
196
591
  return false;
197
592
  }
198
593
  };
199
- var ConfigError = class extends Data.TaggedError("ConfigError") {
594
+ var ConfigError2 = class extends Data.TaggedError("ConfigError") {
200
595
  };
201
- var ConfigUrlError = class extends Data.TaggedError("ConfigUrlError") {
596
+ var ConfigUrlError2 = class extends Data.TaggedError("ConfigUrlError") {
202
597
  };
203
- var ConfigValidationError = class extends Data.TaggedError("ConfigValidationError") {
598
+ var ConfigValidationError2 = class extends Data.TaggedError("ConfigValidationError") {
204
599
  };
205
- var ConfigFileError = class extends Data.TaggedError("ConfigFileError") {
600
+ var ConfigFileError2 = class extends Data.TaggedError("ConfigFileError") {
206
601
  };
207
- var ServiceDetectionError = class extends Data.TaggedError("ServiceDetectionError") {
602
+ var ServiceDetectionError2 = class extends Data.TaggedError("ServiceDetectionError") {
208
603
  };
209
- var InitializationError = class extends Data.TaggedError("InitializationError") {
604
+ var InitializationError2 = class extends Data.TaggedError("InitializationError") {
210
605
  };
211
- var ExportError = class extends Data.TaggedError("ExportError") {
606
+ var ExportError2 = class extends Data.TaggedError("ExportError") {
212
607
  };
213
- var ShutdownError = class extends Data.TaggedError("ShutdownError") {
608
+ var ShutdownError2 = class extends Data.TaggedError("ShutdownError") {
214
609
  };
215
610
 
216
611
  // src/core/service-detector.ts
@@ -227,7 +622,7 @@ var detectServiceInfo = Effect.gen(
227
622
  const packageJsonPath = join(process.cwd(), "package.json");
228
623
  const packageJsonContent = yield* Effect.tryPromise({
229
624
  try: () => readFile(packageJsonPath, "utf-8"),
230
- catch: (error) => new ServiceDetectionError({
625
+ catch: (error) => new ServiceDetectionError2({
231
626
  reason: `Failed to read package.json at ${packageJsonPath}`,
232
627
  cause: error
233
628
  })
@@ -237,7 +632,7 @@ var detectServiceInfo = Effect.gen(
237
632
  parsed = JSON.parse(packageJsonContent);
238
633
  } catch (error) {
239
634
  yield* Effect.fail(
240
- new ServiceDetectionError({
635
+ new ServiceDetectionError2({
241
636
  reason: "Invalid JSON in package.json",
242
637
  cause: error
243
638
  })
@@ -253,7 +648,7 @@ var detectServiceInfo = Effect.gen(
253
648
  }
254
649
  }
255
650
  return yield* Effect.fail(
256
- new ServiceDetectionError({
651
+ new ServiceDetectionError2({
257
652
  reason: 'package.json exists but has no "name" field'
258
653
  })
259
654
  );
@@ -652,6 +1047,8 @@ function logInitialization(config, serviceName, serviceVersion, options, autoIns
652
1047
  logger.log(` - OTLP endpoint: ${endpoint}`);
653
1048
  logger.log("");
654
1049
  }
1050
+
1051
+ // src/api.ts
655
1052
  async function initializeInstrumentation(options = {}) {
656
1053
  const sdk = await initializeSdk(options);
657
1054
  if (sdk) {
@@ -671,7 +1068,7 @@ async function initializePatternMatchingOnly(options = {}) {
671
1068
  var initializeInstrumentationEffect = (options = {}) => Effect.gen(function* () {
672
1069
  const sdk = yield* Effect.tryPromise({
673
1070
  try: () => initializeSdk(options),
674
- catch: (error) => new InitializationError({
1071
+ catch: (error) => new InitializationError2({
675
1072
  reason: "SDK initialization failed",
676
1073
  cause: error
677
1074
  })
@@ -679,7 +1076,7 @@ var initializeInstrumentationEffect = (options = {}) => Effect.gen(function* ()
679
1076
  if (sdk) {
680
1077
  yield* Effect.tryPromise({
681
1078
  try: () => loadConfigWithOptions(options),
682
- catch: (error) => new ConfigError({
1079
+ catch: (error) => new ConfigError2({
683
1080
  reason: "Failed to load config for pattern matcher",
684
1081
  cause: error
685
1082
  })
@@ -696,7 +1093,7 @@ var initializeInstrumentationEffect = (options = {}) => Effect.gen(function* ()
696
1093
  var initializePatternMatchingOnlyEffect = (options = {}) => Effect.gen(function* () {
697
1094
  const config = yield* Effect.tryPromise({
698
1095
  try: () => loadConfigWithOptions(options),
699
- catch: (error) => new ConfigError({
1096
+ catch: (error) => new ConfigError2({
700
1097
  reason: "Failed to load configuration",
701
1098
  cause: error
702
1099
  })
@@ -804,6 +1201,6 @@ function suppressShutdownErrors() {
804
1201
  });
805
1202
  }
806
1203
 
807
- export { ConfigError, ConfigFileError, ConfigUrlError, ConfigValidationError, ExportError, InitializationError, PatternSpanProcessor, ServiceDetectionError, ShutdownError, annotateCacheOperation, annotateDbQuery, annotateHttpRequest, _resetConfigLoaderCache as clearConfigCache, createOtlpExporter, detectServiceInfoAsync as detectServiceInfo, detectServiceInfo as detectServiceInfoEffect, getOtlpEndpoint, getSdkInstance, getServiceInfoWithFallback, getServiceNameAsync as getServiceName, getServiceName as getServiceNameEffect, getServiceVersionAsync as getServiceVersion, getServiceVersion as getServiceVersionEffect, initializeInstrumentation, initializeInstrumentationEffect, initializePatternMatchingOnly, initializePatternMatchingOnlyEffect, loadConfig, loadConfigFromInline, loadConfigWithOptions, markSpanError, markSpanSuccess, recordException, resetSdk, setSpanAttributes, shutdownSdk, suppressShutdownErrors };
1204
+ export { ConfigError2 as ConfigError, ConfigFileError2 as ConfigFileError, ConfigUrlError2 as ConfigUrlError, ConfigValidationError2 as ConfigValidationError, ExportError2 as ExportError, InitializationError2 as InitializationError, PatternMatcher, PatternSpanProcessor, ServiceDetectionError2 as ServiceDetectionError, ShutdownError2 as ShutdownError, annotateCacheOperation, annotateDbQuery, annotateHttpRequest, _resetConfigLoaderCache as clearConfigCache, createOtlpExporter, detectServiceInfoAsync as detectServiceInfo, detectServiceInfo as detectServiceInfoEffect, getOtlpEndpoint, getPatternMatcher, getSdkInstance, getServiceInfoWithFallback, getServiceNameAsync as getServiceName, getServiceName as getServiceNameEffect, getServiceVersionAsync as getServiceVersion, getServiceVersion as getServiceVersionEffect, initializeInstrumentation, initializeInstrumentationEffect, initializePatternMatchingOnly, initializePatternMatchingOnlyEffect, loadConfig, loadConfigFromInline, loadConfigWithOptions, markSpanError, markSpanSuccess, recordException, resetSdk, setSpanAttributes, shouldInstrumentSpan, shutdownSdk, suppressShutdownErrors };
808
1205
  //# sourceMappingURL=index.js.map
809
1206
  //# sourceMappingURL=index.js.map