@wardx/core 0.1.0
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 +289 -0
- package/defaults.json +14 -0
- package/package.json +19 -0
- package/src/WardxCore.js +198 -0
- package/src/buffers/EventBuffer.js +28 -0
- package/src/buffers/LogBuffer.js +48 -0
- package/src/config/ConfigStore.js +31 -0
- package/src/config/ExperimentResolver.js +118 -0
- package/src/config/hash.js +27 -0
- package/src/frame/FrameBuilder.js +132 -0
- package/src/ids.js +30 -0
- package/src/index.js +14 -0
- package/src/internal/InternalMetrics.js +53 -0
- package/src/metrics/Counter.js +23 -0
- package/src/metrics/Gauge.js +22 -0
- package/src/metrics/Histogram.js +85 -0
- package/src/metrics/MetricsRegistry.js +172 -0
- package/src/metrics/Timer.js +6 -0
- package/src/metrics/dimensions.js +42 -0
- package/src/protocol.js +53 -0
- package/src/settings.js +97 -0
- package/src/trace/emit.js +5 -0
- package/src/trace/wrap.js +46 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export function dimKey(dims) {
|
|
2
|
+
if (dims == null) return '';
|
|
3
|
+
const keys = Object.keys(dims);
|
|
4
|
+
const n = keys.length;
|
|
5
|
+
if (n === 0) return '';
|
|
6
|
+
if (n === 1) return keys[0] + '=' + String(dims[keys[0]]);
|
|
7
|
+
keys.sort();
|
|
8
|
+
let out = keys[0] + '=' + String(dims[keys[0]]);
|
|
9
|
+
for (let i = 1; i < n; i++) {
|
|
10
|
+
out += '\n' + keys[i] + '=' + String(dims[keys[i]]);
|
|
11
|
+
}
|
|
12
|
+
return out;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function validateDimensions(dims, maxDimensionKeys, maxDimensionValueLength) {
|
|
16
|
+
if (dims == null) return { ok: true, dims: null };
|
|
17
|
+
if (typeof dims !== 'object' || Array.isArray(dims)) {
|
|
18
|
+
throw new Error('dimensions must be a plain object');
|
|
19
|
+
}
|
|
20
|
+
const keys = Object.keys(dims);
|
|
21
|
+
if (keys.length === 0) return { ok: true, dims: null };
|
|
22
|
+
if (keys.length > maxDimensionKeys) {
|
|
23
|
+
return { ok: false, reason: 'maxDimensionKeys' };
|
|
24
|
+
}
|
|
25
|
+
for (let i = 0; i < keys.length; i++) {
|
|
26
|
+
const value = dims[keys[i]];
|
|
27
|
+
const type = typeof value;
|
|
28
|
+
if (type !== 'string' && type !== 'number' && type !== 'boolean') {
|
|
29
|
+
throw new Error(`dimension ${keys[i]} must be string, number, or boolean`);
|
|
30
|
+
}
|
|
31
|
+
if (String(value).length > maxDimensionValueLength) {
|
|
32
|
+
return { ok: false, reason: 'maxDimensionValueLength' };
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return { ok: true, dims };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function assertMetricName(name) {
|
|
39
|
+
if (typeof name !== 'string' || name.length === 0) {
|
|
40
|
+
throw new Error('metric name must be a non-empty string');
|
|
41
|
+
}
|
|
42
|
+
}
|
package/src/protocol.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export const PROTOCOL_VERSION = 1;
|
|
2
|
+
export const SDK_NAME = 'wardx-node';
|
|
3
|
+
export const PLATFORM = 'node';
|
|
4
|
+
|
|
5
|
+
export const INTERNAL = {
|
|
6
|
+
eventsBuffered: 'wardx.internal.events_buffered',
|
|
7
|
+
logsBuffered: 'wardx.internal.logs_buffered',
|
|
8
|
+
eventsDropped: 'wardx.internal.events_dropped',
|
|
9
|
+
logsDropped: 'wardx.internal.logs_dropped',
|
|
10
|
+
cardinalityDropped: 'wardx.internal.cardinality_dropped',
|
|
11
|
+
framesSent: 'wardx.internal.frames_sent',
|
|
12
|
+
framesFailed: 'wardx.internal.frames_failed',
|
|
13
|
+
bytesUncompressed: 'wardx.internal.bytes_uncompressed',
|
|
14
|
+
bytesCompressed: 'wardx.internal.bytes_compressed',
|
|
15
|
+
lastSyncMs: 'wardx.internal.last_sync_ms',
|
|
16
|
+
configVersion: 'wardx.internal.config_version',
|
|
17
|
+
processRssBytes: 'wardx.internal.process_rss_bytes'
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const LOG_LEVELS = ['debug', 'info', 'warn', 'error'];
|
|
21
|
+
|
|
22
|
+
export const LOG_RANK = {
|
|
23
|
+
debug: 0,
|
|
24
|
+
info: 1,
|
|
25
|
+
warn: 2,
|
|
26
|
+
error: 3
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export const UINT32 = 4294967296;
|
|
30
|
+
|
|
31
|
+
export const REQUIRED_CREATE_KEYS = [
|
|
32
|
+
'endpoint',
|
|
33
|
+
'projectKey',
|
|
34
|
+
'project',
|
|
35
|
+
'role',
|
|
36
|
+
'appVersion',
|
|
37
|
+
'environment'
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
export const REQUIRED_SDK_DEFAULT_KEYS = [
|
|
41
|
+
'aggregateIntervalMs',
|
|
42
|
+
'syncIntervalMs',
|
|
43
|
+
'syncJitterMin',
|
|
44
|
+
'syncJitterMax',
|
|
45
|
+
'maxBufferedEvents',
|
|
46
|
+
'maxBufferedLogs',
|
|
47
|
+
'maxFrameBytes',
|
|
48
|
+
'maxSeriesPerMetric',
|
|
49
|
+
'maxDimensionKeys',
|
|
50
|
+
'maxDimensionValueLength',
|
|
51
|
+
'httpTimeoutMs',
|
|
52
|
+
'histogramBuckets'
|
|
53
|
+
];
|
package/src/settings.js
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { REQUIRED_CREATE_KEYS, REQUIRED_SDK_DEFAULT_KEYS } from './protocol.js';
|
|
5
|
+
|
|
6
|
+
const DEFAULTS_PATH = join(dirname(fileURLToPath(import.meta.url)), '..', 'defaults.json');
|
|
7
|
+
|
|
8
|
+
export function requireKeys(object, keys, label) {
|
|
9
|
+
const missing = [];
|
|
10
|
+
for (const key of keys) {
|
|
11
|
+
if (object[key] === undefined || object[key] === null) missing.push(key);
|
|
12
|
+
}
|
|
13
|
+
if (missing.length > 0) {
|
|
14
|
+
throw new Error(`${label} missing required keys: ${missing.join(', ')}`);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function loadSdkDefaults() {
|
|
19
|
+
let raw;
|
|
20
|
+
try {
|
|
21
|
+
raw = readFileSync(DEFAULTS_PATH, 'utf8');
|
|
22
|
+
} catch {
|
|
23
|
+
throw new Error(`Wardx SDK defaults file is missing at ${DEFAULTS_PATH}`);
|
|
24
|
+
}
|
|
25
|
+
const parsed = JSON.parse(raw);
|
|
26
|
+
requireKeys(parsed, REQUIRED_SDK_DEFAULT_KEYS, 'SDK defaults');
|
|
27
|
+
if (!Array.isArray(parsed.histogramBuckets) || parsed.histogramBuckets.length === 0) {
|
|
28
|
+
throw new Error('SDK defaults histogramBuckets must be a non-empty array');
|
|
29
|
+
}
|
|
30
|
+
return parsed;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function assertPositiveNumber(settings, key) {
|
|
34
|
+
const value = settings[key];
|
|
35
|
+
if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0) {
|
|
36
|
+
throw new Error(`${key} must be a finite number > 0`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function assertNumberInRange(settings, key, min, max) {
|
|
41
|
+
const value = settings[key];
|
|
42
|
+
if (typeof value !== 'number' || !Number.isFinite(value) || value < min || value > max) {
|
|
43
|
+
throw new Error(`${key} must be a finite number in [${min}, ${max}]`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function resolveSettings(options) {
|
|
48
|
+
if (options === null || typeof options !== 'object') {
|
|
49
|
+
throw new Error('createWardx requires an options object');
|
|
50
|
+
}
|
|
51
|
+
requireKeys(options, REQUIRED_CREATE_KEYS, 'createWardx');
|
|
52
|
+
const defaults = loadSdkDefaults();
|
|
53
|
+
const settings = { ...defaults, ...options };
|
|
54
|
+
if (typeof settings.role !== 'string' || settings.role.length === 0) {
|
|
55
|
+
throw new Error('role must be a non-empty string');
|
|
56
|
+
}
|
|
57
|
+
if (settings.role === '*') {
|
|
58
|
+
throw new Error('role cannot be *');
|
|
59
|
+
}
|
|
60
|
+
if (settings.privacySalt === undefined || settings.privacySalt === null || settings.privacySalt === '') {
|
|
61
|
+
settings.privacySalt = settings.projectKey;
|
|
62
|
+
}
|
|
63
|
+
assertPositiveNumber(settings, 'aggregateIntervalMs');
|
|
64
|
+
assertPositiveNumber(settings, 'syncIntervalMs');
|
|
65
|
+
assertPositiveNumber(settings, 'maxBufferedEvents');
|
|
66
|
+
assertPositiveNumber(settings, 'maxBufferedLogs');
|
|
67
|
+
assertPositiveNumber(settings, 'maxFrameBytes');
|
|
68
|
+
assertPositiveNumber(settings, 'maxSeriesPerMetric');
|
|
69
|
+
assertPositiveNumber(settings, 'maxDimensionKeys');
|
|
70
|
+
assertPositiveNumber(settings, 'maxDimensionValueLength');
|
|
71
|
+
assertPositiveNumber(settings, 'httpTimeoutMs');
|
|
72
|
+
assertNumberInRange(settings, 'syncJitterMin', 0, 1);
|
|
73
|
+
assertNumberInRange(settings, 'syncJitterMax', 1, 2);
|
|
74
|
+
if (settings.syncJitterMin > settings.syncJitterMax) {
|
|
75
|
+
throw new Error('syncJitterMin must be <= syncJitterMax');
|
|
76
|
+
}
|
|
77
|
+
if (settings.tracer != null && (typeof settings.tracer !== 'object' || Array.isArray(settings.tracer))) {
|
|
78
|
+
throw new Error('tracer must be an object');
|
|
79
|
+
}
|
|
80
|
+
if (!Array.isArray(settings.histogramBuckets) || settings.histogramBuckets.length === 0) {
|
|
81
|
+
throw new Error('histogramBuckets must be a non-empty array');
|
|
82
|
+
}
|
|
83
|
+
let prev = -Infinity;
|
|
84
|
+
for (const bound of settings.histogramBuckets) {
|
|
85
|
+
if (typeof bound !== 'number' || !Number.isFinite(bound) || bound <= prev) {
|
|
86
|
+
throw new Error('histogramBuckets must be strictly increasing finite numbers');
|
|
87
|
+
}
|
|
88
|
+
prev = bound;
|
|
89
|
+
}
|
|
90
|
+
return settings;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function nextSyncDelayMs(settings) {
|
|
94
|
+
const span = settings.syncJitterMax - settings.syncJitterMin;
|
|
95
|
+
const factor = settings.syncJitterMin + Math.random() * span;
|
|
96
|
+
return settings.syncIntervalMs * factor;
|
|
97
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { emit } from './emit.js';
|
|
2
|
+
|
|
3
|
+
export function wrapCounter(tracer, series, noop, name, dims) {
|
|
4
|
+
const n = noop ? name : series.name;
|
|
5
|
+
const d = noop ? (dims ?? null) : series.dims;
|
|
6
|
+
return {
|
|
7
|
+
inc() {
|
|
8
|
+
series.inc();
|
|
9
|
+
emit(tracer, 'measure', { type: 'counter', name: n, dims: d, op: 'inc', value: 1, noop });
|
|
10
|
+
},
|
|
11
|
+
add(value) {
|
|
12
|
+
series.add(value);
|
|
13
|
+
emit(tracer, 'measure', { type: 'counter', name: n, dims: d, op: 'add', value, noop });
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function wrapGauge(tracer, series, noop, name, dims) {
|
|
19
|
+
const n = noop ? name : series.name;
|
|
20
|
+
const d = noop ? (dims ?? null) : series.dims;
|
|
21
|
+
return {
|
|
22
|
+
set(value) {
|
|
23
|
+
series.set(value);
|
|
24
|
+
emit(tracer, 'measure', { type: 'gauge', name: n, dims: d, op: 'set', value, noop });
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function wrapHistogram(tracer, series, noop, name, dims) {
|
|
30
|
+
const n = noop ? name : series.name;
|
|
31
|
+
const d = noop ? (dims ?? null) : series.dims;
|
|
32
|
+
return {
|
|
33
|
+
observe(value, attrs) {
|
|
34
|
+
series.observe(value, attrs);
|
|
35
|
+
emit(tracer, 'measure', {
|
|
36
|
+
type: 'histogram',
|
|
37
|
+
name: n,
|
|
38
|
+
dims: d,
|
|
39
|
+
op: 'observe',
|
|
40
|
+
value,
|
|
41
|
+
attrs: attrs ?? null,
|
|
42
|
+
noop
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
}
|