@data-fair/lib-node 2.17.0 → 3.0.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 +77 -9
- package/events-queue.js +4 -4
- package/events-queue.js.map +1 -1
- package/http-agents.js +2 -1
- package/http-agents.js.map +1 -1
- package/locks.js +5 -2
- package/locks.js.map +1 -1
- package/log-err.d.ts +11 -0
- package/log-err.js +41 -0
- package/log-err.js.map +1 -0
- package/log-err.test.d.ts +1 -0
- package/log-err.test.js +64 -0
- package/log-err.test.js.map +1 -0
- package/log-worker.test.d.ts +1 -0
- package/log-worker.test.js +16 -0
- package/log-worker.test.js.map +1 -0
- package/log.d.ts +34 -0
- package/log.js +119 -0
- package/log.js.map +1 -0
- package/log.test.d.ts +1 -0
- package/log.test.js +158 -0
- package/log.test.js.map +1 -0
- package/mongo.js +5 -4
- package/mongo.js.map +1 -1
- package/observer.d.ts +0 -1
- package/observer.js +6 -25
- package/observer.js.map +1 -1
- package/package.json +9 -2
- package/session.js +2 -1
- package/session.js.map +1 -1
- package/ssrf.test.js +4 -2
- package/ssrf.test.js.map +1 -1
- package/test-fixtures/early-debug.d.ts +2 -0
- package/test-fixtures/early-debug.js +4 -0
- package/test-fixtures/early-debug.js.map +1 -0
- package/test-fixtures/log-worker.d.ts +1 -0
- package/test-fixtures/log-worker.js +6 -0
- package/test-fixtures/log-worker.js.map +1 -0
- package/test-spies.js +1 -1
- package/test-spies.js.map +1 -1
- package/upgrade-scripts.js +2 -1
- package/upgrade-scripts.js.map +1 -1
package/log.js
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
// the logger shared by all the services of the data-fair stack
|
|
2
|
+
// JSON lines on stdout in production, pretty output in dev, see README for the conventions
|
|
3
|
+
import { hostname } from 'node:os';
|
|
4
|
+
import { format } from 'node:util';
|
|
5
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
6
|
+
import createDebug from 'debug';
|
|
7
|
+
import { pino } from 'pino';
|
|
8
|
+
import { serializeErr } from './log-err.js';
|
|
9
|
+
// implicit context merged into every log line
|
|
10
|
+
// only small immutable values (ids, keys, short names), never req, documents, buffers, etc
|
|
11
|
+
// long-lived resources (pools, intervals, caches) must be created outside any scope
|
|
12
|
+
// or they would capture it and attribute later lines to the wrong user
|
|
13
|
+
const contextStorage = new AsyncLocalStorage();
|
|
14
|
+
/** run fn in a scope where every log line carries the fields of the parent scope plus these */
|
|
15
|
+
export function withLogContext(fields, fn) {
|
|
16
|
+
return contextStorage.run({ ...contextStorage.getStore(), ...fields }, fn);
|
|
17
|
+
}
|
|
18
|
+
/** a copy of the current context, for example to pass it to a worker thread in a task payload */
|
|
19
|
+
export function getLogContext() {
|
|
20
|
+
return { ...contextStorage.getStore() };
|
|
21
|
+
}
|
|
22
|
+
// optional peer dependency, absent in production images
|
|
23
|
+
let prettyFactory = null;
|
|
24
|
+
try {
|
|
25
|
+
// @ts-ignore optional peer dependency
|
|
26
|
+
prettyFactory = (await import('pino-pretty')).default;
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
prettyFactory = null;
|
|
30
|
+
}
|
|
31
|
+
let service = process.env.OTEL_SERVICE_NAME;
|
|
32
|
+
let instance;
|
|
33
|
+
const defaultFormat = () => {
|
|
34
|
+
if (process.env.LOG_FORMAT === 'json' || process.env.LOG_FORMAT === 'pretty')
|
|
35
|
+
return process.env.LOG_FORMAT;
|
|
36
|
+
return process.env.NODE_ENV === 'production' ? 'json' : 'pretty';
|
|
37
|
+
};
|
|
38
|
+
/** @internal test seam, not API */
|
|
39
|
+
export function _resetLogger(opts = {}) {
|
|
40
|
+
const format = opts.format ?? defaultFormat();
|
|
41
|
+
let destination;
|
|
42
|
+
if (format === 'pretty' && prettyFactory) {
|
|
43
|
+
destination = prettyFactory({ sync: true, customLevels: 'alert:45', destination: opts.destination ?? 1 });
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
destination = opts.destination ?? pino.destination({ dest: 1, sync: true });
|
|
47
|
+
}
|
|
48
|
+
instance = pino({
|
|
49
|
+
level: 'debug',
|
|
50
|
+
customLevels: { alert: 45 },
|
|
51
|
+
base: { hostname: hostname() },
|
|
52
|
+
timestamp: pino.stdTimeFunctions.isoTime,
|
|
53
|
+
formatters: { level: (label) => ({ level: label }) },
|
|
54
|
+
serializers: { err: serializeErr },
|
|
55
|
+
// per-call fields win over the mixin (pino's default merge strategy)
|
|
56
|
+
mixin: () => service ? { service, ...contextStorage.getStore() } : { ...contextStorage.getStore() }
|
|
57
|
+
}, destination);
|
|
58
|
+
}
|
|
59
|
+
_resetLogger();
|
|
60
|
+
// prom-client is an optional peer dependency, without it log lines are simply not counted
|
|
61
|
+
let promClient = null;
|
|
62
|
+
try {
|
|
63
|
+
promClient = await import('prom-client');
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
promClient = null;
|
|
67
|
+
}
|
|
68
|
+
export const logsCounter = promClient && new promClient.Counter({
|
|
69
|
+
name: 'df_logs',
|
|
70
|
+
help: 'A counter of warn, alert and error log lines. The code label comes from the code field of the line, it must be a static string.',
|
|
71
|
+
labelNames: ['level', 'code', 'host']
|
|
72
|
+
});
|
|
73
|
+
export const internalErrorCounter = promClient && new promClient.Counter({
|
|
74
|
+
name: 'df_internal_error',
|
|
75
|
+
help: 'DEPRECATED, use df_logs. A counter of error log lines.',
|
|
76
|
+
labelNames: ['errorCode']
|
|
77
|
+
});
|
|
78
|
+
export const eventsLogCounter = promClient && new promClient.Counter({
|
|
79
|
+
name: 'df_events_log',
|
|
80
|
+
help: 'DEPRECATED, use df_logs. A counter of warn and alert log lines that have a code.',
|
|
81
|
+
labelNames: ['level', 'code', 'host']
|
|
82
|
+
});
|
|
83
|
+
const count = (level, fields) => {
|
|
84
|
+
if (level !== 'warn' && level !== 'alert' && level !== 'error')
|
|
85
|
+
return;
|
|
86
|
+
const code = typeof fields?.code === 'string' ? fields.code : '';
|
|
87
|
+
const host = fields?.host ?? contextStorage.getStore()?.host;
|
|
88
|
+
const hostLabel = typeof host === 'string' && host ? host : '-';
|
|
89
|
+
logsCounter?.inc({ level, code, host: hostLabel });
|
|
90
|
+
if (level === 'error')
|
|
91
|
+
internalErrorCounter?.inc({ errorCode: code });
|
|
92
|
+
else if (code)
|
|
93
|
+
eventsLogCounter?.inc({ level, code, host: hostLabel });
|
|
94
|
+
};
|
|
95
|
+
const write = (level, msg, fields) => {
|
|
96
|
+
try {
|
|
97
|
+
count(level, fields);
|
|
98
|
+
instance[level](fields ?? {}, msg);
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
// logging must never become a new error
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
// the debug module stays the API for debug logs, only its output is redirected here
|
|
105
|
+
// debug reads these properties at each call so instances created earlier are redirected too
|
|
106
|
+
createDebug.formatArgs = function () { };
|
|
107
|
+
createDebug.log = function (...args) {
|
|
108
|
+
write('debug', format(...args), { ns: this.namespace });
|
|
109
|
+
};
|
|
110
|
+
const log = {
|
|
111
|
+
info: (msg, fields) => write('info', msg, fields),
|
|
112
|
+
warn: (msg, fields) => write('warn', msg, fields),
|
|
113
|
+
alert: (msg, fields) => write('alert', msg, fields),
|
|
114
|
+
error: (msg, fields) => write('error', msg, fields),
|
|
115
|
+
init: (opts) => { if (opts.service)
|
|
116
|
+
service = opts.service; }
|
|
117
|
+
};
|
|
118
|
+
export default log;
|
|
119
|
+
//# sourceMappingURL=log.js.map
|
package/log.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"log.js","sourceRoot":"","sources":["log.ts"],"names":[],"mappings":"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","sourcesContent":["// the logger shared by all the services of the data-fair stack\n// JSON lines on stdout in production, pretty output in dev, see README for the conventions\n\nimport { hostname } from 'node:os'\nimport { format } from 'node:util'\nimport { AsyncLocalStorage } from 'node:async_hooks'\nimport createDebug from 'debug'\nimport { pino, type Logger as PinoLogger, type DestinationStream } from 'pino'\nimport type { Counter } from 'prom-client'\nimport { serializeErr } from './log-err.js'\n\nexport type LogLevel = 'debug' | 'info' | 'warn' | 'alert' | 'error'\n\nexport type LogFields = { code?: string, err?: unknown, [key: string]: unknown }\n\nexport interface Logger {\n info (msg: string, fields?: LogFields): void\n warn (msg: string, fields?: LogFields): void\n alert (msg: string, fields?: LogFields): void\n error (msg: string, fields?: LogFields): void\n init (opts: { service?: string }): void\n}\n\ntype LogFormat = 'json' | 'pretty'\n\nexport type LogContext = Record<string, string | number | boolean | undefined>\n\n// implicit context merged into every log line\n// only small immutable values (ids, keys, short names), never req, documents, buffers, etc\n// long-lived resources (pools, intervals, caches) must be created outside any scope\n// or they would capture it and attribute later lines to the wrong user\nconst contextStorage = new AsyncLocalStorage<LogContext>()\n\n/** run fn in a scope where every log line carries the fields of the parent scope plus these */\nexport function withLogContext<T> (fields: LogContext, fn: () => T): T {\n return contextStorage.run({ ...contextStorage.getStore(), ...fields }, fn)\n}\n\n/** a copy of the current context, for example to pass it to a worker thread in a task payload */\nexport function getLogContext (): LogContext {\n return { ...contextStorage.getStore() }\n}\n\n// optional peer dependency, absent in production images\nlet prettyFactory: ((opts: Record<string, unknown>) => DestinationStream) | null = null\ntry {\n // @ts-ignore optional peer dependency\n prettyFactory = (await import('pino-pretty')).default\n} catch {\n prettyFactory = null\n}\n\nlet service: string | undefined = process.env.OTEL_SERVICE_NAME\n\nlet instance: PinoLogger<'alert'>\n\nconst defaultFormat = (): LogFormat => {\n if (process.env.LOG_FORMAT === 'json' || process.env.LOG_FORMAT === 'pretty') return process.env.LOG_FORMAT\n return process.env.NODE_ENV === 'production' ? 'json' : 'pretty'\n}\n\n/** @internal test seam, not API */\nexport function _resetLogger (opts: { destination?: { write (s: string): unknown }, format?: LogFormat } = {}) {\n const format = opts.format ?? defaultFormat()\n let destination: DestinationStream\n if (format === 'pretty' && prettyFactory) {\n destination = prettyFactory({ sync: true, customLevels: 'alert:45', destination: opts.destination ?? 1 })\n } else {\n destination = (opts.destination as DestinationStream) ?? pino.destination({ dest: 1, sync: true })\n }\n instance = pino({\n level: 'debug',\n customLevels: { alert: 45 },\n base: { hostname: hostname() },\n timestamp: pino.stdTimeFunctions.isoTime,\n formatters: { level: (label) => ({ level: label }) },\n serializers: { err: serializeErr },\n // per-call fields win over the mixin (pino's default merge strategy)\n mixin: () => service ? { service, ...contextStorage.getStore() } : { ...contextStorage.getStore() }\n }, destination)\n}\n_resetLogger()\n\n// prom-client is an optional peer dependency, without it log lines are simply not counted\nlet promClient: typeof import('prom-client') | null = null\ntry {\n promClient = await import('prom-client')\n} catch {\n promClient = null\n}\n\nexport const logsCounter: Counter<'level' | 'code' | 'host'> | null = promClient && new promClient.Counter({\n name: 'df_logs',\n help: 'A counter of warn, alert and error log lines. The code label comes from the code field of the line, it must be a static string.',\n labelNames: ['level', 'code', 'host']\n})\n\nexport const internalErrorCounter: Counter<'errorCode'> | null = promClient && new promClient.Counter({\n name: 'df_internal_error',\n help: 'DEPRECATED, use df_logs. A counter of error log lines.',\n labelNames: ['errorCode']\n})\n\nexport const eventsLogCounter: Counter<'level' | 'code' | 'host'> | null = promClient && new promClient.Counter({\n name: 'df_events_log',\n help: 'DEPRECATED, use df_logs. A counter of warn and alert log lines that have a code.',\n labelNames: ['level', 'code', 'host']\n})\n\nconst count = (level: LogLevel, fields?: LogFields) => {\n if (level !== 'warn' && level !== 'alert' && level !== 'error') return\n const code = typeof fields?.code === 'string' ? fields.code : ''\n const host = fields?.host ?? contextStorage.getStore()?.host\n const hostLabel = typeof host === 'string' && host ? host : '-'\n logsCounter?.inc({ level, code, host: hostLabel })\n if (level === 'error') internalErrorCounter?.inc({ errorCode: code })\n else if (code) eventsLogCounter?.inc({ level, code, host: hostLabel })\n}\n\nconst write = (level: LogLevel, msg: string, fields?: LogFields) => {\n try {\n count(level, fields)\n instance[level](fields ?? {}, msg)\n } catch {\n // logging must never become a new error\n }\n}\n\n// the debug module stays the API for debug logs, only its output is redirected here\n// debug reads these properties at each call so instances created earlier are redirected too\ncreateDebug.formatArgs = function () {}\ncreateDebug.log = function (this: { namespace: string }, ...args: unknown[]) {\n write('debug', format(...args), { ns: this.namespace })\n}\n\nconst log: Logger = {\n info: (msg, fields) => write('info', msg, fields),\n warn: (msg, fields) => write('warn', msg, fields),\n alert: (msg, fields) => write('alert', msg, fields),\n error: (msg, fields) => write('error', msg, fields),\n init: (opts) => { if (opts.service) service = opts.service }\n}\n\nexport default log\n"]}
|
package/log.test.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/log.test.js
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { describe, it, after } from 'node:test';
|
|
2
|
+
import { strict as assert } from 'assert';
|
|
3
|
+
import { hostname } from 'node:os';
|
|
4
|
+
import { Writable } from 'node:stream';
|
|
5
|
+
import { register } from 'prom-client';
|
|
6
|
+
import createDebug from 'debug';
|
|
7
|
+
// must be imported before log.js, to check that instances created earlier are redirected too
|
|
8
|
+
import { earlyDebug } from './test-fixtures/early-debug.js';
|
|
9
|
+
import log, { _resetLogger, withLogContext, getLogContext } from './log.js';
|
|
10
|
+
// collect the JSON lines written by the logger
|
|
11
|
+
const capture = () => {
|
|
12
|
+
const lines = [];
|
|
13
|
+
_resetLogger({ format: 'json', destination: { write: (s) => { for (const l of s.split('\n'))
|
|
14
|
+
if (l)
|
|
15
|
+
lines.push(JSON.parse(l)); } } });
|
|
16
|
+
return lines;
|
|
17
|
+
};
|
|
18
|
+
describe('log output', () => {
|
|
19
|
+
after(() => _resetLogger());
|
|
20
|
+
it('writes one JSON object per line with label level, ISO time, hostname and no pid', () => {
|
|
21
|
+
const lines = capture();
|
|
22
|
+
log.info('hello', { a: 1 });
|
|
23
|
+
assert.equal(lines.length, 1);
|
|
24
|
+
assert.equal(lines[0].level, 'info');
|
|
25
|
+
assert.equal(lines[0].msg, 'hello');
|
|
26
|
+
assert.equal(lines[0].a, 1);
|
|
27
|
+
assert.match(lines[0].time, /^\d{4}-\d{2}-\d{2}T/);
|
|
28
|
+
assert.equal(lines[0].hostname, hostname());
|
|
29
|
+
assert.equal(lines[0].pid, undefined);
|
|
30
|
+
});
|
|
31
|
+
it('supports the alert level between warn and error', () => {
|
|
32
|
+
const lines = capture();
|
|
33
|
+
log.alert('x', { code: 'c' });
|
|
34
|
+
assert.equal(lines[0].level, 'alert');
|
|
35
|
+
assert.equal(lines[0].code, 'c');
|
|
36
|
+
});
|
|
37
|
+
it('adds service from init', () => {
|
|
38
|
+
const lines = capture();
|
|
39
|
+
log.init({ service: 'svc' });
|
|
40
|
+
log.warn('x');
|
|
41
|
+
assert.equal(lines[0].service, 'svc');
|
|
42
|
+
});
|
|
43
|
+
it('pretty format writes human readable non-JSON output', () => {
|
|
44
|
+
const out = [];
|
|
45
|
+
// pino-pretty needs a real stream as destination
|
|
46
|
+
_resetLogger({ format: 'pretty', destination: new Writable({ write(chunk, _enc, cb) { out.push(chunk.toString()); cb(); } }) });
|
|
47
|
+
log.info('pretty hello');
|
|
48
|
+
assert.ok(out.join('').includes('pretty hello'));
|
|
49
|
+
assert.throws(() => JSON.parse(out[0]));
|
|
50
|
+
});
|
|
51
|
+
it('serializes err with the safe serializer', () => {
|
|
52
|
+
const lines = capture();
|
|
53
|
+
log.warn('x', { err: Object.assign(new Error('e'), { config: { headers: { Authorization: 'secret' } } }) });
|
|
54
|
+
assert.equal(lines[0].err.message, 'e');
|
|
55
|
+
assert.ok(lines[0].err.stack);
|
|
56
|
+
assert.equal(lines[0].err.config, undefined);
|
|
57
|
+
});
|
|
58
|
+
it('never throws', () => {
|
|
59
|
+
capture();
|
|
60
|
+
const circular = {};
|
|
61
|
+
circular.self = circular;
|
|
62
|
+
assert.doesNotThrow(() => log.info('x', circular));
|
|
63
|
+
assert.doesNotThrow(() => log.error('x', { get boom() { throw new Error('getter'); } }));
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
const metricValues = async (name) => (await register.getSingleMetric(name).get()).values;
|
|
67
|
+
describe('log metrics', () => {
|
|
68
|
+
after(() => _resetLogger());
|
|
69
|
+
it('counts warn/alert/error in df_logs with level, code and host', async () => {
|
|
70
|
+
capture();
|
|
71
|
+
log.warn('w', { code: 'm-w', host: 'h1' });
|
|
72
|
+
log.alert('a', { code: 'm-a' });
|
|
73
|
+
log.error('e', { code: 'm-e' });
|
|
74
|
+
const v = await metricValues('df_logs');
|
|
75
|
+
assert.ok(v.find(x => x.labels.code === 'm-w' && x.labels.level === 'warn' && x.labels.host === 'h1' && x.value === 1));
|
|
76
|
+
assert.ok(v.find(x => x.labels.code === 'm-a' && x.labels.level === 'alert' && x.labels.host === '-' && x.value === 1));
|
|
77
|
+
assert.ok(v.find(x => x.labels.code === 'm-e' && x.labels.level === 'error' && x.value === 1));
|
|
78
|
+
});
|
|
79
|
+
it('does not count info lines', async () => {
|
|
80
|
+
capture();
|
|
81
|
+
log.info('i', { code: 'm-i' });
|
|
82
|
+
assert.ok(!(await metricValues('df_logs')).find(x => x.labels.code === 'm-i'));
|
|
83
|
+
});
|
|
84
|
+
it('counts uncoded lines with an empty code', async () => {
|
|
85
|
+
capture();
|
|
86
|
+
log.warn('x', { host: 'm-uncoded' });
|
|
87
|
+
assert.ok((await metricValues('df_logs')).find(x => x.labels.code === '' && x.labels.host === 'm-uncoded'));
|
|
88
|
+
});
|
|
89
|
+
it('keeps df_internal_error for error lines', async () => {
|
|
90
|
+
capture();
|
|
91
|
+
log.error('e', { code: 'm-ie' });
|
|
92
|
+
assert.ok((await metricValues('df_internal_error')).find(x => x.labels.errorCode === 'm-ie' && x.value === 1));
|
|
93
|
+
});
|
|
94
|
+
it('keeps df_events_log only for coded warn/alert', async () => {
|
|
95
|
+
capture();
|
|
96
|
+
log.alert('a', { code: 'm-ev-a' });
|
|
97
|
+
log.warn('w', { host: 'm-ev-uncoded' });
|
|
98
|
+
log.error('e', { code: 'm-ev-e' });
|
|
99
|
+
const v = await metricValues('df_events_log');
|
|
100
|
+
assert.ok(v.find(x => x.labels.code === 'm-ev-a' && x.labels.level === 'alert' && x.value === 1));
|
|
101
|
+
assert.ok(!v.find(x => x.labels.host === 'm-ev-uncoded'));
|
|
102
|
+
assert.ok(!v.find(x => x.labels.code === 'm-ev-e'));
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
describe('log context', () => {
|
|
106
|
+
after(() => _resetLogger());
|
|
107
|
+
it('merges context into lines, per-call fields win', () => {
|
|
108
|
+
const lines = capture();
|
|
109
|
+
withLogContext({ datasetId: 'd1', host: 'ctx' }, () => withLogContext({ step: 's' }, () => log.info('x', { host: 'call' })));
|
|
110
|
+
assert.equal(lines[0].datasetId, 'd1');
|
|
111
|
+
assert.equal(lines[0].step, 's');
|
|
112
|
+
assert.equal(lines[0].host, 'call');
|
|
113
|
+
});
|
|
114
|
+
it('isolates concurrent scopes across awaits and timers', async () => {
|
|
115
|
+
const lines = capture();
|
|
116
|
+
const run = (u) => withLogContext({ userId: u }, async () => {
|
|
117
|
+
await new Promise(resolve => setTimeout(resolve, u === 'a' ? 10 : 1));
|
|
118
|
+
log.info(u);
|
|
119
|
+
await Promise.resolve();
|
|
120
|
+
log.info(u);
|
|
121
|
+
});
|
|
122
|
+
await Promise.all([run('a'), run('b')]);
|
|
123
|
+
assert.equal(lines.length, 4);
|
|
124
|
+
for (const l of lines)
|
|
125
|
+
assert.equal(l.userId, l.msg);
|
|
126
|
+
});
|
|
127
|
+
it('has no context outside scopes', () => {
|
|
128
|
+
const lines = capture();
|
|
129
|
+
log.info('x');
|
|
130
|
+
assert.equal(lines[0].userId, undefined);
|
|
131
|
+
assert.deepEqual(getLogContext(), {});
|
|
132
|
+
});
|
|
133
|
+
it('uses the context host as counter label', async () => {
|
|
134
|
+
capture();
|
|
135
|
+
withLogContext({ host: 'ctx-host' }, () => log.warn('x', { code: 'm-ctx' }));
|
|
136
|
+
assert.ok((await metricValues('df_logs')).find(x => x.labels.code === 'm-ctx' && x.labels.host === 'ctx-host' && x.value === 1));
|
|
137
|
+
});
|
|
138
|
+
it('getLogContext returns a copy', () => {
|
|
139
|
+
withLogContext({ a: 1 }, () => {
|
|
140
|
+
const c = getLogContext();
|
|
141
|
+
c.a = 2;
|
|
142
|
+
assert.equal(getLogContext().a, 1);
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
describe('debug output', () => {
|
|
147
|
+
after(() => _resetLogger());
|
|
148
|
+
it('redirects debug output, including instances created before the logger loaded', () => {
|
|
149
|
+
createDebug.enable('lt:*,-lt:no');
|
|
150
|
+
const lines = capture();
|
|
151
|
+
earlyDebug('hi %s', 'there');
|
|
152
|
+
createDebug('lt:late')('late', { k: 1 });
|
|
153
|
+
createDebug('lt:no')('nope');
|
|
154
|
+
createDebug('other')('nope');
|
|
155
|
+
assert.deepEqual(lines.map(l => [l.ns, l.msg, l.level]), [['lt:early', 'hi there', 'debug'], ['lt:late', 'late { k: 1 }', 'debug']]);
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
//# sourceMappingURL=log.test.js.map
|
package/log.test.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"log.test.js","sourceRoot":"","sources":["log.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { describe, it, after } from 'node:test'\nimport { strict as assert } from 'assert'\nimport { hostname } from 'node:os'\nimport { Writable } from 'node:stream'\nimport { register } from 'prom-client'\nimport createDebug from 'debug'\n// must be imported before log.js, to check that instances created earlier are redirected too\nimport { earlyDebug } from './test-fixtures/early-debug.js'\nimport log, { _resetLogger, withLogContext, getLogContext } from './log.js'\n\n// collect the JSON lines written by the logger\nconst capture = () => {\n const lines: any[] = []\n _resetLogger({ format: 'json', destination: { write: (s: string) => { for (const l of s.split('\\n')) if (l) lines.push(JSON.parse(l)) } } })\n return lines\n}\n\ndescribe('log output', () => {\n after(() => _resetLogger())\n\n it('writes one JSON object per line with label level, ISO time, hostname and no pid', () => {\n const lines = capture()\n log.info('hello', { a: 1 })\n assert.equal(lines.length, 1)\n assert.equal(lines[0].level, 'info')\n assert.equal(lines[0].msg, 'hello')\n assert.equal(lines[0].a, 1)\n assert.match(lines[0].time, /^\\d{4}-\\d{2}-\\d{2}T/)\n assert.equal(lines[0].hostname, hostname())\n assert.equal(lines[0].pid, undefined)\n })\n\n it('supports the alert level between warn and error', () => {\n const lines = capture()\n log.alert('x', { code: 'c' })\n assert.equal(lines[0].level, 'alert')\n assert.equal(lines[0].code, 'c')\n })\n\n it('adds service from init', () => {\n const lines = capture()\n log.init({ service: 'svc' })\n log.warn('x')\n assert.equal(lines[0].service, 'svc')\n })\n\n it('pretty format writes human readable non-JSON output', () => {\n const out: string[] = []\n // pino-pretty needs a real stream as destination\n _resetLogger({ format: 'pretty', destination: new Writable({ write (chunk, _enc, cb) { out.push(chunk.toString()); cb() } }) })\n log.info('pretty hello')\n assert.ok(out.join('').includes('pretty hello'))\n assert.throws(() => JSON.parse(out[0]))\n })\n\n it('serializes err with the safe serializer', () => {\n const lines = capture()\n log.warn('x', { err: Object.assign(new Error('e'), { config: { headers: { Authorization: 'secret' } } }) })\n assert.equal(lines[0].err.message, 'e')\n assert.ok(lines[0].err.stack)\n assert.equal(lines[0].err.config, undefined)\n })\n\n it('never throws', () => {\n capture()\n const circular: any = {}\n circular.self = circular\n assert.doesNotThrow(() => log.info('x', circular))\n assert.doesNotThrow(() => log.error('x', { get boom () { throw new Error('getter') } }))\n })\n})\n\nconst metricValues = async (name: string) => (await register.getSingleMetric(name)!.get()).values\n\ndescribe('log metrics', () => {\n after(() => _resetLogger())\n\n it('counts warn/alert/error in df_logs with level, code and host', async () => {\n capture()\n log.warn('w', { code: 'm-w', host: 'h1' })\n log.alert('a', { code: 'm-a' })\n log.error('e', { code: 'm-e' })\n const v = await metricValues('df_logs')\n assert.ok(v.find(x => x.labels.code === 'm-w' && x.labels.level === 'warn' && x.labels.host === 'h1' && x.value === 1))\n assert.ok(v.find(x => x.labels.code === 'm-a' && x.labels.level === 'alert' && x.labels.host === '-' && x.value === 1))\n assert.ok(v.find(x => x.labels.code === 'm-e' && x.labels.level === 'error' && x.value === 1))\n })\n\n it('does not count info lines', async () => {\n capture()\n log.info('i', { code: 'm-i' })\n assert.ok(!(await metricValues('df_logs')).find(x => x.labels.code === 'm-i'))\n })\n\n it('counts uncoded lines with an empty code', async () => {\n capture()\n log.warn('x', { host: 'm-uncoded' })\n assert.ok((await metricValues('df_logs')).find(x => x.labels.code === '' && x.labels.host === 'm-uncoded'))\n })\n\n it('keeps df_internal_error for error lines', async () => {\n capture()\n log.error('e', { code: 'm-ie' })\n assert.ok((await metricValues('df_internal_error')).find(x => x.labels.errorCode === 'm-ie' && x.value === 1))\n })\n\n it('keeps df_events_log only for coded warn/alert', async () => {\n capture()\n log.alert('a', { code: 'm-ev-a' })\n log.warn('w', { host: 'm-ev-uncoded' })\n log.error('e', { code: 'm-ev-e' })\n const v = await metricValues('df_events_log')\n assert.ok(v.find(x => x.labels.code === 'm-ev-a' && x.labels.level === 'alert' && x.value === 1))\n assert.ok(!v.find(x => x.labels.host === 'm-ev-uncoded'))\n assert.ok(!v.find(x => x.labels.code === 'm-ev-e'))\n })\n})\n\ndescribe('log context', () => {\n after(() => _resetLogger())\n\n it('merges context into lines, per-call fields win', () => {\n const lines = capture()\n withLogContext({ datasetId: 'd1', host: 'ctx' }, () => withLogContext({ step: 's' }, () => log.info('x', { host: 'call' })))\n assert.equal(lines[0].datasetId, 'd1')\n assert.equal(lines[0].step, 's')\n assert.equal(lines[0].host, 'call')\n })\n\n it('isolates concurrent scopes across awaits and timers', async () => {\n const lines = capture()\n const run = (u: string) => withLogContext({ userId: u }, async () => {\n await new Promise(resolve => setTimeout(resolve, u === 'a' ? 10 : 1))\n log.info(u)\n await Promise.resolve()\n log.info(u)\n })\n await Promise.all([run('a'), run('b')])\n assert.equal(lines.length, 4)\n for (const l of lines) assert.equal(l.userId, l.msg)\n })\n\n it('has no context outside scopes', () => {\n const lines = capture()\n log.info('x')\n assert.equal(lines[0].userId, undefined)\n assert.deepEqual(getLogContext(), {})\n })\n\n it('uses the context host as counter label', async () => {\n capture()\n withLogContext({ host: 'ctx-host' }, () => log.warn('x', { code: 'm-ctx' }))\n assert.ok((await metricValues('df_logs')).find(x => x.labels.code === 'm-ctx' && x.labels.host === 'ctx-host' && x.value === 1))\n })\n\n it('getLogContext returns a copy', () => {\n withLogContext({ a: 1 }, () => {\n const c = getLogContext()\n c.a = 2\n assert.equal(getLogContext().a, 1)\n })\n })\n})\n\ndescribe('debug output', () => {\n after(() => _resetLogger())\n\n it('redirects debug output, including instances created before the logger loaded', () => {\n createDebug.enable('lt:*,-lt:no')\n const lines = capture()\n earlyDebug('hi %s', 'there')\n createDebug('lt:late')('late', { k: 1 })\n createDebug('lt:no')('nope')\n createDebug('other')('nope')\n assert.deepEqual(lines.map(l => [l.ns, l.msg, l.level]), [['lt:early', 'hi there', 'debug'], ['lt:late', 'late { k: 1 }', 'debug']])\n })\n})\n"]}
|
package/mongo.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { MongoClient, MongoError } from 'mongodb';
|
|
2
|
+
import log from './log.js';
|
|
2
3
|
export class Mongo {
|
|
3
4
|
_client;
|
|
4
5
|
_db;
|
|
@@ -14,13 +15,13 @@ export class Mongo {
|
|
|
14
15
|
}
|
|
15
16
|
connect = async (mongoUrl, options = {}) => {
|
|
16
17
|
if (this._client) {
|
|
17
|
-
|
|
18
|
+
log.warn('db already connected');
|
|
18
19
|
return;
|
|
19
20
|
}
|
|
20
|
-
|
|
21
|
+
log.info('connecting to mongodb...');
|
|
21
22
|
this._client = await MongoClient.connect(mongoUrl, { maxPoolSize: 5, ignoreUndefined: true, ...options });
|
|
22
23
|
this._db = this._client.db();
|
|
23
|
-
|
|
24
|
+
log.info('connected to mongodb');
|
|
24
25
|
};
|
|
25
26
|
// Create an index if it does not exist and manage overwriting existing and conflicting index definitions
|
|
26
27
|
ensureIndex = async (collection, key, options = {}) => {
|
|
@@ -31,7 +32,7 @@ export class Mongo {
|
|
|
31
32
|
if (options.name && err instanceof MongoError && (err.code === 85 || err.code === 86)) {
|
|
32
33
|
// if the error is a conflict on keys or params of the index we automatically
|
|
33
34
|
// delete then recreate the index
|
|
34
|
-
|
|
35
|
+
log.info('drop then recreate index', { collection, index: options.name });
|
|
35
36
|
await this.db.collection(collection).dropIndex(options.name);
|
|
36
37
|
await this.db.collection(collection).createIndex(key, options);
|
|
37
38
|
}
|
package/mongo.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mongo.js","sourceRoot":"","sources":["mongo.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"mongo.js","sourceRoot":"","sources":["mongo.ts"],"names":[],"mappings":"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","sourcesContent":["import type { IndexSpecification, CreateIndexesOptions, MongoClientOptions, IndexDirection, Db } from 'mongodb'\nimport { MongoClient, MongoError } from 'mongodb'\nimport log from './log.js'\n\ntype IndexDirections = Record<string, IndexDirection>\n\nexport type IndexDefinitions = Record<string, Record<string, null | IndexDirections | [IndexDirections, CreateIndexesOptions]>>\n\nexport class Mongo {\n private _client: MongoClient | undefined\n private _db: Db | undefined\n\n get client () {\n if (!this._client) throw new Error('db was not connected')\n return this._client\n }\n\n get db () {\n if (!this._db) throw new Error('db was not connected')\n return this._db\n }\n\n connect = async (mongoUrl: string, options: MongoClientOptions = {}) => {\n if (this._client) {\n log.warn('db already connected')\n return\n }\n log.info('connecting to mongodb...')\n this._client = await MongoClient.connect(mongoUrl, { maxPoolSize: 5, ignoreUndefined: true, ...options })\n this._db = this._client.db()\n log.info('connected to mongodb')\n }\n\n // Create an index if it does not exist and manage overwriting existing and conflicting index definitions\n ensureIndex = async (collection: string, key: IndexSpecification, options: CreateIndexesOptions = {}) => {\n try {\n await this.db.collection(collection).createIndex(key, options)\n } catch (err) {\n if (options.name && err instanceof MongoError && (err.code === 85 || err.code === 86)) {\n // if the error is a conflict on keys or params of the index we automatically\n // delete then recreate the index\n log.info('drop then recreate index', { collection, index: options.name })\n await this.db.collection(collection).dropIndex(options.name)\n await this.db.collection(collection).createIndex(key, options)\n } else {\n throw err\n }\n }\n }\n\n /**\n * create a bunch of indexes and overtwrite previous definitions\n * the structure forces giving names to indexes which is better for managing them properly\n */\n configure = async (indexDefinitions: IndexDefinitions) => {\n for (const collectionName in indexDefinitions) {\n try {\n await this.db.createCollection(collectionName)\n } catch (err) {\n // nothing TODO\n }\n for (const indexName in indexDefinitions[collectionName]) {\n let key: IndexDirections, options: CreateIndexesOptions\n const indexDefinition = indexDefinitions[collectionName][indexName]\n if (indexDefinition === null) {\n if (await this.db.collection(collectionName).indexExists(indexName)) {\n await this.db.collection(collectionName).dropIndex(indexName)\n }\n continue\n } else if (Array.isArray(indexDefinition)) {\n [key, options] = indexDefinition\n if (options.name && options.name !== indexName) throw new Error(`inconsistent index name ${indexName} or ${options.name}`)\n options.name = indexName\n } else {\n key = indexDefinition\n options = { name: indexName }\n }\n await this.ensureIndex(collectionName, key, options)\n }\n }\n }\n}\n\nconst mongo = new Mongo()\n\nexport default mongo\n"]}
|
package/observer.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { Registry } from 'prom-client';
|
|
2
2
|
export declare const servicePromRegistry: Registry<"text/plain; version=0.0.4; charset=utf-8">;
|
|
3
|
-
export declare const internalError: (errorCode: string, error: any, ...optionalParams: any) => void;
|
|
4
3
|
export declare const startObserver: (port?: number) => Promise<void>;
|
|
5
4
|
export declare const stopObserver: () => Promise<void>;
|
package/observer.js
CHANGED
|
@@ -3,31 +3,19 @@ import { hostname } from 'node:os';
|
|
|
3
3
|
import { createServer } from 'node:http';
|
|
4
4
|
import { getHeapSnapshot } from 'node:v8';
|
|
5
5
|
import { pipeline } from 'node:stream/promises';
|
|
6
|
-
import { Registry,
|
|
6
|
+
import { Registry, register, collectDefaultMetrics } from 'prom-client';
|
|
7
|
+
import log from './log.js';
|
|
7
8
|
import eventPromise from '@data-fair/lib-utils/event-promise.js';
|
|
8
9
|
// a special registry for metrics that are global
|
|
9
10
|
// not based on this specific process state but instead on the content of db for example
|
|
10
11
|
export const servicePromRegistry = new Registry();
|
|
11
12
|
collectDefaultMetrics();
|
|
12
|
-
// local metrics incremented throughout the code
|
|
13
|
-
const internalErrorCounter = new Counter({
|
|
14
|
-
name: 'df_internal_error',
|
|
15
|
-
help: 'A counter of errors from any service, worker, etc. Do not use for client errors, only for anomalies that should trigger alerts. Each increment should be accompanied by an error log with matching code.',
|
|
16
|
-
labelNames: ['errorCode']
|
|
17
|
-
});
|
|
18
|
-
export const internalError = (errorCode, error, ...optionalParams) => {
|
|
19
|
-
internalErrorCounter.inc({ errorCode });
|
|
20
|
-
const message = error.message || error;
|
|
21
|
-
console.error(`[${errorCode}] ${message}`, ...optionalParams);
|
|
22
|
-
if (error.stack)
|
|
23
|
-
console.error(error.stack);
|
|
24
|
-
};
|
|
25
13
|
/**
|
|
26
14
|
* @param {import('node:http').ServerResponse} res
|
|
27
15
|
* @param {any} err
|
|
28
16
|
*/
|
|
29
17
|
const httpErrorHandler = (res, err) => {
|
|
30
|
-
|
|
18
|
+
log.error('failed to serve prometheus metrics', { code: 'observer-metrics', err });
|
|
31
19
|
res.writeHead(500);
|
|
32
20
|
res.end();
|
|
33
21
|
};
|
|
@@ -60,9 +48,9 @@ const serveRetainedHeapSnapshot = async (res) => {
|
|
|
60
48
|
res.setHeader('Content-Disposition', `attachment; filename="data-fair-${hostname()}-${new Date().toISOString()}.heapsnapshot"`);
|
|
61
49
|
res.setHeader('Content-Type', 'application/octet-stream');
|
|
62
50
|
const start = Date.now();
|
|
63
|
-
|
|
51
|
+
log.info('observer: starting full heap snapshot — event loop will be blocked');
|
|
64
52
|
await pipeline(getHeapSnapshot(), res);
|
|
65
|
-
|
|
53
|
+
log.info('observer: full heap snapshot done', { duration: Date.now() - start });
|
|
66
54
|
};
|
|
67
55
|
// live heap allocation sampler
|
|
68
56
|
const serveHeapSnapshot = async (res, includeCollected, duration) => {
|
|
@@ -111,14 +99,7 @@ export const startObserver = async (port = 9090) => {
|
|
|
111
99
|
});
|
|
112
100
|
server.listen(port);
|
|
113
101
|
await eventPromise(server, 'listening');
|
|
114
|
-
|
|
115
|
-
GET /metrics -> get prometheus metrics for this instance
|
|
116
|
-
GET /service-metrics -> get prometheus metrics shared accross all instances
|
|
117
|
-
GET /cpu-profile -> generate and fetch a CPU profile
|
|
118
|
-
GET /heap-profile -> generate and fetch a sampled heap allocation snapshot
|
|
119
|
-
GET /heap-profile-all -> generate and fetch a sampled heap allocation snapshot including objects collected by GC
|
|
120
|
-
GET /heap-snapshot -> dump a full retained heap snapshot (blocks the event loop while V8 walks the heap)
|
|
121
|
-
`);
|
|
102
|
+
log.info(`Observer server available on http://localhost:${port}`, { routes: ['/metrics', '/service-metrics', '/cpu-profile', '/heap-profile', '/heap-profile-all', '/heap-snapshot'] });
|
|
122
103
|
};
|
|
123
104
|
export const stopObserver = async () => {
|
|
124
105
|
if (server)
|
package/observer.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"observer.js","sourceRoot":"","sources":["observer.ts"],"names":[],"mappings":"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","sourcesContent":["// very light web server to expose prometheus metrics, export a cpu profile and maybe other incoming obervability features\n\n// follow this doc for prometheus metrics naming conventions https://prometheus.io/docs/practices/naming/\n// /metrics serves container/process/pod specific metrics while /global-metrics\n// serves metrics for the whole service installation no matter the scaling\n\nimport type { Server, ServerResponse } from 'node:http'\nimport { hostname } from 'node:os'\nimport { createServer } from 'node:http'\nimport { getHeapSnapshot } from 'node:v8'\nimport { pipeline } from 'node:stream/promises'\nimport { Registry, Counter, register, collectDefaultMetrics } from 'prom-client'\nimport eventPromise from '@data-fair/lib-utils/event-promise.js'\n\n// a special registry for metrics that are global\n// not based on this specific process state but instead on the content of db for example\nexport const servicePromRegistry = new Registry()\n\ncollectDefaultMetrics()\n\n// local metrics incremented throughout the code\nconst internalErrorCounter = new Counter({\n name: 'df_internal_error',\n help: 'A counter of errors from any service, worker, etc. Do not use for client errors, only for anomalies that should trigger alerts. Each increment should be accompanied by an error log with matching code.',\n labelNames: ['errorCode']\n})\n\nexport const internalError = (errorCode: string, error: any, ...optionalParams: any) => {\n internalErrorCounter.inc({ errorCode })\n const message = error.message || error\n console.error(`[${errorCode}] ${message}`, ...optionalParams)\n if (error.stack) console.error(error.stack)\n}\n\n/**\n * @param {import('node:http').ServerResponse} res\n * @param {any} err\n */\nconst httpErrorHandler = (res: ServerResponse, err: any) => {\n console.error('failed to server prometheus /metrics', err)\n res.writeHead(500)\n res.end()\n}\n\nconst serveRegistry = async (res: ServerResponse, registry: Registry) => {\n const metrics = await registry.metrics()\n res.setHeader('Content-Type', registry.contentType)\n res.writeHead(200)\n res.write(metrics)\n res.end()\n}\n\n// live CPU performance inspection\nconst serveCPUProfile = async (res: ServerResponse, duration: number) => {\n const { Session } = await import('node:inspector/promises')\n const session = new Session()\n session.connect()\n await session.post('Profiler.enable')\n await session.post('Profiler.start')\n await new Promise(resolve => setTimeout(resolve, duration))\n const { profile } = await session.post('Profiler.stop')\n await session.post('Profiler.disable')\n session.disconnect()\n\n res.setHeader('Content-Disposition', `attachment; filename=\"data-fair-${hostname()}-${new Date().toISOString()}.cpuprofile\"`)\n res.write(JSON.stringify(profile))\n res.end()\n}\n\n// full retained heap snapshot (the V8 inspector \"take heap snapshot\"). Blocks the event loop while V8\n// walks the heap: ~5-20 ms per MB of live heap, so ~10-30 s for a 1-1.5 GB process; all in-flight\n// requests stall for the duration. Use sparingly, ideally after detaching the pod from its Service.\nconst serveRetainedHeapSnapshot = async (res: ServerResponse) => {\n res.setHeader('Content-Disposition', `attachment; filename=\"data-fair-${hostname()}-${new Date().toISOString()}.heapsnapshot\"`)\n res.setHeader('Content-Type', 'application/octet-stream')\n const start = Date.now()\n console.log('observer: starting full heap snapshot — event loop will be blocked')\n await pipeline(getHeapSnapshot(), res)\n console.log(`observer: full heap snapshot done in ${Date.now() - start}ms`)\n}\n\n// live heap allocation sampler\nconst serveHeapSnapshot = async (res: ServerResponse, includeCollected: boolean, duration: number) => {\n const { Session } = await import('node:inspector/promises')\n const session = new Session()\n session.connect()\n await session.post('HeapProfiler.enable')\n await session.post('HeapProfiler.startSampling', { includeObjectsCollectedByMajorGC: includeCollected, includeObjectsCollectedByMinorGC: true })\n await new Promise(resolve => setTimeout(resolve, duration))\n const { profile } = await session.post('HeapProfiler.stopSampling')\n await session.post('HeapProfiler.disable')\n session.disconnect()\n\n res.setHeader('Content-Disposition', `attachment; filename=\"data-fair-${hostname()}-${new Date().toISOString()}.heapprofile\"`)\n res.write(JSON.stringify(profile))\n res.end()\n}\n\nlet server: Server\n\nexport const startObserver = async (port = 9090) => {\n server = createServer((req, res) => {\n const url = new URL(req.url || '', `http://${req.headers.host}`)\n if (req.method === 'GET' && url.pathname === '/metrics') {\n serveRegistry(res, register).catch(err => httpErrorHandler(res, err))\n } else if (req.method === 'GET' && (url.pathname === '/global-metrics' || url.pathname === '/service-metrics')) {\n serveRegistry(res, servicePromRegistry).catch(err => httpErrorHandler(res, err))\n } else if (req.method === 'GET' && url.pathname === '/cpu-profile') {\n const duration = parseInt(url.searchParams.get('duration') || '2000')\n serveCPUProfile(res, duration).catch(err => httpErrorHandler(res, err))\n } else if (req.method === 'GET' && url.pathname === '/heap-profile') {\n const duration = parseInt(url.searchParams.get('duration') || '2000')\n serveHeapSnapshot(res, false, duration).catch(err => httpErrorHandler(res, err))\n } else if (req.method === 'GET' && url.pathname === '/heap-profile-all') {\n const duration = parseInt(url.searchParams.get('duration') || '2000')\n serveHeapSnapshot(res, true, duration).catch(err => httpErrorHandler(res, err))\n } else if (req.method === 'GET' && url.pathname === '/heap-snapshot') {\n serveRetainedHeapSnapshot(res).catch(err => httpErrorHandler(res, err))\n } else {\n res.writeHead(404)\n res.end()\n }\n })\n server.listen(port)\n await eventPromise(server, 'listening')\n console.log(`Observer server available on http://localhost:${port}\n GET /metrics -> get prometheus metrics for this instance\n GET /service-metrics -> get prometheus metrics shared accross all instances\n GET /cpu-profile -> generate and fetch a CPU profile\n GET /heap-profile -> generate and fetch a sampled heap allocation snapshot\n GET /heap-profile-all -> generate and fetch a sampled heap allocation snapshot including objects collected by GC\n GET /heap-snapshot -> dump a full retained heap snapshot (blocks the event loop while V8 walks the heap)\n`)\n}\n\nexport const stopObserver = async () => {\n if (server) server.close()\n}\n"]}
|
|
1
|
+
{"version":3,"file":"observer.js","sourceRoot":"","sources":["observer.ts"],"names":[],"mappings":"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","sourcesContent":["// very light web server to expose prometheus metrics, export a cpu profile and maybe other incoming obervability features\n\n// follow this doc for prometheus metrics naming conventions https://prometheus.io/docs/practices/naming/\n// /metrics serves container/process/pod specific metrics while /global-metrics\n// serves metrics for the whole service installation no matter the scaling\n\nimport type { Server, ServerResponse } from 'node:http'\nimport { hostname } from 'node:os'\nimport { createServer } from 'node:http'\nimport { getHeapSnapshot } from 'node:v8'\nimport { pipeline } from 'node:stream/promises'\nimport { Registry, register, collectDefaultMetrics } from 'prom-client'\nimport log from './log.js'\nimport eventPromise from '@data-fair/lib-utils/event-promise.js'\n\n// a special registry for metrics that are global\n// not based on this specific process state but instead on the content of db for example\nexport const servicePromRegistry = new Registry()\n\ncollectDefaultMetrics()\n\n/**\n * @param {import('node:http').ServerResponse} res\n * @param {any} err\n */\nconst httpErrorHandler = (res: ServerResponse, err: any) => {\n log.error('failed to serve prometheus metrics', { code: 'observer-metrics', err })\n res.writeHead(500)\n res.end()\n}\n\nconst serveRegistry = async (res: ServerResponse, registry: Registry) => {\n const metrics = await registry.metrics()\n res.setHeader('Content-Type', registry.contentType)\n res.writeHead(200)\n res.write(metrics)\n res.end()\n}\n\n// live CPU performance inspection\nconst serveCPUProfile = async (res: ServerResponse, duration: number) => {\n const { Session } = await import('node:inspector/promises')\n const session = new Session()\n session.connect()\n await session.post('Profiler.enable')\n await session.post('Profiler.start')\n await new Promise(resolve => setTimeout(resolve, duration))\n const { profile } = await session.post('Profiler.stop')\n await session.post('Profiler.disable')\n session.disconnect()\n\n res.setHeader('Content-Disposition', `attachment; filename=\"data-fair-${hostname()}-${new Date().toISOString()}.cpuprofile\"`)\n res.write(JSON.stringify(profile))\n res.end()\n}\n\n// full retained heap snapshot (the V8 inspector \"take heap snapshot\"). Blocks the event loop while V8\n// walks the heap: ~5-20 ms per MB of live heap, so ~10-30 s for a 1-1.5 GB process; all in-flight\n// requests stall for the duration. Use sparingly, ideally after detaching the pod from its Service.\nconst serveRetainedHeapSnapshot = async (res: ServerResponse) => {\n res.setHeader('Content-Disposition', `attachment; filename=\"data-fair-${hostname()}-${new Date().toISOString()}.heapsnapshot\"`)\n res.setHeader('Content-Type', 'application/octet-stream')\n const start = Date.now()\n log.info('observer: starting full heap snapshot — event loop will be blocked')\n await pipeline(getHeapSnapshot(), res)\n log.info('observer: full heap snapshot done', { duration: Date.now() - start })\n}\n\n// live heap allocation sampler\nconst serveHeapSnapshot = async (res: ServerResponse, includeCollected: boolean, duration: number) => {\n const { Session } = await import('node:inspector/promises')\n const session = new Session()\n session.connect()\n await session.post('HeapProfiler.enable')\n await session.post('HeapProfiler.startSampling', { includeObjectsCollectedByMajorGC: includeCollected, includeObjectsCollectedByMinorGC: true })\n await new Promise(resolve => setTimeout(resolve, duration))\n const { profile } = await session.post('HeapProfiler.stopSampling')\n await session.post('HeapProfiler.disable')\n session.disconnect()\n\n res.setHeader('Content-Disposition', `attachment; filename=\"data-fair-${hostname()}-${new Date().toISOString()}.heapprofile\"`)\n res.write(JSON.stringify(profile))\n res.end()\n}\n\nlet server: Server\n\nexport const startObserver = async (port = 9090) => {\n server = createServer((req, res) => {\n const url = new URL(req.url || '', `http://${req.headers.host}`)\n if (req.method === 'GET' && url.pathname === '/metrics') {\n serveRegistry(res, register).catch(err => httpErrorHandler(res, err))\n } else if (req.method === 'GET' && (url.pathname === '/global-metrics' || url.pathname === '/service-metrics')) {\n serveRegistry(res, servicePromRegistry).catch(err => httpErrorHandler(res, err))\n } else if (req.method === 'GET' && url.pathname === '/cpu-profile') {\n const duration = parseInt(url.searchParams.get('duration') || '2000')\n serveCPUProfile(res, duration).catch(err => httpErrorHandler(res, err))\n } else if (req.method === 'GET' && url.pathname === '/heap-profile') {\n const duration = parseInt(url.searchParams.get('duration') || '2000')\n serveHeapSnapshot(res, false, duration).catch(err => httpErrorHandler(res, err))\n } else if (req.method === 'GET' && url.pathname === '/heap-profile-all') {\n const duration = parseInt(url.searchParams.get('duration') || '2000')\n serveHeapSnapshot(res, true, duration).catch(err => httpErrorHandler(res, err))\n } else if (req.method === 'GET' && url.pathname === '/heap-snapshot') {\n serveRetainedHeapSnapshot(res).catch(err => httpErrorHandler(res, err))\n } else {\n res.writeHead(404)\n res.end()\n }\n })\n server.listen(port)\n await eventPromise(server, 'listening')\n log.info(`Observer server available on http://localhost:${port}`, { routes: ['/metrics', '/service-metrics', '/cpu-profile', '/heap-profile', '/heap-profile-all', '/heap-snapshot'] })\n}\n\nexport const stopObserver = async () => {\n if (server) server.close()\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@data-fair/lib-node",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "Small nodejs related utility functions for the data-fair stack.",
|
|
5
5
|
"files": [
|
|
6
6
|
"**/*.js",
|
|
@@ -21,16 +21,19 @@
|
|
|
21
21
|
"axios": "^1.7.7",
|
|
22
22
|
"cacheable-lookup": "^7.0.0",
|
|
23
23
|
"cookie": "^0.7.2",
|
|
24
|
+
"debug": "^4.4.3",
|
|
24
25
|
"ipaddr.js": "^2.5.0",
|
|
25
26
|
"jsonwebtoken": "^9.0.2",
|
|
26
27
|
"jwks-rsa": "^3.1.0",
|
|
28
|
+
"pino": "^10.4.0",
|
|
27
29
|
"semver": "^7.6.3"
|
|
28
30
|
},
|
|
29
31
|
"peerDependencies": {
|
|
30
32
|
"mongodb": "6",
|
|
31
33
|
"prom-client": "15",
|
|
32
34
|
"tough-cookie": "5",
|
|
33
|
-
"ws": "8"
|
|
35
|
+
"ws": "8",
|
|
36
|
+
"pino-pretty": "13"
|
|
34
37
|
},
|
|
35
38
|
"peerDependenciesMeta": {
|
|
36
39
|
"mongodb": {
|
|
@@ -44,11 +47,15 @@
|
|
|
44
47
|
},
|
|
45
48
|
"tough-cookie": {
|
|
46
49
|
"optional": true
|
|
50
|
+
},
|
|
51
|
+
"pino-pretty": {
|
|
52
|
+
"optional": true
|
|
47
53
|
}
|
|
48
54
|
},
|
|
49
55
|
"type": "module",
|
|
50
56
|
"devDependencies": {
|
|
51
57
|
"mongodb": "^6.9.0",
|
|
58
|
+
"pino-pretty": "^13.2.0",
|
|
52
59
|
"prom-client": "^15.1.3",
|
|
53
60
|
"tough-cookie": "^5.0.0",
|
|
54
61
|
"ws": "^8.18.0"
|
package/session.js
CHANGED
|
@@ -4,6 +4,7 @@ import jwt from 'jsonwebtoken';
|
|
|
4
4
|
import JwksClient from 'jwks-rsa';
|
|
5
5
|
import cookie from 'cookie';
|
|
6
6
|
import { httpError } from '@data-fair/lib-utils/http-errors.js';
|
|
7
|
+
import log from './log.js';
|
|
7
8
|
// simple-directory names the keys of its scoped key set scoped-<kid>; the signature still has to
|
|
8
9
|
// verify against that set, so a forged kid gains nothing
|
|
9
10
|
export const keySetOfKid = (kid) => kid?.startsWith('scoped-') ? 'scoped' : 'session';
|
|
@@ -212,7 +213,7 @@ export class SessionHandler {
|
|
|
212
213
|
this.assertTokenKind('cookie', verified.header, verified.payload, verified.keySet);
|
|
213
214
|
}
|
|
214
215
|
catch (err) {
|
|
215
|
-
|
|
216
|
+
log.warn('failure to verify a session token', { code: 'session-token', err });
|
|
216
217
|
if (!isTokenFailure(err)) {
|
|
217
218
|
// happens in case of temporary unavailability of SD
|
|
218
219
|
// better not to disconnect user in this case
|