@sentienguard/apm 1.0.19 → 1.0.20
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 +141 -141
- package/package.json +1 -1
- package/src/aggregator.js +465 -465
- package/src/config.js +179 -179
- package/src/dependencies.js +374 -374
- package/src/errors.js +132 -132
- package/src/index.d.ts +120 -120
- package/src/index.js +248 -242
- package/src/mongodb.js +31 -1
- package/src/openai.js +520 -520
- package/src/spanExporter.js +238 -232
- package/src/tracing.js +235 -230
- package/src/transport.js +8 -0
package/src/config.js
CHANGED
|
@@ -1,179 +1,179 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SDK Configuration
|
|
3
|
-
* Environment-driven configuration with sensible defaults.
|
|
4
|
-
* No config → SDK disables itself silently.
|
|
5
|
-
*
|
|
6
|
-
* Config is loaded lazily (at initialize() time, not import time)
|
|
7
|
-
* so that dotenv or other env loaders can run first.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
let configLoaded = false;
|
|
11
|
-
|
|
12
|
-
const config = {
|
|
13
|
-
apiKey: '',
|
|
14
|
-
service: '',
|
|
15
|
-
environment: 'production',
|
|
16
|
-
endpoint: 'https://sentienguard-dev.the-algo.com/api/v1/apm/ingest',
|
|
17
|
-
tracesEndpoint: '',
|
|
18
|
-
flushInterval: 10,
|
|
19
|
-
maxRoutes: 100,
|
|
20
|
-
maxPayloadSize: 1024 * 1024,
|
|
21
|
-
enabled: true,
|
|
22
|
-
debug: false,
|
|
23
|
-
mongodb: {
|
|
24
|
-
enabled: true,
|
|
25
|
-
slowQueryMs: 100,
|
|
26
|
-
poolStatsInterval: 10000
|
|
27
|
-
},
|
|
28
|
-
circuitBreaker: {
|
|
29
|
-
enabled: false,
|
|
30
|
-
timeout: 3000,
|
|
31
|
-
errorThresholdPercentage: 50,
|
|
32
|
-
resetTimeout: 30000,
|
|
33
|
-
volumeThreshold: 5
|
|
34
|
-
},
|
|
35
|
-
openai: {
|
|
36
|
-
enabled: true,
|
|
37
|
-
trackTokens: true,
|
|
38
|
-
trackCosts: true,
|
|
39
|
-
slowCallMs: 5000
|
|
40
|
-
},
|
|
41
|
-
/** OpenTelemetry + W3C trace context; set SENTIENGUARD_TRACING=false for legacy HTTP patches only */
|
|
42
|
-
tracing: {
|
|
43
|
-
enabled: true,
|
|
44
|
-
/** When true, outgoing HTTP to localhost is traced (for multi-service dev). Default false. */
|
|
45
|
-
traceLocalHttp: false,
|
|
46
|
-
/** Port -> display name for local peers, from SENTIENGUARD_PEER_SERVICE_MAP */
|
|
47
|
-
peerServiceMap: {},
|
|
48
|
-
/** Sampling rate for exporting raw spans (0..1). Parent-based. */
|
|
49
|
-
sampleRate: 0.05,
|
|
50
|
-
/** Span export queue + batch sizes (drop-on-pressure). */
|
|
51
|
-
maxQueueSize: 2048,
|
|
52
|
-
maxBatchSize: 256
|
|
53
|
-
}
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Load configuration from environment variables.
|
|
58
|
-
* Called lazily on first use (e.g., from initialize() or isEnabled()).
|
|
59
|
-
* Safe to call multiple times — only reads env vars once.
|
|
60
|
-
*/
|
|
61
|
-
export function loadConfig({ force = false } = {}) {
|
|
62
|
-
if (configLoaded && !force) return;
|
|
63
|
-
configLoaded = true;
|
|
64
|
-
|
|
65
|
-
config.apiKey = process.env.SENTIENGUARD_APM_KEY || '';
|
|
66
|
-
config.service = process.env.SENTIENGUARD_SERVICE || '';
|
|
67
|
-
config.environment = process.env.SENTIENGUARD_ENV || 'production';
|
|
68
|
-
config.endpoint = process.env.SENTIENGUARD_ENDPOINT || 'https://sentienguard-dev.the-algo.com/api/v1/apm/ingest';
|
|
69
|
-
config.tracesEndpoint =
|
|
70
|
-
process.env.SENTIENGUARD_TRACES_ENDPOINT ||
|
|
71
|
-
deriveTracesEndpoint(config.endpoint) ||
|
|
72
|
-
'https://sentienguard-dev.the-algo.com/api/v1/apm/traces';
|
|
73
|
-
config.flushInterval = parseInt(process.env.SENTIENGUARD_FLUSH_INTERVAL, 10) || 10;
|
|
74
|
-
config.maxRoutes = parseInt(process.env.SENTIENGUARD_MAX_ROUTES, 10) || 100;
|
|
75
|
-
config.maxPayloadSize = parseInt(process.env.SENTIENGUARD_MAX_PAYLOAD_SIZE, 10) || 1024 * 1024;
|
|
76
|
-
config.enabled = process.env.SENTIENGUARD_ENABLED !== 'false';
|
|
77
|
-
config.debug = process.env.SENTIENGUARD_DEBUG === 'true';
|
|
78
|
-
|
|
79
|
-
config.mongodb.enabled = process.env.SENTIENGUARD_MONGODB_ENABLED !== 'false';
|
|
80
|
-
config.mongodb.slowQueryMs = parseInt(process.env.SENTIENGUARD_MONGODB_SLOW_QUERY_MS, 10) || 100;
|
|
81
|
-
config.mongodb.poolStatsInterval = parseInt(process.env.SENTIENGUARD_MONGODB_POOL_INTERVAL, 10) || 10000;
|
|
82
|
-
|
|
83
|
-
config.circuitBreaker.enabled = process.env.SENTIENGUARD_CIRCUIT_BREAKER_ENABLED === 'true';
|
|
84
|
-
config.circuitBreaker.timeout = parseInt(process.env.SENTIENGUARD_CIRCUIT_TIMEOUT_MS, 10) || 3000;
|
|
85
|
-
config.circuitBreaker.errorThresholdPercentage = parseInt(process.env.SENTIENGUARD_CIRCUIT_ERROR_THRESHOLD, 10) || 50;
|
|
86
|
-
config.circuitBreaker.resetTimeout = parseInt(process.env.SENTIENGUARD_CIRCUIT_RESET_TIMEOUT, 10) || 30000;
|
|
87
|
-
config.circuitBreaker.volumeThreshold = parseInt(process.env.SENTIENGUARD_CIRCUIT_VOLUME_THRESHOLD, 10) || 5;
|
|
88
|
-
|
|
89
|
-
config.openai.enabled = process.env.SENTIENGUARD_OPENAI_ENABLED !== 'false';
|
|
90
|
-
config.openai.trackTokens = process.env.SENTIENGUARD_OPENAI_TRACK_TOKENS !== 'false';
|
|
91
|
-
config.openai.trackCosts = process.env.SENTIENGUARD_OPENAI_TRACK_COSTS !== 'false';
|
|
92
|
-
config.openai.slowCallMs = parseInt(process.env.SENTIENGUARD_OPENAI_SLOW_CALL_MS, 10) || 5000;
|
|
93
|
-
|
|
94
|
-
config.tracing.enabled = process.env.SENTIENGUARD_TRACING !== 'false';
|
|
95
|
-
const sampleRaw = process.env.SENTIENGUARD_TRACE_SAMPLE_RATE;
|
|
96
|
-
const sample = sampleRaw != null ? Number(sampleRaw) : NaN;
|
|
97
|
-
if (!Number.isNaN(sample) && sample >= 0 && sample <= 1) {
|
|
98
|
-
config.tracing.sampleRate = sample;
|
|
99
|
-
}
|
|
100
|
-
config.tracing.maxQueueSize = parseInt(process.env.SENTIENGUARD_TRACE_MAX_QUEUE_SIZE, 10) || config.tracing.maxQueueSize;
|
|
101
|
-
config.tracing.maxBatchSize = parseInt(process.env.SENTIENGUARD_TRACE_MAX_BATCH_SIZE, 10) || config.tracing.maxBatchSize;
|
|
102
|
-
// Default behavior:
|
|
103
|
-
// - production: do NOT record localhost dependency edges (noise + self-calls)
|
|
104
|
-
// - non-production: DO record localhost edges (local multi-service dev "just works")
|
|
105
|
-
// Can always be overridden explicitly via SENTIENGUARD_TRACE_LOCAL_HTTP=true/false.
|
|
106
|
-
const explicitLocal =
|
|
107
|
-
process.env.SENTIENGUARD_TRACE_LOCAL_HTTP === 'true'
|
|
108
|
-
? true
|
|
109
|
-
: process.env.SENTIENGUARD_TRACE_LOCAL_HTTP === 'false'
|
|
110
|
-
? false
|
|
111
|
-
: undefined;
|
|
112
|
-
const isProd = (config.environment || '').toLowerCase() === 'production' ||
|
|
113
|
-
(process.env.NODE_ENV || '').toLowerCase() === 'production';
|
|
114
|
-
config.tracing.traceLocalHttp = explicitLocal ?? !isProd;
|
|
115
|
-
config.tracing.peerServiceMap = parsePeerServiceMap(process.env.SENTIENGUARD_PEER_SERVICE_MAP || '');
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/** "3001:payments-api,3002:auth-api" -> { "3001": "payments-api", ... } */
|
|
119
|
-
function parsePeerServiceMap(raw) {
|
|
120
|
-
const map = Object.create(null);
|
|
121
|
-
if (!raw || typeof raw !== 'string') return map;
|
|
122
|
-
for (const part of raw.split(',')) {
|
|
123
|
-
const trimmed = part.trim();
|
|
124
|
-
if (!trimmed) continue;
|
|
125
|
-
const idx = trimmed.indexOf(':');
|
|
126
|
-
if (idx === -1) continue;
|
|
127
|
-
const port = trimmed.slice(0, idx).trim();
|
|
128
|
-
const name = trimmed.slice(idx + 1).trim();
|
|
129
|
-
if (port && name) map[port] = name;
|
|
130
|
-
}
|
|
131
|
-
return map;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
function deriveTracesEndpoint(ingestEndpoint) {
|
|
135
|
-
try {
|
|
136
|
-
const u = new URL(String(ingestEndpoint));
|
|
137
|
-
// Common default: /api/v1/apm/ingest -> /api/v1/apm/traces
|
|
138
|
-
u.pathname = u.pathname.replace(/\/apm\/ingest\/?$/i, '/apm/traces');
|
|
139
|
-
return u.toString();
|
|
140
|
-
} catch {
|
|
141
|
-
return '';
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Check if SDK is properly configured and should be active.
|
|
147
|
-
* Triggers lazy config load if not yet loaded.
|
|
148
|
-
*/
|
|
149
|
-
export function isEnabled() {
|
|
150
|
-
loadConfig();
|
|
151
|
-
return config.enabled && !!config.apiKey && !!config.service;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* Get validated configuration.
|
|
156
|
-
* Triggers lazy config load if not yet loaded.
|
|
157
|
-
*/
|
|
158
|
-
export function getConfig() {
|
|
159
|
-
loadConfig();
|
|
160
|
-
return { ...config };
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* Log debug message if debug mode is enabled
|
|
165
|
-
*/
|
|
166
|
-
export function debug(...args) {
|
|
167
|
-
if (config.debug) {
|
|
168
|
-
console.log('[SentienGuard APM]', ...args);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* Log warning (always shown)
|
|
174
|
-
*/
|
|
175
|
-
export function warn(...args) {
|
|
176
|
-
console.warn('[SentienGuard APM]', ...args);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
export default config;
|
|
1
|
+
/**
|
|
2
|
+
* SDK Configuration
|
|
3
|
+
* Environment-driven configuration with sensible defaults.
|
|
4
|
+
* No config → SDK disables itself silently.
|
|
5
|
+
*
|
|
6
|
+
* Config is loaded lazily (at initialize() time, not import time)
|
|
7
|
+
* so that dotenv or other env loaders can run first.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
let configLoaded = false;
|
|
11
|
+
|
|
12
|
+
const config = {
|
|
13
|
+
apiKey: '',
|
|
14
|
+
service: '',
|
|
15
|
+
environment: 'production',
|
|
16
|
+
endpoint: 'https://sentienguard-dev.the-algo.com/api/v1/apm/ingest',
|
|
17
|
+
tracesEndpoint: '',
|
|
18
|
+
flushInterval: 10,
|
|
19
|
+
maxRoutes: 100,
|
|
20
|
+
maxPayloadSize: 1024 * 1024,
|
|
21
|
+
enabled: true,
|
|
22
|
+
debug: false,
|
|
23
|
+
mongodb: {
|
|
24
|
+
enabled: true,
|
|
25
|
+
slowQueryMs: 100,
|
|
26
|
+
poolStatsInterval: 10000
|
|
27
|
+
},
|
|
28
|
+
circuitBreaker: {
|
|
29
|
+
enabled: false,
|
|
30
|
+
timeout: 3000,
|
|
31
|
+
errorThresholdPercentage: 50,
|
|
32
|
+
resetTimeout: 30000,
|
|
33
|
+
volumeThreshold: 5
|
|
34
|
+
},
|
|
35
|
+
openai: {
|
|
36
|
+
enabled: true,
|
|
37
|
+
trackTokens: true,
|
|
38
|
+
trackCosts: true,
|
|
39
|
+
slowCallMs: 5000
|
|
40
|
+
},
|
|
41
|
+
/** OpenTelemetry + W3C trace context; set SENTIENGUARD_TRACING=false for legacy HTTP patches only */
|
|
42
|
+
tracing: {
|
|
43
|
+
enabled: true,
|
|
44
|
+
/** When true, outgoing HTTP to localhost is traced (for multi-service dev). Default false. */
|
|
45
|
+
traceLocalHttp: false,
|
|
46
|
+
/** Port -> display name for local peers, from SENTIENGUARD_PEER_SERVICE_MAP */
|
|
47
|
+
peerServiceMap: {},
|
|
48
|
+
/** Sampling rate for exporting raw spans (0..1). Parent-based. */
|
|
49
|
+
sampleRate: 0.05,
|
|
50
|
+
/** Span export queue + batch sizes (drop-on-pressure). */
|
|
51
|
+
maxQueueSize: 2048,
|
|
52
|
+
maxBatchSize: 256
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Load configuration from environment variables.
|
|
58
|
+
* Called lazily on first use (e.g., from initialize() or isEnabled()).
|
|
59
|
+
* Safe to call multiple times — only reads env vars once.
|
|
60
|
+
*/
|
|
61
|
+
export function loadConfig({ force = false } = {}) {
|
|
62
|
+
if (configLoaded && !force) return;
|
|
63
|
+
configLoaded = true;
|
|
64
|
+
|
|
65
|
+
config.apiKey = process.env.SENTIENGUARD_APM_KEY || '';
|
|
66
|
+
config.service = process.env.SENTIENGUARD_SERVICE || '';
|
|
67
|
+
config.environment = process.env.SENTIENGUARD_ENV || 'production';
|
|
68
|
+
config.endpoint = process.env.SENTIENGUARD_ENDPOINT || 'https://sentienguard-dev.the-algo.com/api/v1/apm/ingest';
|
|
69
|
+
config.tracesEndpoint =
|
|
70
|
+
process.env.SENTIENGUARD_TRACES_ENDPOINT ||
|
|
71
|
+
deriveTracesEndpoint(config.endpoint) ||
|
|
72
|
+
'https://sentienguard-dev.the-algo.com/api/v1/apm/traces';
|
|
73
|
+
config.flushInterval = parseInt(process.env.SENTIENGUARD_FLUSH_INTERVAL, 10) || 10;
|
|
74
|
+
config.maxRoutes = parseInt(process.env.SENTIENGUARD_MAX_ROUTES, 10) || 100;
|
|
75
|
+
config.maxPayloadSize = parseInt(process.env.SENTIENGUARD_MAX_PAYLOAD_SIZE, 10) || 1024 * 1024;
|
|
76
|
+
config.enabled = process.env.SENTIENGUARD_ENABLED !== 'false';
|
|
77
|
+
config.debug = process.env.SENTIENGUARD_DEBUG === 'true';
|
|
78
|
+
|
|
79
|
+
config.mongodb.enabled = process.env.SENTIENGUARD_MONGODB_ENABLED !== 'false';
|
|
80
|
+
config.mongodb.slowQueryMs = parseInt(process.env.SENTIENGUARD_MONGODB_SLOW_QUERY_MS, 10) || 100;
|
|
81
|
+
config.mongodb.poolStatsInterval = parseInt(process.env.SENTIENGUARD_MONGODB_POOL_INTERVAL, 10) || 10000;
|
|
82
|
+
|
|
83
|
+
config.circuitBreaker.enabled = process.env.SENTIENGUARD_CIRCUIT_BREAKER_ENABLED === 'true';
|
|
84
|
+
config.circuitBreaker.timeout = parseInt(process.env.SENTIENGUARD_CIRCUIT_TIMEOUT_MS, 10) || 3000;
|
|
85
|
+
config.circuitBreaker.errorThresholdPercentage = parseInt(process.env.SENTIENGUARD_CIRCUIT_ERROR_THRESHOLD, 10) || 50;
|
|
86
|
+
config.circuitBreaker.resetTimeout = parseInt(process.env.SENTIENGUARD_CIRCUIT_RESET_TIMEOUT, 10) || 30000;
|
|
87
|
+
config.circuitBreaker.volumeThreshold = parseInt(process.env.SENTIENGUARD_CIRCUIT_VOLUME_THRESHOLD, 10) || 5;
|
|
88
|
+
|
|
89
|
+
config.openai.enabled = process.env.SENTIENGUARD_OPENAI_ENABLED !== 'false';
|
|
90
|
+
config.openai.trackTokens = process.env.SENTIENGUARD_OPENAI_TRACK_TOKENS !== 'false';
|
|
91
|
+
config.openai.trackCosts = process.env.SENTIENGUARD_OPENAI_TRACK_COSTS !== 'false';
|
|
92
|
+
config.openai.slowCallMs = parseInt(process.env.SENTIENGUARD_OPENAI_SLOW_CALL_MS, 10) || 5000;
|
|
93
|
+
|
|
94
|
+
config.tracing.enabled = process.env.SENTIENGUARD_TRACING !== 'false';
|
|
95
|
+
const sampleRaw = process.env.SENTIENGUARD_TRACE_SAMPLE_RATE;
|
|
96
|
+
const sample = sampleRaw != null ? Number(sampleRaw) : NaN;
|
|
97
|
+
if (!Number.isNaN(sample) && sample >= 0 && sample <= 1) {
|
|
98
|
+
config.tracing.sampleRate = sample;
|
|
99
|
+
}
|
|
100
|
+
config.tracing.maxQueueSize = parseInt(process.env.SENTIENGUARD_TRACE_MAX_QUEUE_SIZE, 10) || config.tracing.maxQueueSize;
|
|
101
|
+
config.tracing.maxBatchSize = parseInt(process.env.SENTIENGUARD_TRACE_MAX_BATCH_SIZE, 10) || config.tracing.maxBatchSize;
|
|
102
|
+
// Default behavior:
|
|
103
|
+
// - production: do NOT record localhost dependency edges (noise + self-calls)
|
|
104
|
+
// - non-production: DO record localhost edges (local multi-service dev "just works")
|
|
105
|
+
// Can always be overridden explicitly via SENTIENGUARD_TRACE_LOCAL_HTTP=true/false.
|
|
106
|
+
const explicitLocal =
|
|
107
|
+
process.env.SENTIENGUARD_TRACE_LOCAL_HTTP === 'true'
|
|
108
|
+
? true
|
|
109
|
+
: process.env.SENTIENGUARD_TRACE_LOCAL_HTTP === 'false'
|
|
110
|
+
? false
|
|
111
|
+
: undefined;
|
|
112
|
+
const isProd = (config.environment || '').toLowerCase() === 'production' ||
|
|
113
|
+
(process.env.NODE_ENV || '').toLowerCase() === 'production';
|
|
114
|
+
config.tracing.traceLocalHttp = explicitLocal ?? !isProd;
|
|
115
|
+
config.tracing.peerServiceMap = parsePeerServiceMap(process.env.SENTIENGUARD_PEER_SERVICE_MAP || '');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** "3001:payments-api,3002:auth-api" -> { "3001": "payments-api", ... } */
|
|
119
|
+
function parsePeerServiceMap(raw) {
|
|
120
|
+
const map = Object.create(null);
|
|
121
|
+
if (!raw || typeof raw !== 'string') return map;
|
|
122
|
+
for (const part of raw.split(',')) {
|
|
123
|
+
const trimmed = part.trim();
|
|
124
|
+
if (!trimmed) continue;
|
|
125
|
+
const idx = trimmed.indexOf(':');
|
|
126
|
+
if (idx === -1) continue;
|
|
127
|
+
const port = trimmed.slice(0, idx).trim();
|
|
128
|
+
const name = trimmed.slice(idx + 1).trim();
|
|
129
|
+
if (port && name) map[port] = name;
|
|
130
|
+
}
|
|
131
|
+
return map;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function deriveTracesEndpoint(ingestEndpoint) {
|
|
135
|
+
try {
|
|
136
|
+
const u = new URL(String(ingestEndpoint));
|
|
137
|
+
// Common default: /api/v1/apm/ingest -> /api/v1/apm/traces
|
|
138
|
+
u.pathname = u.pathname.replace(/\/apm\/ingest\/?$/i, '/apm/traces');
|
|
139
|
+
return u.toString();
|
|
140
|
+
} catch {
|
|
141
|
+
return '';
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Check if SDK is properly configured and should be active.
|
|
147
|
+
* Triggers lazy config load if not yet loaded.
|
|
148
|
+
*/
|
|
149
|
+
export function isEnabled() {
|
|
150
|
+
loadConfig();
|
|
151
|
+
return config.enabled && !!config.apiKey && !!config.service;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Get validated configuration.
|
|
156
|
+
* Triggers lazy config load if not yet loaded.
|
|
157
|
+
*/
|
|
158
|
+
export function getConfig() {
|
|
159
|
+
loadConfig();
|
|
160
|
+
return { ...config };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Log debug message if debug mode is enabled
|
|
165
|
+
*/
|
|
166
|
+
export function debug(...args) {
|
|
167
|
+
if (config.debug) {
|
|
168
|
+
console.log('[SentienGuard APM]', ...args);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Log warning (always shown)
|
|
174
|
+
*/
|
|
175
|
+
export function warn(...args) {
|
|
176
|
+
console.warn('[SentienGuard APM]', ...args);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export default config;
|