@atrim/instrument-node 0.7.0 → 0.7.1-dev.14fdea7.20260108220010
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/package.json +6 -1
- package/target/dist/index.cjs +90 -2
- package/target/dist/index.cjs.map +1 -1
- package/target/dist/index.js +90 -2
- package/target/dist/index.js.map +1 -1
- package/target/dist/integrations/effect/auto/index.cjs +1109 -0
- package/target/dist/integrations/effect/auto/index.cjs.map +1 -0
- package/target/dist/integrations/effect/auto/index.d.cts +359 -0
- package/target/dist/integrations/effect/auto/index.d.ts +359 -0
- package/target/dist/integrations/effect/auto/index.js +1066 -0
- package/target/dist/integrations/effect/auto/index.js.map +1 -0
- package/target/dist/integrations/effect/index.cjs +90 -2
- package/target/dist/integrations/effect/index.cjs.map +1 -1
- package/target/dist/integrations/effect/index.js +90 -2
- package/target/dist/integrations/effect/index.js.map +1 -1
|
@@ -0,0 +1,1109 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var effect = require('effect');
|
|
4
|
+
var OtelApi = require('@opentelemetry/api');
|
|
5
|
+
var sdkTraceBase = require('@opentelemetry/sdk-trace-base');
|
|
6
|
+
var exporterTraceOtlpHttp = require('@opentelemetry/exporter-trace-otlp-http');
|
|
7
|
+
var resources = require('@opentelemetry/resources');
|
|
8
|
+
var semanticConventions = require('@opentelemetry/semantic-conventions');
|
|
9
|
+
var FileSystem = require('@effect/platform/FileSystem');
|
|
10
|
+
var HttpClient = require('@effect/platform/HttpClient');
|
|
11
|
+
var HttpClientRequest = require('@effect/platform/HttpClientRequest');
|
|
12
|
+
var yaml = require('yaml');
|
|
13
|
+
var zod = require('zod');
|
|
14
|
+
var path = require('path');
|
|
15
|
+
|
|
16
|
+
function _interopNamespace(e) {
|
|
17
|
+
if (e && e.__esModule) return e;
|
|
18
|
+
var n = Object.create(null);
|
|
19
|
+
if (e) {
|
|
20
|
+
Object.keys(e).forEach(function (k) {
|
|
21
|
+
if (k !== 'default') {
|
|
22
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
23
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
24
|
+
enumerable: true,
|
|
25
|
+
get: function () { return e[k]; }
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
n.default = e;
|
|
31
|
+
return Object.freeze(n);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
var OtelApi__namespace = /*#__PURE__*/_interopNamespace(OtelApi);
|
|
35
|
+
var HttpClient__namespace = /*#__PURE__*/_interopNamespace(HttpClient);
|
|
36
|
+
var HttpClientRequest__namespace = /*#__PURE__*/_interopNamespace(HttpClientRequest);
|
|
37
|
+
var path__namespace = /*#__PURE__*/_interopNamespace(path);
|
|
38
|
+
|
|
39
|
+
var __defProp = Object.defineProperty;
|
|
40
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
41
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
42
|
+
var __defProp2 = Object.defineProperty;
|
|
43
|
+
var __defNormalProp2 = (obj, key, value) => key in obj ? __defProp2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
44
|
+
var __publicField2 = (obj, key, value) => __defNormalProp2(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
45
|
+
var PatternConfigSchema = zod.z.object({
|
|
46
|
+
pattern: zod.z.string(),
|
|
47
|
+
enabled: zod.z.boolean().optional(),
|
|
48
|
+
description: zod.z.string().optional()
|
|
49
|
+
});
|
|
50
|
+
var AutoIsolationConfigSchema = zod.z.object({
|
|
51
|
+
// Global enable/disable for auto-isolation
|
|
52
|
+
enabled: zod.z.boolean().default(false),
|
|
53
|
+
// Which operators to auto-isolate
|
|
54
|
+
operators: zod.z.object({
|
|
55
|
+
fiberset_run: zod.z.boolean().default(true),
|
|
56
|
+
effect_fork: zod.z.boolean().default(true),
|
|
57
|
+
effect_fork_daemon: zod.z.boolean().default(true),
|
|
58
|
+
effect_fork_in: zod.z.boolean().default(false)
|
|
59
|
+
}).default({}),
|
|
60
|
+
// Virtual parent tracking configuration
|
|
61
|
+
tracking: zod.z.object({
|
|
62
|
+
use_span_links: zod.z.boolean().default(true),
|
|
63
|
+
use_attributes: zod.z.boolean().default(true),
|
|
64
|
+
capture_logical_parent: zod.z.boolean().default(true)
|
|
65
|
+
}).default({}),
|
|
66
|
+
// Span categorization
|
|
67
|
+
attributes: zod.z.object({
|
|
68
|
+
category: zod.z.string().default("background_task"),
|
|
69
|
+
add_metadata: zod.z.boolean().default(true)
|
|
70
|
+
}).default({})
|
|
71
|
+
});
|
|
72
|
+
var SpanNamingRuleSchema = zod.z.object({
|
|
73
|
+
// Match criteria (all specified criteria must match)
|
|
74
|
+
match: zod.z.object({
|
|
75
|
+
// Regex pattern to match file path
|
|
76
|
+
file: zod.z.string().optional(),
|
|
77
|
+
// Regex pattern to match function name
|
|
78
|
+
function: zod.z.string().optional(),
|
|
79
|
+
// Regex pattern to match module name
|
|
80
|
+
module: zod.z.string().optional()
|
|
81
|
+
}),
|
|
82
|
+
// Span name template with variables:
|
|
83
|
+
// {fiber_id} - Fiber ID
|
|
84
|
+
// {function} - Function name
|
|
85
|
+
// {module} - Module name
|
|
86
|
+
// {file} - File path
|
|
87
|
+
// {line} - Line number
|
|
88
|
+
// {operator} - Effect operator (gen, all, forEach, etc.)
|
|
89
|
+
// {match:field:N} - Captured regex group from match
|
|
90
|
+
name: zod.z.string()
|
|
91
|
+
});
|
|
92
|
+
var AutoInstrumentationConfigSchema = zod.z.object({
|
|
93
|
+
// Enable/disable auto-instrumentation
|
|
94
|
+
enabled: zod.z.boolean().default(false),
|
|
95
|
+
// Tracing granularity
|
|
96
|
+
// - 'fiber': Trace at fiber creation (recommended, lower overhead)
|
|
97
|
+
// - 'operator': Trace each Effect operator (higher granularity, more overhead)
|
|
98
|
+
granularity: zod.z.enum(["fiber", "operator"]).default("fiber"),
|
|
99
|
+
// Smart span naming configuration
|
|
100
|
+
span_naming: zod.z.object({
|
|
101
|
+
// Default span name template when no rules match
|
|
102
|
+
default: zod.z.string().default("effect.fiber.{fiber_id}"),
|
|
103
|
+
// Infer span names from source code (requires stack trace parsing)
|
|
104
|
+
// Adds ~50-100μs overhead per fiber
|
|
105
|
+
infer_from_source: zod.z.boolean().default(true),
|
|
106
|
+
// Naming rules (first match wins)
|
|
107
|
+
rules: zod.z.array(SpanNamingRuleSchema).default([])
|
|
108
|
+
}).default({}),
|
|
109
|
+
// Pattern-based filtering
|
|
110
|
+
filter: zod.z.object({
|
|
111
|
+
// Only trace spans matching these patterns (empty = trace all)
|
|
112
|
+
include: zod.z.array(zod.z.string()).default([]),
|
|
113
|
+
// Never trace spans matching these patterns
|
|
114
|
+
exclude: zod.z.array(zod.z.string()).default([])
|
|
115
|
+
}).default({}),
|
|
116
|
+
// Performance controls
|
|
117
|
+
performance: zod.z.object({
|
|
118
|
+
// Sample rate (0.0 - 1.0)
|
|
119
|
+
sampling_rate: zod.z.number().min(0).max(1).default(1),
|
|
120
|
+
// Skip fibers shorter than this duration (e.g., "10ms", "100 millis")
|
|
121
|
+
min_duration: zod.z.string().default("0ms"),
|
|
122
|
+
// Maximum concurrent traced fibers (0 = unlimited)
|
|
123
|
+
max_concurrent: zod.z.number().default(0)
|
|
124
|
+
}).default({}),
|
|
125
|
+
// Automatic metadata extraction
|
|
126
|
+
metadata: zod.z.object({
|
|
127
|
+
// Extract Effect fiber information
|
|
128
|
+
fiber_info: zod.z.boolean().default(true),
|
|
129
|
+
// Extract source location (file:line)
|
|
130
|
+
source_location: zod.z.boolean().default(true),
|
|
131
|
+
// Extract parent fiber information
|
|
132
|
+
parent_fiber: zod.z.boolean().default(true)
|
|
133
|
+
}).default({})
|
|
134
|
+
});
|
|
135
|
+
var HttpFilteringConfigSchema = zod.z.object({
|
|
136
|
+
// Patterns to ignore for outgoing HTTP requests (string patterns only in YAML)
|
|
137
|
+
ignore_outgoing_urls: zod.z.array(zod.z.string()).optional(),
|
|
138
|
+
// Patterns to ignore for incoming HTTP requests (string patterns only in YAML)
|
|
139
|
+
ignore_incoming_paths: zod.z.array(zod.z.string()).optional(),
|
|
140
|
+
// Require parent span for outgoing requests (prevents root spans for HTTP calls)
|
|
141
|
+
require_parent_for_outgoing_spans: zod.z.boolean().optional(),
|
|
142
|
+
// Trace context propagation configuration
|
|
143
|
+
// Controls which cross-origin requests receive W3C Trace Context headers (traceparent, tracestate)
|
|
144
|
+
propagate_trace_context: zod.z.object({
|
|
145
|
+
// Strategy for trace propagation
|
|
146
|
+
// - "all": Propagate to all cross-origin requests (may cause CORS errors)
|
|
147
|
+
// - "none": Never propagate trace headers
|
|
148
|
+
// - "same-origin": Only propagate to same-origin requests (default, safe)
|
|
149
|
+
// - "patterns": Propagate based on include_urls patterns
|
|
150
|
+
strategy: zod.z.enum(["all", "none", "same-origin", "patterns"]).default("same-origin"),
|
|
151
|
+
// URL patterns to include when strategy is "patterns"
|
|
152
|
+
// Supports regex patterns (e.g., "^https://api\\.myapp\\.com")
|
|
153
|
+
include_urls: zod.z.array(zod.z.string()).optional()
|
|
154
|
+
}).optional()
|
|
155
|
+
});
|
|
156
|
+
var ExporterConfigSchema = zod.z.object({
|
|
157
|
+
// Exporter type: 'otlp' | 'console' | 'none'
|
|
158
|
+
// - 'otlp': Export to OTLP endpoint (production)
|
|
159
|
+
// - 'console': Log spans to console (development)
|
|
160
|
+
// - 'none': No export (disable tracing)
|
|
161
|
+
type: zod.z.enum(["otlp", "console", "none"]).default("otlp"),
|
|
162
|
+
// OTLP endpoint URL (for type: otlp)
|
|
163
|
+
// Defaults to OTEL_EXPORTER_OTLP_ENDPOINT env var or http://localhost:4318
|
|
164
|
+
endpoint: zod.z.string().optional(),
|
|
165
|
+
// Span processor type
|
|
166
|
+
// - 'batch': Batch spans for export (production, lower overhead)
|
|
167
|
+
// - 'simple': Export immediately (development, no batching delay)
|
|
168
|
+
processor: zod.z.enum(["batch", "simple"]).default("batch"),
|
|
169
|
+
// Batch processor settings (for processor: batch)
|
|
170
|
+
batch: zod.z.object({
|
|
171
|
+
// Max time to wait before exporting (milliseconds)
|
|
172
|
+
scheduled_delay_millis: zod.z.number().default(1e3),
|
|
173
|
+
// Max batch size
|
|
174
|
+
max_export_batch_size: zod.z.number().default(100)
|
|
175
|
+
}).optional()
|
|
176
|
+
});
|
|
177
|
+
var InstrumentationConfigSchema = zod.z.object({
|
|
178
|
+
version: zod.z.string(),
|
|
179
|
+
instrumentation: zod.z.object({
|
|
180
|
+
enabled: zod.z.boolean(),
|
|
181
|
+
description: zod.z.string().optional(),
|
|
182
|
+
logging: zod.z.enum(["on", "off", "minimal"]).optional().default("on"),
|
|
183
|
+
instrument_patterns: zod.z.array(PatternConfigSchema),
|
|
184
|
+
ignore_patterns: zod.z.array(PatternConfigSchema)
|
|
185
|
+
}),
|
|
186
|
+
effect: zod.z.object({
|
|
187
|
+
// Enable/disable Effect tracing entirely
|
|
188
|
+
// When false, EffectInstrumentationLive returns Layer.empty
|
|
189
|
+
enabled: zod.z.boolean().default(true),
|
|
190
|
+
// Exporter mode (legacy - use exporter.type instead):
|
|
191
|
+
// - "unified": Use global TracerProvider from Node SDK (recommended, enables filtering)
|
|
192
|
+
// - "standalone": Use Effect's own OTLP exporter (bypasses Node SDK filtering)
|
|
193
|
+
exporter: zod.z.enum(["unified", "standalone"]).default("unified"),
|
|
194
|
+
// Exporter configuration (for auto-instrumentation)
|
|
195
|
+
exporter_config: ExporterConfigSchema.optional(),
|
|
196
|
+
auto_extract_metadata: zod.z.boolean(),
|
|
197
|
+
auto_isolation: AutoIsolationConfigSchema.optional(),
|
|
198
|
+
// Auto-instrumentation: automatic tracing of all Effect fibers
|
|
199
|
+
auto_instrumentation: AutoInstrumentationConfigSchema.optional()
|
|
200
|
+
}).optional(),
|
|
201
|
+
http: HttpFilteringConfigSchema.optional()
|
|
202
|
+
});
|
|
203
|
+
var defaultConfig = {
|
|
204
|
+
version: "1.0",
|
|
205
|
+
instrumentation: {
|
|
206
|
+
enabled: true,
|
|
207
|
+
logging: "on",
|
|
208
|
+
description: "Default instrumentation configuration",
|
|
209
|
+
instrument_patterns: [
|
|
210
|
+
{ pattern: "^app\\.", enabled: true, description: "Application operations" },
|
|
211
|
+
{ pattern: "^http\\.server\\.", enabled: true, description: "HTTP server operations" },
|
|
212
|
+
{ pattern: "^http\\.client\\.", enabled: true, description: "HTTP client operations" }
|
|
213
|
+
],
|
|
214
|
+
ignore_patterns: [
|
|
215
|
+
{ pattern: "^test\\.", description: "Test utilities" },
|
|
216
|
+
{ pattern: "^internal\\.", description: "Internal operations" },
|
|
217
|
+
{ pattern: "^health\\.", description: "Health checks" }
|
|
218
|
+
]
|
|
219
|
+
},
|
|
220
|
+
effect: {
|
|
221
|
+
enabled: true,
|
|
222
|
+
exporter: "unified",
|
|
223
|
+
auto_extract_metadata: true
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
function parseAndValidateConfig(content) {
|
|
227
|
+
let parsed;
|
|
228
|
+
if (typeof content === "string") {
|
|
229
|
+
parsed = yaml.parse(content);
|
|
230
|
+
} else {
|
|
231
|
+
parsed = content;
|
|
232
|
+
}
|
|
233
|
+
return InstrumentationConfigSchema.parse(parsed);
|
|
234
|
+
}
|
|
235
|
+
(class extends effect.Data.TaggedError("ConfigError") {
|
|
236
|
+
get message() {
|
|
237
|
+
return this.reason;
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
var ConfigUrlError = class extends effect.Data.TaggedError("ConfigUrlError") {
|
|
241
|
+
get message() {
|
|
242
|
+
return this.reason;
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
var ConfigValidationError = class extends effect.Data.TaggedError("ConfigValidationError") {
|
|
246
|
+
get message() {
|
|
247
|
+
return this.reason;
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
var ConfigFileError = class extends effect.Data.TaggedError("ConfigFileError") {
|
|
251
|
+
get message() {
|
|
252
|
+
return this.reason;
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
(class extends effect.Data.TaggedError("ServiceDetectionError") {
|
|
256
|
+
get message() {
|
|
257
|
+
return this.reason;
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
(class extends effect.Data.TaggedError("InitializationError") {
|
|
261
|
+
get message() {
|
|
262
|
+
return this.reason;
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
(class extends effect.Data.TaggedError("ExportError") {
|
|
266
|
+
get message() {
|
|
267
|
+
return this.reason;
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
(class extends effect.Data.TaggedError("ShutdownError") {
|
|
271
|
+
get message() {
|
|
272
|
+
return this.reason;
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
var SECURITY_DEFAULTS = {
|
|
276
|
+
maxConfigSize: 1e6,
|
|
277
|
+
// 1MB
|
|
278
|
+
requestTimeout: 5e3
|
|
279
|
+
// 5 seconds
|
|
280
|
+
};
|
|
281
|
+
var ConfigLoader = class extends effect.Context.Tag("ConfigLoader")() {
|
|
282
|
+
};
|
|
283
|
+
var parseYamlContent = (content, uri) => effect.Effect.gen(function* () {
|
|
284
|
+
const parsed = yield* effect.Effect.try({
|
|
285
|
+
try: () => yaml.parse(content),
|
|
286
|
+
catch: (error) => new ConfigValidationError({
|
|
287
|
+
reason: uri ? `Failed to parse YAML from ${uri}` : "Failed to parse YAML",
|
|
288
|
+
cause: error
|
|
289
|
+
})
|
|
290
|
+
});
|
|
291
|
+
return yield* effect.Effect.try({
|
|
292
|
+
try: () => InstrumentationConfigSchema.parse(parsed),
|
|
293
|
+
catch: (error) => new ConfigValidationError({
|
|
294
|
+
reason: uri ? `Invalid configuration schema from ${uri}` : "Invalid configuration schema",
|
|
295
|
+
cause: error
|
|
296
|
+
})
|
|
297
|
+
});
|
|
298
|
+
});
|
|
299
|
+
var loadFromFileWithFs = (fs, path2, uri) => effect.Effect.gen(function* () {
|
|
300
|
+
const content = yield* fs.readFileString(path2).pipe(
|
|
301
|
+
effect.Effect.mapError(
|
|
302
|
+
(error) => new ConfigFileError({
|
|
303
|
+
reason: `Failed to read config file at ${uri}`,
|
|
304
|
+
cause: error
|
|
305
|
+
})
|
|
306
|
+
)
|
|
307
|
+
);
|
|
308
|
+
if (content.length > SECURITY_DEFAULTS.maxConfigSize) {
|
|
309
|
+
return yield* effect.Effect.fail(
|
|
310
|
+
new ConfigFileError({
|
|
311
|
+
reason: `Config file exceeds maximum size of ${SECURITY_DEFAULTS.maxConfigSize} bytes`
|
|
312
|
+
})
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
return yield* parseYamlContent(content, uri);
|
|
316
|
+
});
|
|
317
|
+
var loadFromHttpWithClient = (client, url) => effect.Effect.scoped(
|
|
318
|
+
effect.Effect.gen(function* () {
|
|
319
|
+
if (url.startsWith("http://")) {
|
|
320
|
+
return yield* effect.Effect.fail(
|
|
321
|
+
new ConfigUrlError({
|
|
322
|
+
reason: "Insecure protocol: only HTTPS URLs are allowed"
|
|
323
|
+
})
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
const request = HttpClientRequest__namespace.get(url).pipe(
|
|
327
|
+
HttpClientRequest__namespace.setHeaders({
|
|
328
|
+
Accept: "application/yaml, text/yaml, application/x-yaml"
|
|
329
|
+
})
|
|
330
|
+
);
|
|
331
|
+
const response = yield* client.execute(request).pipe(
|
|
332
|
+
effect.Effect.timeout(`${SECURITY_DEFAULTS.requestTimeout} millis`),
|
|
333
|
+
effect.Effect.mapError((error) => {
|
|
334
|
+
if (error._tag === "TimeoutException") {
|
|
335
|
+
return new ConfigUrlError({
|
|
336
|
+
reason: `Config fetch timeout after ${SECURITY_DEFAULTS.requestTimeout}ms from ${url}`
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
return new ConfigUrlError({
|
|
340
|
+
reason: `Failed to load config from URL: ${url}`,
|
|
341
|
+
cause: error
|
|
342
|
+
});
|
|
343
|
+
})
|
|
344
|
+
);
|
|
345
|
+
if (response.status >= 400) {
|
|
346
|
+
return yield* effect.Effect.fail(
|
|
347
|
+
new ConfigUrlError({
|
|
348
|
+
reason: `HTTP ${response.status} from ${url}`
|
|
349
|
+
})
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
const text = yield* response.text.pipe(
|
|
353
|
+
effect.Effect.mapError(
|
|
354
|
+
(error) => new ConfigUrlError({
|
|
355
|
+
reason: `Failed to read response body from ${url}`,
|
|
356
|
+
cause: error
|
|
357
|
+
})
|
|
358
|
+
)
|
|
359
|
+
);
|
|
360
|
+
if (text.length > SECURITY_DEFAULTS.maxConfigSize) {
|
|
361
|
+
return yield* effect.Effect.fail(
|
|
362
|
+
new ConfigUrlError({
|
|
363
|
+
reason: `Config exceeds maximum size of ${SECURITY_DEFAULTS.maxConfigSize} bytes`
|
|
364
|
+
})
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
return yield* parseYamlContent(text, url);
|
|
368
|
+
})
|
|
369
|
+
);
|
|
370
|
+
var makeConfigLoader = effect.Effect.gen(function* () {
|
|
371
|
+
const fs = yield* effect.Effect.serviceOption(FileSystem.FileSystem);
|
|
372
|
+
const http = yield* HttpClient__namespace.HttpClient;
|
|
373
|
+
const loadFromUriUncached = (uri) => effect.Effect.gen(function* () {
|
|
374
|
+
if (uri.startsWith("file://")) {
|
|
375
|
+
const path2 = uri.slice(7);
|
|
376
|
+
if (fs._tag === "None") {
|
|
377
|
+
return yield* effect.Effect.fail(
|
|
378
|
+
new ConfigFileError({
|
|
379
|
+
reason: "FileSystem not available (browser environment?)",
|
|
380
|
+
cause: { uri }
|
|
381
|
+
})
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
return yield* loadFromFileWithFs(fs.value, path2, uri);
|
|
385
|
+
}
|
|
386
|
+
if (uri.startsWith("http://") || uri.startsWith("https://")) {
|
|
387
|
+
return yield* loadFromHttpWithClient(http, uri);
|
|
388
|
+
}
|
|
389
|
+
if (fs._tag === "Some") {
|
|
390
|
+
return yield* loadFromFileWithFs(fs.value, uri, uri);
|
|
391
|
+
} else {
|
|
392
|
+
return yield* loadFromHttpWithClient(http, uri);
|
|
393
|
+
}
|
|
394
|
+
});
|
|
395
|
+
const loadFromUriCached = yield* effect.Effect.cachedFunction(loadFromUriUncached);
|
|
396
|
+
return ConfigLoader.of({
|
|
397
|
+
loadFromUri: loadFromUriCached,
|
|
398
|
+
loadFromInline: (content) => effect.Effect.gen(function* () {
|
|
399
|
+
if (typeof content === "string") {
|
|
400
|
+
return yield* parseYamlContent(content);
|
|
401
|
+
}
|
|
402
|
+
return yield* effect.Effect.try({
|
|
403
|
+
try: () => InstrumentationConfigSchema.parse(content),
|
|
404
|
+
catch: (error) => new ConfigValidationError({
|
|
405
|
+
reason: "Invalid configuration schema",
|
|
406
|
+
cause: error
|
|
407
|
+
})
|
|
408
|
+
});
|
|
409
|
+
})
|
|
410
|
+
});
|
|
411
|
+
});
|
|
412
|
+
effect.Layer.effect(ConfigLoader, makeConfigLoader);
|
|
413
|
+
var Logger = class {
|
|
414
|
+
constructor() {
|
|
415
|
+
__publicField2(this, "level", "on");
|
|
416
|
+
__publicField2(this, "hasLoggedMinimal", false);
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Set the logging level
|
|
420
|
+
*/
|
|
421
|
+
setLevel(level) {
|
|
422
|
+
this.level = level;
|
|
423
|
+
this.hasLoggedMinimal = false;
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* Get the current logging level
|
|
427
|
+
*/
|
|
428
|
+
getLevel() {
|
|
429
|
+
return this.level;
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* Log a minimal initialization message (only shown once in minimal mode)
|
|
433
|
+
*/
|
|
434
|
+
minimal(message) {
|
|
435
|
+
if (this.level === "off") {
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
if (this.level === "minimal" && !this.hasLoggedMinimal) {
|
|
439
|
+
console.log(message);
|
|
440
|
+
this.hasLoggedMinimal = true;
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
if (this.level === "on") {
|
|
444
|
+
console.log(message);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* Log an informational message
|
|
449
|
+
*/
|
|
450
|
+
log(...args) {
|
|
451
|
+
if (this.level === "on") {
|
|
452
|
+
console.log(...args);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* Log a warning message (shown in minimal mode)
|
|
457
|
+
*/
|
|
458
|
+
warn(...args) {
|
|
459
|
+
if (this.level !== "off") {
|
|
460
|
+
console.warn(...args);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* Log an error message (shown in minimal mode)
|
|
465
|
+
*/
|
|
466
|
+
error(...args) {
|
|
467
|
+
if (this.level !== "off") {
|
|
468
|
+
console.error(...args);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
/**
|
|
472
|
+
* Check if full logging is enabled
|
|
473
|
+
*/
|
|
474
|
+
isEnabled() {
|
|
475
|
+
return this.level === "on";
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Check if minimal logging is enabled
|
|
479
|
+
*/
|
|
480
|
+
isMinimal() {
|
|
481
|
+
return this.level === "minimal";
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* Check if logging is completely disabled
|
|
485
|
+
*/
|
|
486
|
+
isDisabled() {
|
|
487
|
+
return this.level === "off";
|
|
488
|
+
}
|
|
489
|
+
};
|
|
490
|
+
var logger = new Logger();
|
|
491
|
+
var compiledRulesCache = /* @__PURE__ */ new WeakMap();
|
|
492
|
+
function compileNamingRules(rules) {
|
|
493
|
+
const cached = compiledRulesCache.get(rules);
|
|
494
|
+
if (cached) return cached;
|
|
495
|
+
const compiled = rules.map((rule) => ({
|
|
496
|
+
original: rule,
|
|
497
|
+
filePattern: rule.match.file ? new RegExp(rule.match.file) : void 0,
|
|
498
|
+
functionPattern: rule.match.function ? new RegExp(rule.match.function) : void 0,
|
|
499
|
+
modulePattern: rule.match.module ? new RegExp(rule.match.module) : void 0
|
|
500
|
+
}));
|
|
501
|
+
compiledRulesCache.set(rules, compiled);
|
|
502
|
+
return compiled;
|
|
503
|
+
}
|
|
504
|
+
function extractModuleName(filePath) {
|
|
505
|
+
const basename2 = path__namespace.basename(filePath, path__namespace.extname(filePath));
|
|
506
|
+
return basename2.replace(/\.(service|controller|handler|util|helper|model|repo|repository)$/i, "").replace(/(Service|Controller|Handler|Util|Helper|Model|Repo|Repository)$/i, "");
|
|
507
|
+
}
|
|
508
|
+
function applyTemplate(template, variables, matchGroups) {
|
|
509
|
+
let result = template;
|
|
510
|
+
for (const [key, value] of Object.entries(variables)) {
|
|
511
|
+
result = result.replace(new RegExp(`\\{${key}\\}`, "g"), value);
|
|
512
|
+
}
|
|
513
|
+
if (matchGroups) {
|
|
514
|
+
result = result.replace(
|
|
515
|
+
/\{match:(\w+):(\d+)\}/g,
|
|
516
|
+
(_fullMatch, field, index) => {
|
|
517
|
+
const groups = matchGroups.get(field);
|
|
518
|
+
const idx = parseInt(index, 10);
|
|
519
|
+
if (groups && groups[idx]) {
|
|
520
|
+
return groups[idx];
|
|
521
|
+
}
|
|
522
|
+
return "";
|
|
523
|
+
}
|
|
524
|
+
);
|
|
525
|
+
}
|
|
526
|
+
return result;
|
|
527
|
+
}
|
|
528
|
+
function findMatchingRule(sourceInfo, config) {
|
|
529
|
+
const rules = config.span_naming?.rules;
|
|
530
|
+
if (!rules || rules.length === 0) return void 0;
|
|
531
|
+
const compiledRules = compileNamingRules(rules);
|
|
532
|
+
for (const rule of compiledRules) {
|
|
533
|
+
const matchGroups = /* @__PURE__ */ new Map();
|
|
534
|
+
let allMatched = true;
|
|
535
|
+
if (rule.filePattern) {
|
|
536
|
+
if (sourceInfo?.file) {
|
|
537
|
+
const match = sourceInfo.file.match(rule.filePattern);
|
|
538
|
+
if (match) {
|
|
539
|
+
matchGroups.set("file", match.slice(1));
|
|
540
|
+
} else {
|
|
541
|
+
allMatched = false;
|
|
542
|
+
}
|
|
543
|
+
} else {
|
|
544
|
+
allMatched = false;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
if (rule.functionPattern && allMatched) {
|
|
548
|
+
if (sourceInfo?.function) {
|
|
549
|
+
const match = sourceInfo.function.match(rule.functionPattern);
|
|
550
|
+
if (match) {
|
|
551
|
+
matchGroups.set("function", match.slice(1));
|
|
552
|
+
} else {
|
|
553
|
+
allMatched = false;
|
|
554
|
+
}
|
|
555
|
+
} else {
|
|
556
|
+
allMatched = false;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
if (rule.modulePattern && allMatched) {
|
|
560
|
+
const moduleName = sourceInfo?.file ? extractModuleName(sourceInfo.file) : "";
|
|
561
|
+
if (moduleName) {
|
|
562
|
+
const match = moduleName.match(rule.modulePattern);
|
|
563
|
+
if (match) {
|
|
564
|
+
matchGroups.set("module", match.slice(1));
|
|
565
|
+
} else {
|
|
566
|
+
allMatched = false;
|
|
567
|
+
}
|
|
568
|
+
} else {
|
|
569
|
+
allMatched = false;
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
if (allMatched) {
|
|
573
|
+
return { rule, matchGroups };
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
return void 0;
|
|
577
|
+
}
|
|
578
|
+
function inferSpanName(fiberId, sourceInfo, config) {
|
|
579
|
+
const variables = {
|
|
580
|
+
fiber_id: String(fiberId),
|
|
581
|
+
function: sourceInfo?.function || "anonymous",
|
|
582
|
+
module: sourceInfo?.file ? extractModuleName(sourceInfo.file) : "unknown",
|
|
583
|
+
file: sourceInfo?.file || "unknown",
|
|
584
|
+
line: sourceInfo?.line ? String(sourceInfo.line) : "0",
|
|
585
|
+
operator: "gen"
|
|
586
|
+
// Default operator, could be enhanced with more context
|
|
587
|
+
};
|
|
588
|
+
const match = findMatchingRule(sourceInfo, config);
|
|
589
|
+
if (match) {
|
|
590
|
+
return applyTemplate(match.rule.original.name, variables, match.matchGroups);
|
|
591
|
+
}
|
|
592
|
+
const defaultTemplate = config.span_naming?.default || "effect.fiber.{fiber_id}";
|
|
593
|
+
return applyTemplate(defaultTemplate, variables);
|
|
594
|
+
}
|
|
595
|
+
function sanitizeSpanName(name) {
|
|
596
|
+
if (!name) return "effect.fiber.unknown";
|
|
597
|
+
let sanitized = name.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
598
|
+
sanitized = sanitized.replace(/_+/g, "_");
|
|
599
|
+
sanitized = sanitized.replace(/^_+|_+$/g, "");
|
|
600
|
+
if (!sanitized) return "effect.fiber.unknown";
|
|
601
|
+
if (sanitized.length > 256) {
|
|
602
|
+
sanitized = sanitized.substring(0, 256);
|
|
603
|
+
}
|
|
604
|
+
return sanitized;
|
|
605
|
+
}
|
|
606
|
+
async function loadFromFile(filePath) {
|
|
607
|
+
const { readFile } = await import('fs/promises');
|
|
608
|
+
const content = await readFile(filePath, "utf-8");
|
|
609
|
+
return parseAndValidateConfig(content);
|
|
610
|
+
}
|
|
611
|
+
async function loadFromUrl(url) {
|
|
612
|
+
const response = await fetch(url);
|
|
613
|
+
if (!response.ok) {
|
|
614
|
+
throw new Error(`Failed to fetch config from ${url}: ${response.statusText}`);
|
|
615
|
+
}
|
|
616
|
+
const content = await response.text();
|
|
617
|
+
return parseAndValidateConfig(content);
|
|
618
|
+
}
|
|
619
|
+
async function loadConfig(uri, _options) {
|
|
620
|
+
if (uri.startsWith("http://") || uri.startsWith("https://")) {
|
|
621
|
+
return loadFromUrl(uri);
|
|
622
|
+
}
|
|
623
|
+
if (uri.startsWith("file://")) {
|
|
624
|
+
const filePath = uri.slice(7);
|
|
625
|
+
return loadFromFile(filePath);
|
|
626
|
+
}
|
|
627
|
+
return loadFromFile(uri);
|
|
628
|
+
}
|
|
629
|
+
async function loadConfigFromInline(content) {
|
|
630
|
+
return parseAndValidateConfig(content);
|
|
631
|
+
}
|
|
632
|
+
async function loadConfigWithOptions(options = {}) {
|
|
633
|
+
if (options.config) {
|
|
634
|
+
return loadConfigFromInline(options.config);
|
|
635
|
+
}
|
|
636
|
+
const envConfigPath = process.env.ATRIM_INSTRUMENTATION_CONFIG;
|
|
637
|
+
if (envConfigPath) {
|
|
638
|
+
return loadConfig(envConfigPath);
|
|
639
|
+
}
|
|
640
|
+
if (options.configUrl) {
|
|
641
|
+
return loadConfig(options.configUrl);
|
|
642
|
+
}
|
|
643
|
+
if (options.configPath) {
|
|
644
|
+
return loadConfig(options.configPath);
|
|
645
|
+
}
|
|
646
|
+
const { existsSync } = await import('fs');
|
|
647
|
+
const { join } = await import('path');
|
|
648
|
+
const defaultPath = join(process.cwd(), "instrumentation.yaml");
|
|
649
|
+
if (existsSync(defaultPath)) {
|
|
650
|
+
return loadConfig(defaultPath);
|
|
651
|
+
}
|
|
652
|
+
return defaultConfig;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
// src/integrations/effect/auto/config.ts
|
|
656
|
+
var defaultAutoTracingConfig = {
|
|
657
|
+
enabled: false,
|
|
658
|
+
granularity: "fiber",
|
|
659
|
+
span_naming: {
|
|
660
|
+
default: "effect.fiber.{fiber_id}",
|
|
661
|
+
infer_from_source: true,
|
|
662
|
+
rules: []
|
|
663
|
+
},
|
|
664
|
+
filter: {
|
|
665
|
+
include: [],
|
|
666
|
+
exclude: []
|
|
667
|
+
},
|
|
668
|
+
performance: {
|
|
669
|
+
sampling_rate: 1,
|
|
670
|
+
min_duration: "0ms",
|
|
671
|
+
max_concurrent: 0
|
|
672
|
+
},
|
|
673
|
+
metadata: {
|
|
674
|
+
fiber_info: true,
|
|
675
|
+
source_location: true,
|
|
676
|
+
parent_fiber: true
|
|
677
|
+
}
|
|
678
|
+
};
|
|
679
|
+
var AutoTracingConfig = class extends effect.Context.Tag("AutoTracingConfig")() {
|
|
680
|
+
};
|
|
681
|
+
var loadAutoTracingConfig = (options) => effect.Effect.gen(function* () {
|
|
682
|
+
const config = yield* effect.Effect.tryPromise({
|
|
683
|
+
try: () => loadConfigWithOptions(options),
|
|
684
|
+
catch: (error) => {
|
|
685
|
+
logger.log(`@atrim/auto-trace: Failed to load config: ${error}`);
|
|
686
|
+
return error;
|
|
687
|
+
}
|
|
688
|
+
}).pipe(effect.Effect.catchAll(() => effect.Effect.succeed(null)));
|
|
689
|
+
if (!config) {
|
|
690
|
+
logger.log("@atrim/auto-trace: No config found, using defaults");
|
|
691
|
+
return defaultAutoTracingConfig;
|
|
692
|
+
}
|
|
693
|
+
const autoConfig = config.effect?.auto_instrumentation;
|
|
694
|
+
if (!autoConfig) {
|
|
695
|
+
logger.log("@atrim/auto-trace: No auto_instrumentation config, using defaults");
|
|
696
|
+
return defaultAutoTracingConfig;
|
|
697
|
+
}
|
|
698
|
+
const parsed = AutoInstrumentationConfigSchema.safeParse(autoConfig);
|
|
699
|
+
if (!parsed.success) {
|
|
700
|
+
logger.log(`@atrim/auto-trace: Invalid config, using defaults: ${parsed.error.message}`);
|
|
701
|
+
return defaultAutoTracingConfig;
|
|
702
|
+
}
|
|
703
|
+
logger.log("@atrim/auto-trace: Loaded config from instrumentation.yaml");
|
|
704
|
+
return parsed.data;
|
|
705
|
+
});
|
|
706
|
+
var loadAutoTracingConfigSync = () => {
|
|
707
|
+
return defaultAutoTracingConfig;
|
|
708
|
+
};
|
|
709
|
+
var AutoTracingConfigLive = effect.Layer.effect(AutoTracingConfig, loadAutoTracingConfig());
|
|
710
|
+
var AutoTracingConfigLayer = (config) => effect.Layer.succeed(AutoTracingConfig, config);
|
|
711
|
+
var loadFullConfig = (options) => effect.Effect.gen(function* () {
|
|
712
|
+
const config = yield* effect.Effect.tryPromise({
|
|
713
|
+
try: () => loadConfigWithOptions(options),
|
|
714
|
+
catch: (error) => {
|
|
715
|
+
logger.log(`@atrim/auto-trace: Failed to load config: ${error}`);
|
|
716
|
+
return error;
|
|
717
|
+
}
|
|
718
|
+
}).pipe(effect.Effect.catchAll(() => effect.Effect.succeed(null)));
|
|
719
|
+
if (!config) {
|
|
720
|
+
logger.log("@atrim/auto-trace: No config found, using defaults");
|
|
721
|
+
return defaultConfig;
|
|
722
|
+
}
|
|
723
|
+
return config;
|
|
724
|
+
});
|
|
725
|
+
|
|
726
|
+
// src/integrations/effect/auto/supervisor.ts
|
|
727
|
+
var AutoTracingEnabled = effect.FiberRef.unsafeMake(true);
|
|
728
|
+
var AutoTracingSpanName = effect.FiberRef.unsafeMake(effect.Option.none());
|
|
729
|
+
var AutoTracingSupervisor = class extends effect.Supervisor.AbstractSupervisor {
|
|
730
|
+
constructor(config) {
|
|
731
|
+
super();
|
|
732
|
+
this.config = config;
|
|
733
|
+
// WeakMap to associate fibers with their OTel spans
|
|
734
|
+
__publicField(this, "fiberSpans", /* @__PURE__ */ new WeakMap());
|
|
735
|
+
// WeakMap for fiber start times (for min_duration filtering)
|
|
736
|
+
__publicField(this, "fiberStartTimes", /* @__PURE__ */ new WeakMap());
|
|
737
|
+
// OpenTelemetry tracer - lazily initialized
|
|
738
|
+
__publicField(this, "_tracer", null);
|
|
739
|
+
// Compiled filter patterns
|
|
740
|
+
__publicField(this, "includePatterns");
|
|
741
|
+
__publicField(this, "excludePatterns");
|
|
742
|
+
// Active fiber count (for max_concurrent limiting)
|
|
743
|
+
__publicField(this, "activeFiberCount", 0);
|
|
744
|
+
// Root span for parent context (set by withAutoTracing)
|
|
745
|
+
__publicField(this, "_rootSpan", null);
|
|
746
|
+
this.includePatterns = (config.filter?.include || []).map((p) => new RegExp(p));
|
|
747
|
+
this.excludePatterns = (config.filter?.exclude || []).map((p) => new RegExp(p));
|
|
748
|
+
logger.log("@atrim/auto-trace: Supervisor initialized");
|
|
749
|
+
logger.log(` Granularity: ${config.granularity || "fiber"}`);
|
|
750
|
+
logger.log(` Sampling rate: ${config.performance?.sampling_rate ?? 1}`);
|
|
751
|
+
logger.log(` Infer from source: ${config.span_naming?.infer_from_source ?? true}`);
|
|
752
|
+
}
|
|
753
|
+
/**
|
|
754
|
+
* Set the root span for parent context propagation
|
|
755
|
+
*/
|
|
756
|
+
setRootSpan(span) {
|
|
757
|
+
this._rootSpan = span;
|
|
758
|
+
}
|
|
759
|
+
/**
|
|
760
|
+
* Get the tracer lazily - this allows time for the NodeSdk layer to register the global provider
|
|
761
|
+
*/
|
|
762
|
+
get tracer() {
|
|
763
|
+
if (!this._tracer) {
|
|
764
|
+
this._tracer = OtelApi__namespace.trace.getTracer("@atrim/auto-trace", "1.0.0");
|
|
765
|
+
}
|
|
766
|
+
return this._tracer;
|
|
767
|
+
}
|
|
768
|
+
/**
|
|
769
|
+
* Returns the current value (void for this supervisor)
|
|
770
|
+
*/
|
|
771
|
+
get value() {
|
|
772
|
+
return effect.Effect.void;
|
|
773
|
+
}
|
|
774
|
+
/**
|
|
775
|
+
* Called when a fiber starts executing
|
|
776
|
+
*/
|
|
777
|
+
onStart(_context, _effect, parent, fiber) {
|
|
778
|
+
const fiberRefsValue = fiber.getFiberRefs();
|
|
779
|
+
const enabled = effect.FiberRefs.getOrDefault(fiberRefsValue, AutoTracingEnabled);
|
|
780
|
+
if (!enabled) {
|
|
781
|
+
return;
|
|
782
|
+
}
|
|
783
|
+
const samplingRate = this.config.performance?.sampling_rate ?? 1;
|
|
784
|
+
if (samplingRate < 1 && Math.random() > samplingRate) {
|
|
785
|
+
return;
|
|
786
|
+
}
|
|
787
|
+
const maxConcurrent = this.config.performance?.max_concurrent ?? 0;
|
|
788
|
+
if (maxConcurrent > 0 && this.activeFiberCount >= maxConcurrent) {
|
|
789
|
+
return;
|
|
790
|
+
}
|
|
791
|
+
const nameOverride = effect.FiberRefs.getOrDefault(fiberRefsValue, AutoTracingSpanName);
|
|
792
|
+
const sourceInfo = this.config.span_naming?.infer_from_source ? this.parseStackTrace() : void 0;
|
|
793
|
+
let spanName;
|
|
794
|
+
if (effect.Option.isSome(nameOverride)) {
|
|
795
|
+
spanName = nameOverride.value;
|
|
796
|
+
} else {
|
|
797
|
+
spanName = inferSpanName(fiber.id().id, sourceInfo, this.config);
|
|
798
|
+
}
|
|
799
|
+
if (!this.shouldTrace(spanName)) {
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
802
|
+
let parentContext = OtelApi__namespace.ROOT_CONTEXT;
|
|
803
|
+
let parentFiberId;
|
|
804
|
+
if (effect.Option.isSome(parent)) {
|
|
805
|
+
parentFiberId = parent.value.id().id;
|
|
806
|
+
const parentSpan = this.fiberSpans.get(parent.value);
|
|
807
|
+
if (parentSpan) {
|
|
808
|
+
parentContext = OtelApi__namespace.trace.setSpan(OtelApi__namespace.ROOT_CONTEXT, parentSpan);
|
|
809
|
+
} else if (this._rootSpan) {
|
|
810
|
+
parentContext = OtelApi__namespace.trace.setSpan(OtelApi__namespace.ROOT_CONTEXT, this._rootSpan);
|
|
811
|
+
}
|
|
812
|
+
} else if (this._rootSpan) {
|
|
813
|
+
parentContext = OtelApi__namespace.trace.setSpan(OtelApi__namespace.ROOT_CONTEXT, this._rootSpan);
|
|
814
|
+
}
|
|
815
|
+
const span = this.tracer.startSpan(
|
|
816
|
+
spanName,
|
|
817
|
+
{
|
|
818
|
+
kind: OtelApi__namespace.SpanKind.INTERNAL,
|
|
819
|
+
attributes: this.getInitialAttributes(fiber, sourceInfo, parentFiberId)
|
|
820
|
+
},
|
|
821
|
+
parentContext
|
|
822
|
+
);
|
|
823
|
+
this.fiberSpans.set(fiber, span);
|
|
824
|
+
this.fiberStartTimes.set(fiber, process.hrtime.bigint());
|
|
825
|
+
this.activeFiberCount++;
|
|
826
|
+
}
|
|
827
|
+
/**
|
|
828
|
+
* Called when a fiber completes (success or failure)
|
|
829
|
+
*/
|
|
830
|
+
onEnd(exit, fiber) {
|
|
831
|
+
const span = this.fiberSpans.get(fiber);
|
|
832
|
+
if (!span) {
|
|
833
|
+
return;
|
|
834
|
+
}
|
|
835
|
+
const startTime = this.fiberStartTimes.get(fiber);
|
|
836
|
+
if (startTime) {
|
|
837
|
+
const duration = process.hrtime.bigint() - startTime;
|
|
838
|
+
const minDuration = this.parseMinDuration(this.config.performance?.min_duration);
|
|
839
|
+
if (minDuration > 0 && duration < minDuration) {
|
|
840
|
+
this.fiberSpans.delete(fiber);
|
|
841
|
+
this.fiberStartTimes.delete(fiber);
|
|
842
|
+
this.activeFiberCount--;
|
|
843
|
+
return;
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
if (effect.Exit.isSuccess(exit)) {
|
|
847
|
+
span.setStatus({ code: OtelApi__namespace.SpanStatusCode.OK });
|
|
848
|
+
} else {
|
|
849
|
+
span.setStatus({
|
|
850
|
+
code: OtelApi__namespace.SpanStatusCode.ERROR,
|
|
851
|
+
message: "Fiber failed"
|
|
852
|
+
});
|
|
853
|
+
span.setAttribute("effect.fiber.failed", true);
|
|
854
|
+
}
|
|
855
|
+
span.end();
|
|
856
|
+
this.fiberSpans.delete(fiber);
|
|
857
|
+
this.fiberStartTimes.delete(fiber);
|
|
858
|
+
this.activeFiberCount--;
|
|
859
|
+
}
|
|
860
|
+
/**
|
|
861
|
+
* Check if a span name should be traced based on filter patterns
|
|
862
|
+
*/
|
|
863
|
+
shouldTrace(spanName) {
|
|
864
|
+
for (const pattern of this.excludePatterns) {
|
|
865
|
+
if (pattern.test(spanName)) {
|
|
866
|
+
return false;
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
if (this.includePatterns.length > 0) {
|
|
870
|
+
for (const pattern of this.includePatterns) {
|
|
871
|
+
if (pattern.test(spanName)) {
|
|
872
|
+
return true;
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
return false;
|
|
876
|
+
}
|
|
877
|
+
return true;
|
|
878
|
+
}
|
|
879
|
+
/**
|
|
880
|
+
* Get initial span attributes for a fiber
|
|
881
|
+
*/
|
|
882
|
+
getInitialAttributes(fiber, sourceInfo, parentFiberId) {
|
|
883
|
+
const attrs = {
|
|
884
|
+
"effect.auto_traced": true
|
|
885
|
+
};
|
|
886
|
+
if (this.config.metadata?.fiber_info !== false) {
|
|
887
|
+
attrs["effect.fiber.id"] = fiber.id().id;
|
|
888
|
+
}
|
|
889
|
+
if (this.config.metadata?.source_location !== false && sourceInfo) {
|
|
890
|
+
attrs["code.function"] = sourceInfo.function;
|
|
891
|
+
attrs["code.filepath"] = sourceInfo.file;
|
|
892
|
+
attrs["code.lineno"] = sourceInfo.line;
|
|
893
|
+
attrs["code.column"] = sourceInfo.column;
|
|
894
|
+
}
|
|
895
|
+
if (this.config.metadata?.parent_fiber !== false && parentFiberId !== void 0) {
|
|
896
|
+
attrs["effect.fiber.parent_id"] = parentFiberId;
|
|
897
|
+
}
|
|
898
|
+
return attrs;
|
|
899
|
+
}
|
|
900
|
+
/**
|
|
901
|
+
* Parse stack trace to get source info
|
|
902
|
+
*/
|
|
903
|
+
parseStackTrace() {
|
|
904
|
+
const stack = new Error().stack;
|
|
905
|
+
if (!stack) return void 0;
|
|
906
|
+
const lines = stack.split("\n");
|
|
907
|
+
for (let i = 3; i < lines.length; i++) {
|
|
908
|
+
const line = lines[i];
|
|
909
|
+
if (line === void 0) continue;
|
|
910
|
+
if (!line.includes("node_modules/effect") && !line.includes("@atrim/instrument") && !line.includes("auto/supervisor")) {
|
|
911
|
+
const match = line.match(/at\s+(?:(.+?)\s+)?\(?(.+?):(\d+):(\d+)\)?/);
|
|
912
|
+
if (match) {
|
|
913
|
+
const [, funcName, filePath, lineNum, colNum] = match;
|
|
914
|
+
return {
|
|
915
|
+
function: funcName ?? "anonymous",
|
|
916
|
+
file: filePath ?? "unknown",
|
|
917
|
+
line: parseInt(lineNum ?? "0", 10),
|
|
918
|
+
column: parseInt(colNum ?? "0", 10)
|
|
919
|
+
};
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
return void 0;
|
|
924
|
+
}
|
|
925
|
+
/**
|
|
926
|
+
* Parse min_duration string to nanoseconds
|
|
927
|
+
*/
|
|
928
|
+
parseMinDuration(duration) {
|
|
929
|
+
if (!duration || duration === "0ms") return BigInt(0);
|
|
930
|
+
const match = duration.match(/^(\d+)\s*(ms|millis|s|sec|seconds|us|micros)?$/i);
|
|
931
|
+
if (!match) return BigInt(0);
|
|
932
|
+
const value = parseInt(match[1] ?? "0", 10);
|
|
933
|
+
const unit = (match[2] ?? "ms").toLowerCase();
|
|
934
|
+
switch (unit) {
|
|
935
|
+
case "us":
|
|
936
|
+
case "micros":
|
|
937
|
+
return BigInt(value) * BigInt(1e3);
|
|
938
|
+
case "ms":
|
|
939
|
+
case "millis":
|
|
940
|
+
return BigInt(value) * BigInt(1e6);
|
|
941
|
+
case "s":
|
|
942
|
+
case "sec":
|
|
943
|
+
case "seconds":
|
|
944
|
+
return BigInt(value) * BigInt(1e9);
|
|
945
|
+
default:
|
|
946
|
+
return BigInt(value) * BigInt(1e6);
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
};
|
|
950
|
+
var createAutoTracingSupervisor = (config) => {
|
|
951
|
+
return new AutoTracingSupervisor(config);
|
|
952
|
+
};
|
|
953
|
+
var createAutoTracingLayer = (options) => {
|
|
954
|
+
return effect.Layer.unwrapEffect(
|
|
955
|
+
effect.Effect.gen(function* () {
|
|
956
|
+
const config = options?.config ?? (yield* loadAutoTracingConfig());
|
|
957
|
+
if (!config.enabled) {
|
|
958
|
+
logger.log("@atrim/auto-trace: Auto-tracing disabled via config");
|
|
959
|
+
return effect.Layer.empty;
|
|
960
|
+
}
|
|
961
|
+
const supervisor = createAutoTracingSupervisor(config);
|
|
962
|
+
return effect.Supervisor.addSupervisor(supervisor);
|
|
963
|
+
})
|
|
964
|
+
);
|
|
965
|
+
};
|
|
966
|
+
var withAutoTracing = (effect$1, config, mainSpanName) => {
|
|
967
|
+
if (!config.enabled) {
|
|
968
|
+
logger.log("@atrim/auto-trace: Auto-tracing disabled via config");
|
|
969
|
+
return effect$1;
|
|
970
|
+
}
|
|
971
|
+
const supervisor = createAutoTracingSupervisor(config);
|
|
972
|
+
const tracer = OtelApi__namespace.trace.getTracer("@atrim/auto-trace", "1.0.0");
|
|
973
|
+
const spanName = mainSpanName ?? inferSpanName(0, void 0, config);
|
|
974
|
+
const mainSpan = tracer.startSpan(spanName, {
|
|
975
|
+
kind: OtelApi__namespace.SpanKind.INTERNAL,
|
|
976
|
+
attributes: {
|
|
977
|
+
"effect.auto_traced": true,
|
|
978
|
+
"effect.fiber.id": 0,
|
|
979
|
+
// Main fiber
|
|
980
|
+
"effect.main_fiber": true
|
|
981
|
+
}
|
|
982
|
+
});
|
|
983
|
+
supervisor.setRootSpan(mainSpan);
|
|
984
|
+
return effect.Effect.acquireUseRelease(
|
|
985
|
+
// Acquire: return the span (already started)
|
|
986
|
+
effect.Effect.succeed(mainSpan),
|
|
987
|
+
// Use: run the supervised effect
|
|
988
|
+
() => effect.Effect.supervised(supervisor)(effect$1),
|
|
989
|
+
// Release: end the span
|
|
990
|
+
(span, exit) => effect.Effect.sync(() => {
|
|
991
|
+
if (effect.Exit.isSuccess(exit)) {
|
|
992
|
+
span.setStatus({ code: OtelApi__namespace.SpanStatusCode.OK });
|
|
993
|
+
} else {
|
|
994
|
+
span.setStatus({
|
|
995
|
+
code: OtelApi__namespace.SpanStatusCode.ERROR,
|
|
996
|
+
message: "Effect failed"
|
|
997
|
+
});
|
|
998
|
+
}
|
|
999
|
+
span.end();
|
|
1000
|
+
})
|
|
1001
|
+
);
|
|
1002
|
+
};
|
|
1003
|
+
var AutoTracingLive = createAutoTracingLayer();
|
|
1004
|
+
var withoutAutoTracing = (effect$1) => effect$1.pipe(effect.Effect.locally(AutoTracingEnabled, false));
|
|
1005
|
+
var setSpanName = (name) => (effect$1) => effect$1.pipe(effect.Effect.locally(AutoTracingSpanName, effect.Option.some(name)));
|
|
1006
|
+
var createExporterLayer = (exporterConfig, serviceName, serviceVersion) => {
|
|
1007
|
+
const config = exporterConfig ?? {
|
|
1008
|
+
type: "otlp",
|
|
1009
|
+
processor: "batch",
|
|
1010
|
+
batch: {
|
|
1011
|
+
scheduled_delay_millis: 1e3,
|
|
1012
|
+
max_export_batch_size: 100
|
|
1013
|
+
}
|
|
1014
|
+
};
|
|
1015
|
+
if (config.type === "none") {
|
|
1016
|
+
logger.log('@atrim/auto-trace: Exporter type is "none", no spans will be exported');
|
|
1017
|
+
return effect.Layer.empty;
|
|
1018
|
+
}
|
|
1019
|
+
const createSpanExporter = () => {
|
|
1020
|
+
if (config.type === "console") {
|
|
1021
|
+
logger.log("@atrim/auto-trace: Using ConsoleSpanExporter");
|
|
1022
|
+
return new sdkTraceBase.ConsoleSpanExporter();
|
|
1023
|
+
}
|
|
1024
|
+
const endpoint = config.endpoint || process.env.OTEL_EXPORTER_OTLP_ENDPOINT || "http://localhost:4318";
|
|
1025
|
+
logger.log(`@atrim/auto-trace: Using OTLPTraceExporter (${endpoint})`);
|
|
1026
|
+
return new exporterTraceOtlpHttp.OTLPTraceExporter({
|
|
1027
|
+
url: `${endpoint}/v1/traces`
|
|
1028
|
+
});
|
|
1029
|
+
};
|
|
1030
|
+
const createSpanProcessor = () => {
|
|
1031
|
+
const exporter = createSpanExporter();
|
|
1032
|
+
if (config.processor === "simple" || config.type === "console") {
|
|
1033
|
+
logger.log("@atrim/auto-trace: Using SimpleSpanProcessor");
|
|
1034
|
+
return new sdkTraceBase.SimpleSpanProcessor(exporter);
|
|
1035
|
+
}
|
|
1036
|
+
const batchConfig = config.batch ?? {
|
|
1037
|
+
scheduled_delay_millis: 1e3,
|
|
1038
|
+
max_export_batch_size: 100
|
|
1039
|
+
};
|
|
1040
|
+
logger.log("@atrim/auto-trace: Using BatchSpanProcessor");
|
|
1041
|
+
return new sdkTraceBase.BatchSpanProcessor(exporter, {
|
|
1042
|
+
scheduledDelayMillis: batchConfig.scheduled_delay_millis,
|
|
1043
|
+
maxExportBatchSize: batchConfig.max_export_batch_size
|
|
1044
|
+
});
|
|
1045
|
+
};
|
|
1046
|
+
return effect.Layer.effectDiscard(
|
|
1047
|
+
effect.Effect.sync(() => {
|
|
1048
|
+
const provider = new sdkTraceBase.BasicTracerProvider({
|
|
1049
|
+
resource: resources.resourceFromAttributes({
|
|
1050
|
+
[semanticConventions.ATTR_SERVICE_NAME]: serviceName,
|
|
1051
|
+
[semanticConventions.ATTR_SERVICE_VERSION]: serviceVersion
|
|
1052
|
+
}),
|
|
1053
|
+
spanProcessors: [createSpanProcessor()]
|
|
1054
|
+
});
|
|
1055
|
+
OtelApi__namespace.trace.setGlobalTracerProvider(provider);
|
|
1056
|
+
logger.log("@atrim/auto-trace: Global TracerProvider registered");
|
|
1057
|
+
return () => {
|
|
1058
|
+
provider.shutdown().catch((err) => {
|
|
1059
|
+
logger.log(`@atrim/auto-trace: Error shutting down provider: ${err}`);
|
|
1060
|
+
});
|
|
1061
|
+
};
|
|
1062
|
+
})
|
|
1063
|
+
);
|
|
1064
|
+
};
|
|
1065
|
+
var createFullAutoTracingLayer = () => {
|
|
1066
|
+
return effect.Layer.unwrapEffect(
|
|
1067
|
+
effect.Effect.gen(function* () {
|
|
1068
|
+
const config = yield* loadFullConfig();
|
|
1069
|
+
const serviceName = process.env.OTEL_SERVICE_NAME || "effect-service";
|
|
1070
|
+
const serviceVersion = process.env.npm_package_version || "1.0.0";
|
|
1071
|
+
const autoConfig = config.effect?.auto_instrumentation;
|
|
1072
|
+
if (!autoConfig?.enabled) {
|
|
1073
|
+
logger.log("@atrim/auto-trace: Auto-instrumentation disabled via config");
|
|
1074
|
+
return effect.Layer.empty;
|
|
1075
|
+
}
|
|
1076
|
+
const exporterConfig = config.effect?.exporter_config;
|
|
1077
|
+
logger.log("@atrim/auto-trace: Full auto-instrumentation enabled");
|
|
1078
|
+
logger.log(` Service: ${serviceName}`);
|
|
1079
|
+
logger.log(` Exporter: ${exporterConfig?.type ?? "otlp"}`);
|
|
1080
|
+
const exporterLayer = createExporterLayer(exporterConfig, serviceName, serviceVersion);
|
|
1081
|
+
const supervisor = createAutoTracingSupervisor(autoConfig);
|
|
1082
|
+
const supervisorLayer = effect.Supervisor.addSupervisor(supervisor);
|
|
1083
|
+
return effect.Layer.mergeAll(exporterLayer, supervisorLayer);
|
|
1084
|
+
})
|
|
1085
|
+
);
|
|
1086
|
+
};
|
|
1087
|
+
var FullAutoTracingLive = createFullAutoTracingLayer();
|
|
1088
|
+
|
|
1089
|
+
exports.AutoTracingConfig = AutoTracingConfig;
|
|
1090
|
+
exports.AutoTracingConfigLayer = AutoTracingConfigLayer;
|
|
1091
|
+
exports.AutoTracingConfigLive = AutoTracingConfigLive;
|
|
1092
|
+
exports.AutoTracingEnabled = AutoTracingEnabled;
|
|
1093
|
+
exports.AutoTracingLive = AutoTracingLive;
|
|
1094
|
+
exports.AutoTracingSpanName = AutoTracingSpanName;
|
|
1095
|
+
exports.AutoTracingSupervisor = AutoTracingSupervisor;
|
|
1096
|
+
exports.FullAutoTracingLive = FullAutoTracingLive;
|
|
1097
|
+
exports.createAutoTracingLayer = createAutoTracingLayer;
|
|
1098
|
+
exports.createAutoTracingSupervisor = createAutoTracingSupervisor;
|
|
1099
|
+
exports.createFullAutoTracingLayer = createFullAutoTracingLayer;
|
|
1100
|
+
exports.defaultAutoTracingConfig = defaultAutoTracingConfig;
|
|
1101
|
+
exports.inferSpanName = inferSpanName;
|
|
1102
|
+
exports.loadAutoTracingConfig = loadAutoTracingConfig;
|
|
1103
|
+
exports.loadAutoTracingConfigSync = loadAutoTracingConfigSync;
|
|
1104
|
+
exports.sanitizeSpanName = sanitizeSpanName;
|
|
1105
|
+
exports.setSpanName = setSpanName;
|
|
1106
|
+
exports.withAutoTracing = withAutoTracing;
|
|
1107
|
+
exports.withoutAutoTracing = withoutAutoTracing;
|
|
1108
|
+
//# sourceMappingURL=index.cjs.map
|
|
1109
|
+
//# sourceMappingURL=index.cjs.map
|