openvibers/openvibe.live Commits

Real-user measurements: the ten most frequent script errors on /metrics too (live_rum_errors{message}), readable on the host without a staff session (#218)

OP OpenVibers committed 27 days ago · 59063822dd · 3 files, +9 −1

Changes

ddiff --git a/server/index.js b/server/index.js
iindex e4eb181f..08aaf661 100644
---- a/server/index.js
++++ b/server/index.js
@@@ -880,6 +880,7 @@ observability.registerDomainGauges(metricsRegistry, {
outboxStatus: async () => await require('./events/stream-events').counts(),
});
metricsRegistry.gauge({ name: 'live_rum_p75', help: 'Real-user p75 per page kind and device class (ms; cls unitless), from the newest beacons', labelNames: ['metric', 'page', 'device'], collect: () => rum.p75Gauge() });
++metricsRegistry.gauge({ name: 'live_rum_errors', help: 'Script errors visitors reported since this process started: the ten most frequent messages', labelNames: ['message'], collect: () => rum.errorsGauge() });
app.get('/api/admin/rum', requireAuth, permissions.requireAdmin, (req, res) => { res.set('Cache-Control', 'no-store'); res.json(rum.snapshot()); });
// ── Updates / Changelog ──────────────────────────────────────
ddiff --git a/server/web/rum.js b/server/web/rum.js
iindex a3b97e11..da3ff30f 100644
---- a/server/web/rum.js
++++ b/server/web/rum.js
@@@ -11,6 +11,8 @@
* POST /api/rum the beacon (text/plain JSON from navigator.sendBeacon); always 204
* rum.snapshot() p50/p75/p95 per page kind × device, and the most frequent errors (GET /api/admin/rum)
* rum.p75Gauge() the p75s for /metrics (live_rum_p75{metric,page,device})
++ * rum.errorsGauge() the ten most frequent error messages for /metrics (live_rum_errors{message}), so they can
++ * be read on the host without a staff session
*/
const express = require('express');
@@@ -113,7 +115,11 @@ function createRum({ now = () => Date.now() } = {}) {
return out;
}
-- return { router, record, snapshot, p75Gauge };
++ function errorsGauge() {
++ return [...errors.entries()].sort((a, b) => b[1] - a[1]).slice(0, 10).map(([message, count]) => ({ labels: { message: message.slice(0, 100) }, value: count }));
++ }
++
++ return { router, record, snapshot, p75Gauge, errorsGauge };
}
module.exports = { createRum, pageKind, deviceClass };
ddiff --git a/test/rum.test.js b/test/rum.test.js
iindex 1c541079..58328b3a 100644
---- a/test/rum.test.js
++++ b/test/rum.test.js
@@@ -56,6 +56,7 @@ const { createRum, pageKind, deviceClass } = require('../server/web/rum');
const g = rum.p75Gauge();
assert.ok(g.some((x) => x.labels.metric === 'lcp' && x.labels.page === 'home' && x.labels.device === 'phone' && x.value === 8000));
assert.ok(!g.some((x) => x.labels.page === 'channel'), 'one channel sample is not a p75');
++ assert.deepStrictEqual(rum.errorsGauge().map((x) => [x.labels.message, x.value]), [['TypeError: x is undefined', 1], ['b', 1], ['c', 1]]);
// A flood from one address: 20 a minute, then dropped until the next minute.
const before = rum.snapshot().pages['clip|phone'];