dashboard-blipburst 0.2.0 → 0.2.4
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 +21 -0
- package/bin/cli.js +13 -4
- package/index.d.ts +22 -1
- package/index.js +1 -1
- package/package.json +1 -1
- package/src/adapter.js +32 -17
- package/src/port.js +28 -3
- package/src/server.js +6 -0
package/README.md
CHANGED
|
@@ -26,6 +26,23 @@ Override the preferred port with `--port` / `-p`, or the `BLIPBURST_DASHBOARD_PO
|
|
|
26
26
|
blipburst-dashboard --port 5000
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
+
### Hosted environments
|
|
30
|
+
|
|
31
|
+
Locally, the SDK-side adapter finds the dashboard automatically via the `.blipburst/port` file written to the current directory — that only works because both processes share a filesystem. In a hosted setup (the dashboard deployed somewhere, the BlipBurst-instrumented app deployed somewhere else), there's no shared file, so use env vars on both sides instead:
|
|
32
|
+
|
|
33
|
+
**On the dashboard's deployment**, set `PORT` (the convention Render/Railway/Fly/Heroku-style platforms already inject) or `BLIPBURST_DASHBOARD_PORT`. Either one makes the server bind exactly there and **fail loudly instead of silently drifting to another port** on conflict — unlike a bare local run, which auto-increments for dev convenience, a hosted deployment can only be reached on the port its platform is routing to, so drifting silently would just make it unreachable:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
# most hosting platforms set PORT for you automatically — nothing else needed
|
|
37
|
+
blipburst-dashboard
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
**On the app reporting events**, set `BLIPBURST_DASHBOARD_URL` to the dashboard's public URL — every adapter function (`toDashboardTransport`, `toDashboardWebhookUrl`, `wrapForDashboard`) picks it up automatically, no code change needed:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
BLIPBURST_DASHBOARD_URL=https://blipburst-dashboard.internal.example.com node server.js
|
|
44
|
+
```
|
|
45
|
+
|
|
29
46
|
## Wire it up to BlipBurst
|
|
30
47
|
|
|
31
48
|
No changes to `blipburst` itself are needed — everything below plugs into config options `blipburst` already supports.
|
|
@@ -78,6 +95,10 @@ await sim.makeRequest(); // now tracked whether or not a fault fired
|
|
|
78
95
|
|
|
79
96
|
This also unlocks exact MTTR (measured time from a failed call to the next success on that endpoint) instead of the log-only estimate (duration of consecutive fault bursts).
|
|
80
97
|
|
|
98
|
+
### A quiet dashboard in production is expected, not broken
|
|
99
|
+
|
|
100
|
+
BlipBurst's `enabled: false` option (or `BLIPBURST_ENABLED=false`) is a global kill switch — when it's off, no faults ever fire, so nothing reaches any of the ingest endpoints above regardless of how the transport/webhook/wrapper is wired. If you point the dashboard at a production deployment that correctly disables chaos there, an empty live feed and heatmap is the dashboard working correctly, not a wiring bug. Point it at a dev/staging deployment (where chaos is actually enabled) to see live data.
|
|
101
|
+
|
|
81
102
|
## What the dashboard shows
|
|
82
103
|
|
|
83
104
|
- **Live fault feed** — streamed over SSE as `fault.injected` / `request.failed` events arrive.
|
package/bin/cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { startDashboardServer } from '../src/server.js';
|
|
3
|
-
import { DEFAULT_PORT } from '../src/port.js';
|
|
3
|
+
import { DEFAULT_PORT, explicitPortFromEnv } from '../src/port.js';
|
|
4
4
|
|
|
5
5
|
function parseArgs(argv) {
|
|
6
6
|
const out = { port: undefined };
|
|
@@ -16,16 +16,25 @@ function parseArgs(argv) {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
async function main() {
|
|
19
|
-
const { port } = parseArgs(process.argv.slice(2));
|
|
20
|
-
|
|
19
|
+
const { port: cliPort } = parseArgs(process.argv.slice(2));
|
|
20
|
+
// --port wins, then BLIPBURST_DASHBOARD_PORT / the hosting-platform-standard
|
|
21
|
+
// PORT (Render/Railway/Fly/Heroku all inject this). Any of these being set
|
|
22
|
+
// means something upstream expects this exact port — a hosted deployment
|
|
23
|
+
// routes traffic there and nowhere else — so silently drifting to another
|
|
24
|
+
// port on conflict would make the deployment unreachable. Bare local runs
|
|
25
|
+
// with nothing set get the old convenience behavior: auto-increment.
|
|
26
|
+
const explicitPort = Number.isFinite(cliPort) ? cliPort : explicitPortFromEnv();
|
|
27
|
+
const preferredPort = explicitPort ?? DEFAULT_PORT;
|
|
28
|
+
const maxPortAttempts = explicitPort ? 0 : 20;
|
|
21
29
|
|
|
22
|
-
const { port: resolvedPort, store } = await startDashboardServer({ port: preferredPort });
|
|
30
|
+
const { port: resolvedPort, store } = await startDashboardServer({ port: preferredPort, maxPortAttempts });
|
|
23
31
|
|
|
24
32
|
console.log(`BlipBurst dashboard running at http://localhost:${resolvedPort}`);
|
|
25
33
|
if (resolvedPort !== preferredPort) {
|
|
26
34
|
console.log(`(port ${preferredPort} was in use — auto-selected ${resolvedPort})`);
|
|
27
35
|
}
|
|
28
36
|
console.log(`Port written to .blipburst/port — point BlipBurst's logger/webhook adapter at it automatically.`);
|
|
37
|
+
console.log(`Hosted setup: set BLIPBURST_DASHBOARD_URL on the reporting app's environment to point it at this dashboard's public URL instead of relying on .blipburst/port.`);
|
|
29
38
|
|
|
30
39
|
const shutdown = () => {
|
|
31
40
|
store.flushNow(); // persist the last few seconds of heatmap/MTTR/run aggregates before exiting
|
package/index.d.ts
CHANGED
|
@@ -23,7 +23,11 @@ export interface BlipBurstLike {
|
|
|
23
23
|
export interface DashboardAdapterOptions {
|
|
24
24
|
/** Group events from this call under a specific run id instead of a freshly generated one. */
|
|
25
25
|
runId?: string;
|
|
26
|
-
/**
|
|
26
|
+
/**
|
|
27
|
+
* Dashboard port override for the local `http://localhost:<port>` case.
|
|
28
|
+
* Ignored when BLIPBURST_DASHBOARD_URL is set — see resolveDashboardUrl.
|
|
29
|
+
* Otherwise resolved from BLIPBURST_DASHBOARD_PORT / PORT / `.blipburst/port` / 4477.
|
|
30
|
+
*/
|
|
27
31
|
port?: number;
|
|
28
32
|
}
|
|
29
33
|
|
|
@@ -70,3 +74,20 @@ export function startDashboardServer(options?: StartDashboardServerOptions): Pro
|
|
|
70
74
|
|
|
71
75
|
export const DEFAULT_PORT: number;
|
|
72
76
|
export function resolvePort(explicitPort?: number): number;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Resolve the full base URL to reach the dashboard at. Set
|
|
80
|
+
* BLIPBURST_DASHBOARD_URL (e.g. `https://dashboard.internal.example.com`)
|
|
81
|
+
* on the reporting app's environment to point every adapter function at a
|
|
82
|
+
* dashboard running on a different host — no code change needed. Falls
|
|
83
|
+
* back to `http://localhost:<resolvePort()>` otherwise.
|
|
84
|
+
*/
|
|
85
|
+
export function resolveDashboardUrl(explicitPort?: number): string;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* A port from BLIPBURST_DASHBOARD_PORT or the hosting-platform-standard
|
|
89
|
+
* PORT (Render/Railway/Fly/Heroku), or `null` if neither is set. Used to
|
|
90
|
+
* decide whether binding should fail loudly on conflict instead of
|
|
91
|
+
* auto-incrementing — see `startDashboardServer`'s `maxPortAttempts`.
|
|
92
|
+
*/
|
|
93
|
+
export function explicitPortFromEnv(): number | null;
|
package/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { toDashboardTransport, toDashboardWebhookUrl, wrapForDashboard, flushBuffer } from './src/adapter.js';
|
|
2
2
|
export { startDashboardServer } from './src/server.js';
|
|
3
|
-
export { DEFAULT_PORT, resolvePort } from './src/port.js';
|
|
3
|
+
export { DEFAULT_PORT, resolvePort, resolveDashboardUrl, explicitPortFromEnv } from './src/port.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dashboard-blipburst",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
4
4
|
"description": "Local live dashboard for BlipBurst — fault feed, experiment run history, endpoint/tenant chaos heatmap, and MTTR per fault type. Install globally, point BlipBurst's logger/webhook at it.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.js",
|
package/src/adapter.js
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
import { appendFileSync, existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
3
|
-
import {
|
|
3
|
+
import { resolveDashboardUrl, ensureBlipDir, BUFFER_FILE } from './port.js';
|
|
4
|
+
|
|
5
|
+
// toDashboardTransport() and wrapForDashboard() are commonly used together
|
|
6
|
+
// in the same process (the README's "full request coverage" combo) — they
|
|
7
|
+
// need to land in the same run, so default to one shared per-process runId
|
|
8
|
+
// generated lazily on first use, rather than each function minting its own.
|
|
9
|
+
// An explicit `options.runId` still overrides this per call.
|
|
10
|
+
let _defaultRunId = null;
|
|
11
|
+
function getDefaultRunId() {
|
|
12
|
+
return (_defaultRunId ??= randomUUID());
|
|
13
|
+
}
|
|
4
14
|
|
|
5
15
|
function bufferEnvelope(envelope) {
|
|
6
16
|
ensureBlipDir();
|
|
@@ -11,8 +21,8 @@ function bufferEnvelope(envelope) {
|
|
|
11
21
|
}
|
|
12
22
|
}
|
|
13
23
|
|
|
14
|
-
async function postJson(
|
|
15
|
-
const res = await fetch(
|
|
24
|
+
async function postJson(baseUrl, path, payload) {
|
|
25
|
+
const res = await fetch(`${baseUrl}${path}`, {
|
|
16
26
|
method: 'POST',
|
|
17
27
|
headers: { 'Content-Type': 'application/json' },
|
|
18
28
|
body: JSON.stringify(payload),
|
|
@@ -21,9 +31,9 @@ async function postJson(port, path, payload) {
|
|
|
21
31
|
}
|
|
22
32
|
|
|
23
33
|
/** Best-effort send: on any failure (dashboard not running, network error) buffer to disk instead of throwing. */
|
|
24
|
-
async function sendOrBuffer(
|
|
34
|
+
async function sendOrBuffer(baseUrl, path, payload) {
|
|
25
35
|
try {
|
|
26
|
-
await postJson(
|
|
36
|
+
await postJson(baseUrl, path, payload);
|
|
27
37
|
} catch {
|
|
28
38
|
bufferEnvelope({ path, payload });
|
|
29
39
|
}
|
|
@@ -35,7 +45,7 @@ async function sendOrBuffer(port, path, payload) {
|
|
|
35
45
|
* drained. Failures leave the buffer file untouched so nothing is lost.
|
|
36
46
|
*/
|
|
37
47
|
export async function flushBuffer(port) {
|
|
38
|
-
const
|
|
48
|
+
const baseUrl = resolveDashboardUrl(port);
|
|
39
49
|
if (!existsSync(BUFFER_FILE)) return { flushed: 0 };
|
|
40
50
|
|
|
41
51
|
let lines;
|
|
@@ -56,7 +66,7 @@ export async function flushBuffer(port) {
|
|
|
56
66
|
continue; // drop corrupt line
|
|
57
67
|
}
|
|
58
68
|
try {
|
|
59
|
-
await postJson(
|
|
69
|
+
await postJson(baseUrl, envelope.path, envelope.payload);
|
|
60
70
|
flushed++;
|
|
61
71
|
} catch {
|
|
62
72
|
remaining.push(line);
|
|
@@ -79,22 +89,27 @@ export async function flushBuffer(port) {
|
|
|
79
89
|
* are flushed automatically the next time this transport is created (i.e.
|
|
80
90
|
* the next process start) or on demand via `flushBuffer()`.
|
|
81
91
|
*
|
|
92
|
+
* Locally this discovers the dashboard via `.blipburst/port` automatically.
|
|
93
|
+
* In a hosted setup where the app and the dashboard run on different hosts,
|
|
94
|
+
* set BLIPBURST_DASHBOARD_URL (e.g. `https://dashboard.internal.example.com`)
|
|
95
|
+
* on the app's environment instead — no code change needed.
|
|
96
|
+
*
|
|
82
97
|
* Usage:
|
|
83
98
|
* import { toDashboardTransport } from 'dashboard-blipburst';
|
|
84
99
|
* const sim = new BlipBurst({ logger: { transport: toDashboardTransport() } });
|
|
85
100
|
*/
|
|
86
101
|
export function toDashboardTransport(port, options = {}) {
|
|
87
|
-
const
|
|
88
|
-
const runId = options.runId ??
|
|
102
|
+
const baseUrl = resolveDashboardUrl(port);
|
|
103
|
+
const runId = options.runId ?? getDefaultRunId();
|
|
89
104
|
|
|
90
105
|
// Try to drain anything buffered from a previous run without blocking
|
|
91
106
|
// transport creation.
|
|
92
|
-
flushBuffer(
|
|
107
|
+
flushBuffer(port).catch(() => {});
|
|
93
108
|
|
|
94
109
|
return function dashboardTransport(entry) {
|
|
95
110
|
// LogTransport is synchronous/fire-and-forget by contract — never await
|
|
96
111
|
// or throw here, BlipBurst calls this inline on the request path.
|
|
97
|
-
sendOrBuffer(
|
|
112
|
+
sendOrBuffer(baseUrl, '/ingest/log', { runId, entry, source: 'logger' }).catch(() => {});
|
|
98
113
|
};
|
|
99
114
|
}
|
|
100
115
|
|
|
@@ -102,14 +117,14 @@ export function toDashboardTransport(port, options = {}) {
|
|
|
102
117
|
* Returns the URL to hand to BlipBurst's `webhook.url` option so its
|
|
103
118
|
* built-in WebhookEmitter posts fault/circuit events straight at the
|
|
104
119
|
* dashboard — no adapter function needed on that path since BlipBurst's
|
|
105
|
-
* webhook is already URL-based.
|
|
120
|
+
* webhook is already URL-based. Respects BLIPBURST_DASHBOARD_URL the same
|
|
121
|
+
* way toDashboardTransport does.
|
|
106
122
|
*
|
|
107
123
|
* Usage:
|
|
108
124
|
* const sim = new BlipBurst({ webhook: { url: toDashboardWebhookUrl() } });
|
|
109
125
|
*/
|
|
110
126
|
export function toDashboardWebhookUrl(port) {
|
|
111
|
-
|
|
112
|
-
return `http://localhost:${resolvedPort}/ingest/webhook`;
|
|
127
|
+
return `${resolveDashboardUrl(port)}/ingest/webhook`;
|
|
113
128
|
}
|
|
114
129
|
|
|
115
130
|
function faultKindDiff(before, after) {
|
|
@@ -135,8 +150,8 @@ function faultKindDiff(before, after) {
|
|
|
135
150
|
* await sim.makeRequest();
|
|
136
151
|
*/
|
|
137
152
|
export function wrapForDashboard(sim, options = {}) {
|
|
138
|
-
const
|
|
139
|
-
const runId = options.runId ??
|
|
153
|
+
const baseUrl = resolveDashboardUrl(options.port);
|
|
154
|
+
const runId = options.runId ?? getDefaultRunId();
|
|
140
155
|
const original = sim.makeRequest.bind(sim);
|
|
141
156
|
|
|
142
157
|
sim.makeRequest = async function wrappedMakeRequest(overrideUrl) {
|
|
@@ -156,7 +171,7 @@ export function wrapForDashboard(sim, options = {}) {
|
|
|
156
171
|
} finally {
|
|
157
172
|
const after = sim.getMetrics().faultStats;
|
|
158
173
|
const faultKind = faultKindDiff(before, after);
|
|
159
|
-
sendOrBuffer(
|
|
174
|
+
sendOrBuffer(baseUrl, '/ingest/request', {
|
|
160
175
|
runId,
|
|
161
176
|
url,
|
|
162
177
|
faultKind,
|
package/src/port.js
CHANGED
|
@@ -36,10 +36,35 @@ export function writePortFile(port) {
|
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
/**
|
|
40
|
+
* A port set via BLIPBURST_DASHBOARD_PORT or the hosting-platform-standard
|
|
41
|
+
* PORT (Render/Railway/Fly/Heroku all inject this) means the deployment
|
|
42
|
+
* expects the process to bind exactly there — unlike the local port-file
|
|
43
|
+
* convenience, silently drifting to a different port would make it
|
|
44
|
+
* unreachable, so callers use this to decide whether auto-increment is safe.
|
|
45
|
+
*/
|
|
46
|
+
export function explicitPortFromEnv() {
|
|
47
|
+
const raw = process.env.BLIPBURST_DASHBOARD_PORT ?? process.env.PORT;
|
|
48
|
+
const parsed = raw ? parseInt(raw, 10) : NaN;
|
|
49
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
|
|
50
|
+
}
|
|
51
|
+
|
|
39
52
|
/** Resolve the port the dashboard is (or should be) reachable on. */
|
|
40
53
|
export function resolvePort(explicitPort) {
|
|
41
54
|
if (explicitPort) return explicitPort;
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
55
|
+
return explicitPortFromEnv() ?? readPortFile() ?? DEFAULT_PORT;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Resolve the full base URL to reach the dashboard at. In a hosted setup
|
|
60
|
+
* where the app reporting events and the dashboard aren't on the same
|
|
61
|
+
* machine (so there's no shared `.blipburst/port` file to read), set
|
|
62
|
+
* BLIPBURST_DASHBOARD_URL (e.g. `https://dashboard.internal.example.com`)
|
|
63
|
+
* on the app's side and every adapter function picks it up automatically.
|
|
64
|
+
* Falls back to the local `http://localhost:<port>` behavior otherwise.
|
|
65
|
+
*/
|
|
66
|
+
export function resolveDashboardUrl(explicitPort) {
|
|
67
|
+
const fromEnv = process.env.BLIPBURST_DASHBOARD_URL;
|
|
68
|
+
if (fromEnv) return fromEnv.replace(/\/+$/, '');
|
|
69
|
+
return `http://localhost:${resolvePort(explicitPort)}`;
|
|
45
70
|
}
|
package/src/server.js
CHANGED
|
@@ -145,6 +145,12 @@ function listenWithAutoIncrement(server, port, attemptsLeft) {
|
|
|
145
145
|
server.once('error', (err) => {
|
|
146
146
|
if (err.code === 'EADDRINUSE' && attemptsLeft > 0) {
|
|
147
147
|
resolve(listenWithAutoIncrement(server, port + 1, attemptsLeft - 1));
|
|
148
|
+
} else if (err.code === 'EADDRINUSE') {
|
|
149
|
+
reject(new Error(
|
|
150
|
+
`Port ${port} is already in use and auto-increment is disabled (maxPortAttempts: 0) — ` +
|
|
151
|
+
`this happens when the port came from --port, BLIPBURST_DASHBOARD_PORT, or PORT, since a ` +
|
|
152
|
+
`hosting platform expects the process to bind exactly there. Free the port or change the env var.`
|
|
153
|
+
));
|
|
148
154
|
} else {
|
|
149
155
|
reject(err);
|
|
150
156
|
}
|