@atrim/instrument-node 0.4.0 → 0.5.0-c05e3a1-20251119131235
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 +8 -7
- package/target/dist/index.cjs +317 -264
- package/target/dist/index.cjs.map +1 -1
- package/target/dist/index.d.cts +163 -142
- package/target/dist/index.d.ts +163 -142
- package/target/dist/index.js +292 -258
- package/target/dist/index.js.map +1 -1
- package/target/dist/integrations/effect/index.cjs +234 -192
- 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 +233 -193
- package/target/dist/integrations/effect/index.js.map +1 -1
package/target/dist/index.js
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
|
-
import { Data,
|
|
1
|
+
import { Data, Context, Effect, Layer, Deferred } from 'effect';
|
|
2
2
|
import { NodeSDK } from '@opentelemetry/sdk-node';
|
|
3
3
|
import { SimpleSpanProcessor, BatchSpanProcessor } from '@opentelemetry/sdk-trace-base';
|
|
4
4
|
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
|
|
5
5
|
import { trace } from '@opentelemetry/api';
|
|
6
|
-
import {
|
|
7
|
-
import
|
|
6
|
+
import { FileSystem } from '@effect/platform/FileSystem';
|
|
7
|
+
import * as HttpClient from '@effect/platform/HttpClient';
|
|
8
|
+
import * as HttpClientRequest from '@effect/platform/HttpClientRequest';
|
|
8
9
|
import { parse } from 'yaml';
|
|
9
10
|
import { z } from 'zod';
|
|
10
11
|
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
|
|
11
12
|
import { readFile } from 'fs/promises';
|
|
13
|
+
import { join } from 'path';
|
|
14
|
+
import { NodeContext } from '@effect/platform-node';
|
|
15
|
+
import { FetchHttpClient } from '@effect/platform';
|
|
12
16
|
|
|
13
17
|
var __defProp = Object.defineProperty;
|
|
14
18
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
@@ -55,7 +59,20 @@ var HttpFilteringConfigSchema = z.object({
|
|
|
55
59
|
// Patterns to ignore for incoming HTTP requests (string patterns only in YAML)
|
|
56
60
|
ignore_incoming_paths: z.array(z.string()).optional(),
|
|
57
61
|
// Require parent span for outgoing requests (prevents root spans for HTTP calls)
|
|
58
|
-
require_parent_for_outgoing_spans: z.boolean().optional()
|
|
62
|
+
require_parent_for_outgoing_spans: z.boolean().optional(),
|
|
63
|
+
// Trace context propagation configuration
|
|
64
|
+
// Controls which cross-origin requests receive W3C Trace Context headers (traceparent, tracestate)
|
|
65
|
+
propagate_trace_context: z.object({
|
|
66
|
+
// Strategy for trace propagation
|
|
67
|
+
// - "all": Propagate to all cross-origin requests (may cause CORS errors)
|
|
68
|
+
// - "none": Never propagate trace headers
|
|
69
|
+
// - "same-origin": Only propagate to same-origin requests (default, safe)
|
|
70
|
+
// - "patterns": Propagate based on include_urls patterns
|
|
71
|
+
strategy: z.enum(["all", "none", "same-origin", "patterns"]).default("same-origin"),
|
|
72
|
+
// URL patterns to include when strategy is "patterns"
|
|
73
|
+
// Supports regex patterns (e.g., "^https://api\\.myapp\\.com")
|
|
74
|
+
include_urls: z.array(z.string()).optional()
|
|
75
|
+
}).optional()
|
|
59
76
|
});
|
|
60
77
|
var InstrumentationConfigSchema = z.object({
|
|
61
78
|
version: z.string(),
|
|
@@ -73,233 +90,183 @@ var InstrumentationConfigSchema = z.object({
|
|
|
73
90
|
http: HttpFilteringConfigSchema.optional()
|
|
74
91
|
});
|
|
75
92
|
(class extends Data.TaggedError("ConfigError") {
|
|
93
|
+
get message() {
|
|
94
|
+
return this.reason;
|
|
95
|
+
}
|
|
76
96
|
});
|
|
77
97
|
var ConfigUrlError = class extends Data.TaggedError("ConfigUrlError") {
|
|
98
|
+
get message() {
|
|
99
|
+
return this.reason;
|
|
100
|
+
}
|
|
78
101
|
};
|
|
79
102
|
var ConfigValidationError = class extends Data.TaggedError("ConfigValidationError") {
|
|
103
|
+
get message() {
|
|
104
|
+
return this.reason;
|
|
105
|
+
}
|
|
80
106
|
};
|
|
81
107
|
var ConfigFileError = class extends Data.TaggedError("ConfigFileError") {
|
|
108
|
+
get message() {
|
|
109
|
+
return this.reason;
|
|
110
|
+
}
|
|
82
111
|
};
|
|
83
112
|
(class extends Data.TaggedError("ServiceDetectionError") {
|
|
113
|
+
get message() {
|
|
114
|
+
return this.reason;
|
|
115
|
+
}
|
|
84
116
|
});
|
|
85
117
|
(class extends Data.TaggedError("InitializationError") {
|
|
118
|
+
get message() {
|
|
119
|
+
return this.reason;
|
|
120
|
+
}
|
|
86
121
|
});
|
|
87
122
|
(class extends Data.TaggedError("ExportError") {
|
|
123
|
+
get message() {
|
|
124
|
+
return this.reason;
|
|
125
|
+
}
|
|
88
126
|
});
|
|
89
127
|
(class extends Data.TaggedError("ShutdownError") {
|
|
128
|
+
get message() {
|
|
129
|
+
return this.reason;
|
|
130
|
+
}
|
|
90
131
|
});
|
|
91
132
|
var SECURITY_DEFAULTS = {
|
|
92
133
|
maxConfigSize: 1e6,
|
|
93
134
|
// 1MB
|
|
94
|
-
requestTimeout: 5e3
|
|
95
|
-
|
|
96
|
-
// Only HTTPS for remote configs
|
|
97
|
-
cacheTimeout: 3e5
|
|
98
|
-
// 5 minutes
|
|
135
|
+
requestTimeout: 5e3
|
|
136
|
+
// 5 seconds
|
|
99
137
|
};
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
{ pattern: "^internal\\.", description: "Internal operations" },
|
|
115
|
-
{ pattern: "^health\\.", description: "Health checks" }
|
|
116
|
-
]
|
|
117
|
-
},
|
|
118
|
-
effect: {
|
|
119
|
-
auto_extract_metadata: true
|
|
120
|
-
}
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
var validateConfigEffect = (rawConfig) => Effect.try({
|
|
124
|
-
try: () => InstrumentationConfigSchema.parse(rawConfig),
|
|
125
|
-
catch: (error) => new ConfigValidationError({
|
|
126
|
-
reason: "Invalid configuration schema",
|
|
127
|
-
cause: error
|
|
128
|
-
})
|
|
129
|
-
});
|
|
130
|
-
var loadConfigFromFileEffect = (filePath) => Effect.gen(function* () {
|
|
131
|
-
const fileContents = yield* Effect.try({
|
|
132
|
-
try: () => readFileSync(filePath, "utf8"),
|
|
133
|
-
catch: (error) => new ConfigFileError({
|
|
134
|
-
reason: `Failed to read config file at ${filePath}`,
|
|
138
|
+
var ConfigLoader = class extends Context.Tag("ConfigLoader")() {
|
|
139
|
+
};
|
|
140
|
+
var parseYamlContent = (content, uri) => Effect.gen(function* () {
|
|
141
|
+
const parsed = yield* Effect.try({
|
|
142
|
+
try: () => parse(content),
|
|
143
|
+
catch: (error) => new ConfigValidationError({
|
|
144
|
+
reason: uri ? `Failed to parse YAML from ${uri}` : "Failed to parse YAML",
|
|
145
|
+
cause: error
|
|
146
|
+
})
|
|
147
|
+
});
|
|
148
|
+
return yield* Effect.try({
|
|
149
|
+
try: () => InstrumentationConfigSchema.parse(parsed),
|
|
150
|
+
catch: (error) => new ConfigValidationError({
|
|
151
|
+
reason: uri ? `Invalid configuration schema from ${uri}` : "Invalid configuration schema",
|
|
135
152
|
cause: error
|
|
136
153
|
})
|
|
137
154
|
});
|
|
138
|
-
|
|
155
|
+
});
|
|
156
|
+
var loadFromFileWithFs = (fs, path, uri) => Effect.gen(function* () {
|
|
157
|
+
const content = yield* fs.readFileString(path).pipe(
|
|
158
|
+
Effect.mapError(
|
|
159
|
+
(error) => new ConfigFileError({
|
|
160
|
+
reason: `Failed to read config file at ${uri}`,
|
|
161
|
+
cause: error
|
|
162
|
+
})
|
|
163
|
+
)
|
|
164
|
+
);
|
|
165
|
+
if (content.length > SECURITY_DEFAULTS.maxConfigSize) {
|
|
139
166
|
return yield* Effect.fail(
|
|
140
167
|
new ConfigFileError({
|
|
141
168
|
reason: `Config file exceeds maximum size of ${SECURITY_DEFAULTS.maxConfigSize} bytes`
|
|
142
169
|
})
|
|
143
170
|
);
|
|
144
171
|
}
|
|
145
|
-
|
|
146
|
-
try {
|
|
147
|
-
rawConfig = parse(fileContents);
|
|
148
|
-
} catch (error) {
|
|
149
|
-
return yield* Effect.fail(
|
|
150
|
-
new ConfigValidationError({
|
|
151
|
-
reason: "Invalid YAML syntax",
|
|
152
|
-
cause: error
|
|
153
|
-
})
|
|
154
|
-
);
|
|
155
|
-
}
|
|
156
|
-
return yield* validateConfigEffect(rawConfig);
|
|
172
|
+
return yield* parseYamlContent(content, uri);
|
|
157
173
|
});
|
|
158
|
-
var
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
174
|
+
var loadFromHttpWithClient = (client, url) => Effect.scoped(
|
|
175
|
+
Effect.gen(function* () {
|
|
176
|
+
if (url.startsWith("http://")) {
|
|
177
|
+
return yield* Effect.fail(
|
|
178
|
+
new ConfigUrlError({
|
|
179
|
+
reason: "Insecure protocol: only HTTPS URLs are allowed"
|
|
180
|
+
})
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
const request = HttpClientRequest.get(url).pipe(
|
|
184
|
+
HttpClientRequest.setHeaders({
|
|
185
|
+
Accept: "application/yaml, text/yaml, application/x-yaml"
|
|
167
186
|
})
|
|
168
187
|
);
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
188
|
+
const response = yield* client.execute(request).pipe(
|
|
189
|
+
Effect.timeout(`${SECURITY_DEFAULTS.requestTimeout} millis`),
|
|
190
|
+
Effect.mapError((error) => {
|
|
191
|
+
if (error._tag === "TimeoutException") {
|
|
192
|
+
return new ConfigUrlError({
|
|
193
|
+
reason: `Config fetch timeout after ${SECURITY_DEFAULTS.requestTimeout}ms from ${url}`
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
return new ConfigUrlError({
|
|
197
|
+
reason: `Failed to load config from URL: ${url}`,
|
|
198
|
+
cause: error
|
|
199
|
+
});
|
|
174
200
|
})
|
|
175
201
|
);
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
202
|
+
if (response.status >= 400) {
|
|
203
|
+
return yield* Effect.fail(
|
|
204
|
+
new ConfigUrlError({
|
|
205
|
+
reason: `HTTP ${response.status} from ${url}`
|
|
206
|
+
})
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
const text = yield* response.text.pipe(
|
|
210
|
+
Effect.mapError(
|
|
211
|
+
(error) => new ConfigUrlError({
|
|
212
|
+
reason: `Failed to read response body from ${url}`,
|
|
213
|
+
cause: error
|
|
214
|
+
})
|
|
215
|
+
)
|
|
216
|
+
);
|
|
217
|
+
if (text.length > SECURITY_DEFAULTS.maxConfigSize) {
|
|
218
|
+
return yield* Effect.fail(
|
|
219
|
+
new ConfigUrlError({
|
|
220
|
+
reason: `Config exceeds maximum size of ${SECURITY_DEFAULTS.maxConfigSize} bytes`
|
|
221
|
+
})
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
return yield* parseYamlContent(text, url);
|
|
225
|
+
})
|
|
226
|
+
);
|
|
227
|
+
var makeConfigLoader = Effect.gen(function* () {
|
|
228
|
+
const fs = yield* Effect.serviceOption(FileSystem);
|
|
229
|
+
const http = yield* HttpClient.HttpClient;
|
|
230
|
+
const loadFromUriUncached = (uri) => Effect.gen(function* () {
|
|
231
|
+
if (uri.startsWith("file://")) {
|
|
232
|
+
const path = uri.slice(7);
|
|
233
|
+
if (fs._tag === "None") {
|
|
234
|
+
return yield* Effect.fail(
|
|
235
|
+
new ConfigFileError({
|
|
236
|
+
reason: "FileSystem not available (browser environment?)",
|
|
237
|
+
cause: { uri }
|
|
199
238
|
})
|
|
200
239
|
);
|
|
201
240
|
}
|
|
202
|
-
return
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
241
|
+
return yield* loadFromFileWithFs(fs.value, path, uri);
|
|
242
|
+
}
|
|
243
|
+
if (uri.startsWith("http://") || uri.startsWith("https://")) {
|
|
244
|
+
return yield* loadFromHttpWithClient(http, uri);
|
|
245
|
+
}
|
|
246
|
+
if (fs._tag === "Some") {
|
|
247
|
+
return yield* loadFromFileWithFs(fs.value, uri, uri);
|
|
248
|
+
} else {
|
|
249
|
+
return yield* loadFromHttpWithClient(http, uri);
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
const loadFromUriCached = yield* Effect.cachedFunction(loadFromUriUncached);
|
|
253
|
+
return ConfigLoader.of({
|
|
254
|
+
loadFromUri: loadFromUriCached,
|
|
255
|
+
loadFromInline: (content) => Effect.gen(function* () {
|
|
256
|
+
if (typeof content === "string") {
|
|
257
|
+
return yield* parseYamlContent(content);
|
|
258
|
+
}
|
|
259
|
+
return yield* Effect.try({
|
|
260
|
+
try: () => InstrumentationConfigSchema.parse(content),
|
|
261
|
+
catch: (error) => new ConfigValidationError({
|
|
262
|
+
reason: "Invalid configuration schema",
|
|
263
|
+
cause: error
|
|
264
|
+
})
|
|
265
|
+
});
|
|
225
266
|
})
|
|
226
267
|
});
|
|
227
|
-
if (text.length > SECURITY_DEFAULTS.maxConfigSize) {
|
|
228
|
-
return yield* Effect.fail(
|
|
229
|
-
new ConfigUrlError({
|
|
230
|
-
reason: `Config exceeds maximum size of ${SECURITY_DEFAULTS.maxConfigSize} bytes`
|
|
231
|
-
})
|
|
232
|
-
);
|
|
233
|
-
}
|
|
234
|
-
let rawConfig;
|
|
235
|
-
try {
|
|
236
|
-
rawConfig = parse(text);
|
|
237
|
-
} catch (error) {
|
|
238
|
-
return yield* Effect.fail(
|
|
239
|
-
new ConfigValidationError({
|
|
240
|
-
reason: "Invalid YAML syntax",
|
|
241
|
-
cause: error
|
|
242
|
-
})
|
|
243
|
-
);
|
|
244
|
-
}
|
|
245
|
-
return yield* validateConfigEffect(rawConfig);
|
|
246
|
-
});
|
|
247
|
-
var makeConfigCache = () => Cache.make({
|
|
248
|
-
capacity: 100,
|
|
249
|
-
timeToLive: Duration.minutes(5),
|
|
250
|
-
lookup: (url) => fetchAndParseConfig(url)
|
|
251
|
-
});
|
|
252
|
-
var cacheInstance = null;
|
|
253
|
-
var getCache = Effect.gen(function* () {
|
|
254
|
-
if (!cacheInstance) {
|
|
255
|
-
cacheInstance = yield* makeConfigCache();
|
|
256
|
-
}
|
|
257
|
-
return cacheInstance;
|
|
258
|
-
});
|
|
259
|
-
var loadConfigFromUrlEffect = (url, cacheTimeout = SECURITY_DEFAULTS.cacheTimeout) => Effect.gen(function* () {
|
|
260
|
-
if (cacheTimeout === 0) {
|
|
261
|
-
return yield* fetchAndParseConfig(url);
|
|
262
|
-
}
|
|
263
|
-
const cache = yield* getCache;
|
|
264
|
-
return yield* cache.get(url);
|
|
265
|
-
});
|
|
266
|
-
var loadConfigEffect = (options = {}) => Effect.gen(function* () {
|
|
267
|
-
if (options.config) {
|
|
268
|
-
return yield* validateConfigEffect(options.config);
|
|
269
|
-
}
|
|
270
|
-
const envConfigPath = process.env.ATRIM_INSTRUMENTATION_CONFIG;
|
|
271
|
-
if (envConfigPath) {
|
|
272
|
-
if (envConfigPath.startsWith("http://") || envConfigPath.startsWith("https://")) {
|
|
273
|
-
return yield* loadConfigFromUrlEffect(envConfigPath, options.cacheTimeout);
|
|
274
|
-
}
|
|
275
|
-
return yield* loadConfigFromFileEffect(envConfigPath);
|
|
276
|
-
}
|
|
277
|
-
if (options.configUrl) {
|
|
278
|
-
return yield* loadConfigFromUrlEffect(options.configUrl, options.cacheTimeout);
|
|
279
|
-
}
|
|
280
|
-
if (options.configPath) {
|
|
281
|
-
return yield* loadConfigFromFileEffect(options.configPath);
|
|
282
|
-
}
|
|
283
|
-
const defaultPath = join(process.cwd(), "instrumentation.yaml");
|
|
284
|
-
const exists = yield* Effect.sync(() => existsSync(defaultPath));
|
|
285
|
-
if (exists) {
|
|
286
|
-
return yield* loadConfigFromFileEffect(defaultPath);
|
|
287
|
-
}
|
|
288
|
-
return getDefaultConfig();
|
|
289
268
|
});
|
|
290
|
-
|
|
291
|
-
return Effect.runPromise(
|
|
292
|
-
loadConfigEffect(options).pipe(
|
|
293
|
-
// Convert typed errors to regular Error with reason message for backward compatibility
|
|
294
|
-
Effect.mapError((error) => {
|
|
295
|
-
const message = error.reason;
|
|
296
|
-
const newError = new Error(message);
|
|
297
|
-
newError.cause = error.cause;
|
|
298
|
-
return newError;
|
|
299
|
-
})
|
|
300
|
-
)
|
|
301
|
-
);
|
|
302
|
-
}
|
|
269
|
+
var ConfigLoaderLive = Layer.effect(ConfigLoader, makeConfigLoader);
|
|
303
270
|
var PatternMatcher = class {
|
|
304
271
|
constructor(config) {
|
|
305
272
|
__publicField2(this, "ignorePatterns", []);
|
|
@@ -716,19 +683,88 @@ var getServiceInfoWithFallback = detectServiceInfo.pipe(
|
|
|
716
683
|
})
|
|
717
684
|
)
|
|
718
685
|
);
|
|
719
|
-
|
|
720
|
-
|
|
686
|
+
var NodeConfigLoaderLive = ConfigLoaderLive.pipe(
|
|
687
|
+
Layer.provide(Layer.mergeAll(NodeContext.layer, FetchHttpClient.layer))
|
|
688
|
+
);
|
|
689
|
+
var cachedLoaderPromise = null;
|
|
690
|
+
function getCachedLoader() {
|
|
691
|
+
if (!cachedLoaderPromise) {
|
|
692
|
+
cachedLoaderPromise = Effect.runPromise(
|
|
693
|
+
Effect.gen(function* () {
|
|
694
|
+
return yield* ConfigLoader;
|
|
695
|
+
}).pipe(Effect.provide(NodeConfigLoaderLive))
|
|
696
|
+
);
|
|
697
|
+
}
|
|
698
|
+
return cachedLoaderPromise;
|
|
699
|
+
}
|
|
700
|
+
function _resetConfigLoaderCache() {
|
|
701
|
+
cachedLoaderPromise = null;
|
|
721
702
|
}
|
|
722
|
-
async function
|
|
723
|
-
|
|
703
|
+
async function loadConfig(uri, options) {
|
|
704
|
+
if (options?.cacheTimeout === 0) {
|
|
705
|
+
const program = Effect.gen(function* () {
|
|
706
|
+
const loader2 = yield* ConfigLoader;
|
|
707
|
+
return yield* loader2.loadFromUri(uri);
|
|
708
|
+
});
|
|
709
|
+
return Effect.runPromise(program.pipe(Effect.provide(NodeConfigLoaderLive)));
|
|
710
|
+
}
|
|
711
|
+
const loader = await getCachedLoader();
|
|
712
|
+
return Effect.runPromise(loader.loadFromUri(uri));
|
|
713
|
+
}
|
|
714
|
+
async function loadConfigFromInline(content) {
|
|
715
|
+
const loader = await getCachedLoader();
|
|
716
|
+
return Effect.runPromise(loader.loadFromInline(content));
|
|
717
|
+
}
|
|
718
|
+
function getDefaultConfig() {
|
|
719
|
+
return {
|
|
720
|
+
version: "1.0",
|
|
721
|
+
instrumentation: {
|
|
722
|
+
enabled: true,
|
|
723
|
+
logging: "on",
|
|
724
|
+
description: "Default instrumentation configuration",
|
|
725
|
+
instrument_patterns: [
|
|
726
|
+
{ pattern: "^app\\.", enabled: true, description: "Application operations" },
|
|
727
|
+
{ pattern: "^http\\.server\\.", enabled: true, description: "HTTP server operations" },
|
|
728
|
+
{ pattern: "^http\\.client\\.", enabled: true, description: "HTTP client operations" }
|
|
729
|
+
],
|
|
730
|
+
ignore_patterns: [
|
|
731
|
+
{ pattern: "^test\\.", description: "Test utilities" },
|
|
732
|
+
{ pattern: "^internal\\.", description: "Internal operations" },
|
|
733
|
+
{ pattern: "^health\\.", description: "Health checks" }
|
|
734
|
+
]
|
|
735
|
+
},
|
|
736
|
+
effect: {
|
|
737
|
+
auto_extract_metadata: true
|
|
738
|
+
}
|
|
739
|
+
};
|
|
724
740
|
}
|
|
725
|
-
async function
|
|
726
|
-
|
|
741
|
+
async function loadConfigWithOptions(options = {}) {
|
|
742
|
+
const loadOptions = options.cacheTimeout !== void 0 ? { cacheTimeout: options.cacheTimeout } : void 0;
|
|
743
|
+
if (options.config) {
|
|
744
|
+
return loadConfigFromInline(options.config);
|
|
745
|
+
}
|
|
746
|
+
const envConfigPath = process.env.ATRIM_INSTRUMENTATION_CONFIG;
|
|
747
|
+
if (envConfigPath) {
|
|
748
|
+
return loadConfig(envConfigPath, loadOptions);
|
|
749
|
+
}
|
|
750
|
+
if (options.configUrl) {
|
|
751
|
+
return loadConfig(options.configUrl, loadOptions);
|
|
752
|
+
}
|
|
753
|
+
if (options.configPath) {
|
|
754
|
+
return loadConfig(options.configPath, loadOptions);
|
|
755
|
+
}
|
|
756
|
+
const { existsSync } = await import('fs');
|
|
757
|
+
const { join: join2 } = await import('path');
|
|
758
|
+
const defaultPath = join2(process.cwd(), "instrumentation.yaml");
|
|
759
|
+
if (existsSync(defaultPath)) {
|
|
760
|
+
return loadConfig(defaultPath, loadOptions);
|
|
761
|
+
}
|
|
762
|
+
return getDefaultConfig();
|
|
727
763
|
}
|
|
728
764
|
|
|
729
765
|
// src/core/sdk-initializer.ts
|
|
730
766
|
var sdkInstance = null;
|
|
731
|
-
var
|
|
767
|
+
var initializationDeferred = null;
|
|
732
768
|
function buildHttpInstrumentationConfig(options, config, _otlpEndpoint) {
|
|
733
769
|
const httpConfig = { enabled: true };
|
|
734
770
|
const programmaticPatterns = options.http?.ignoreOutgoingUrls || [];
|
|
@@ -844,27 +880,38 @@ function isTracingAlreadyInitialized() {
|
|
|
844
880
|
return false;
|
|
845
881
|
}
|
|
846
882
|
}
|
|
847
|
-
|
|
883
|
+
var initializeSdkEffect = (options = {}) => Effect.gen(function* () {
|
|
848
884
|
if (sdkInstance) {
|
|
849
885
|
logger.warn("@atrim/instrumentation: SDK already initialized. Returning existing instance.");
|
|
850
886
|
return sdkInstance;
|
|
851
887
|
}
|
|
852
|
-
if (
|
|
888
|
+
if (initializationDeferred) {
|
|
853
889
|
logger.log(
|
|
854
|
-
"@atrim/instrumentation: SDK
|
|
890
|
+
"@atrim/instrumentation: SDK initialization in progress, waiting for completion..."
|
|
855
891
|
);
|
|
856
|
-
return
|
|
857
|
-
}
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
892
|
+
return yield* Deferred.await(initializationDeferred);
|
|
893
|
+
}
|
|
894
|
+
const deferred = yield* Deferred.make();
|
|
895
|
+
initializationDeferred = deferred;
|
|
896
|
+
const result = yield* performInitializationEffect(options).pipe(
|
|
897
|
+
Effect.tap((sdk) => Deferred.succeed(deferred, sdk)),
|
|
898
|
+
Effect.tapError((error) => Deferred.fail(deferred, error)),
|
|
899
|
+
Effect.ensuring(
|
|
900
|
+
Effect.sync(() => {
|
|
901
|
+
initializationDeferred = null;
|
|
902
|
+
})
|
|
903
|
+
)
|
|
904
|
+
);
|
|
905
|
+
return result;
|
|
906
|
+
});
|
|
907
|
+
var performInitializationEffect = (options) => Effect.gen(function* () {
|
|
908
|
+
const config = yield* Effect.tryPromise({
|
|
909
|
+
try: () => loadConfigWithOptions(options),
|
|
910
|
+
catch: (error) => new InitializationError2({
|
|
911
|
+
reason: "Failed to load configuration",
|
|
912
|
+
cause: error
|
|
913
|
+
})
|
|
914
|
+
});
|
|
868
915
|
const loggingLevel = config.instrumentation.logging || "on";
|
|
869
916
|
logger.setLevel(loggingLevel);
|
|
870
917
|
const alreadyInitialized = isTracingAlreadyInitialized();
|
|
@@ -880,14 +927,25 @@ async function performInitialization(options) {
|
|
|
880
927
|
logger.log("");
|
|
881
928
|
return null;
|
|
882
929
|
}
|
|
883
|
-
const serviceInfo =
|
|
930
|
+
const serviceInfo = yield* detectServiceInfo.pipe(
|
|
931
|
+
Effect.catchAll(
|
|
932
|
+
() => Effect.succeed({
|
|
933
|
+
name: "unknown-service",
|
|
934
|
+
version: void 0
|
|
935
|
+
})
|
|
936
|
+
)
|
|
937
|
+
);
|
|
884
938
|
const serviceName = options.serviceName || serviceInfo.name;
|
|
885
939
|
const serviceVersion = options.serviceVersion || serviceInfo.version;
|
|
886
|
-
const rawExporter = createOtlpExporter(options.otlp);
|
|
887
|
-
const exporter = new SafeSpanExporter(rawExporter);
|
|
940
|
+
const rawExporter = yield* Effect.sync(() => createOtlpExporter(options.otlp));
|
|
941
|
+
const exporter = yield* Effect.sync(() => new SafeSpanExporter(rawExporter));
|
|
888
942
|
const useSimpleProcessor = process.env.NODE_ENV === "test" || process.env.OTEL_USE_SIMPLE_PROCESSOR === "true";
|
|
889
|
-
const baseProcessor =
|
|
890
|
-
|
|
943
|
+
const baseProcessor = yield* Effect.sync(
|
|
944
|
+
() => useSimpleProcessor ? new SimpleSpanProcessor(exporter) : new BatchSpanProcessor(exporter)
|
|
945
|
+
);
|
|
946
|
+
const patternProcessor = yield* Effect.sync(
|
|
947
|
+
() => new PatternSpanProcessor(config, baseProcessor)
|
|
948
|
+
);
|
|
891
949
|
const instrumentations = [];
|
|
892
950
|
const hasWebFramework = hasWebFrameworkInstalled();
|
|
893
951
|
const enableAutoInstrumentation = shouldEnableAutoInstrumentation(
|
|
@@ -900,15 +958,11 @@ async function performInitialization(options) {
|
|
|
900
958
|
const undiciConfig = buildUndiciInstrumentationConfig(options, config);
|
|
901
959
|
instrumentations.push(
|
|
902
960
|
...getNodeAutoInstrumentations({
|
|
903
|
-
// Enable HTTP instrumentation with filtering (for http/https modules)
|
|
904
961
|
"@opentelemetry/instrumentation-http": httpConfig,
|
|
905
|
-
// Enable undici instrumentation with filtering (for fetch API)
|
|
906
962
|
"@opentelemetry/instrumentation-undici": undiciConfig,
|
|
907
|
-
// Enable web framework instrumentations
|
|
908
963
|
"@opentelemetry/instrumentation-express": { enabled: true },
|
|
909
964
|
"@opentelemetry/instrumentation-fastify": { enabled: true },
|
|
910
965
|
"@opentelemetry/instrumentation-koa": { enabled: true },
|
|
911
|
-
// Disable noisy instrumentations by default
|
|
912
966
|
"@opentelemetry/instrumentation-fs": { enabled: false },
|
|
913
967
|
"@opentelemetry/instrumentation-dns": { enabled: false }
|
|
914
968
|
})
|
|
@@ -936,18 +990,20 @@ async function performInitialization(options) {
|
|
|
936
990
|
serviceName,
|
|
937
991
|
...serviceVersion && { serviceVersion },
|
|
938
992
|
instrumentations,
|
|
939
|
-
// Allow advanced overrides
|
|
940
993
|
...options.sdk
|
|
941
994
|
};
|
|
942
|
-
const sdk =
|
|
943
|
-
|
|
995
|
+
const sdk = yield* Effect.sync(() => {
|
|
996
|
+
const s = new NodeSDK(sdkConfig);
|
|
997
|
+
s.start();
|
|
998
|
+
return s;
|
|
999
|
+
});
|
|
944
1000
|
sdkInstance = sdk;
|
|
945
1001
|
if (!options.disableAutoShutdown) {
|
|
946
|
-
registerShutdownHandlers(sdk);
|
|
1002
|
+
yield* Effect.sync(() => registerShutdownHandlers(sdk));
|
|
947
1003
|
}
|
|
948
1004
|
logInitialization(config, serviceName, serviceVersion, options, enableAutoInstrumentation);
|
|
949
1005
|
return sdk;
|
|
950
|
-
}
|
|
1006
|
+
});
|
|
951
1007
|
function getSdkInstance() {
|
|
952
1008
|
return sdkInstance;
|
|
953
1009
|
}
|
|
@@ -960,7 +1016,7 @@ async function shutdownSdk() {
|
|
|
960
1016
|
}
|
|
961
1017
|
function resetSdk() {
|
|
962
1018
|
sdkInstance = null;
|
|
963
|
-
|
|
1019
|
+
initializationDeferred = null;
|
|
964
1020
|
}
|
|
965
1021
|
function registerShutdownHandlers(sdk) {
|
|
966
1022
|
const shutdown = async (signal) => {
|
|
@@ -1017,33 +1073,11 @@ function logInitialization(config, serviceName, serviceVersion, options, autoIns
|
|
|
1017
1073
|
}
|
|
1018
1074
|
|
|
1019
1075
|
// src/api.ts
|
|
1020
|
-
|
|
1021
|
-
const sdk =
|
|
1022
|
-
if (sdk) {
|
|
1023
|
-
const config = await loadConfig(options);
|
|
1024
|
-
initializePatternMatcher(config);
|
|
1025
|
-
}
|
|
1026
|
-
return sdk;
|
|
1027
|
-
}
|
|
1028
|
-
async function initializePatternMatchingOnly(options = {}) {
|
|
1029
|
-
const config = await loadConfig(options);
|
|
1030
|
-
initializePatternMatcher(config);
|
|
1031
|
-
logger.log("@atrim/instrumentation: Pattern matching initialized (legacy mode)");
|
|
1032
|
-
logger.log(
|
|
1033
|
-
" Note: NodeSDK is not initialized. Use initializeInstrumentation() for complete setup."
|
|
1034
|
-
);
|
|
1035
|
-
}
|
|
1036
|
-
var initializeInstrumentationEffect = (options = {}) => Effect.gen(function* () {
|
|
1037
|
-
const sdk = yield* Effect.tryPromise({
|
|
1038
|
-
try: () => initializeSdk(options),
|
|
1039
|
-
catch: (error) => new InitializationError2({
|
|
1040
|
-
reason: "SDK initialization failed",
|
|
1041
|
-
cause: error
|
|
1042
|
-
})
|
|
1043
|
-
});
|
|
1076
|
+
var initializeInstrumentation = (options = {}) => Effect.gen(function* () {
|
|
1077
|
+
const sdk = yield* initializeSdkEffect(options);
|
|
1044
1078
|
if (sdk) {
|
|
1045
1079
|
yield* Effect.tryPromise({
|
|
1046
|
-
try: () =>
|
|
1080
|
+
try: () => loadConfigWithOptions(options),
|
|
1047
1081
|
catch: (error) => new ConfigError2({
|
|
1048
1082
|
reason: "Failed to load config for pattern matcher",
|
|
1049
1083
|
cause: error
|
|
@@ -1058,9 +1092,9 @@ var initializeInstrumentationEffect = (options = {}) => Effect.gen(function* ()
|
|
|
1058
1092
|
}
|
|
1059
1093
|
return sdk;
|
|
1060
1094
|
});
|
|
1061
|
-
var
|
|
1095
|
+
var initializePatternMatchingOnly = (options = {}) => Effect.gen(function* () {
|
|
1062
1096
|
const config = yield* Effect.tryPromise({
|
|
1063
|
-
try: () =>
|
|
1097
|
+
try: () => loadConfigWithOptions(options),
|
|
1064
1098
|
catch: (error) => new ConfigError2({
|
|
1065
1099
|
reason: "Failed to load configuration",
|
|
1066
1100
|
cause: error
|
|
@@ -1068,7 +1102,7 @@ var initializePatternMatchingOnlyEffect = (options = {}) => Effect.gen(function*
|
|
|
1068
1102
|
});
|
|
1069
1103
|
yield* Effect.sync(() => {
|
|
1070
1104
|
initializePatternMatcher(config);
|
|
1071
|
-
logger.log("@atrim/instrumentation: Pattern matching initialized (
|
|
1105
|
+
logger.log("@atrim/instrumentation: Pattern matching initialized (pattern-only mode)");
|
|
1072
1106
|
logger.log(
|
|
1073
1107
|
" Note: NodeSDK is not initialized. Use initializeInstrumentation() for complete setup."
|
|
1074
1108
|
);
|
|
@@ -1169,6 +1203,6 @@ function suppressShutdownErrors() {
|
|
|
1169
1203
|
});
|
|
1170
1204
|
}
|
|
1171
1205
|
|
|
1172
|
-
export { ConfigError2 as ConfigError, ConfigFileError2 as ConfigFileError, ConfigUrlError2 as ConfigUrlError, ConfigValidationError2 as ConfigValidationError, ExportError2 as ExportError, InitializationError2 as InitializationError, PatternMatcher, PatternSpanProcessor, ServiceDetectionError2 as ServiceDetectionError, ShutdownError2 as ShutdownError, annotateCacheOperation, annotateDbQuery, annotateHttpRequest,
|
|
1206
|
+
export { ConfigError2 as ConfigError, ConfigFileError2 as ConfigFileError, ConfigUrlError2 as ConfigUrlError, ConfigValidationError2 as ConfigValidationError, ExportError2 as ExportError, InitializationError2 as InitializationError, PatternMatcher, PatternSpanProcessor, ServiceDetectionError2 as ServiceDetectionError, ShutdownError2 as ShutdownError, annotateCacheOperation, annotateDbQuery, annotateHttpRequest, _resetConfigLoaderCache as clearConfigCache, createOtlpExporter, detectServiceInfo, getOtlpEndpoint, getPatternMatcher, getSdkInstance, getServiceInfoWithFallback, getServiceName, getServiceVersion, initializeInstrumentation, initializePatternMatchingOnly, loadConfig, loadConfigFromInline, loadConfigWithOptions, markSpanError, markSpanSuccess, recordException, resetSdk, setSpanAttributes, shouldInstrumentSpan, shutdownSdk, suppressShutdownErrors };
|
|
1173
1207
|
//# sourceMappingURL=index.js.map
|
|
1174
1208
|
//# sourceMappingURL=index.js.map
|