@sentienguard/apm 1.0.21 → 1.0.22-debug.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/README.md +141 -141
- package/package.json +1 -1
- 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 +251 -242
- package/src/mongodb.js +73 -10
- package/src/openai.js +520 -520
- package/src/spanExporter.js +6 -0
- package/src/traceSpanExporter.js +165 -186
- package/src/tracing.js +5 -0
- package/src/transport.js +8 -0
package/README.md
CHANGED
|
@@ -1,141 +1,141 @@
|
|
|
1
|
-
# @sentienguard/apm
|
|
2
|
-
|
|
3
|
-
Minimal, production-safe APM SDK for Node.js applications. Zero-config setup with automatic instrumentation.
|
|
4
|
-
|
|
5
|
-
## Installation
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
npm install @sentienguard/apm
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
## Quick Start
|
|
12
|
-
|
|
13
|
-
```js
|
|
14
|
-
// If using dotenv, import it first
|
|
15
|
-
import 'dotenv/config';
|
|
16
|
-
|
|
17
|
-
// Then import the SDK (before other app modules for best instrumentation coverage)
|
|
18
|
-
import '@sentienguard/apm';
|
|
19
|
-
|
|
20
|
-
// Your app code
|
|
21
|
-
import express from 'express';
|
|
22
|
-
const app = express();
|
|
23
|
-
// ...
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
Set environment variables:
|
|
27
|
-
|
|
28
|
-
```bash
|
|
29
|
-
SENTIENGUARD_APM_KEY=your-app-key
|
|
30
|
-
SENTIENGUARD_SERVICE=my-api
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
That's it. The SDK automatically instruments your application and sends metrics to SentienGuard.
|
|
34
|
-
|
|
35
|
-
## Configuration
|
|
36
|
-
|
|
37
|
-
All configuration is via environment variables:
|
|
38
|
-
|
|
39
|
-
| Variable | Required | Default | Description |
|
|
40
|
-
|----------|----------|---------|-------------|
|
|
41
|
-
| `SENTIENGUARD_APM_KEY` | Yes | - | Your application's APM key |
|
|
42
|
-
| `SENTIENGUARD_SERVICE` | Yes | - | Service name (e.g., `orders-api`) |
|
|
43
|
-
| `SENTIENGUARD_ENV` | No | `production` | Environment (`production`, `staging`, `development`) |
|
|
44
|
-
| `SENTIENGUARD_ENDPOINT` | No | `https://sentienguard-dev.the-algo.com/api/v1` | SentienGuard backend URL |
|
|
45
|
-
| `SENTIENGUARD_FLUSH_INTERVAL` | No | `10` | Metrics flush interval in seconds |
|
|
46
|
-
|
|
47
|
-
> **Note:** If `SENTIENGUARD_APM_KEY` or `SENTIENGUARD_SERVICE` is missing, the SDK disables itself silently without affecting your application.
|
|
48
|
-
|
|
49
|
-
## What Gets Tracked
|
|
50
|
-
|
|
51
|
-
- **HTTP Requests** - Incoming requests with method, route, status, and latency
|
|
52
|
-
- **Dependencies** - Outgoing HTTP/HTTPS calls to external services
|
|
53
|
-
- **Errors** - Uncaught exceptions and unhandled rejections
|
|
54
|
-
|
|
55
|
-
## Framework Integration
|
|
56
|
-
|
|
57
|
-
### Express
|
|
58
|
-
|
|
59
|
-
For better route extraction, add the middleware:
|
|
60
|
-
|
|
61
|
-
```js
|
|
62
|
-
import 'dotenv/config'; // if using dotenv
|
|
63
|
-
import '@sentienguard/apm';
|
|
64
|
-
import { expressMiddleware, expressErrorMiddleware } from '@sentienguard/apm';
|
|
65
|
-
import express from 'express';
|
|
66
|
-
|
|
67
|
-
const app = express();
|
|
68
|
-
|
|
69
|
-
// Add early in middleware chain
|
|
70
|
-
app.use(expressMiddleware());
|
|
71
|
-
|
|
72
|
-
// Your routes
|
|
73
|
-
app.get('/users/:id', (req, res) => { ... });
|
|
74
|
-
|
|
75
|
-
// Add error middleware last
|
|
76
|
-
app.use(expressErrorMiddleware());
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
### Fastify
|
|
80
|
-
|
|
81
|
-
```js
|
|
82
|
-
import 'dotenv/config'; // if using dotenv
|
|
83
|
-
import '@sentienguard/apm';
|
|
84
|
-
import { fastifyPlugin, fastifyErrorHandler } from '@sentienguard/apm';
|
|
85
|
-
import Fastify from 'fastify';
|
|
86
|
-
|
|
87
|
-
const app = Fastify();
|
|
88
|
-
|
|
89
|
-
// Register plugin
|
|
90
|
-
app.register(fastifyPlugin);
|
|
91
|
-
|
|
92
|
-
// Add error handler
|
|
93
|
-
app.setErrorHandler(fastifyErrorHandler);
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
## API Reference
|
|
97
|
-
|
|
98
|
-
### Functions
|
|
99
|
-
|
|
100
|
-
```js
|
|
101
|
-
import {
|
|
102
|
-
shutdown, // Graceful shutdown (flushes pending metrics)
|
|
103
|
-
getStatus, // Get SDK status and stats
|
|
104
|
-
flush, // Force flush metrics now
|
|
105
|
-
isEnabled // Check if SDK is enabled
|
|
106
|
-
} from '@sentienguard/apm';
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
### Graceful Shutdown
|
|
110
|
-
|
|
111
|
-
The SDK automatically handles `SIGTERM` and `SIGINT` signals. For manual shutdown:
|
|
112
|
-
|
|
113
|
-
```js
|
|
114
|
-
import { shutdown } from '@sentienguard/apm';
|
|
115
|
-
|
|
116
|
-
process.on('exit', async () => {
|
|
117
|
-
await shutdown();
|
|
118
|
-
});
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
### Check Status
|
|
122
|
-
|
|
123
|
-
```js
|
|
124
|
-
import { getStatus } from '@sentienguard/apm';
|
|
125
|
-
|
|
126
|
-
console.log(getStatus());
|
|
127
|
-
// {
|
|
128
|
-
// enabled: true,
|
|
129
|
-
// initialized: true,
|
|
130
|
-
// config: { service: 'my-api', environment: 'production', flushInterval: 10 },
|
|
131
|
-
// stats: { requests: 150, dependencies: 45, errors: 2 }
|
|
132
|
-
// }
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
## Requirements
|
|
136
|
-
|
|
137
|
-
- Node.js >= 16.0.0
|
|
138
|
-
|
|
139
|
-
## License
|
|
140
|
-
|
|
141
|
-
MIT
|
|
1
|
+
# @sentienguard/apm
|
|
2
|
+
|
|
3
|
+
Minimal, production-safe APM SDK for Node.js applications. Zero-config setup with automatic instrumentation.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @sentienguard/apm
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```js
|
|
14
|
+
// If using dotenv, import it first
|
|
15
|
+
import 'dotenv/config';
|
|
16
|
+
|
|
17
|
+
// Then import the SDK (before other app modules for best instrumentation coverage)
|
|
18
|
+
import '@sentienguard/apm';
|
|
19
|
+
|
|
20
|
+
// Your app code
|
|
21
|
+
import express from 'express';
|
|
22
|
+
const app = express();
|
|
23
|
+
// ...
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Set environment variables:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
SENTIENGUARD_APM_KEY=your-app-key
|
|
30
|
+
SENTIENGUARD_SERVICE=my-api
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
That's it. The SDK automatically instruments your application and sends metrics to SentienGuard.
|
|
34
|
+
|
|
35
|
+
## Configuration
|
|
36
|
+
|
|
37
|
+
All configuration is via environment variables:
|
|
38
|
+
|
|
39
|
+
| Variable | Required | Default | Description |
|
|
40
|
+
|----------|----------|---------|-------------|
|
|
41
|
+
| `SENTIENGUARD_APM_KEY` | Yes | - | Your application's APM key |
|
|
42
|
+
| `SENTIENGUARD_SERVICE` | Yes | - | Service name (e.g., `orders-api`) |
|
|
43
|
+
| `SENTIENGUARD_ENV` | No | `production` | Environment (`production`, `staging`, `development`) |
|
|
44
|
+
| `SENTIENGUARD_ENDPOINT` | No | `https://sentienguard-dev.the-algo.com/api/v1` | SentienGuard backend URL |
|
|
45
|
+
| `SENTIENGUARD_FLUSH_INTERVAL` | No | `10` | Metrics flush interval in seconds |
|
|
46
|
+
|
|
47
|
+
> **Note:** If `SENTIENGUARD_APM_KEY` or `SENTIENGUARD_SERVICE` is missing, the SDK disables itself silently without affecting your application.
|
|
48
|
+
|
|
49
|
+
## What Gets Tracked
|
|
50
|
+
|
|
51
|
+
- **HTTP Requests** - Incoming requests with method, route, status, and latency
|
|
52
|
+
- **Dependencies** - Outgoing HTTP/HTTPS calls to external services
|
|
53
|
+
- **Errors** - Uncaught exceptions and unhandled rejections
|
|
54
|
+
|
|
55
|
+
## Framework Integration
|
|
56
|
+
|
|
57
|
+
### Express
|
|
58
|
+
|
|
59
|
+
For better route extraction, add the middleware:
|
|
60
|
+
|
|
61
|
+
```js
|
|
62
|
+
import 'dotenv/config'; // if using dotenv
|
|
63
|
+
import '@sentienguard/apm';
|
|
64
|
+
import { expressMiddleware, expressErrorMiddleware } from '@sentienguard/apm';
|
|
65
|
+
import express from 'express';
|
|
66
|
+
|
|
67
|
+
const app = express();
|
|
68
|
+
|
|
69
|
+
// Add early in middleware chain
|
|
70
|
+
app.use(expressMiddleware());
|
|
71
|
+
|
|
72
|
+
// Your routes
|
|
73
|
+
app.get('/users/:id', (req, res) => { ... });
|
|
74
|
+
|
|
75
|
+
// Add error middleware last
|
|
76
|
+
app.use(expressErrorMiddleware());
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Fastify
|
|
80
|
+
|
|
81
|
+
```js
|
|
82
|
+
import 'dotenv/config'; // if using dotenv
|
|
83
|
+
import '@sentienguard/apm';
|
|
84
|
+
import { fastifyPlugin, fastifyErrorHandler } from '@sentienguard/apm';
|
|
85
|
+
import Fastify from 'fastify';
|
|
86
|
+
|
|
87
|
+
const app = Fastify();
|
|
88
|
+
|
|
89
|
+
// Register plugin
|
|
90
|
+
app.register(fastifyPlugin);
|
|
91
|
+
|
|
92
|
+
// Add error handler
|
|
93
|
+
app.setErrorHandler(fastifyErrorHandler);
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## API Reference
|
|
97
|
+
|
|
98
|
+
### Functions
|
|
99
|
+
|
|
100
|
+
```js
|
|
101
|
+
import {
|
|
102
|
+
shutdown, // Graceful shutdown (flushes pending metrics)
|
|
103
|
+
getStatus, // Get SDK status and stats
|
|
104
|
+
flush, // Force flush metrics now
|
|
105
|
+
isEnabled // Check if SDK is enabled
|
|
106
|
+
} from '@sentienguard/apm';
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Graceful Shutdown
|
|
110
|
+
|
|
111
|
+
The SDK automatically handles `SIGTERM` and `SIGINT` signals. For manual shutdown:
|
|
112
|
+
|
|
113
|
+
```js
|
|
114
|
+
import { shutdown } from '@sentienguard/apm';
|
|
115
|
+
|
|
116
|
+
process.on('exit', async () => {
|
|
117
|
+
await shutdown();
|
|
118
|
+
});
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Check Status
|
|
122
|
+
|
|
123
|
+
```js
|
|
124
|
+
import { getStatus } from '@sentienguard/apm';
|
|
125
|
+
|
|
126
|
+
console.log(getStatus());
|
|
127
|
+
// {
|
|
128
|
+
// enabled: true,
|
|
129
|
+
// initialized: true,
|
|
130
|
+
// config: { service: 'my-api', environment: 'production', flushInterval: 10 },
|
|
131
|
+
// stats: { requests: 150, dependencies: 45, errors: 2 }
|
|
132
|
+
// }
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Requirements
|
|
136
|
+
|
|
137
|
+
- Node.js >= 16.0.0
|
|
138
|
+
|
|
139
|
+
## License
|
|
140
|
+
|
|
141
|
+
MIT
|
package/package.json
CHANGED
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;
|