@atrim/instrument-node 0.4.0 → 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/package.json +7 -6
- package/target/dist/index.cjs +251 -195
- package/target/dist/index.cjs.map +1 -1
- package/target/dist/index.d.cts +57 -3
- package/target/dist/index.d.ts +57 -3
- package/target/dist/index.js +229 -197
- package/target/dist/index.js.map +1 -1
- package/target/dist/integrations/effect/index.cjs +220 -191
- package/target/dist/integrations/effect/index.cjs.map +1 -1
- package/target/dist/integrations/effect/index.d.cts +17 -1
- package/target/dist/integrations/effect/index.d.ts +17 -1
- package/target/dist/integrations/effect/index.js +219 -192
- package/target/dist/integrations/effect/index.js.map +1 -1
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import { Data,
|
|
1
|
+
import { Data, Context, Effect, Layer, FiberSet as FiberSet$1, Tracer } from 'effect';
|
|
2
2
|
import * as Otlp from '@effect/opentelemetry/Otlp';
|
|
3
3
|
import { FetchHttpClient } from '@effect/platform';
|
|
4
4
|
import { TraceFlags, trace, context } from '@opentelemetry/api';
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
5
|
+
import { FileSystem } from '@effect/platform/FileSystem';
|
|
6
|
+
import * as HttpClient from '@effect/platform/HttpClient';
|
|
7
|
+
import * as HttpClientRequest from '@effect/platform/HttpClientRequest';
|
|
7
8
|
import { parse } from 'yaml';
|
|
8
9
|
import { z } from 'zod';
|
|
10
|
+
import { NodeContext } from '@effect/platform-node';
|
|
9
11
|
|
|
10
12
|
// src/integrations/effect/effect-tracer.ts
|
|
11
13
|
var __defProp = Object.defineProperty;
|
|
@@ -62,233 +64,183 @@ var InstrumentationConfigSchema = z.object({
|
|
|
62
64
|
http: HttpFilteringConfigSchema.optional()
|
|
63
65
|
});
|
|
64
66
|
(class extends Data.TaggedError("ConfigError") {
|
|
67
|
+
get message() {
|
|
68
|
+
return this.reason;
|
|
69
|
+
}
|
|
65
70
|
});
|
|
66
71
|
var ConfigUrlError = class extends Data.TaggedError("ConfigUrlError") {
|
|
72
|
+
get message() {
|
|
73
|
+
return this.reason;
|
|
74
|
+
}
|
|
67
75
|
};
|
|
68
76
|
var ConfigValidationError = class extends Data.TaggedError("ConfigValidationError") {
|
|
77
|
+
get message() {
|
|
78
|
+
return this.reason;
|
|
79
|
+
}
|
|
69
80
|
};
|
|
70
81
|
var ConfigFileError = class extends Data.TaggedError("ConfigFileError") {
|
|
82
|
+
get message() {
|
|
83
|
+
return this.reason;
|
|
84
|
+
}
|
|
71
85
|
};
|
|
72
86
|
(class extends Data.TaggedError("ServiceDetectionError") {
|
|
87
|
+
get message() {
|
|
88
|
+
return this.reason;
|
|
89
|
+
}
|
|
73
90
|
});
|
|
74
91
|
(class extends Data.TaggedError("InitializationError") {
|
|
92
|
+
get message() {
|
|
93
|
+
return this.reason;
|
|
94
|
+
}
|
|
75
95
|
});
|
|
76
96
|
(class extends Data.TaggedError("ExportError") {
|
|
97
|
+
get message() {
|
|
98
|
+
return this.reason;
|
|
99
|
+
}
|
|
77
100
|
});
|
|
78
101
|
(class extends Data.TaggedError("ShutdownError") {
|
|
102
|
+
get message() {
|
|
103
|
+
return this.reason;
|
|
104
|
+
}
|
|
79
105
|
});
|
|
80
106
|
var SECURITY_DEFAULTS = {
|
|
81
107
|
maxConfigSize: 1e6,
|
|
82
108
|
// 1MB
|
|
83
|
-
requestTimeout: 5e3
|
|
84
|
-
|
|
85
|
-
// Only HTTPS for remote configs
|
|
86
|
-
cacheTimeout: 3e5
|
|
87
|
-
// 5 minutes
|
|
109
|
+
requestTimeout: 5e3
|
|
110
|
+
// 5 seconds
|
|
88
111
|
};
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
{ pattern: "^internal\\.", description: "Internal operations" },
|
|
104
|
-
{ pattern: "^health\\.", description: "Health checks" }
|
|
105
|
-
]
|
|
106
|
-
},
|
|
107
|
-
effect: {
|
|
108
|
-
auto_extract_metadata: true
|
|
109
|
-
}
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
var validateConfigEffect = (rawConfig) => Effect.try({
|
|
113
|
-
try: () => InstrumentationConfigSchema.parse(rawConfig),
|
|
114
|
-
catch: (error) => new ConfigValidationError({
|
|
115
|
-
reason: "Invalid configuration schema",
|
|
116
|
-
cause: error
|
|
117
|
-
})
|
|
118
|
-
});
|
|
119
|
-
var loadConfigFromFileEffect = (filePath) => Effect.gen(function* () {
|
|
120
|
-
const fileContents = yield* Effect.try({
|
|
121
|
-
try: () => readFileSync(filePath, "utf8"),
|
|
122
|
-
catch: (error) => new ConfigFileError({
|
|
123
|
-
reason: `Failed to read config file at ${filePath}`,
|
|
112
|
+
var ConfigLoader = class extends Context.Tag("ConfigLoader")() {
|
|
113
|
+
};
|
|
114
|
+
var parseYamlContent = (content, uri) => Effect.gen(function* () {
|
|
115
|
+
const parsed = yield* Effect.try({
|
|
116
|
+
try: () => parse(content),
|
|
117
|
+
catch: (error) => new ConfigValidationError({
|
|
118
|
+
reason: uri ? `Failed to parse YAML from ${uri}` : "Failed to parse YAML",
|
|
119
|
+
cause: error
|
|
120
|
+
})
|
|
121
|
+
});
|
|
122
|
+
return yield* Effect.try({
|
|
123
|
+
try: () => InstrumentationConfigSchema.parse(parsed),
|
|
124
|
+
catch: (error) => new ConfigValidationError({
|
|
125
|
+
reason: uri ? `Invalid configuration schema from ${uri}` : "Invalid configuration schema",
|
|
124
126
|
cause: error
|
|
125
127
|
})
|
|
126
128
|
});
|
|
127
|
-
|
|
129
|
+
});
|
|
130
|
+
var loadFromFileWithFs = (fs, path, uri) => Effect.gen(function* () {
|
|
131
|
+
const content = yield* fs.readFileString(path).pipe(
|
|
132
|
+
Effect.mapError(
|
|
133
|
+
(error) => new ConfigFileError({
|
|
134
|
+
reason: `Failed to read config file at ${uri}`,
|
|
135
|
+
cause: error
|
|
136
|
+
})
|
|
137
|
+
)
|
|
138
|
+
);
|
|
139
|
+
if (content.length > SECURITY_DEFAULTS.maxConfigSize) {
|
|
128
140
|
return yield* Effect.fail(
|
|
129
141
|
new ConfigFileError({
|
|
130
142
|
reason: `Config file exceeds maximum size of ${SECURITY_DEFAULTS.maxConfigSize} bytes`
|
|
131
143
|
})
|
|
132
144
|
);
|
|
133
145
|
}
|
|
134
|
-
|
|
135
|
-
try {
|
|
136
|
-
rawConfig = parse(fileContents);
|
|
137
|
-
} catch (error) {
|
|
138
|
-
return yield* Effect.fail(
|
|
139
|
-
new ConfigValidationError({
|
|
140
|
-
reason: "Invalid YAML syntax",
|
|
141
|
-
cause: error
|
|
142
|
-
})
|
|
143
|
-
);
|
|
144
|
-
}
|
|
145
|
-
return yield* validateConfigEffect(rawConfig);
|
|
146
|
+
return yield* parseYamlContent(content, uri);
|
|
146
147
|
});
|
|
147
|
-
var
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
148
|
+
var loadFromHttpWithClient = (client, url) => Effect.scoped(
|
|
149
|
+
Effect.gen(function* () {
|
|
150
|
+
if (url.startsWith("http://")) {
|
|
151
|
+
return yield* Effect.fail(
|
|
152
|
+
new ConfigUrlError({
|
|
153
|
+
reason: "Insecure protocol: only HTTPS URLs are allowed"
|
|
154
|
+
})
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
const request = HttpClientRequest.get(url).pipe(
|
|
158
|
+
HttpClientRequest.setHeaders({
|
|
159
|
+
Accept: "application/yaml, text/yaml, application/x-yaml"
|
|
156
160
|
})
|
|
157
161
|
);
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
162
|
+
const response = yield* client.execute(request).pipe(
|
|
163
|
+
Effect.timeout(`${SECURITY_DEFAULTS.requestTimeout} millis`),
|
|
164
|
+
Effect.mapError((error) => {
|
|
165
|
+
if (error._tag === "TimeoutException") {
|
|
166
|
+
return new ConfigUrlError({
|
|
167
|
+
reason: `Config fetch timeout after ${SECURITY_DEFAULTS.requestTimeout}ms from ${url}`
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
return new ConfigUrlError({
|
|
171
|
+
reason: `Failed to load config from URL: ${url}`,
|
|
172
|
+
cause: error
|
|
173
|
+
});
|
|
163
174
|
})
|
|
164
175
|
);
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
176
|
+
if (response.status >= 400) {
|
|
177
|
+
return yield* Effect.fail(
|
|
178
|
+
new ConfigUrlError({
|
|
179
|
+
reason: `HTTP ${response.status} from ${url}`
|
|
180
|
+
})
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
const text = yield* response.text.pipe(
|
|
184
|
+
Effect.mapError(
|
|
185
|
+
(error) => new ConfigUrlError({
|
|
186
|
+
reason: `Failed to read response body from ${url}`,
|
|
187
|
+
cause: error
|
|
188
|
+
})
|
|
189
|
+
)
|
|
190
|
+
);
|
|
191
|
+
if (text.length > SECURITY_DEFAULTS.maxConfigSize) {
|
|
192
|
+
return yield* Effect.fail(
|
|
193
|
+
new ConfigUrlError({
|
|
194
|
+
reason: `Config exceeds maximum size of ${SECURITY_DEFAULTS.maxConfigSize} bytes`
|
|
195
|
+
})
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
return yield* parseYamlContent(text, url);
|
|
199
|
+
})
|
|
200
|
+
);
|
|
201
|
+
var makeConfigLoader = Effect.gen(function* () {
|
|
202
|
+
const fs = yield* Effect.serviceOption(FileSystem);
|
|
203
|
+
const http = yield* HttpClient.HttpClient;
|
|
204
|
+
const loadFromUriUncached = (uri) => Effect.gen(function* () {
|
|
205
|
+
if (uri.startsWith("file://")) {
|
|
206
|
+
const path = uri.slice(7);
|
|
207
|
+
if (fs._tag === "None") {
|
|
208
|
+
return yield* Effect.fail(
|
|
209
|
+
new ConfigFileError({
|
|
210
|
+
reason: "FileSystem not available (browser environment?)",
|
|
211
|
+
cause: { uri }
|
|
188
212
|
})
|
|
189
213
|
);
|
|
190
214
|
}
|
|
191
|
-
return
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
215
|
+
return yield* loadFromFileWithFs(fs.value, path, uri);
|
|
216
|
+
}
|
|
217
|
+
if (uri.startsWith("http://") || uri.startsWith("https://")) {
|
|
218
|
+
return yield* loadFromHttpWithClient(http, uri);
|
|
219
|
+
}
|
|
220
|
+
if (fs._tag === "Some") {
|
|
221
|
+
return yield* loadFromFileWithFs(fs.value, uri, uri);
|
|
222
|
+
} else {
|
|
223
|
+
return yield* loadFromHttpWithClient(http, uri);
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
const loadFromUriCached = yield* Effect.cachedFunction(loadFromUriUncached);
|
|
227
|
+
return ConfigLoader.of({
|
|
228
|
+
loadFromUri: loadFromUriCached,
|
|
229
|
+
loadFromInline: (content) => Effect.gen(function* () {
|
|
230
|
+
if (typeof content === "string") {
|
|
231
|
+
return yield* parseYamlContent(content);
|
|
232
|
+
}
|
|
233
|
+
return yield* Effect.try({
|
|
234
|
+
try: () => InstrumentationConfigSchema.parse(content),
|
|
235
|
+
catch: (error) => new ConfigValidationError({
|
|
236
|
+
reason: "Invalid configuration schema",
|
|
237
|
+
cause: error
|
|
238
|
+
})
|
|
239
|
+
});
|
|
214
240
|
})
|
|
215
241
|
});
|
|
216
|
-
if (text.length > SECURITY_DEFAULTS.maxConfigSize) {
|
|
217
|
-
return yield* Effect.fail(
|
|
218
|
-
new ConfigUrlError({
|
|
219
|
-
reason: `Config exceeds maximum size of ${SECURITY_DEFAULTS.maxConfigSize} bytes`
|
|
220
|
-
})
|
|
221
|
-
);
|
|
222
|
-
}
|
|
223
|
-
let rawConfig;
|
|
224
|
-
try {
|
|
225
|
-
rawConfig = parse(text);
|
|
226
|
-
} catch (error) {
|
|
227
|
-
return yield* Effect.fail(
|
|
228
|
-
new ConfigValidationError({
|
|
229
|
-
reason: "Invalid YAML syntax",
|
|
230
|
-
cause: error
|
|
231
|
-
})
|
|
232
|
-
);
|
|
233
|
-
}
|
|
234
|
-
return yield* validateConfigEffect(rawConfig);
|
|
235
|
-
});
|
|
236
|
-
var makeConfigCache = () => Cache.make({
|
|
237
|
-
capacity: 100,
|
|
238
|
-
timeToLive: Duration.minutes(5),
|
|
239
|
-
lookup: (url) => fetchAndParseConfig(url)
|
|
240
|
-
});
|
|
241
|
-
var cacheInstance = null;
|
|
242
|
-
var getCache = Effect.gen(function* () {
|
|
243
|
-
if (!cacheInstance) {
|
|
244
|
-
cacheInstance = yield* makeConfigCache();
|
|
245
|
-
}
|
|
246
|
-
return cacheInstance;
|
|
247
|
-
});
|
|
248
|
-
var loadConfigFromUrlEffect = (url, cacheTimeout = SECURITY_DEFAULTS.cacheTimeout) => Effect.gen(function* () {
|
|
249
|
-
if (cacheTimeout === 0) {
|
|
250
|
-
return yield* fetchAndParseConfig(url);
|
|
251
|
-
}
|
|
252
|
-
const cache = yield* getCache;
|
|
253
|
-
return yield* cache.get(url);
|
|
254
242
|
});
|
|
255
|
-
var
|
|
256
|
-
if (options.config) {
|
|
257
|
-
return yield* validateConfigEffect(options.config);
|
|
258
|
-
}
|
|
259
|
-
const envConfigPath = process.env.ATRIM_INSTRUMENTATION_CONFIG;
|
|
260
|
-
if (envConfigPath) {
|
|
261
|
-
if (envConfigPath.startsWith("http://") || envConfigPath.startsWith("https://")) {
|
|
262
|
-
return yield* loadConfigFromUrlEffect(envConfigPath, options.cacheTimeout);
|
|
263
|
-
}
|
|
264
|
-
return yield* loadConfigFromFileEffect(envConfigPath);
|
|
265
|
-
}
|
|
266
|
-
if (options.configUrl) {
|
|
267
|
-
return yield* loadConfigFromUrlEffect(options.configUrl, options.cacheTimeout);
|
|
268
|
-
}
|
|
269
|
-
if (options.configPath) {
|
|
270
|
-
return yield* loadConfigFromFileEffect(options.configPath);
|
|
271
|
-
}
|
|
272
|
-
const defaultPath = join(process.cwd(), "instrumentation.yaml");
|
|
273
|
-
const exists = yield* Effect.sync(() => existsSync(defaultPath));
|
|
274
|
-
if (exists) {
|
|
275
|
-
return yield* loadConfigFromFileEffect(defaultPath);
|
|
276
|
-
}
|
|
277
|
-
return getDefaultConfig();
|
|
278
|
-
});
|
|
279
|
-
async function loadConfig(options = {}) {
|
|
280
|
-
return Effect.runPromise(
|
|
281
|
-
loadConfigEffect(options).pipe(
|
|
282
|
-
// Convert typed errors to regular Error with reason message for backward compatibility
|
|
283
|
-
Effect.mapError((error) => {
|
|
284
|
-
const message = error.reason;
|
|
285
|
-
const newError = new Error(message);
|
|
286
|
-
newError.cause = error.cause;
|
|
287
|
-
return newError;
|
|
288
|
-
})
|
|
289
|
-
)
|
|
290
|
-
);
|
|
291
|
-
}
|
|
243
|
+
var ConfigLoaderLive = Layer.effect(ConfigLoader, makeConfigLoader);
|
|
292
244
|
var PatternMatcher = class {
|
|
293
245
|
constructor(config) {
|
|
294
246
|
__publicField(this, "ignorePatterns", []);
|
|
@@ -436,13 +388,88 @@ var Logger = class {
|
|
|
436
388
|
}
|
|
437
389
|
};
|
|
438
390
|
var logger = new Logger();
|
|
391
|
+
var NodeConfigLoaderLive = ConfigLoaderLive.pipe(
|
|
392
|
+
Layer.provide(Layer.mergeAll(NodeContext.layer, FetchHttpClient.layer))
|
|
393
|
+
);
|
|
394
|
+
var cachedLoaderPromise = null;
|
|
395
|
+
function getCachedLoader() {
|
|
396
|
+
if (!cachedLoaderPromise) {
|
|
397
|
+
cachedLoaderPromise = Effect.runPromise(
|
|
398
|
+
Effect.gen(function* () {
|
|
399
|
+
return yield* ConfigLoader;
|
|
400
|
+
}).pipe(Effect.provide(NodeConfigLoaderLive))
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
return cachedLoaderPromise;
|
|
404
|
+
}
|
|
405
|
+
async function loadConfig(uri, options) {
|
|
406
|
+
if (options?.cacheTimeout === 0) {
|
|
407
|
+
const program = Effect.gen(function* () {
|
|
408
|
+
const loader2 = yield* ConfigLoader;
|
|
409
|
+
return yield* loader2.loadFromUri(uri);
|
|
410
|
+
});
|
|
411
|
+
return Effect.runPromise(program.pipe(Effect.provide(NodeConfigLoaderLive)));
|
|
412
|
+
}
|
|
413
|
+
const loader = await getCachedLoader();
|
|
414
|
+
return Effect.runPromise(loader.loadFromUri(uri));
|
|
415
|
+
}
|
|
416
|
+
async function loadConfigFromInline(content) {
|
|
417
|
+
const loader = await getCachedLoader();
|
|
418
|
+
return Effect.runPromise(loader.loadFromInline(content));
|
|
419
|
+
}
|
|
420
|
+
function getDefaultConfig() {
|
|
421
|
+
return {
|
|
422
|
+
version: "1.0",
|
|
423
|
+
instrumentation: {
|
|
424
|
+
enabled: true,
|
|
425
|
+
logging: "on",
|
|
426
|
+
description: "Default instrumentation configuration",
|
|
427
|
+
instrument_patterns: [
|
|
428
|
+
{ pattern: "^app\\.", enabled: true, description: "Application operations" },
|
|
429
|
+
{ pattern: "^http\\.server\\.", enabled: true, description: "HTTP server operations" },
|
|
430
|
+
{ pattern: "^http\\.client\\.", enabled: true, description: "HTTP client operations" }
|
|
431
|
+
],
|
|
432
|
+
ignore_patterns: [
|
|
433
|
+
{ pattern: "^test\\.", description: "Test utilities" },
|
|
434
|
+
{ pattern: "^internal\\.", description: "Internal operations" },
|
|
435
|
+
{ pattern: "^health\\.", description: "Health checks" }
|
|
436
|
+
]
|
|
437
|
+
},
|
|
438
|
+
effect: {
|
|
439
|
+
auto_extract_metadata: true
|
|
440
|
+
}
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
async function loadConfigWithOptions(options = {}) {
|
|
444
|
+
const loadOptions = options.cacheTimeout !== void 0 ? { cacheTimeout: options.cacheTimeout } : void 0;
|
|
445
|
+
if (options.config) {
|
|
446
|
+
return loadConfigFromInline(options.config);
|
|
447
|
+
}
|
|
448
|
+
const envConfigPath = process.env.ATRIM_INSTRUMENTATION_CONFIG;
|
|
449
|
+
if (envConfigPath) {
|
|
450
|
+
return loadConfig(envConfigPath, loadOptions);
|
|
451
|
+
}
|
|
452
|
+
if (options.configUrl) {
|
|
453
|
+
return loadConfig(options.configUrl, loadOptions);
|
|
454
|
+
}
|
|
455
|
+
if (options.configPath) {
|
|
456
|
+
return loadConfig(options.configPath, loadOptions);
|
|
457
|
+
}
|
|
458
|
+
const { existsSync } = await import('fs');
|
|
459
|
+
const { join } = await import('path');
|
|
460
|
+
const defaultPath = join(process.cwd(), "instrumentation.yaml");
|
|
461
|
+
if (existsSync(defaultPath)) {
|
|
462
|
+
return loadConfig(defaultPath, loadOptions);
|
|
463
|
+
}
|
|
464
|
+
return getDefaultConfig();
|
|
465
|
+
}
|
|
439
466
|
|
|
440
467
|
// src/integrations/effect/effect-tracer.ts
|
|
441
468
|
function createEffectInstrumentation(options = {}) {
|
|
442
469
|
return Layer.unwrapEffect(
|
|
443
470
|
Effect.gen(function* () {
|
|
444
471
|
const config = yield* Effect.tryPromise({
|
|
445
|
-
try: () =>
|
|
472
|
+
try: () => loadConfigWithOptions(options),
|
|
446
473
|
catch: (error) => ({
|
|
447
474
|
_tag: "ConfigError",
|
|
448
475
|
message: error instanceof Error ? error.message : String(error)
|