@atrim/instrument-node 0.5.0 → 0.5.1-1451fcf-20260105212505
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 +97 -269
- package/package.json +20 -19
- package/target/dist/index.cjs +149 -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 +148 -60
- package/target/dist/index.js.map +1 -1
- package/target/dist/integrations/effect/index.cjs +301 -130
- package/target/dist/integrations/effect/index.cjs.map +1 -1
- package/target/dist/integrations/effect/index.d.cts +205 -36
- package/target/dist/integrations/effect/index.d.ts +205 -36
- package/target/dist/integrations/effect/index.js +298 -132
- 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,50 @@ 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(),
|
|
98
118
|
auto_isolation: AutoIsolationConfigSchema.optional()
|
|
99
119
|
}).optional(),
|
|
100
120
|
http: HttpFilteringConfigSchema.optional()
|
|
101
121
|
});
|
|
122
|
+
var defaultConfig = {
|
|
123
|
+
version: "1.0",
|
|
124
|
+
instrumentation: {
|
|
125
|
+
enabled: true,
|
|
126
|
+
logging: "on",
|
|
127
|
+
description: "Default instrumentation configuration",
|
|
128
|
+
instrument_patterns: [
|
|
129
|
+
{ pattern: "^app\\.", enabled: true, description: "Application operations" },
|
|
130
|
+
{ pattern: "^http\\.server\\.", enabled: true, description: "HTTP server operations" },
|
|
131
|
+
{ pattern: "^http\\.client\\.", enabled: true, description: "HTTP client operations" }
|
|
132
|
+
],
|
|
133
|
+
ignore_patterns: [
|
|
134
|
+
{ pattern: "^test\\.", description: "Test utilities" },
|
|
135
|
+
{ pattern: "^internal\\.", description: "Internal operations" },
|
|
136
|
+
{ pattern: "^health\\.", description: "Health checks" }
|
|
137
|
+
]
|
|
138
|
+
},
|
|
139
|
+
effect: {
|
|
140
|
+
enabled: true,
|
|
141
|
+
exporter: "unified",
|
|
142
|
+
auto_extract_metadata: true
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
function parseAndValidateConfig(content) {
|
|
146
|
+
let parsed;
|
|
147
|
+
if (typeof content === "string") {
|
|
148
|
+
parsed = yaml.parse(content);
|
|
149
|
+
} else {
|
|
150
|
+
parsed = content;
|
|
151
|
+
}
|
|
152
|
+
return InstrumentationConfigSchema.parse(parsed);
|
|
153
|
+
}
|
|
102
154
|
(class extends effect.Data.TaggedError("ConfigError") {
|
|
103
155
|
get message() {
|
|
104
156
|
return this.reason;
|
|
@@ -276,7 +328,7 @@ var makeConfigLoader = effect.Effect.gen(function* () {
|
|
|
276
328
|
})
|
|
277
329
|
});
|
|
278
330
|
});
|
|
279
|
-
|
|
331
|
+
effect.Layer.effect(ConfigLoader, makeConfigLoader);
|
|
280
332
|
var PatternMatcher = class {
|
|
281
333
|
constructor(config) {
|
|
282
334
|
__publicField2(this, "ignorePatterns", []);
|
|
@@ -440,8 +492,14 @@ var PatternSpanProcessor = class {
|
|
|
440
492
|
constructor(config, wrappedProcessor) {
|
|
441
493
|
__publicField(this, "matcher");
|
|
442
494
|
__publicField(this, "wrappedProcessor");
|
|
495
|
+
__publicField(this, "httpIgnorePatterns", []);
|
|
443
496
|
this.matcher = new PatternMatcher(config);
|
|
444
497
|
this.wrappedProcessor = wrappedProcessor;
|
|
498
|
+
if (config.http?.ignore_incoming_paths) {
|
|
499
|
+
this.httpIgnorePatterns = config.http.ignore_incoming_paths.map(
|
|
500
|
+
(pattern) => new RegExp(pattern)
|
|
501
|
+
);
|
|
502
|
+
}
|
|
445
503
|
}
|
|
446
504
|
/**
|
|
447
505
|
* Called when a span is started
|
|
@@ -459,12 +517,40 @@ var PatternSpanProcessor = class {
|
|
|
459
517
|
* Called when a span is ended
|
|
460
518
|
*
|
|
461
519
|
* This is where we make the final decision on whether to export the span.
|
|
520
|
+
* We check both span name patterns and HTTP path patterns.
|
|
462
521
|
*/
|
|
463
522
|
onEnd(span) {
|
|
464
523
|
const spanName = span.name;
|
|
465
|
-
if (this.matcher.shouldInstrument(spanName)) {
|
|
466
|
-
|
|
524
|
+
if (!this.matcher.shouldInstrument(spanName)) {
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
if (this.shouldIgnoreHttpSpan(span)) {
|
|
528
|
+
return;
|
|
467
529
|
}
|
|
530
|
+
this.wrappedProcessor.onEnd(span);
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* Check if span should be ignored based on HTTP path attributes
|
|
534
|
+
*
|
|
535
|
+
* This checks the span's url.path, http.route, or http.target attributes
|
|
536
|
+
* against the configured http.ignore_incoming_paths patterns.
|
|
537
|
+
*
|
|
538
|
+
* This enables filtering of Effect HTTP spans (and any other HTTP spans)
|
|
539
|
+
* based on path patterns, which is essential for filtering out OTLP
|
|
540
|
+
* endpoint requests like /v1/traces, /v1/logs, /v1/metrics.
|
|
541
|
+
*/
|
|
542
|
+
shouldIgnoreHttpSpan(span) {
|
|
543
|
+
if (this.httpIgnorePatterns.length === 0) {
|
|
544
|
+
return false;
|
|
545
|
+
}
|
|
546
|
+
const urlPath = span.attributes["url.path"];
|
|
547
|
+
const httpRoute = span.attributes["http.route"];
|
|
548
|
+
const httpTarget = span.attributes["http.target"];
|
|
549
|
+
const pathToCheck = urlPath || httpRoute || httpTarget;
|
|
550
|
+
if (!pathToCheck) {
|
|
551
|
+
return false;
|
|
552
|
+
}
|
|
553
|
+
return this.httpIgnorePatterns.some((pattern) => pattern.test(pathToCheck));
|
|
468
554
|
}
|
|
469
555
|
/**
|
|
470
556
|
* Shutdown the processor
|
|
@@ -702,83 +788,55 @@ async function getServiceNameAsync() {
|
|
|
702
788
|
async function getServiceVersionAsync() {
|
|
703
789
|
return effect.Effect.runPromise(getServiceVersion);
|
|
704
790
|
}
|
|
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;
|
|
791
|
+
async function loadFromFile(filePath) {
|
|
792
|
+
const { readFile: readFile2 } = await import('fs/promises');
|
|
793
|
+
const content = await readFile2(filePath, "utf-8");
|
|
794
|
+
return parseAndValidateConfig(content);
|
|
718
795
|
}
|
|
719
|
-
function
|
|
720
|
-
|
|
796
|
+
async function loadFromUrl(url) {
|
|
797
|
+
const response = await fetch(url);
|
|
798
|
+
if (!response.ok) {
|
|
799
|
+
throw new Error(`Failed to fetch config from ${url}: ${response.statusText}`);
|
|
800
|
+
}
|
|
801
|
+
const content = await response.text();
|
|
802
|
+
return parseAndValidateConfig(content);
|
|
721
803
|
}
|
|
722
|
-
async function loadConfig(uri,
|
|
723
|
-
if (
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
return
|
|
804
|
+
async function loadConfig(uri, _options) {
|
|
805
|
+
if (uri.startsWith("http://") || uri.startsWith("https://")) {
|
|
806
|
+
return loadFromUrl(uri);
|
|
807
|
+
}
|
|
808
|
+
if (uri.startsWith("file://")) {
|
|
809
|
+
const filePath = uri.slice(7);
|
|
810
|
+
return loadFromFile(filePath);
|
|
729
811
|
}
|
|
730
|
-
|
|
731
|
-
return effect.Effect.runPromise(loader.loadFromUri(uri));
|
|
812
|
+
return loadFromFile(uri);
|
|
732
813
|
}
|
|
733
814
|
async function loadConfigFromInline(content) {
|
|
734
|
-
|
|
735
|
-
return effect.Effect.runPromise(loader.loadFromInline(content));
|
|
815
|
+
return parseAndValidateConfig(content);
|
|
736
816
|
}
|
|
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
|
-
};
|
|
817
|
+
function _resetConfigLoaderCache() {
|
|
759
818
|
}
|
|
760
819
|
async function loadConfigWithOptions(options = {}) {
|
|
761
|
-
const loadOptions = options.cacheTimeout !== void 0 ? { cacheTimeout: options.cacheTimeout } : void 0;
|
|
762
820
|
if (options.config) {
|
|
763
821
|
return loadConfigFromInline(options.config);
|
|
764
822
|
}
|
|
765
823
|
const envConfigPath = process.env.ATRIM_INSTRUMENTATION_CONFIG;
|
|
766
824
|
if (envConfigPath) {
|
|
767
|
-
return loadConfig(envConfigPath
|
|
825
|
+
return loadConfig(envConfigPath);
|
|
768
826
|
}
|
|
769
827
|
if (options.configUrl) {
|
|
770
|
-
return loadConfig(options.configUrl
|
|
828
|
+
return loadConfig(options.configUrl);
|
|
771
829
|
}
|
|
772
830
|
if (options.configPath) {
|
|
773
|
-
return loadConfig(options.configPath
|
|
831
|
+
return loadConfig(options.configPath);
|
|
774
832
|
}
|
|
775
833
|
const { existsSync } = await import('fs');
|
|
776
834
|
const { join: join2 } = await import('path');
|
|
777
835
|
const defaultPath = join2(process.cwd(), "instrumentation.yaml");
|
|
778
836
|
if (existsSync(defaultPath)) {
|
|
779
|
-
return loadConfig(defaultPath
|
|
837
|
+
return loadConfig(defaultPath);
|
|
780
838
|
}
|
|
781
|
-
return
|
|
839
|
+
return defaultConfig;
|
|
782
840
|
}
|
|
783
841
|
|
|
784
842
|
// src/core/sdk-initializer.ts
|
|
@@ -1070,9 +1128,39 @@ function logInitialization(config, serviceName, serviceVersion, options, autoIns
|
|
|
1070
1128
|
logger.log(` - OTLP endpoint: ${endpoint}`);
|
|
1071
1129
|
logger.log("");
|
|
1072
1130
|
}
|
|
1131
|
+
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)));
|
|
1132
|
+
function validateOpenTelemetryApi() {
|
|
1133
|
+
try {
|
|
1134
|
+
require2.resolve("@opentelemetry/api");
|
|
1135
|
+
} catch {
|
|
1136
|
+
throw new Error(
|
|
1137
|
+
"@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"
|
|
1138
|
+
);
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
function validateEffectDependencies() {
|
|
1142
|
+
const packages = ["effect", "@effect/opentelemetry", "@effect/platform"];
|
|
1143
|
+
for (const pkg of packages) {
|
|
1144
|
+
try {
|
|
1145
|
+
require2.resolve(pkg);
|
|
1146
|
+
} catch {
|
|
1147
|
+
return false;
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
return true;
|
|
1151
|
+
}
|
|
1152
|
+
var validateDependencies = effect.Effect.try({
|
|
1153
|
+
try: () => validateOpenTelemetryApi(),
|
|
1154
|
+
catch: (error) => new InitializationError2({
|
|
1155
|
+
reason: error instanceof Error ? error.message : "Dependency validation failed",
|
|
1156
|
+
cause: error
|
|
1157
|
+
})
|
|
1158
|
+
});
|
|
1159
|
+
effect.Effect.sync(() => validateEffectDependencies());
|
|
1073
1160
|
|
|
1074
1161
|
// src/api.ts
|
|
1075
1162
|
async function initializeInstrumentation(options = {}) {
|
|
1163
|
+
validateOpenTelemetryApi();
|
|
1076
1164
|
const sdk = await initializeSdk(options);
|
|
1077
1165
|
if (sdk) {
|
|
1078
1166
|
const config = await loadConfigWithOptions(options);
|
|
@@ -1089,6 +1177,7 @@ async function initializePatternMatchingOnly(options = {}) {
|
|
|
1089
1177
|
);
|
|
1090
1178
|
}
|
|
1091
1179
|
var initializeInstrumentationEffect = (options = {}) => effect.Effect.gen(function* () {
|
|
1180
|
+
yield* validateDependencies;
|
|
1092
1181
|
const sdk = yield* effect.Effect.tryPromise({
|
|
1093
1182
|
try: () => initializeSdk(options),
|
|
1094
1183
|
catch: (error) => new InitializationError2({
|