@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.
- package/LICENSE +21 -0
- package/package.json +24 -24
- package/target/dist/index.cjs +501 -91
- package/target/dist/index.cjs.map +1 -1
- package/target/dist/index.js +415 -18
- package/target/dist/index.js.map +1 -1
- package/target/dist/integrations/effect/index.cjs +399 -15
- package/target/dist/integrations/effect/index.cjs.map +1 -1
- package/target/dist/integrations/effect/index.js +384 -2
- package/target/dist/integrations/effect/index.js.map +1 -1
package/target/dist/index.cjs
CHANGED
|
@@ -5,13 +5,38 @@ var sdkNode = require('@opentelemetry/sdk-node');
|
|
|
5
5
|
var sdkTraceBase = require('@opentelemetry/sdk-trace-base');
|
|
6
6
|
var autoInstrumentationsNode = require('@opentelemetry/auto-instrumentations-node');
|
|
7
7
|
var api = require('@opentelemetry/api');
|
|
8
|
-
var
|
|
8
|
+
var FileSystem = require('@effect/platform/FileSystem');
|
|
9
|
+
var HttpClient = require('@effect/platform/HttpClient');
|
|
10
|
+
var HttpClientRequest = require('@effect/platform/HttpClientRequest');
|
|
11
|
+
var yaml = require('yaml');
|
|
12
|
+
var zod = require('zod');
|
|
9
13
|
var exporterTraceOtlpHttp = require('@opentelemetry/exporter-trace-otlp-http');
|
|
10
14
|
var promises = require('fs/promises');
|
|
11
15
|
var path = require('path');
|
|
12
16
|
var platformNode = require('@effect/platform-node');
|
|
13
17
|
var platform = require('@effect/platform');
|
|
14
18
|
|
|
19
|
+
function _interopNamespace(e) {
|
|
20
|
+
if (e && e.__esModule) return e;
|
|
21
|
+
var n = Object.create(null);
|
|
22
|
+
if (e) {
|
|
23
|
+
Object.keys(e).forEach(function (k) {
|
|
24
|
+
if (k !== 'default') {
|
|
25
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
26
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
27
|
+
enumerable: true,
|
|
28
|
+
get: function () { return e[k]; }
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
n.default = e;
|
|
34
|
+
return Object.freeze(n);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
var HttpClient__namespace = /*#__PURE__*/_interopNamespace(HttpClient);
|
|
38
|
+
var HttpClientRequest__namespace = /*#__PURE__*/_interopNamespace(HttpClientRequest);
|
|
39
|
+
|
|
15
40
|
var __defProp = Object.defineProperty;
|
|
16
41
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
17
42
|
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
@@ -21,11 +46,401 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
|
|
|
21
46
|
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
22
47
|
});
|
|
23
48
|
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
49
|
+
var __defProp2 = Object.defineProperty;
|
|
50
|
+
var __defNormalProp2 = (obj, key, value) => key in obj ? __defProp2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
51
|
+
var __publicField2 = (obj, key, value) => __defNormalProp2(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
52
|
+
var PatternConfigSchema = zod.z.object({
|
|
53
|
+
pattern: zod.z.string(),
|
|
54
|
+
enabled: zod.z.boolean().optional(),
|
|
55
|
+
description: zod.z.string().optional()
|
|
56
|
+
});
|
|
57
|
+
var AutoIsolationConfigSchema = zod.z.object({
|
|
58
|
+
// Global enable/disable for auto-isolation
|
|
59
|
+
enabled: zod.z.boolean().default(false),
|
|
60
|
+
// Which operators to auto-isolate
|
|
61
|
+
operators: zod.z.object({
|
|
62
|
+
fiberset_run: zod.z.boolean().default(true),
|
|
63
|
+
effect_fork: zod.z.boolean().default(true),
|
|
64
|
+
effect_fork_daemon: zod.z.boolean().default(true),
|
|
65
|
+
effect_fork_in: zod.z.boolean().default(false)
|
|
66
|
+
}).default({}),
|
|
67
|
+
// Virtual parent tracking configuration
|
|
68
|
+
tracking: zod.z.object({
|
|
69
|
+
use_span_links: zod.z.boolean().default(true),
|
|
70
|
+
use_attributes: zod.z.boolean().default(true),
|
|
71
|
+
capture_logical_parent: zod.z.boolean().default(true)
|
|
72
|
+
}).default({}),
|
|
73
|
+
// Span categorization
|
|
74
|
+
attributes: zod.z.object({
|
|
75
|
+
category: zod.z.string().default("background_task"),
|
|
76
|
+
add_metadata: zod.z.boolean().default(true)
|
|
77
|
+
}).default({})
|
|
78
|
+
});
|
|
79
|
+
var HttpFilteringConfigSchema = zod.z.object({
|
|
80
|
+
// Patterns to ignore for outgoing HTTP requests (string patterns only in YAML)
|
|
81
|
+
ignore_outgoing_urls: zod.z.array(zod.z.string()).optional(),
|
|
82
|
+
// Patterns to ignore for incoming HTTP requests (string patterns only in YAML)
|
|
83
|
+
ignore_incoming_paths: zod.z.array(zod.z.string()).optional(),
|
|
84
|
+
// Require parent span for outgoing requests (prevents root spans for HTTP calls)
|
|
85
|
+
require_parent_for_outgoing_spans: zod.z.boolean().optional()
|
|
86
|
+
});
|
|
87
|
+
var InstrumentationConfigSchema = zod.z.object({
|
|
88
|
+
version: zod.z.string(),
|
|
89
|
+
instrumentation: zod.z.object({
|
|
90
|
+
enabled: zod.z.boolean(),
|
|
91
|
+
description: zod.z.string().optional(),
|
|
92
|
+
logging: zod.z.enum(["on", "off", "minimal"]).optional().default("on"),
|
|
93
|
+
instrument_patterns: zod.z.array(PatternConfigSchema),
|
|
94
|
+
ignore_patterns: zod.z.array(PatternConfigSchema)
|
|
95
|
+
}),
|
|
96
|
+
effect: zod.z.object({
|
|
97
|
+
auto_extract_metadata: zod.z.boolean(),
|
|
98
|
+
auto_isolation: AutoIsolationConfigSchema.optional()
|
|
99
|
+
}).optional(),
|
|
100
|
+
http: HttpFilteringConfigSchema.optional()
|
|
101
|
+
});
|
|
102
|
+
(class extends effect.Data.TaggedError("ConfigError") {
|
|
103
|
+
get message() {
|
|
104
|
+
return this.reason;
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
var ConfigUrlError = class extends effect.Data.TaggedError("ConfigUrlError") {
|
|
108
|
+
get message() {
|
|
109
|
+
return this.reason;
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
var ConfigValidationError = class extends effect.Data.TaggedError("ConfigValidationError") {
|
|
113
|
+
get message() {
|
|
114
|
+
return this.reason;
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
var ConfigFileError = class extends effect.Data.TaggedError("ConfigFileError") {
|
|
118
|
+
get message() {
|
|
119
|
+
return this.reason;
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
(class extends effect.Data.TaggedError("ServiceDetectionError") {
|
|
123
|
+
get message() {
|
|
124
|
+
return this.reason;
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
(class extends effect.Data.TaggedError("InitializationError") {
|
|
128
|
+
get message() {
|
|
129
|
+
return this.reason;
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
(class extends effect.Data.TaggedError("ExportError") {
|
|
133
|
+
get message() {
|
|
134
|
+
return this.reason;
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
(class extends effect.Data.TaggedError("ShutdownError") {
|
|
138
|
+
get message() {
|
|
139
|
+
return this.reason;
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
var SECURITY_DEFAULTS = {
|
|
143
|
+
maxConfigSize: 1e6,
|
|
144
|
+
// 1MB
|
|
145
|
+
requestTimeout: 5e3
|
|
146
|
+
// 5 seconds
|
|
147
|
+
};
|
|
148
|
+
var ConfigLoader = class extends effect.Context.Tag("ConfigLoader")() {
|
|
149
|
+
};
|
|
150
|
+
var parseYamlContent = (content, uri) => effect.Effect.gen(function* () {
|
|
151
|
+
const parsed = yield* effect.Effect.try({
|
|
152
|
+
try: () => yaml.parse(content),
|
|
153
|
+
catch: (error) => new ConfigValidationError({
|
|
154
|
+
reason: uri ? `Failed to parse YAML from ${uri}` : "Failed to parse YAML",
|
|
155
|
+
cause: error
|
|
156
|
+
})
|
|
157
|
+
});
|
|
158
|
+
return yield* effect.Effect.try({
|
|
159
|
+
try: () => InstrumentationConfigSchema.parse(parsed),
|
|
160
|
+
catch: (error) => new ConfigValidationError({
|
|
161
|
+
reason: uri ? `Invalid configuration schema from ${uri}` : "Invalid configuration schema",
|
|
162
|
+
cause: error
|
|
163
|
+
})
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
var loadFromFileWithFs = (fs, path, uri) => effect.Effect.gen(function* () {
|
|
167
|
+
const content = yield* fs.readFileString(path).pipe(
|
|
168
|
+
effect.Effect.mapError(
|
|
169
|
+
(error) => new ConfigFileError({
|
|
170
|
+
reason: `Failed to read config file at ${uri}`,
|
|
171
|
+
cause: error
|
|
172
|
+
})
|
|
173
|
+
)
|
|
174
|
+
);
|
|
175
|
+
if (content.length > SECURITY_DEFAULTS.maxConfigSize) {
|
|
176
|
+
return yield* effect.Effect.fail(
|
|
177
|
+
new ConfigFileError({
|
|
178
|
+
reason: `Config file exceeds maximum size of ${SECURITY_DEFAULTS.maxConfigSize} bytes`
|
|
179
|
+
})
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
return yield* parseYamlContent(content, uri);
|
|
183
|
+
});
|
|
184
|
+
var loadFromHttpWithClient = (client, url) => effect.Effect.scoped(
|
|
185
|
+
effect.Effect.gen(function* () {
|
|
186
|
+
if (url.startsWith("http://")) {
|
|
187
|
+
return yield* effect.Effect.fail(
|
|
188
|
+
new ConfigUrlError({
|
|
189
|
+
reason: "Insecure protocol: only HTTPS URLs are allowed"
|
|
190
|
+
})
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
const request = HttpClientRequest__namespace.get(url).pipe(
|
|
194
|
+
HttpClientRequest__namespace.setHeaders({
|
|
195
|
+
Accept: "application/yaml, text/yaml, application/x-yaml"
|
|
196
|
+
})
|
|
197
|
+
);
|
|
198
|
+
const response = yield* client.execute(request).pipe(
|
|
199
|
+
effect.Effect.timeout(`${SECURITY_DEFAULTS.requestTimeout} millis`),
|
|
200
|
+
effect.Effect.mapError((error) => {
|
|
201
|
+
if (error._tag === "TimeoutException") {
|
|
202
|
+
return new ConfigUrlError({
|
|
203
|
+
reason: `Config fetch timeout after ${SECURITY_DEFAULTS.requestTimeout}ms from ${url}`
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
return new ConfigUrlError({
|
|
207
|
+
reason: `Failed to load config from URL: ${url}`,
|
|
208
|
+
cause: error
|
|
209
|
+
});
|
|
210
|
+
})
|
|
211
|
+
);
|
|
212
|
+
if (response.status >= 400) {
|
|
213
|
+
return yield* effect.Effect.fail(
|
|
214
|
+
new ConfigUrlError({
|
|
215
|
+
reason: `HTTP ${response.status} from ${url}`
|
|
216
|
+
})
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
const text = yield* response.text.pipe(
|
|
220
|
+
effect.Effect.mapError(
|
|
221
|
+
(error) => new ConfigUrlError({
|
|
222
|
+
reason: `Failed to read response body from ${url}`,
|
|
223
|
+
cause: error
|
|
224
|
+
})
|
|
225
|
+
)
|
|
226
|
+
);
|
|
227
|
+
if (text.length > SECURITY_DEFAULTS.maxConfigSize) {
|
|
228
|
+
return yield* effect.Effect.fail(
|
|
229
|
+
new ConfigUrlError({
|
|
230
|
+
reason: `Config exceeds maximum size of ${SECURITY_DEFAULTS.maxConfigSize} bytes`
|
|
231
|
+
})
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
return yield* parseYamlContent(text, url);
|
|
235
|
+
})
|
|
236
|
+
);
|
|
237
|
+
var makeConfigLoader = effect.Effect.gen(function* () {
|
|
238
|
+
const fs = yield* effect.Effect.serviceOption(FileSystem.FileSystem);
|
|
239
|
+
const http = yield* HttpClient__namespace.HttpClient;
|
|
240
|
+
const loadFromUriUncached = (uri) => effect.Effect.gen(function* () {
|
|
241
|
+
if (uri.startsWith("file://")) {
|
|
242
|
+
const path = uri.slice(7);
|
|
243
|
+
if (fs._tag === "None") {
|
|
244
|
+
return yield* effect.Effect.fail(
|
|
245
|
+
new ConfigFileError({
|
|
246
|
+
reason: "FileSystem not available (browser environment?)",
|
|
247
|
+
cause: { uri }
|
|
248
|
+
})
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
return yield* loadFromFileWithFs(fs.value, path, uri);
|
|
252
|
+
}
|
|
253
|
+
if (uri.startsWith("http://") || uri.startsWith("https://")) {
|
|
254
|
+
return yield* loadFromHttpWithClient(http, uri);
|
|
255
|
+
}
|
|
256
|
+
if (fs._tag === "Some") {
|
|
257
|
+
return yield* loadFromFileWithFs(fs.value, uri, uri);
|
|
258
|
+
} else {
|
|
259
|
+
return yield* loadFromHttpWithClient(http, uri);
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
const loadFromUriCached = yield* effect.Effect.cachedFunction(loadFromUriUncached);
|
|
263
|
+
return ConfigLoader.of({
|
|
264
|
+
loadFromUri: loadFromUriCached,
|
|
265
|
+
loadFromInline: (content) => effect.Effect.gen(function* () {
|
|
266
|
+
if (typeof content === "string") {
|
|
267
|
+
return yield* parseYamlContent(content);
|
|
268
|
+
}
|
|
269
|
+
return yield* effect.Effect.try({
|
|
270
|
+
try: () => InstrumentationConfigSchema.parse(content),
|
|
271
|
+
catch: (error) => new ConfigValidationError({
|
|
272
|
+
reason: "Invalid configuration schema",
|
|
273
|
+
cause: error
|
|
274
|
+
})
|
|
275
|
+
});
|
|
276
|
+
})
|
|
277
|
+
});
|
|
278
|
+
});
|
|
279
|
+
var ConfigLoaderLive = effect.Layer.effect(ConfigLoader, makeConfigLoader);
|
|
280
|
+
var PatternMatcher = class {
|
|
281
|
+
constructor(config) {
|
|
282
|
+
__publicField2(this, "ignorePatterns", []);
|
|
283
|
+
__publicField2(this, "instrumentPatterns", []);
|
|
284
|
+
__publicField2(this, "enabled", true);
|
|
285
|
+
this.enabled = config.instrumentation.enabled;
|
|
286
|
+
this.ignorePatterns = config.instrumentation.ignore_patterns.map((p) => this.compilePattern(p));
|
|
287
|
+
this.instrumentPatterns = config.instrumentation.instrument_patterns.filter((p) => p.enabled !== false).map((p) => this.compilePattern(p));
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Compile a pattern configuration into a RegExp
|
|
291
|
+
*/
|
|
292
|
+
compilePattern(pattern) {
|
|
293
|
+
try {
|
|
294
|
+
const compiled = {
|
|
295
|
+
regex: new RegExp(pattern.pattern),
|
|
296
|
+
enabled: pattern.enabled !== false
|
|
297
|
+
};
|
|
298
|
+
if (pattern.description !== void 0) {
|
|
299
|
+
compiled.description = pattern.description;
|
|
300
|
+
}
|
|
301
|
+
return compiled;
|
|
302
|
+
} catch (error) {
|
|
303
|
+
throw new Error(
|
|
304
|
+
`Failed to compile pattern "${pattern.pattern}": ${error instanceof Error ? error.message : String(error)}`
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Check if a span should be instrumented
|
|
310
|
+
*
|
|
311
|
+
* Returns true if the span should be created, false otherwise.
|
|
312
|
+
*
|
|
313
|
+
* Logic:
|
|
314
|
+
* 1. If instrumentation disabled globally, return false
|
|
315
|
+
* 2. Check ignore patterns - if any match, return false
|
|
316
|
+
* 3. Check instrument patterns - if any match, return true
|
|
317
|
+
* 4. Default: return true (fail-open - create span if no patterns match)
|
|
318
|
+
*/
|
|
319
|
+
shouldInstrument(spanName) {
|
|
320
|
+
if (!this.enabled) {
|
|
321
|
+
return false;
|
|
322
|
+
}
|
|
323
|
+
for (const pattern of this.ignorePatterns) {
|
|
324
|
+
if (pattern.regex.test(spanName)) {
|
|
325
|
+
return false;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
for (const pattern of this.instrumentPatterns) {
|
|
329
|
+
if (pattern.enabled && pattern.regex.test(spanName)) {
|
|
330
|
+
return true;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
return true;
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Get statistics about pattern matching (for debugging/monitoring)
|
|
337
|
+
*/
|
|
338
|
+
getStats() {
|
|
339
|
+
return {
|
|
340
|
+
enabled: this.enabled,
|
|
341
|
+
ignorePatternCount: this.ignorePatterns.length,
|
|
342
|
+
instrumentPatternCount: this.instrumentPatterns.filter((p) => p.enabled).length
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
};
|
|
346
|
+
var globalMatcher = null;
|
|
347
|
+
function initializePatternMatcher(config) {
|
|
348
|
+
globalMatcher = new PatternMatcher(config);
|
|
349
|
+
}
|
|
350
|
+
function shouldInstrumentSpan(spanName) {
|
|
351
|
+
if (!globalMatcher) {
|
|
352
|
+
return true;
|
|
353
|
+
}
|
|
354
|
+
return globalMatcher.shouldInstrument(spanName);
|
|
355
|
+
}
|
|
356
|
+
function getPatternMatcher() {
|
|
357
|
+
return globalMatcher;
|
|
358
|
+
}
|
|
359
|
+
var Logger = class {
|
|
360
|
+
constructor() {
|
|
361
|
+
__publicField2(this, "level", "on");
|
|
362
|
+
__publicField2(this, "hasLoggedMinimal", false);
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* Set the logging level
|
|
366
|
+
*/
|
|
367
|
+
setLevel(level) {
|
|
368
|
+
this.level = level;
|
|
369
|
+
this.hasLoggedMinimal = false;
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Get the current logging level
|
|
373
|
+
*/
|
|
374
|
+
getLevel() {
|
|
375
|
+
return this.level;
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Log a minimal initialization message (only shown once in minimal mode)
|
|
379
|
+
*/
|
|
380
|
+
minimal(message) {
|
|
381
|
+
if (this.level === "off") {
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
if (this.level === "minimal" && !this.hasLoggedMinimal) {
|
|
385
|
+
console.log(message);
|
|
386
|
+
this.hasLoggedMinimal = true;
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
if (this.level === "on") {
|
|
390
|
+
console.log(message);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Log an informational message
|
|
395
|
+
*/
|
|
396
|
+
log(...args) {
|
|
397
|
+
if (this.level === "on") {
|
|
398
|
+
console.log(...args);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Log a warning message (shown in minimal mode)
|
|
403
|
+
*/
|
|
404
|
+
warn(...args) {
|
|
405
|
+
if (this.level !== "off") {
|
|
406
|
+
console.warn(...args);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Log an error message (shown in minimal mode)
|
|
411
|
+
*/
|
|
412
|
+
error(...args) {
|
|
413
|
+
if (this.level !== "off") {
|
|
414
|
+
console.error(...args);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* Check if full logging is enabled
|
|
419
|
+
*/
|
|
420
|
+
isEnabled() {
|
|
421
|
+
return this.level === "on";
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* Check if minimal logging is enabled
|
|
425
|
+
*/
|
|
426
|
+
isMinimal() {
|
|
427
|
+
return this.level === "minimal";
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* Check if logging is completely disabled
|
|
431
|
+
*/
|
|
432
|
+
isDisabled() {
|
|
433
|
+
return this.level === "off";
|
|
434
|
+
}
|
|
435
|
+
};
|
|
436
|
+
var logger = new Logger();
|
|
437
|
+
|
|
438
|
+
// src/core/span-processor.ts
|
|
24
439
|
var PatternSpanProcessor = class {
|
|
25
440
|
constructor(config, wrappedProcessor) {
|
|
26
441
|
__publicField(this, "matcher");
|
|
27
442
|
__publicField(this, "wrappedProcessor");
|
|
28
|
-
this.matcher = new
|
|
443
|
+
this.matcher = new PatternMatcher(config);
|
|
29
444
|
this.wrappedProcessor = wrappedProcessor;
|
|
30
445
|
}
|
|
31
446
|
/**
|
|
@@ -98,6 +513,8 @@ function getOtlpEndpoint(options = {}) {
|
|
|
98
513
|
const endpoint = options.endpoint || process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT || process.env.OTEL_EXPORTER_OTLP_ENDPOINT || DEFAULT_OTLP_ENDPOINT;
|
|
99
514
|
return normalizeEndpoint(endpoint);
|
|
100
515
|
}
|
|
516
|
+
|
|
517
|
+
// src/core/safe-exporter.ts
|
|
101
518
|
var SafeSpanExporter = class {
|
|
102
519
|
// Log errors max once per minute
|
|
103
520
|
constructor(exporter) {
|
|
@@ -134,7 +551,7 @@ var SafeSpanExporter = class {
|
|
|
134
551
|
try {
|
|
135
552
|
await this.exporter.shutdown();
|
|
136
553
|
} catch (error) {
|
|
137
|
-
|
|
554
|
+
logger.error(
|
|
138
555
|
"@atrim/instrumentation: Error during exporter shutdown (non-critical):",
|
|
139
556
|
error instanceof Error ? error.message : String(error)
|
|
140
557
|
);
|
|
@@ -163,14 +580,14 @@ var SafeSpanExporter = class {
|
|
|
163
580
|
if (shouldLog) {
|
|
164
581
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
165
582
|
if (this.isConnectionError(error)) {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
583
|
+
logger.warn(`@atrim/instrumentation: Unable to export spans - collector not available`);
|
|
584
|
+
logger.warn(` Error: ${errorMessage}`);
|
|
585
|
+
logger.warn(` Spans will be dropped. Ensure OTEL collector is running.`);
|
|
169
586
|
} else {
|
|
170
|
-
|
|
587
|
+
logger.error("@atrim/instrumentation: Span export failed:", errorMessage);
|
|
171
588
|
}
|
|
172
589
|
if (this.errorCount > 1) {
|
|
173
|
-
|
|
590
|
+
logger.warn(` (${this.errorCount} errors total, throttled to 1/min)`);
|
|
174
591
|
}
|
|
175
592
|
this.lastErrorTime = now;
|
|
176
593
|
this.errorCount = 0;
|
|
@@ -197,21 +614,21 @@ var SafeSpanExporter = class {
|
|
|
197
614
|
return false;
|
|
198
615
|
}
|
|
199
616
|
};
|
|
200
|
-
var
|
|
617
|
+
var ConfigError2 = class extends effect.Data.TaggedError("ConfigError") {
|
|
201
618
|
};
|
|
202
|
-
var
|
|
619
|
+
var ConfigUrlError2 = class extends effect.Data.TaggedError("ConfigUrlError") {
|
|
203
620
|
};
|
|
204
|
-
var
|
|
621
|
+
var ConfigValidationError2 = class extends effect.Data.TaggedError("ConfigValidationError") {
|
|
205
622
|
};
|
|
206
|
-
var
|
|
623
|
+
var ConfigFileError2 = class extends effect.Data.TaggedError("ConfigFileError") {
|
|
207
624
|
};
|
|
208
|
-
var
|
|
625
|
+
var ServiceDetectionError2 = class extends effect.Data.TaggedError("ServiceDetectionError") {
|
|
209
626
|
};
|
|
210
|
-
var
|
|
627
|
+
var InitializationError2 = class extends effect.Data.TaggedError("InitializationError") {
|
|
211
628
|
};
|
|
212
|
-
var
|
|
629
|
+
var ExportError2 = class extends effect.Data.TaggedError("ExportError") {
|
|
213
630
|
};
|
|
214
|
-
var
|
|
631
|
+
var ShutdownError2 = class extends effect.Data.TaggedError("ShutdownError") {
|
|
215
632
|
};
|
|
216
633
|
|
|
217
634
|
// src/core/service-detector.ts
|
|
@@ -228,7 +645,7 @@ var detectServiceInfo = effect.Effect.gen(
|
|
|
228
645
|
const packageJsonPath = path.join(process.cwd(), "package.json");
|
|
229
646
|
const packageJsonContent = yield* effect.Effect.tryPromise({
|
|
230
647
|
try: () => promises.readFile(packageJsonPath, "utf-8"),
|
|
231
|
-
catch: (error) => new
|
|
648
|
+
catch: (error) => new ServiceDetectionError2({
|
|
232
649
|
reason: `Failed to read package.json at ${packageJsonPath}`,
|
|
233
650
|
cause: error
|
|
234
651
|
})
|
|
@@ -238,7 +655,7 @@ var detectServiceInfo = effect.Effect.gen(
|
|
|
238
655
|
parsed = JSON.parse(packageJsonContent);
|
|
239
656
|
} catch (error) {
|
|
240
657
|
yield* effect.Effect.fail(
|
|
241
|
-
new
|
|
658
|
+
new ServiceDetectionError2({
|
|
242
659
|
reason: "Invalid JSON in package.json",
|
|
243
660
|
cause: error
|
|
244
661
|
})
|
|
@@ -254,7 +671,7 @@ var detectServiceInfo = effect.Effect.gen(
|
|
|
254
671
|
}
|
|
255
672
|
}
|
|
256
673
|
return yield* effect.Effect.fail(
|
|
257
|
-
new
|
|
674
|
+
new ServiceDetectionError2({
|
|
258
675
|
reason: 'package.json exists but has no "name" field'
|
|
259
676
|
})
|
|
260
677
|
);
|
|
@@ -285,7 +702,7 @@ async function getServiceNameAsync() {
|
|
|
285
702
|
async function getServiceVersionAsync() {
|
|
286
703
|
return effect.Effect.runPromise(getServiceVersion);
|
|
287
704
|
}
|
|
288
|
-
var NodeConfigLoaderLive =
|
|
705
|
+
var NodeConfigLoaderLive = ConfigLoaderLive.pipe(
|
|
289
706
|
effect.Layer.provide(effect.Layer.mergeAll(platformNode.NodeContext.layer, platform.FetchHttpClient.layer))
|
|
290
707
|
);
|
|
291
708
|
var cachedLoaderPromise = null;
|
|
@@ -293,7 +710,7 @@ function getCachedLoader() {
|
|
|
293
710
|
if (!cachedLoaderPromise) {
|
|
294
711
|
cachedLoaderPromise = effect.Effect.runPromise(
|
|
295
712
|
effect.Effect.gen(function* () {
|
|
296
|
-
return yield*
|
|
713
|
+
return yield* ConfigLoader;
|
|
297
714
|
}).pipe(effect.Effect.provide(NodeConfigLoaderLive))
|
|
298
715
|
);
|
|
299
716
|
}
|
|
@@ -305,7 +722,7 @@ function _resetConfigLoaderCache() {
|
|
|
305
722
|
async function loadConfig(uri, options) {
|
|
306
723
|
if (options?.cacheTimeout === 0) {
|
|
307
724
|
const program = effect.Effect.gen(function* () {
|
|
308
|
-
const loader2 = yield*
|
|
725
|
+
const loader2 = yield* ConfigLoader;
|
|
309
726
|
return yield* loader2.loadFromUri(uri);
|
|
310
727
|
});
|
|
311
728
|
return effect.Effect.runPromise(program.pipe(effect.Effect.provide(NodeConfigLoaderLive)));
|
|
@@ -375,7 +792,7 @@ function buildHttpInstrumentationConfig(options, config, _otlpEndpoint) {
|
|
|
375
792
|
...programmaticPatterns.map((p) => typeof p === "string" ? new RegExp(p) : p),
|
|
376
793
|
...yamlPatterns.map((p) => new RegExp(p))
|
|
377
794
|
];
|
|
378
|
-
|
|
795
|
+
logger.log(`HTTP filtering: ${allOutgoingPatterns.length} outgoing patterns configured`);
|
|
379
796
|
if (options.http?.ignoreOutgoingRequestHook) {
|
|
380
797
|
httpConfig.ignoreOutgoingRequestHook = options.http.ignoreOutgoingRequestHook;
|
|
381
798
|
} else if (allOutgoingPatterns.length > 0) {
|
|
@@ -444,9 +861,9 @@ function shouldEnableAutoInstrumentation(explicitValue, hasWebFramework) {
|
|
|
444
861
|
}
|
|
445
862
|
const isEffect = isEffectProject();
|
|
446
863
|
if (isEffect && !hasWebFramework) {
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
864
|
+
logger.log("@atrim/instrumentation: Detected Effect-TS without web framework");
|
|
865
|
+
logger.log(" - Auto-instrumentation disabled by default");
|
|
866
|
+
logger.log(" - Effect.withSpan() will create spans");
|
|
450
867
|
return false;
|
|
451
868
|
}
|
|
452
869
|
return true;
|
|
@@ -484,11 +901,11 @@ function isTracingAlreadyInitialized() {
|
|
|
484
901
|
}
|
|
485
902
|
async function initializeSdk(options = {}) {
|
|
486
903
|
if (sdkInstance) {
|
|
487
|
-
|
|
904
|
+
logger.warn("@atrim/instrumentation: SDK already initialized. Returning existing instance.");
|
|
488
905
|
return sdkInstance;
|
|
489
906
|
}
|
|
490
907
|
if (initializationPromise) {
|
|
491
|
-
|
|
908
|
+
logger.log(
|
|
492
909
|
"@atrim/instrumentation: SDK already initialized, waiting for initialization to complete..."
|
|
493
910
|
);
|
|
494
911
|
return initializationPromise;
|
|
@@ -504,18 +921,18 @@ async function initializeSdk(options = {}) {
|
|
|
504
921
|
async function performInitialization(options) {
|
|
505
922
|
const config = await loadConfigWithOptions(options);
|
|
506
923
|
const loggingLevel = config.instrumentation.logging || "on";
|
|
507
|
-
|
|
924
|
+
logger.setLevel(loggingLevel);
|
|
508
925
|
const alreadyInitialized = isTracingAlreadyInitialized();
|
|
509
926
|
if (alreadyInitialized) {
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
927
|
+
logger.log("@atrim/instrumentation: Detected existing OpenTelemetry initialization.");
|
|
928
|
+
logger.log(" - Skipping NodeSDK setup");
|
|
929
|
+
logger.log(" - Setting up pattern-based filtering only");
|
|
930
|
+
logger.log("");
|
|
931
|
+
initializePatternMatcher(config);
|
|
932
|
+
logger.log("@atrim/instrumentation: Pattern filtering initialized");
|
|
933
|
+
logger.log(" \u26A0\uFE0F Note: Pattern filtering will only work with manual spans");
|
|
934
|
+
logger.log(" \u26A0\uFE0F Auto-instrumentation must be configured separately");
|
|
935
|
+
logger.log("");
|
|
519
936
|
return null;
|
|
520
937
|
}
|
|
521
938
|
const serviceInfo = await detectServiceInfoAsync();
|
|
@@ -551,7 +968,7 @@ async function performInitialization(options) {
|
|
|
551
968
|
"@opentelemetry/instrumentation-dns": { enabled: false }
|
|
552
969
|
})
|
|
553
970
|
);
|
|
554
|
-
|
|
971
|
+
logger.log(`Auto-instrumentation: ${instrumentations.length} instrumentations enabled`);
|
|
555
972
|
}
|
|
556
973
|
if (options.instrumentations) {
|
|
557
974
|
instrumentations.push(...options.instrumentations);
|
|
@@ -559,14 +976,14 @@ async function performInitialization(options) {
|
|
|
559
976
|
if (!enableAutoInstrumentation && instrumentations.length === 0) {
|
|
560
977
|
const wasExplicit = options.autoInstrument === false;
|
|
561
978
|
const detectionMessage = wasExplicit ? "@atrim/instrumentation: Auto-instrumentation: disabled" : "@atrim/instrumentation: Pure Effect-TS app detected (auto-detected)";
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
979
|
+
logger.log(detectionMessage);
|
|
980
|
+
logger.log(" - Skipping NodeSDK setup");
|
|
981
|
+
logger.log(" - Pattern matching configured from instrumentation.yaml");
|
|
565
982
|
if (!wasExplicit) {
|
|
566
|
-
|
|
983
|
+
logger.log(" - Use EffectInstrumentationLive for tracing");
|
|
567
984
|
}
|
|
568
|
-
|
|
569
|
-
|
|
985
|
+
logger.log("");
|
|
986
|
+
initializePatternMatcher(config);
|
|
570
987
|
return null;
|
|
571
988
|
}
|
|
572
989
|
const sdkConfig = {
|
|
@@ -602,14 +1019,14 @@ function resetSdk() {
|
|
|
602
1019
|
}
|
|
603
1020
|
function registerShutdownHandlers(sdk) {
|
|
604
1021
|
const shutdown = async (signal) => {
|
|
605
|
-
|
|
1022
|
+
logger.log(`
|
|
606
1023
|
@atrim/instrumentation: Received ${signal}, shutting down gracefully...`);
|
|
607
1024
|
try {
|
|
608
1025
|
await sdk.shutdown();
|
|
609
|
-
|
|
1026
|
+
logger.log("@atrim/instrumentation: Shutdown complete");
|
|
610
1027
|
process.exit(0);
|
|
611
1028
|
} catch (error) {
|
|
612
|
-
|
|
1029
|
+
logger.error(
|
|
613
1030
|
"@atrim/instrumentation: Error during shutdown:",
|
|
614
1031
|
error instanceof Error ? error.message : String(error)
|
|
615
1032
|
);
|
|
@@ -619,60 +1036,62 @@ function registerShutdownHandlers(sdk) {
|
|
|
619
1036
|
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
620
1037
|
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
621
1038
|
process.on("uncaughtException", async (error) => {
|
|
622
|
-
|
|
1039
|
+
logger.error("@atrim/instrumentation: Uncaught exception:", error);
|
|
623
1040
|
await sdk.shutdown();
|
|
624
1041
|
process.exit(1);
|
|
625
1042
|
});
|
|
626
1043
|
process.on("unhandledRejection", async (reason) => {
|
|
627
|
-
|
|
1044
|
+
logger.error("@atrim/instrumentation: Unhandled rejection:", reason);
|
|
628
1045
|
await sdk.shutdown();
|
|
629
1046
|
process.exit(1);
|
|
630
1047
|
});
|
|
631
1048
|
}
|
|
632
1049
|
function logInitialization(config, serviceName, serviceVersion, options, autoInstrumentEnabled) {
|
|
633
|
-
|
|
634
|
-
|
|
1050
|
+
logger.minimal("@atrim/instrumentation: SDK initialized successfully");
|
|
1051
|
+
logger.log(` - Service: ${serviceName}${serviceVersion ? ` v${serviceVersion}` : ""}`);
|
|
635
1052
|
if (config.instrumentation.enabled) {
|
|
636
1053
|
const instrumentCount = config.instrumentation.instrument_patterns.filter(
|
|
637
1054
|
(p) => p.enabled !== false
|
|
638
1055
|
).length;
|
|
639
1056
|
const ignoreCount = config.instrumentation.ignore_patterns.length;
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
1057
|
+
logger.log(` - Pattern filtering: enabled`);
|
|
1058
|
+
logger.log(` - Instrument patterns: ${instrumentCount}`);
|
|
1059
|
+
logger.log(` - Ignore patterns: ${ignoreCount}`);
|
|
643
1060
|
} else {
|
|
644
|
-
|
|
1061
|
+
logger.log(` - Pattern filtering: disabled`);
|
|
645
1062
|
}
|
|
646
1063
|
const autoInstrumentLabel = autoInstrumentEnabled ? "enabled" : "disabled";
|
|
647
1064
|
const autoDetected = options.autoInstrument === void 0 ? " (auto-detected)" : "";
|
|
648
|
-
|
|
1065
|
+
logger.log(` - Auto-instrumentation: ${autoInstrumentLabel}${autoDetected}`);
|
|
649
1066
|
if (options.instrumentations && options.instrumentations.length > 0) {
|
|
650
|
-
|
|
1067
|
+
logger.log(` - Custom instrumentations: ${options.instrumentations.length}`);
|
|
651
1068
|
}
|
|
652
1069
|
const endpoint = options.otlp?.endpoint || process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT || process.env.OTEL_EXPORTER_OTLP_ENDPOINT || "http://localhost:4318/v1/traces";
|
|
653
|
-
|
|
654
|
-
|
|
1070
|
+
logger.log(` - OTLP endpoint: ${endpoint}`);
|
|
1071
|
+
logger.log("");
|
|
655
1072
|
}
|
|
1073
|
+
|
|
1074
|
+
// src/api.ts
|
|
656
1075
|
async function initializeInstrumentation(options = {}) {
|
|
657
1076
|
const sdk = await initializeSdk(options);
|
|
658
1077
|
if (sdk) {
|
|
659
1078
|
const config = await loadConfigWithOptions(options);
|
|
660
|
-
|
|
1079
|
+
initializePatternMatcher(config);
|
|
661
1080
|
}
|
|
662
1081
|
return sdk;
|
|
663
1082
|
}
|
|
664
1083
|
async function initializePatternMatchingOnly(options = {}) {
|
|
665
1084
|
const config = await loadConfigWithOptions(options);
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
1085
|
+
initializePatternMatcher(config);
|
|
1086
|
+
logger.log("@atrim/instrumentation: Pattern matching initialized (legacy mode)");
|
|
1087
|
+
logger.log(
|
|
669
1088
|
" Note: NodeSDK is not initialized. Use initializeInstrumentation() for complete setup."
|
|
670
1089
|
);
|
|
671
1090
|
}
|
|
672
1091
|
var initializeInstrumentationEffect = (options = {}) => effect.Effect.gen(function* () {
|
|
673
1092
|
const sdk = yield* effect.Effect.tryPromise({
|
|
674
1093
|
try: () => initializeSdk(options),
|
|
675
|
-
catch: (error) => new
|
|
1094
|
+
catch: (error) => new InitializationError2({
|
|
676
1095
|
reason: "SDK initialization failed",
|
|
677
1096
|
cause: error
|
|
678
1097
|
})
|
|
@@ -680,14 +1099,14 @@ var initializeInstrumentationEffect = (options = {}) => effect.Effect.gen(functi
|
|
|
680
1099
|
if (sdk) {
|
|
681
1100
|
yield* effect.Effect.tryPromise({
|
|
682
1101
|
try: () => loadConfigWithOptions(options),
|
|
683
|
-
catch: (error) => new
|
|
1102
|
+
catch: (error) => new ConfigError2({
|
|
684
1103
|
reason: "Failed to load config for pattern matcher",
|
|
685
1104
|
cause: error
|
|
686
1105
|
})
|
|
687
1106
|
}).pipe(
|
|
688
1107
|
effect.Effect.tap(
|
|
689
1108
|
(config) => effect.Effect.sync(() => {
|
|
690
|
-
|
|
1109
|
+
initializePatternMatcher(config);
|
|
691
1110
|
})
|
|
692
1111
|
)
|
|
693
1112
|
);
|
|
@@ -697,15 +1116,15 @@ var initializeInstrumentationEffect = (options = {}) => effect.Effect.gen(functi
|
|
|
697
1116
|
var initializePatternMatchingOnlyEffect = (options = {}) => effect.Effect.gen(function* () {
|
|
698
1117
|
const config = yield* effect.Effect.tryPromise({
|
|
699
1118
|
try: () => loadConfigWithOptions(options),
|
|
700
|
-
catch: (error) => new
|
|
1119
|
+
catch: (error) => new ConfigError2({
|
|
701
1120
|
reason: "Failed to load configuration",
|
|
702
1121
|
cause: error
|
|
703
1122
|
})
|
|
704
1123
|
});
|
|
705
1124
|
yield* effect.Effect.sync(() => {
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
1125
|
+
initializePatternMatcher(config);
|
|
1126
|
+
logger.log("@atrim/instrumentation: Pattern matching initialized (legacy mode)");
|
|
1127
|
+
logger.log(
|
|
709
1128
|
" Note: NodeSDK is not initialized. Use initializeInstrumentation() for complete setup."
|
|
710
1129
|
);
|
|
711
1130
|
});
|
|
@@ -805,27 +1224,16 @@ function suppressShutdownErrors() {
|
|
|
805
1224
|
});
|
|
806
1225
|
}
|
|
807
1226
|
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
});
|
|
816
|
-
Object.defineProperty(exports, "shouldInstrumentSpan", {
|
|
817
|
-
enumerable: true,
|
|
818
|
-
get: function () { return instrumentCore.shouldInstrumentSpan; }
|
|
819
|
-
});
|
|
820
|
-
exports.ConfigError = ConfigError;
|
|
821
|
-
exports.ConfigFileError = ConfigFileError;
|
|
822
|
-
exports.ConfigUrlError = ConfigUrlError;
|
|
823
|
-
exports.ConfigValidationError = ConfigValidationError;
|
|
824
|
-
exports.ExportError = ExportError;
|
|
825
|
-
exports.InitializationError = InitializationError;
|
|
1227
|
+
exports.ConfigError = ConfigError2;
|
|
1228
|
+
exports.ConfigFileError = ConfigFileError2;
|
|
1229
|
+
exports.ConfigUrlError = ConfigUrlError2;
|
|
1230
|
+
exports.ConfigValidationError = ConfigValidationError2;
|
|
1231
|
+
exports.ExportError = ExportError2;
|
|
1232
|
+
exports.InitializationError = InitializationError2;
|
|
1233
|
+
exports.PatternMatcher = PatternMatcher;
|
|
826
1234
|
exports.PatternSpanProcessor = PatternSpanProcessor;
|
|
827
|
-
exports.ServiceDetectionError =
|
|
828
|
-
exports.ShutdownError =
|
|
1235
|
+
exports.ServiceDetectionError = ServiceDetectionError2;
|
|
1236
|
+
exports.ShutdownError = ShutdownError2;
|
|
829
1237
|
exports.annotateCacheOperation = annotateCacheOperation;
|
|
830
1238
|
exports.annotateDbQuery = annotateDbQuery;
|
|
831
1239
|
exports.annotateHttpRequest = annotateHttpRequest;
|
|
@@ -834,6 +1242,7 @@ exports.createOtlpExporter = createOtlpExporter;
|
|
|
834
1242
|
exports.detectServiceInfo = detectServiceInfoAsync;
|
|
835
1243
|
exports.detectServiceInfoEffect = detectServiceInfo;
|
|
836
1244
|
exports.getOtlpEndpoint = getOtlpEndpoint;
|
|
1245
|
+
exports.getPatternMatcher = getPatternMatcher;
|
|
837
1246
|
exports.getSdkInstance = getSdkInstance;
|
|
838
1247
|
exports.getServiceInfoWithFallback = getServiceInfoWithFallback;
|
|
839
1248
|
exports.getServiceName = getServiceNameAsync;
|
|
@@ -852,6 +1261,7 @@ exports.markSpanSuccess = markSpanSuccess;
|
|
|
852
1261
|
exports.recordException = recordException;
|
|
853
1262
|
exports.resetSdk = resetSdk;
|
|
854
1263
|
exports.setSpanAttributes = setSpanAttributes;
|
|
1264
|
+
exports.shouldInstrumentSpan = shouldInstrumentSpan;
|
|
855
1265
|
exports.shutdownSdk = shutdownSdk;
|
|
856
1266
|
exports.suppressShutdownErrors = suppressShutdownErrors;
|
|
857
1267
|
//# sourceMappingURL=index.cjs.map
|