@atrim/instrument-node 0.5.0 → 0.5.1-21bb978-20260105202350
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/README.md +73 -274
- package/package.json +20 -19
- package/target/dist/index.cjs +155 -60
- package/target/dist/index.cjs.map +1 -1
- package/target/dist/index.d.cts +28 -13
- package/target/dist/index.d.ts +28 -13
- package/target/dist/index.js +154 -60
- package/target/dist/index.js.map +1 -1
- package/target/dist/integrations/effect/index.cjs +346 -130
- package/target/dist/integrations/effect/index.cjs.map +1 -1
- package/target/dist/integrations/effect/index.d.cts +335 -36
- package/target/dist/integrations/effect/index.d.ts +335 -36
- package/target/dist/integrations/effect/index.js +340 -134
- package/target/dist/integrations/effect/index.js.map +1 -1
package/target/dist/index.cjs
CHANGED
|
@@ -13,9 +13,9 @@ var zod = require('zod');
|
|
|
13
13
|
var exporterTraceOtlpHttp = require('@opentelemetry/exporter-trace-otlp-http');
|
|
14
14
|
var promises = require('fs/promises');
|
|
15
15
|
var path = require('path');
|
|
16
|
-
var
|
|
17
|
-
var platform = require('@effect/platform');
|
|
16
|
+
var module$1 = require('module');
|
|
18
17
|
|
|
18
|
+
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
19
19
|
function _interopNamespace(e) {
|
|
20
20
|
if (e && e.__esModule) return e;
|
|
21
21
|
var n = Object.create(null);
|
|
@@ -82,7 +82,20 @@ var HttpFilteringConfigSchema = zod.z.object({
|
|
|
82
82
|
// Patterns to ignore for incoming HTTP requests (string patterns only in YAML)
|
|
83
83
|
ignore_incoming_paths: zod.z.array(zod.z.string()).optional(),
|
|
84
84
|
// Require parent span for outgoing requests (prevents root spans for HTTP calls)
|
|
85
|
-
require_parent_for_outgoing_spans: zod.z.boolean().optional()
|
|
85
|
+
require_parent_for_outgoing_spans: zod.z.boolean().optional(),
|
|
86
|
+
// Trace context propagation configuration
|
|
87
|
+
// Controls which cross-origin requests receive W3C Trace Context headers (traceparent, tracestate)
|
|
88
|
+
propagate_trace_context: zod.z.object({
|
|
89
|
+
// Strategy for trace propagation
|
|
90
|
+
// - "all": Propagate to all cross-origin requests (may cause CORS errors)
|
|
91
|
+
// - "none": Never propagate trace headers
|
|
92
|
+
// - "same-origin": Only propagate to same-origin requests (default, safe)
|
|
93
|
+
// - "patterns": Propagate based on include_urls patterns
|
|
94
|
+
strategy: zod.z.enum(["all", "none", "same-origin", "patterns"]).default("same-origin"),
|
|
95
|
+
// URL patterns to include when strategy is "patterns"
|
|
96
|
+
// Supports regex patterns (e.g., "^https://api\\.myapp\\.com")
|
|
97
|
+
include_urls: zod.z.array(zod.z.string()).optional()
|
|
98
|
+
}).optional()
|
|
86
99
|
});
|
|
87
100
|
var InstrumentationConfigSchema = zod.z.object({
|
|
88
101
|
version: zod.z.string(),
|
|
@@ -94,11 +107,56 @@ var InstrumentationConfigSchema = zod.z.object({
|
|
|
94
107
|
ignore_patterns: zod.z.array(PatternConfigSchema)
|
|
95
108
|
}),
|
|
96
109
|
effect: zod.z.object({
|
|
110
|
+
// Enable/disable Effect tracing entirely
|
|
111
|
+
// When false, EffectInstrumentationLive returns Layer.empty
|
|
112
|
+
enabled: zod.z.boolean().default(true),
|
|
113
|
+
// Exporter mode:
|
|
114
|
+
// - "unified": Use global TracerProvider from Node SDK (recommended, enables filtering)
|
|
115
|
+
// - "standalone": Use Effect's own OTLP exporter (bypasses Node SDK filtering)
|
|
116
|
+
exporter: zod.z.enum(["unified", "standalone"]).default("unified"),
|
|
97
117
|
auto_extract_metadata: zod.z.boolean(),
|
|
118
|
+
// Auto-bridge OpenTelemetry context to Effect spans
|
|
119
|
+
// When true, Effect spans automatically become children of the active OTel span
|
|
120
|
+
// (e.g., HTTP request span from auto-instrumentation)
|
|
121
|
+
// This is essential for proper trace hierarchy when using Effect with HTTP frameworks
|
|
122
|
+
auto_bridge_context: zod.z.boolean().default(true),
|
|
98
123
|
auto_isolation: AutoIsolationConfigSchema.optional()
|
|
99
124
|
}).optional(),
|
|
100
125
|
http: HttpFilteringConfigSchema.optional()
|
|
101
126
|
});
|
|
127
|
+
var defaultConfig = {
|
|
128
|
+
version: "1.0",
|
|
129
|
+
instrumentation: {
|
|
130
|
+
enabled: true,
|
|
131
|
+
logging: "on",
|
|
132
|
+
description: "Default instrumentation configuration",
|
|
133
|
+
instrument_patterns: [
|
|
134
|
+
{ pattern: "^app\\.", enabled: true, description: "Application operations" },
|
|
135
|
+
{ pattern: "^http\\.server\\.", enabled: true, description: "HTTP server operations" },
|
|
136
|
+
{ pattern: "^http\\.client\\.", enabled: true, description: "HTTP client operations" }
|
|
137
|
+
],
|
|
138
|
+
ignore_patterns: [
|
|
139
|
+
{ pattern: "^test\\.", description: "Test utilities" },
|
|
140
|
+
{ pattern: "^internal\\.", description: "Internal operations" },
|
|
141
|
+
{ pattern: "^health\\.", description: "Health checks" }
|
|
142
|
+
]
|
|
143
|
+
},
|
|
144
|
+
effect: {
|
|
145
|
+
enabled: true,
|
|
146
|
+
exporter: "unified",
|
|
147
|
+
auto_extract_metadata: true,
|
|
148
|
+
auto_bridge_context: true
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
function parseAndValidateConfig(content) {
|
|
152
|
+
let parsed;
|
|
153
|
+
if (typeof content === "string") {
|
|
154
|
+
parsed = yaml.parse(content);
|
|
155
|
+
} else {
|
|
156
|
+
parsed = content;
|
|
157
|
+
}
|
|
158
|
+
return InstrumentationConfigSchema.parse(parsed);
|
|
159
|
+
}
|
|
102
160
|
(class extends effect.Data.TaggedError("ConfigError") {
|
|
103
161
|
get message() {
|
|
104
162
|
return this.reason;
|
|
@@ -276,7 +334,7 @@ var makeConfigLoader = effect.Effect.gen(function* () {
|
|
|
276
334
|
})
|
|
277
335
|
});
|
|
278
336
|
});
|
|
279
|
-
|
|
337
|
+
effect.Layer.effect(ConfigLoader, makeConfigLoader);
|
|
280
338
|
var PatternMatcher = class {
|
|
281
339
|
constructor(config) {
|
|
282
340
|
__publicField2(this, "ignorePatterns", []);
|
|
@@ -440,8 +498,14 @@ var PatternSpanProcessor = class {
|
|
|
440
498
|
constructor(config, wrappedProcessor) {
|
|
441
499
|
__publicField(this, "matcher");
|
|
442
500
|
__publicField(this, "wrappedProcessor");
|
|
501
|
+
__publicField(this, "httpIgnorePatterns", []);
|
|
443
502
|
this.matcher = new PatternMatcher(config);
|
|
444
503
|
this.wrappedProcessor = wrappedProcessor;
|
|
504
|
+
if (config.http?.ignore_incoming_paths) {
|
|
505
|
+
this.httpIgnorePatterns = config.http.ignore_incoming_paths.map(
|
|
506
|
+
(pattern) => new RegExp(pattern)
|
|
507
|
+
);
|
|
508
|
+
}
|
|
445
509
|
}
|
|
446
510
|
/**
|
|
447
511
|
* Called when a span is started
|
|
@@ -459,12 +523,40 @@ var PatternSpanProcessor = class {
|
|
|
459
523
|
* Called when a span is ended
|
|
460
524
|
*
|
|
461
525
|
* This is where we make the final decision on whether to export the span.
|
|
526
|
+
* We check both span name patterns and HTTP path patterns.
|
|
462
527
|
*/
|
|
463
528
|
onEnd(span) {
|
|
464
529
|
const spanName = span.name;
|
|
465
|
-
if (this.matcher.shouldInstrument(spanName)) {
|
|
466
|
-
|
|
530
|
+
if (!this.matcher.shouldInstrument(spanName)) {
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
if (this.shouldIgnoreHttpSpan(span)) {
|
|
534
|
+
return;
|
|
467
535
|
}
|
|
536
|
+
this.wrappedProcessor.onEnd(span);
|
|
537
|
+
}
|
|
538
|
+
/**
|
|
539
|
+
* Check if span should be ignored based on HTTP path attributes
|
|
540
|
+
*
|
|
541
|
+
* This checks the span's url.path, http.route, or http.target attributes
|
|
542
|
+
* against the configured http.ignore_incoming_paths patterns.
|
|
543
|
+
*
|
|
544
|
+
* This enables filtering of Effect HTTP spans (and any other HTTP spans)
|
|
545
|
+
* based on path patterns, which is essential for filtering out OTLP
|
|
546
|
+
* endpoint requests like /v1/traces, /v1/logs, /v1/metrics.
|
|
547
|
+
*/
|
|
548
|
+
shouldIgnoreHttpSpan(span) {
|
|
549
|
+
if (this.httpIgnorePatterns.length === 0) {
|
|
550
|
+
return false;
|
|
551
|
+
}
|
|
552
|
+
const urlPath = span.attributes["url.path"];
|
|
553
|
+
const httpRoute = span.attributes["http.route"];
|
|
554
|
+
const httpTarget = span.attributes["http.target"];
|
|
555
|
+
const pathToCheck = urlPath || httpRoute || httpTarget;
|
|
556
|
+
if (!pathToCheck) {
|
|
557
|
+
return false;
|
|
558
|
+
}
|
|
559
|
+
return this.httpIgnorePatterns.some((pattern) => pattern.test(pathToCheck));
|
|
468
560
|
}
|
|
469
561
|
/**
|
|
470
562
|
* Shutdown the processor
|
|
@@ -702,83 +794,55 @@ async function getServiceNameAsync() {
|
|
|
702
794
|
async function getServiceVersionAsync() {
|
|
703
795
|
return effect.Effect.runPromise(getServiceVersion);
|
|
704
796
|
}
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
);
|
|
708
|
-
|
|
709
|
-
function getCachedLoader() {
|
|
710
|
-
if (!cachedLoaderPromise) {
|
|
711
|
-
cachedLoaderPromise = effect.Effect.runPromise(
|
|
712
|
-
effect.Effect.gen(function* () {
|
|
713
|
-
return yield* ConfigLoader;
|
|
714
|
-
}).pipe(effect.Effect.provide(NodeConfigLoaderLive))
|
|
715
|
-
);
|
|
716
|
-
}
|
|
717
|
-
return cachedLoaderPromise;
|
|
797
|
+
async function loadFromFile(filePath) {
|
|
798
|
+
const { readFile: readFile2 } = await import('fs/promises');
|
|
799
|
+
const content = await readFile2(filePath, "utf-8");
|
|
800
|
+
return parseAndValidateConfig(content);
|
|
718
801
|
}
|
|
719
|
-
function
|
|
720
|
-
|
|
802
|
+
async function loadFromUrl(url) {
|
|
803
|
+
const response = await fetch(url);
|
|
804
|
+
if (!response.ok) {
|
|
805
|
+
throw new Error(`Failed to fetch config from ${url}: ${response.statusText}`);
|
|
806
|
+
}
|
|
807
|
+
const content = await response.text();
|
|
808
|
+
return parseAndValidateConfig(content);
|
|
721
809
|
}
|
|
722
|
-
async function loadConfig(uri,
|
|
723
|
-
if (
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
return
|
|
810
|
+
async function loadConfig(uri, _options) {
|
|
811
|
+
if (uri.startsWith("http://") || uri.startsWith("https://")) {
|
|
812
|
+
return loadFromUrl(uri);
|
|
813
|
+
}
|
|
814
|
+
if (uri.startsWith("file://")) {
|
|
815
|
+
const filePath = uri.slice(7);
|
|
816
|
+
return loadFromFile(filePath);
|
|
729
817
|
}
|
|
730
|
-
|
|
731
|
-
return effect.Effect.runPromise(loader.loadFromUri(uri));
|
|
818
|
+
return loadFromFile(uri);
|
|
732
819
|
}
|
|
733
820
|
async function loadConfigFromInline(content) {
|
|
734
|
-
|
|
735
|
-
return effect.Effect.runPromise(loader.loadFromInline(content));
|
|
821
|
+
return parseAndValidateConfig(content);
|
|
736
822
|
}
|
|
737
|
-
function
|
|
738
|
-
return {
|
|
739
|
-
version: "1.0",
|
|
740
|
-
instrumentation: {
|
|
741
|
-
enabled: true,
|
|
742
|
-
logging: "on",
|
|
743
|
-
description: "Default instrumentation configuration",
|
|
744
|
-
instrument_patterns: [
|
|
745
|
-
{ pattern: "^app\\.", enabled: true, description: "Application operations" },
|
|
746
|
-
{ pattern: "^http\\.server\\.", enabled: true, description: "HTTP server operations" },
|
|
747
|
-
{ pattern: "^http\\.client\\.", enabled: true, description: "HTTP client operations" }
|
|
748
|
-
],
|
|
749
|
-
ignore_patterns: [
|
|
750
|
-
{ pattern: "^test\\.", description: "Test utilities" },
|
|
751
|
-
{ pattern: "^internal\\.", description: "Internal operations" },
|
|
752
|
-
{ pattern: "^health\\.", description: "Health checks" }
|
|
753
|
-
]
|
|
754
|
-
},
|
|
755
|
-
effect: {
|
|
756
|
-
auto_extract_metadata: true
|
|
757
|
-
}
|
|
758
|
-
};
|
|
823
|
+
function _resetConfigLoaderCache() {
|
|
759
824
|
}
|
|
760
825
|
async function loadConfigWithOptions(options = {}) {
|
|
761
|
-
const loadOptions = options.cacheTimeout !== void 0 ? { cacheTimeout: options.cacheTimeout } : void 0;
|
|
762
826
|
if (options.config) {
|
|
763
827
|
return loadConfigFromInline(options.config);
|
|
764
828
|
}
|
|
765
829
|
const envConfigPath = process.env.ATRIM_INSTRUMENTATION_CONFIG;
|
|
766
830
|
if (envConfigPath) {
|
|
767
|
-
return loadConfig(envConfigPath
|
|
831
|
+
return loadConfig(envConfigPath);
|
|
768
832
|
}
|
|
769
833
|
if (options.configUrl) {
|
|
770
|
-
return loadConfig(options.configUrl
|
|
834
|
+
return loadConfig(options.configUrl);
|
|
771
835
|
}
|
|
772
836
|
if (options.configPath) {
|
|
773
|
-
return loadConfig(options.configPath
|
|
837
|
+
return loadConfig(options.configPath);
|
|
774
838
|
}
|
|
775
839
|
const { existsSync } = await import('fs');
|
|
776
840
|
const { join: join2 } = await import('path');
|
|
777
841
|
const defaultPath = join2(process.cwd(), "instrumentation.yaml");
|
|
778
842
|
if (existsSync(defaultPath)) {
|
|
779
|
-
return loadConfig(defaultPath
|
|
843
|
+
return loadConfig(defaultPath);
|
|
780
844
|
}
|
|
781
|
-
return
|
|
845
|
+
return defaultConfig;
|
|
782
846
|
}
|
|
783
847
|
|
|
784
848
|
// src/core/sdk-initializer.ts
|
|
@@ -1070,9 +1134,39 @@ function logInitialization(config, serviceName, serviceVersion, options, autoIns
|
|
|
1070
1134
|
logger.log(` - OTLP endpoint: ${endpoint}`);
|
|
1071
1135
|
logger.log("");
|
|
1072
1136
|
}
|
|
1137
|
+
var require2 = module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
|
|
1138
|
+
function validateOpenTelemetryApi() {
|
|
1139
|
+
try {
|
|
1140
|
+
require2.resolve("@opentelemetry/api");
|
|
1141
|
+
} catch {
|
|
1142
|
+
throw new Error(
|
|
1143
|
+
"@atrim/instrument-node requires @opentelemetry/api as a peer dependency.\n\nInstall it with:\n npm install @opentelemetry/api\n\nOr with your preferred package manager:\n pnpm add @opentelemetry/api\n yarn add @opentelemetry/api\n bun add @opentelemetry/api"
|
|
1144
|
+
);
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
function validateEffectDependencies() {
|
|
1148
|
+
const packages = ["effect", "@effect/opentelemetry", "@effect/platform"];
|
|
1149
|
+
for (const pkg of packages) {
|
|
1150
|
+
try {
|
|
1151
|
+
require2.resolve(pkg);
|
|
1152
|
+
} catch {
|
|
1153
|
+
return false;
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
return true;
|
|
1157
|
+
}
|
|
1158
|
+
var validateDependencies = effect.Effect.try({
|
|
1159
|
+
try: () => validateOpenTelemetryApi(),
|
|
1160
|
+
catch: (error) => new InitializationError2({
|
|
1161
|
+
reason: error instanceof Error ? error.message : "Dependency validation failed",
|
|
1162
|
+
cause: error
|
|
1163
|
+
})
|
|
1164
|
+
});
|
|
1165
|
+
effect.Effect.sync(() => validateEffectDependencies());
|
|
1073
1166
|
|
|
1074
1167
|
// src/api.ts
|
|
1075
1168
|
async function initializeInstrumentation(options = {}) {
|
|
1169
|
+
validateOpenTelemetryApi();
|
|
1076
1170
|
const sdk = await initializeSdk(options);
|
|
1077
1171
|
if (sdk) {
|
|
1078
1172
|
const config = await loadConfigWithOptions(options);
|
|
@@ -1089,6 +1183,7 @@ async function initializePatternMatchingOnly(options = {}) {
|
|
|
1089
1183
|
);
|
|
1090
1184
|
}
|
|
1091
1185
|
var initializeInstrumentationEffect = (options = {}) => effect.Effect.gen(function* () {
|
|
1186
|
+
yield* validateDependencies;
|
|
1092
1187
|
const sdk = yield* effect.Effect.tryPromise({
|
|
1093
1188
|
try: () => initializeSdk(options),
|
|
1094
1189
|
catch: (error) => new InitializationError2({
|