@schlessera/brain-ui-server 0.15.0 → 0.17.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 +4 -0
- package/dist/agent/backend.d.ts +31 -10
- package/dist/agent/backend.d.ts.map +1 -1
- package/dist/agent/backend.js +92 -28
- package/dist/agent/backend.js.map +1 -1
- package/dist/app.d.ts +9 -0
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +80 -18
- package/dist/app.js.map +1 -1
- package/dist/config/env-core.d.ts +73 -0
- package/dist/config/env-core.d.ts.map +1 -0
- package/dist/config/env-core.js +62 -0
- package/dist/config/env-core.js.map +1 -0
- package/dist/config/env.d.ts +27 -5
- package/dist/config/env.d.ts.map +1 -1
- package/dist/config/env.js +90 -10
- package/dist/config/env.js.map +1 -1
- package/dist/cron/scheduler.d.ts +20 -0
- package/dist/cron/scheduler.d.ts.map +1 -1
- package/dist/cron/scheduler.js +43 -8
- package/dist/cron/scheduler.js.map +1 -1
- package/dist/db/client.d.ts +10 -1
- package/dist/db/client.d.ts.map +1 -1
- package/dist/db/client.js +5 -14
- package/dist/db/client.js.map +1 -1
- package/dist/db/settings.d.ts +3 -2
- package/dist/db/settings.d.ts.map +1 -1
- package/dist/db/settings.js +8 -4
- package/dist/db/settings.js.map +1 -1
- package/dist/index.d.ts +6 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -2
- package/dist/index.js.map +1 -1
- package/dist/middleware/auth.d.ts +8 -2
- package/dist/middleware/auth.d.ts.map +1 -1
- package/dist/middleware/auth.js +74 -10
- package/dist/middleware/auth.js.map +1 -1
- package/dist/middleware/passkeys.d.ts +10 -0
- package/dist/middleware/passkeys.d.ts.map +1 -1
- package/dist/middleware/passkeys.js +19 -3
- package/dist/middleware/passkeys.js.map +1 -1
- package/dist/observability/index.d.ts +73 -0
- package/dist/observability/index.d.ts.map +1 -0
- package/dist/observability/index.js +74 -0
- package/dist/observability/index.js.map +1 -0
- package/dist/observability/loggers.d.ts +62 -0
- package/dist/observability/loggers.d.ts.map +1 -0
- package/dist/observability/loggers.js +109 -0
- package/dist/observability/loggers.js.map +1 -0
- package/dist/observability/meter.d.ts +39 -0
- package/dist/observability/meter.d.ts.map +1 -0
- package/dist/observability/meter.js +95 -0
- package/dist/observability/meter.js.map +1 -0
- package/dist/observability/types.d.ts +67 -0
- package/dist/observability/types.d.ts.map +1 -0
- package/dist/observability/types.js +44 -0
- package/dist/observability/types.js.map +1 -0
- package/dist/routes/brain.d.ts.map +1 -1
- package/dist/routes/brain.js +9 -1
- package/dist/routes/brain.js.map +1 -1
- package/dist/routes/files.d.ts +2 -0
- package/dist/routes/files.d.ts.map +1 -1
- package/dist/routes/files.js +7 -7
- package/dist/routes/files.js.map +1 -1
- package/dist/routes/graph.d.ts +2 -0
- package/dist/routes/graph.d.ts.map +1 -1
- package/dist/routes/graph.js +7 -7
- package/dist/routes/graph.js.map +1 -1
- package/dist/routes/health.d.ts +7 -14
- package/dist/routes/health.d.ts.map +1 -1
- package/dist/routes/health.js +25 -6
- package/dist/routes/health.js.map +1 -1
- package/dist/routes/models.d.ts +3 -0
- package/dist/routes/models.d.ts.map +1 -1
- package/dist/routes/models.js +5 -1
- package/dist/routes/models.js.map +1 -1
- package/dist/routes/render.d.ts +2 -1
- package/dist/routes/render.d.ts.map +1 -1
- package/dist/routes/render.js +2 -2
- package/dist/routes/render.js.map +1 -1
- package/dist/routes/share.d.ts +3 -0
- package/dist/routes/share.d.ts.map +1 -1
- package/dist/routes/share.js +5 -5
- package/dist/routes/share.js.map +1 -1
- package/dist/share/staging.d.ts +3 -2
- package/dist/share/staging.d.ts.map +1 -1
- package/dist/share/staging.js +12 -4
- package/dist/share/staging.js.map +1 -1
- package/dist/voice/keyterm-builder.d.ts +4 -1
- package/dist/voice/keyterm-builder.d.ts.map +1 -1
- package/dist/voice/keyterm-builder.js +15 -8
- package/dist/voice/keyterm-builder.js.map +1 -1
- package/dist/ws/bridge.d.ts.map +1 -1
- package/dist/ws/bridge.js +23 -3
- package/dist/ws/bridge.js.map +1 -1
- package/dist/ws/clients.d.ts +10 -3
- package/dist/ws/clients.d.ts.map +1 -1
- package/dist/ws/clients.js +16 -4
- package/dist/ws/clients.js.map +1 -1
- package/dist/ws/connection.d.ts +15 -0
- package/dist/ws/connection.d.ts.map +1 -1
- package/dist/ws/connection.js +66 -10
- package/dist/ws/connection.js.map +1 -1
- package/dist/ws/dispatch.d.ts +22 -1
- package/dist/ws/dispatch.d.ts.map +1 -1
- package/dist/ws/dispatch.js +36 -16
- package/dist/ws/dispatch.js.map +1 -1
- package/dist/ws/host.d.ts +73 -0
- package/dist/ws/host.d.ts.map +1 -1
- package/dist/ws/host.js +127 -1
- package/dist/ws/host.js.map +1 -1
- package/dist/ws/rate-limit.d.ts +58 -0
- package/dist/ws/rate-limit.d.ts.map +1 -0
- package/dist/ws/rate-limit.js +62 -0
- package/dist/ws/rate-limit.js.map +1 -0
- package/dist/ws/run-session.d.ts +3 -0
- package/dist/ws/run-session.d.ts.map +1 -1
- package/dist/ws/run-session.js +49 -9
- package/dist/ws/run-session.js.map +1 -1
- package/dist/ws/session-catalog.d.ts +2 -1
- package/dist/ws/session-catalog.d.ts.map +1 -1
- package/dist/ws/session-catalog.js +20 -3
- package/dist/ws/session-catalog.js.map +1 -1
- package/dist/ws/turns.d.ts +21 -0
- package/dist/ws/turns.d.ts.map +1 -1
- package/dist/ws/turns.js +11 -1
- package/dist/ws/turns.js.map +1 -1
- package/package.json +9 -6
- package/src/agent/backend.ts +131 -35
- package/src/app.ts +97 -18
- package/src/config/env-core.ts +93 -0
- package/src/config/env.ts +116 -15
- package/src/cron/scheduler.ts +61 -16
- package/src/db/client.ts +16 -5
- package/src/db/settings.ts +9 -4
- package/src/index.ts +43 -2
- package/src/middleware/auth.ts +80 -15
- package/src/middleware/passkeys.ts +29 -5
- package/src/observability/index.ts +150 -0
- package/src/observability/loggers.ts +174 -0
- package/src/observability/meter.ts +190 -0
- package/src/observability/types.ts +101 -0
- package/src/routes/brain.ts +10 -1
- package/src/routes/files.ts +9 -8
- package/src/routes/graph.ts +9 -8
- package/src/routes/health.ts +29 -6
- package/src/routes/models.ts +8 -5
- package/src/routes/render.ts +3 -2
- package/src/routes/share.ts +17 -10
- package/src/share/staging.ts +18 -4
- package/src/voice/keyterm-builder.ts +19 -10
- package/src/ws/bridge.ts +22 -6
- package/src/ws/clients.ts +24 -6
- package/src/ws/connection.ts +73 -13
- package/src/ws/dispatch.ts +47 -16
- package/src/ws/host.ts +164 -1
- package/src/ws/rate-limit.ts +86 -0
- package/src/ws/run-session.ts +57 -14
- package/src/ws/session-catalog.ts +21 -3
- package/src/ws/turns.ts +27 -3
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Observability for the chat-UI server: what it reports, and who receives it.
|
|
3
|
+
*
|
|
4
|
+
* The split is deliberate and is the whole design. The PRODUCING side is the
|
|
5
|
+
* standard OpenTelemetry API — `logger.emit(...)`, `counter.add(...)` — so
|
|
6
|
+
* instrumentation is portable and a real SDK can take over later without a
|
|
7
|
+
* single call site changing. The CONSUMING side is ours: a console consumer in
|
|
8
|
+
* production, a recording consumer in tests, an in-memory meter feeding
|
|
9
|
+
* `/api/status`. That is what keeps the dependency surface to two packages and
|
|
10
|
+
* makes "what did the server report?" answerable as data.
|
|
11
|
+
*
|
|
12
|
+
* An `Observability` is a value you construct and own, like the app's database
|
|
13
|
+
* handle or a `ScrapeClient`'s rate limiter — NOT a process-global. The
|
|
14
|
+
* OpenTelemetry globals are supported (`installGlobally`) for instrumentation
|
|
15
|
+
* that cannot be reached with an argument, but nothing in this package
|
|
16
|
+
* requires them, so two differently-configured apps can share a process and a
|
|
17
|
+
* test never inherits the previous test's sinks.
|
|
18
|
+
*/
|
|
19
|
+
import { type Meter, type MeterProvider } from "@opentelemetry/api";
|
|
20
|
+
import { type Logger, type LoggerProvider } from "@opentelemetry/api-logs";
|
|
21
|
+
import { type LogReader, type RecordingLoggerProvider } from "./loggers.js";
|
|
22
|
+
import { type InMemoryMeterProvider, type MetricsReader } from "./meter.js";
|
|
23
|
+
import type { Severity } from "./types.js";
|
|
24
|
+
export interface ObservabilityOptions {
|
|
25
|
+
/** Where logs go. Default: the console consumer at `minSeverity`. */
|
|
26
|
+
loggerProvider?: LoggerProvider;
|
|
27
|
+
/** Where metrics go. Default: an in-memory recorder (what /api/status reads). */
|
|
28
|
+
meterProvider?: MeterProvider;
|
|
29
|
+
/** Threshold for the DEFAULT console consumer; ignored if one is passed in. */
|
|
30
|
+
minSeverity?: Severity;
|
|
31
|
+
}
|
|
32
|
+
/** What the app holds, and hands to anything that reports. */
|
|
33
|
+
export interface Observability {
|
|
34
|
+
/** A logger for one instrumentation scope — the `[ws]` / `[auth]` prefix. */
|
|
35
|
+
logger(scope: string): Logger;
|
|
36
|
+
/** A meter for one instrumentation scope. */
|
|
37
|
+
meter(scope: string): Meter;
|
|
38
|
+
/** Recorded metrics, when the meter provider supports reading. */
|
|
39
|
+
metrics?: MetricsReader;
|
|
40
|
+
/** Captured logs, when the logger provider supports reading (tests). */
|
|
41
|
+
logs?: LogReader;
|
|
42
|
+
/**
|
|
43
|
+
* Register these providers as the OpenTelemetry globals, so code that cannot
|
|
44
|
+
* be handed an argument still reports somewhere. Returns a function that
|
|
45
|
+
* restores whatever was there before — a test that installs globally must be
|
|
46
|
+
* able to put the process back.
|
|
47
|
+
*/
|
|
48
|
+
installGlobally(): () => void;
|
|
49
|
+
}
|
|
50
|
+
export declare function createObservability(options?: ObservabilityOptions): Observability;
|
|
51
|
+
/**
|
|
52
|
+
* The test flavour: recording consumers on both signals, with the read APIs
|
|
53
|
+
* guaranteed present so a test does not have to narrow them.
|
|
54
|
+
*
|
|
55
|
+
* Same code path as production — only the sink differs — so an assertion here
|
|
56
|
+
* is an assertion about the real emission, not about a double.
|
|
57
|
+
*/
|
|
58
|
+
export interface RecordingObservability extends Observability {
|
|
59
|
+
metrics: InMemoryMeterProvider;
|
|
60
|
+
logs: RecordingLoggerProvider;
|
|
61
|
+
/** Forget everything captured so far. */
|
|
62
|
+
reset(): void;
|
|
63
|
+
}
|
|
64
|
+
export declare function createRecordingObservability(): RecordingObservability;
|
|
65
|
+
/** An observability that reports nowhere — for a consumer that wants silence. */
|
|
66
|
+
export declare function createSilentObservability(): Observability;
|
|
67
|
+
export { createConsoleLoggerProvider, createRecordingLoggerProvider, createSilentLoggerProvider, } from "./loggers.js";
|
|
68
|
+
export type { ConsoleLoggerProviderOptions, RecordingLoggerProviderOptions, RecordingLoggerProvider, LogReader, LogWriter, } from "./loggers.js";
|
|
69
|
+
export { createInMemoryMeterProvider } from "./meter.js";
|
|
70
|
+
export type { InMemoryMeterProvider, MetricsReader } from "./meter.js";
|
|
71
|
+
export { SEVERITIES, severityRank, seriesKey } from "./types.js";
|
|
72
|
+
export type { Severity, CapturedLog, LogQuery, MetricPoint, MetricSnapshot, } from "./types.js";
|
|
73
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/observability/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,EAAW,KAAK,KAAK,EAAE,KAAK,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAC7E,OAAO,EAAQ,KAAK,MAAM,EAAE,KAAK,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAEjF,OAAO,EAIL,KAAK,SAAS,EACd,KAAK,uBAAuB,EAC7B,MAAM,cAAc,CAAC;AACtB,OAAO,EAA+B,KAAK,qBAAqB,EAAE,KAAK,aAAa,EAAE,MAAM,YAAY,CAAC;AACzG,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C,MAAM,WAAW,oBAAoB;IACnC,qEAAqE;IACrE,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,iFAAiF;IACjF,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,+EAA+E;IAC/E,WAAW,CAAC,EAAE,QAAQ,CAAC;CACxB;AAED,8DAA8D;AAC9D,MAAM,WAAW,aAAa;IAC5B,6EAA6E;IAC7E,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;IAC9B,6CAA6C;IAC7C,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,CAAC;IAC5B,kEAAkE;IAClE,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,wEAAwE;IACxE,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB;;;;;OAKG;IACH,eAAe,IAAI,MAAM,IAAI,CAAC;CAC/B;AAUD,wBAAgB,mBAAmB,CAAC,OAAO,GAAE,oBAAyB,GAAG,aAAa,CAsBrF;AAED;;;;;;GAMG;AACH,MAAM,WAAW,sBAAuB,SAAQ,aAAa;IAC3D,OAAO,EAAE,qBAAqB,CAAC;IAC/B,IAAI,EAAE,uBAAuB,CAAC;IAC9B,yCAAyC;IACzC,KAAK,IAAI,IAAI,CAAC;CACf;AAED,wBAAgB,4BAA4B,IAAI,sBAAsB,CAarE;AAED,iFAAiF;AACjF,wBAAgB,yBAAyB,IAAI,aAAa,CAKzD;AAED,OAAO,EACL,2BAA2B,EAC3B,6BAA6B,EAC7B,0BAA0B,GAC3B,MAAM,cAAc,CAAC;AACtB,YAAY,EACV,4BAA4B,EAC5B,8BAA8B,EAC9B,uBAAuB,EACvB,SAAS,EACT,SAAS,GACV,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,2BAA2B,EAAE,MAAM,YAAY,CAAC;AACzD,YAAY,EAAE,qBAAqB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AACvE,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACjE,YAAY,EACV,QAAQ,EACR,WAAW,EACX,QAAQ,EACR,WAAW,EACX,cAAc,GACf,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Observability for the chat-UI server: what it reports, and who receives it.
|
|
3
|
+
*
|
|
4
|
+
* The split is deliberate and is the whole design. The PRODUCING side is the
|
|
5
|
+
* standard OpenTelemetry API — `logger.emit(...)`, `counter.add(...)` — so
|
|
6
|
+
* instrumentation is portable and a real SDK can take over later without a
|
|
7
|
+
* single call site changing. The CONSUMING side is ours: a console consumer in
|
|
8
|
+
* production, a recording consumer in tests, an in-memory meter feeding
|
|
9
|
+
* `/api/status`. That is what keeps the dependency surface to two packages and
|
|
10
|
+
* makes "what did the server report?" answerable as data.
|
|
11
|
+
*
|
|
12
|
+
* An `Observability` is a value you construct and own, like the app's database
|
|
13
|
+
* handle or a `ScrapeClient`'s rate limiter — NOT a process-global. The
|
|
14
|
+
* OpenTelemetry globals are supported (`installGlobally`) for instrumentation
|
|
15
|
+
* that cannot be reached with an argument, but nothing in this package
|
|
16
|
+
* requires them, so two differently-configured apps can share a process and a
|
|
17
|
+
* test never inherits the previous test's sinks.
|
|
18
|
+
*/
|
|
19
|
+
import { metrics } from "@opentelemetry/api";
|
|
20
|
+
import { logs } from "@opentelemetry/api-logs";
|
|
21
|
+
import { createConsoleLoggerProvider, createRecordingLoggerProvider, createSilentLoggerProvider, } from "./loggers.js";
|
|
22
|
+
import { createInMemoryMeterProvider } from "./meter.js";
|
|
23
|
+
function hasMetricsReader(p) {
|
|
24
|
+
return typeof p.snapshot === "function";
|
|
25
|
+
}
|
|
26
|
+
function hasLogReader(p) {
|
|
27
|
+
return typeof p.records === "function";
|
|
28
|
+
}
|
|
29
|
+
export function createObservability(options = {}) {
|
|
30
|
+
const loggerProvider = options.loggerProvider ?? createConsoleLoggerProvider({ minSeverity: options.minSeverity });
|
|
31
|
+
const meterProvider = options.meterProvider ?? createInMemoryMeterProvider();
|
|
32
|
+
return {
|
|
33
|
+
logger: (scope) => loggerProvider.getLogger(scope),
|
|
34
|
+
meter: (scope) => meterProvider.getMeter(scope),
|
|
35
|
+
metrics: hasMetricsReader(meterProvider) ? meterProvider : undefined,
|
|
36
|
+
logs: hasLogReader(loggerProvider) ? loggerProvider : undefined,
|
|
37
|
+
installGlobally() {
|
|
38
|
+
// The API exposes no "read the current provider object" that round-trips
|
|
39
|
+
// through disable(), so restoration is a disable rather than a swap-back.
|
|
40
|
+
// Good enough for the one case that needs it — a test cleaning up.
|
|
41
|
+
logs.setGlobalLoggerProvider(loggerProvider);
|
|
42
|
+
metrics.setGlobalMeterProvider(meterProvider);
|
|
43
|
+
return () => {
|
|
44
|
+
logs.disable();
|
|
45
|
+
metrics.disable();
|
|
46
|
+
};
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
export function createRecordingObservability() {
|
|
51
|
+
const loggerProvider = createRecordingLoggerProvider();
|
|
52
|
+
const meterProvider = createInMemoryMeterProvider();
|
|
53
|
+
const base = createObservability({ loggerProvider, meterProvider });
|
|
54
|
+
return {
|
|
55
|
+
...base,
|
|
56
|
+
metrics: meterProvider,
|
|
57
|
+
logs: loggerProvider,
|
|
58
|
+
reset() {
|
|
59
|
+
loggerProvider.clear();
|
|
60
|
+
meterProvider.reset();
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/** An observability that reports nowhere — for a consumer that wants silence. */
|
|
65
|
+
export function createSilentObservability() {
|
|
66
|
+
return createObservability({
|
|
67
|
+
loggerProvider: createSilentLoggerProvider(),
|
|
68
|
+
meterProvider: createInMemoryMeterProvider(),
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
export { createConsoleLoggerProvider, createRecordingLoggerProvider, createSilentLoggerProvider, } from "./loggers.js";
|
|
72
|
+
export { createInMemoryMeterProvider } from "./meter.js";
|
|
73
|
+
export { SEVERITIES, severityRank, seriesKey } from "./types.js";
|
|
74
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/observability/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,EAAE,OAAO,EAAkC,MAAM,oBAAoB,CAAC;AAC7E,OAAO,EAAE,IAAI,EAAoC,MAAM,yBAAyB,CAAC;AAEjF,OAAO,EACL,2BAA2B,EAC3B,6BAA6B,EAC7B,0BAA0B,GAG3B,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,2BAA2B,EAAkD,MAAM,YAAY,CAAC;AA+BzG,SAAS,gBAAgB,CAAC,CAAgB;IACxC,OAAO,OAAQ,CAA4B,CAAC,QAAQ,KAAK,UAAU,CAAC;AACtE,CAAC;AAED,SAAS,YAAY,CAAC,CAAiB;IACrC,OAAO,OAAQ,CAAwB,CAAC,OAAO,KAAK,UAAU,CAAC;AACjE,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,UAAgC,EAAE;IACpE,MAAM,cAAc,GAClB,OAAO,CAAC,cAAc,IAAI,2BAA2B,CAAC,EAAE,WAAW,EAAE,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;IAC9F,MAAM,aAAa,GAAG,OAAO,CAAC,aAAa,IAAI,2BAA2B,EAAE,CAAC;IAE7E,OAAO;QACL,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,cAAc,CAAC,SAAS,CAAC,KAAK,CAAC;QAClD,KAAK,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,aAAa,CAAC,QAAQ,CAAC,KAAK,CAAC;QAC/C,OAAO,EAAE,gBAAgB,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,SAAS;QACpE,IAAI,EAAE,YAAY,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,SAAS;QAC/D,eAAe;YACb,yEAAyE;YACzE,0EAA0E;YAC1E,mEAAmE;YACnE,IAAI,CAAC,uBAAuB,CAAC,cAAc,CAAC,CAAC;YAC7C,OAAO,CAAC,sBAAsB,CAAC,aAAa,CAAC,CAAC;YAC9C,OAAO,GAAG,EAAE;gBACV,IAAI,CAAC,OAAO,EAAE,CAAC;gBACf,OAAO,CAAC,OAAO,EAAE,CAAC;YACpB,CAAC,CAAC;QACJ,CAAC;KACF,CAAC;AACJ,CAAC;AAgBD,MAAM,UAAU,4BAA4B;IAC1C,MAAM,cAAc,GAAG,6BAA6B,EAAE,CAAC;IACvD,MAAM,aAAa,GAAG,2BAA2B,EAAE,CAAC;IACpD,MAAM,IAAI,GAAG,mBAAmB,CAAC,EAAE,cAAc,EAAE,aAAa,EAAE,CAAC,CAAC;IACpE,OAAO;QACL,GAAG,IAAI;QACP,OAAO,EAAE,aAAa;QACtB,IAAI,EAAE,cAAc;QACpB,KAAK;YACH,cAAc,CAAC,KAAK,EAAE,CAAC;YACvB,aAAa,CAAC,KAAK,EAAE,CAAC;QACxB,CAAC;KACF,CAAC;AACJ,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,yBAAyB;IACvC,OAAO,mBAAmB,CAAC;QACzB,cAAc,EAAE,0BAA0B,EAAE;QAC5C,aAAa,EAAE,2BAA2B,EAAE;KAC7C,CAAC,CAAC;AACL,CAAC;AAED,OAAO,EACL,2BAA2B,EAC3B,6BAA6B,EAC7B,0BAA0B,GAC3B,MAAM,cAAc,CAAC;AAQtB,OAAO,EAAE,2BAA2B,EAAE,MAAM,YAAY,CAAC;AAEzD,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The two log consumers: one that writes, one that remembers.
|
|
3
|
+
*
|
|
4
|
+
* Both implement `LoggerProvider` from `@opentelemetry/api-logs`, so they are
|
|
5
|
+
* interchangeable at the one call site that installs them, and so a real
|
|
6
|
+
* OpenTelemetry SDK is a third option that needs no change to any
|
|
7
|
+
* instrumentation.
|
|
8
|
+
*
|
|
9
|
+
* Why `@opentelemetry/api-logs` is pinned to an EXACT version in package.json
|
|
10
|
+
* rather than caretted: the logs API is still 0.x. That was checked rather
|
|
11
|
+
* than assumed — the producing surface used here (`getLogger().emit()`) is
|
|
12
|
+
* unchanged across the 0.57 → 0.221 range, and the global registration
|
|
13
|
+
* handshake is keyed on a backwards-compatibility constant that has stayed at
|
|
14
|
+
* 1 throughout, so two copies at different versions still interoperate. The
|
|
15
|
+
* churn that does exist is in `@opentelemetry/sdk-logs`, whose
|
|
16
|
+
* `SimpleLogRecordProcessor` changed constructor shape — which is precisely
|
|
17
|
+
* why the consumers below are ours and that package is not a dependency. If
|
|
18
|
+
* upstream ever breaks the producing surface, the whole API is ~137 lines and
|
|
19
|
+
* forking it is the stated fallback.
|
|
20
|
+
*/
|
|
21
|
+
import type { LoggerProvider } from "@opentelemetry/api-logs";
|
|
22
|
+
import { type CapturedLog, type LogQuery, type Severity } from "./types.js";
|
|
23
|
+
/** Where a console consumer sends a formatted line. Injected so tests capture it. */
|
|
24
|
+
export interface LogWriter {
|
|
25
|
+
(severity: Severity, line: string): void;
|
|
26
|
+
}
|
|
27
|
+
export interface ConsoleLoggerProviderOptions {
|
|
28
|
+
/** Records below this severity are dropped before formatting. Default INFO. */
|
|
29
|
+
minSeverity?: Severity;
|
|
30
|
+
write?: LogWriter;
|
|
31
|
+
}
|
|
32
|
+
/** The production consumer: severity-filtered lines on stdout/stderr. */
|
|
33
|
+
export declare function createConsoleLoggerProvider(options?: ConsoleLoggerProviderOptions): LoggerProvider;
|
|
34
|
+
/** Read side of the recording consumer. */
|
|
35
|
+
export interface LogReader {
|
|
36
|
+
/** Everything captured, in emit order. */
|
|
37
|
+
records(): CapturedLog[];
|
|
38
|
+
/** Records matching every field of the query. */
|
|
39
|
+
find(query?: LogQuery): CapturedLog[];
|
|
40
|
+
/** How many records match — the common assertion. */
|
|
41
|
+
count(query?: LogQuery): number;
|
|
42
|
+
clear(): void;
|
|
43
|
+
}
|
|
44
|
+
export interface RecordingLoggerProvider extends LoggerProvider, LogReader {
|
|
45
|
+
}
|
|
46
|
+
export interface RecordingLoggerProviderOptions {
|
|
47
|
+
/**
|
|
48
|
+
* Cap on retained records. A test asserts on a handful; a long-lived process
|
|
49
|
+
* that installed this by accident should not grow without bound. Oldest are
|
|
50
|
+
* dropped first. Default 1000.
|
|
51
|
+
*/
|
|
52
|
+
limit?: number;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* The consumer a test installs. Because it implements the same interface the
|
|
56
|
+
* production consumer does, a test asserts against the real emission path —
|
|
57
|
+
* the instrumentation under test cannot tell the difference.
|
|
58
|
+
*/
|
|
59
|
+
export declare function createRecordingLoggerProvider(options?: RecordingLoggerProviderOptions): RecordingLoggerProvider;
|
|
60
|
+
/** A consumer that discards everything — the default when nothing is installed. */
|
|
61
|
+
export declare function createSilentLoggerProvider(): LoggerProvider;
|
|
62
|
+
//# sourceMappingURL=loggers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loggers.d.ts","sourceRoot":"","sources":["../../src/observability/loggers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,KAAK,EAAU,cAAc,EAAa,MAAM,yBAAyB,CAAC;AAEjF,OAAO,EAIL,KAAK,WAAW,EAChB,KAAK,QAAQ,EACb,KAAK,QAAQ,EACd,MAAM,YAAY,CAAC;AAEpB,qFAAqF;AACrF,MAAM,WAAW,SAAS;IACxB,CAAC,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1C;AAQD,MAAM,WAAW,4BAA4B;IAC3C,+EAA+E;IAC/E,WAAW,CAAC,EAAE,QAAQ,CAAC;IACvB,KAAK,CAAC,EAAE,SAAS,CAAC;CACnB;AAwBD,yEAAyE;AACzE,wBAAgB,2BAA2B,CACzC,OAAO,GAAE,4BAAiC,GACzC,cAAc,CAmBhB;AAED,2CAA2C;AAC3C,MAAM,WAAW,SAAS;IACxB,0CAA0C;IAC1C,OAAO,IAAI,WAAW,EAAE,CAAC;IACzB,iDAAiD;IACjD,IAAI,CAAC,KAAK,CAAC,EAAE,QAAQ,GAAG,WAAW,EAAE,CAAC;IACtC,qDAAqD;IACrD,KAAK,CAAC,KAAK,CAAC,EAAE,QAAQ,GAAG,MAAM,CAAC;IAChC,KAAK,IAAI,IAAI,CAAC;CACf;AAED,MAAM,WAAW,uBAAwB,SAAQ,cAAc,EAAE,SAAS;CAAG;AAe7E,MAAM,WAAW,8BAA8B;IAC7C;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;GAIG;AACH,wBAAgB,6BAA6B,CAC3C,OAAO,GAAE,8BAAmC,GAC3C,uBAAuB,CAuBzB;AAED,mFAAmF;AACnF,wBAAgB,0BAA0B,IAAI,cAAc,CAW3D"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { bodyToString, flattenAttributes, severityRank, } from "./types.js";
|
|
2
|
+
const defaultWriter = (severity, line) => {
|
|
3
|
+
if (severity === "ERROR" || severity === "FATAL")
|
|
4
|
+
console.error(line);
|
|
5
|
+
else if (severity === "WARN")
|
|
6
|
+
console.warn(line);
|
|
7
|
+
else
|
|
8
|
+
console.log(line);
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Formats to the `[scope] message key=value` shape this repo already logs in,
|
|
12
|
+
* so adopting structured emission does not churn every existing log line's
|
|
13
|
+
* appearance at the same time.
|
|
14
|
+
*/
|
|
15
|
+
function formatLine(record) {
|
|
16
|
+
const attrs = Object.entries(record.attributes)
|
|
17
|
+
.filter(([, v]) => v !== undefined)
|
|
18
|
+
.map(([k, v]) => `${k}=${String(v)}`);
|
|
19
|
+
return `[${record.scope}] ${record.body}${attrs.length ? ` ${attrs.join(" ")}` : ""}`;
|
|
20
|
+
}
|
|
21
|
+
function toCaptured(scope, record) {
|
|
22
|
+
return {
|
|
23
|
+
scope,
|
|
24
|
+
severity: (record.severityText ?? "INFO"),
|
|
25
|
+
body: bodyToString(record.body),
|
|
26
|
+
attributes: flattenAttributes(record.attributes),
|
|
27
|
+
timestamp: Date.now(),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/** The production consumer: severity-filtered lines on stdout/stderr. */
|
|
31
|
+
export function createConsoleLoggerProvider(options = {}) {
|
|
32
|
+
const threshold = severityRank(options.minSeverity ?? "INFO");
|
|
33
|
+
const write = options.write ?? defaultWriter;
|
|
34
|
+
return {
|
|
35
|
+
getLogger(name) {
|
|
36
|
+
return {
|
|
37
|
+
emit(record) {
|
|
38
|
+
if (severityRank(record.severityText) < threshold)
|
|
39
|
+
return;
|
|
40
|
+
const captured = toCaptured(name, record);
|
|
41
|
+
write(captured.severity, formatLine(captured));
|
|
42
|
+
},
|
|
43
|
+
enabled(opts) {
|
|
44
|
+
void opts;
|
|
45
|
+
return true;
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function matches(record, query) {
|
|
52
|
+
if (query.scope !== undefined && record.scope !== query.scope)
|
|
53
|
+
return false;
|
|
54
|
+
if (query.severity !== undefined && record.severity !== query.severity)
|
|
55
|
+
return false;
|
|
56
|
+
if (query.minSeverity !== undefined && severityRank(record.severity) < severityRank(query.minSeverity)) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
if (query.body !== undefined && !record.body.includes(query.body))
|
|
60
|
+
return false;
|
|
61
|
+
for (const [key, value] of Object.entries(query.attributes ?? {})) {
|
|
62
|
+
if (record.attributes[key] !== value)
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* The consumer a test installs. Because it implements the same interface the
|
|
69
|
+
* production consumer does, a test asserts against the real emission path —
|
|
70
|
+
* the instrumentation under test cannot tell the difference.
|
|
71
|
+
*/
|
|
72
|
+
export function createRecordingLoggerProvider(options = {}) {
|
|
73
|
+
const limit = options.limit ?? 1000;
|
|
74
|
+
const captured = [];
|
|
75
|
+
return {
|
|
76
|
+
getLogger(name) {
|
|
77
|
+
return {
|
|
78
|
+
emit(record) {
|
|
79
|
+
captured.push(toCaptured(name, record));
|
|
80
|
+
if (captured.length > limit)
|
|
81
|
+
captured.splice(0, captured.length - limit);
|
|
82
|
+
},
|
|
83
|
+
enabled() {
|
|
84
|
+
return true;
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
},
|
|
88
|
+
records: () => [...captured],
|
|
89
|
+
find: (query = {}) => captured.filter((r) => matches(r, query)),
|
|
90
|
+
count: (query = {}) => captured.reduce((n, r) => n + (matches(r, query) ? 1 : 0), 0),
|
|
91
|
+
clear: () => {
|
|
92
|
+
captured.length = 0;
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
/** A consumer that discards everything — the default when nothing is installed. */
|
|
97
|
+
export function createSilentLoggerProvider() {
|
|
98
|
+
return {
|
|
99
|
+
getLogger() {
|
|
100
|
+
return {
|
|
101
|
+
emit() { },
|
|
102
|
+
enabled() {
|
|
103
|
+
return false;
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=loggers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loggers.js","sourceRoot":"","sources":["../../src/observability/loggers.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An in-process `MeterProvider` — the counters behind `/api/status`, and the
|
|
3
|
+
* assertion surface for tests.
|
|
4
|
+
*
|
|
5
|
+
* One implementation serves both on purpose. A test double that is a different
|
|
6
|
+
* implementation from the production one only proves the double works; this
|
|
7
|
+
* way, the thing a test asserts against is the thing that actually counts in
|
|
8
|
+
* production.
|
|
9
|
+
*
|
|
10
|
+
* It is NOT `@opentelemetry/sdk-metrics`. The producing side is the standard
|
|
11
|
+
* API, so instrumentation is portable and a real SDK can replace this without
|
|
12
|
+
* touching a single `counter.add()` call — but the consuming side is ours,
|
|
13
|
+
* which is what keeps the dependency surface to one stable package.
|
|
14
|
+
*
|
|
15
|
+
* Synchronous instruments (counter, up-down counter, histogram, gauge) record.
|
|
16
|
+
* The observable/async instruments satisfy the interface and do nothing: they
|
|
17
|
+
* require a collection cycle to be meaningful, and nothing here runs one. A
|
|
18
|
+
* caller reaching for one is a signal to bring in a real SDK, not to grow this.
|
|
19
|
+
*/
|
|
20
|
+
import type { Attributes, MeterProvider } from "@opentelemetry/api";
|
|
21
|
+
import { type MetricSnapshot } from "./types.js";
|
|
22
|
+
/** Read side of the recorded metrics. */
|
|
23
|
+
export interface MetricsReader {
|
|
24
|
+
/** Every recorded series, sorted by name then attribute key. */
|
|
25
|
+
snapshot(): MetricSnapshot;
|
|
26
|
+
/** One series' value, or undefined when nothing recorded it. */
|
|
27
|
+
value(name: string, attributes?: Attributes): number | undefined;
|
|
28
|
+
/** Summed across every attribute combination of an instrument. */
|
|
29
|
+
total(name: string): number;
|
|
30
|
+
reset(): void;
|
|
31
|
+
}
|
|
32
|
+
export interface InMemoryMeterProvider extends MeterProvider, MetricsReader {
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* A provider whose meters all write into one recorder, so a snapshot spans
|
|
36
|
+
* every instrumentation scope in the process.
|
|
37
|
+
*/
|
|
38
|
+
export declare function createInMemoryMeterProvider(): InMemoryMeterProvider;
|
|
39
|
+
//# sourceMappingURL=meter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"meter.d.ts","sourceRoot":"","sources":["../../src/observability/meter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,KAAK,EACV,UAAU,EAOV,aAAa,EAOd,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAIL,KAAK,cAAc,EACpB,MAAM,YAAY,CAAC;AAEpB,yCAAyC;AACzC,MAAM,WAAW,aAAa;IAC5B,gEAAgE;IAChE,QAAQ,IAAI,cAAc,CAAC;IAC3B,gEAAgE;IAChE,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,UAAU,GAAG,MAAM,GAAG,SAAS,CAAC;IACjE,kEAAkE;IAClE,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAC5B,KAAK,IAAI,IAAI,CAAC;CACf;AAuHD,MAAM,WAAW,qBAAsB,SAAQ,aAAa,EAAE,aAAa;CAAG;AAE9E;;;GAGG;AACH,wBAAgB,2BAA2B,IAAI,qBAAqB,CAYnE"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { flattenAttributes, seriesKey, } from "./types.js";
|
|
2
|
+
class Recorder {
|
|
3
|
+
series = new Map();
|
|
4
|
+
record(name, kind, value, attributes) {
|
|
5
|
+
const key = seriesKey(name, attributes);
|
|
6
|
+
const existing = this.series.get(key);
|
|
7
|
+
if (existing) {
|
|
8
|
+
// A gauge is a level, not an accumulation.
|
|
9
|
+
existing.value = kind === "gauge" ? value : existing.value + value;
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
this.series.set(key, { key, name, kind, value, attributes: flattenAttributes(attributes) });
|
|
13
|
+
}
|
|
14
|
+
snapshot() {
|
|
15
|
+
return [...this.series.values()]
|
|
16
|
+
.sort((a, b) => (a.key < b.key ? -1 : a.key > b.key ? 1 : 0))
|
|
17
|
+
.map(({ name, attributes, value, kind }) => ({ name, attributes, value, kind }));
|
|
18
|
+
}
|
|
19
|
+
value(name, attributes) {
|
|
20
|
+
return this.series.get(seriesKey(name, attributes))?.value;
|
|
21
|
+
}
|
|
22
|
+
total(name) {
|
|
23
|
+
let sum = 0;
|
|
24
|
+
for (const s of this.series.values())
|
|
25
|
+
if (s.name === name)
|
|
26
|
+
sum += s.value;
|
|
27
|
+
return sum;
|
|
28
|
+
}
|
|
29
|
+
reset() {
|
|
30
|
+
this.series.clear();
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/** Instruments that do nothing, for the async half of the Meter interface. */
|
|
34
|
+
function noopObservable() {
|
|
35
|
+
return {
|
|
36
|
+
addCallback() { },
|
|
37
|
+
removeCallback() { },
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
class InMemoryMeter {
|
|
41
|
+
recorder;
|
|
42
|
+
constructor(recorder) {
|
|
43
|
+
this.recorder = recorder;
|
|
44
|
+
}
|
|
45
|
+
createCounter(name, _options) {
|
|
46
|
+
return {
|
|
47
|
+
add: (value, attributes) => this.recorder.record(name, "counter", value, attributes),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
createUpDownCounter(name, _options) {
|
|
51
|
+
return {
|
|
52
|
+
add: (value, attributes) => this.recorder.record(name, "updowncounter", value, attributes),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
createHistogram(name, _options) {
|
|
56
|
+
return {
|
|
57
|
+
// Count of observations, not the distribution: a real SDK owns buckets.
|
|
58
|
+
record: (_value, attributes) => this.recorder.record(name, "histogram", 1, attributes),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
createGauge(name, _options) {
|
|
62
|
+
return {
|
|
63
|
+
record: (value, attributes) => this.recorder.record(name, "gauge", value, attributes),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
createObservableGauge() {
|
|
67
|
+
return noopObservable();
|
|
68
|
+
}
|
|
69
|
+
createObservableCounter() {
|
|
70
|
+
return noopObservable();
|
|
71
|
+
}
|
|
72
|
+
createObservableUpDownCounter() {
|
|
73
|
+
return noopObservable();
|
|
74
|
+
}
|
|
75
|
+
addBatchObservableCallback(_callback, _observables) { }
|
|
76
|
+
removeBatchObservableCallback(_callback, _observables) { }
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* A provider whose meters all write into one recorder, so a snapshot spans
|
|
80
|
+
* every instrumentation scope in the process.
|
|
81
|
+
*/
|
|
82
|
+
export function createInMemoryMeterProvider() {
|
|
83
|
+
const recorder = new Recorder();
|
|
84
|
+
const meter = new InMemoryMeter(recorder);
|
|
85
|
+
return {
|
|
86
|
+
getMeter(_name, _version, _options) {
|
|
87
|
+
return meter;
|
|
88
|
+
},
|
|
89
|
+
snapshot: () => recorder.snapshot(),
|
|
90
|
+
value: (name, attributes) => recorder.value(name, attributes),
|
|
91
|
+
total: (name) => recorder.total(name),
|
|
92
|
+
reset: () => recorder.reset(),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=meter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"meter.js","sourceRoot":"","sources":["../../src/observability/meter.ts"],"names":[],"mappings":"AAoCA,OAAO,EACL,iBAAiB,EACjB,SAAS,GAGV,MAAM,YAAY,CAAC;AAiBpB,MAAM,QAAQ;IACK,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;IAEpD,MAAM,CACJ,IAAY,EACZ,IAAyB,EACzB,KAAa,EACb,UAAkC;QAElC,MAAM,GAAG,GAAG,SAAS,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;QACxC,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACtC,IAAI,QAAQ,EAAE,CAAC;YACb,2CAA2C;YAC3C,QAAQ,CAAC,KAAK,GAAG,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,GAAG,KAAK,CAAC;YACnE,OAAO;QACT,CAAC;QACD,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,UAAU,EAAE,iBAAiB,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;IAC9F,CAAC;IAED,QAAQ;QACN,OAAO,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;aAC7B,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;aAC5D,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IACrF,CAAC;IAED,KAAK,CAAC,IAAY,EAAE,UAAuB;QACzC,OAAO,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC,EAAE,KAAK,CAAC;IAC7D,CAAC;IAED,KAAK,CAAC,IAAY;QAChB,IAAI,GAAG,GAAG,CAAC,CAAC;QACZ,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE;YAAE,IAAI,CAAC,CAAC,IAAI,KAAK,IAAI;gBAAE,GAAG,IAAI,CAAC,CAAC,KAAK,CAAC;QAC1E,OAAO,GAAG,CAAC;IACb,CAAC;IAED,KAAK;QACH,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;IACtB,CAAC;CACF;AAED,8EAA8E;AAC9E,SAAS,cAAc;IACrB,OAAO;QACL,WAAW,KAAI,CAAC;QAChB,cAAc,KAAI,CAAC;KACf,CAAC;AACT,CAAC;AAED,MAAM,aAAa;IACY;IAA7B,YAA6B,QAAkB;QAAlB,aAAQ,GAAR,QAAQ,CAAU;IAAG,CAAC;IAEnD,aAAa,CACX,IAAY,EACZ,QAAwB;QAExB,OAAO;YACL,GAAG,EAAE,CAAC,KAAa,EAAE,UAAc,EAAE,EAAE,CACrC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,UAAU,CAAC;SAC7C,CAAC;IAClB,CAAC;IAED,mBAAmB,CACjB,IAAY,EACZ,QAAwB;QAExB,OAAO;YACL,GAAG,EAAE,CAAC,KAAa,EAAE,UAAc,EAAE,EAAE,CACrC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,UAAU,CAAC;SAC7C,CAAC;IACxB,CAAC;IAED,eAAe,CACb,IAAY,EACZ,QAAwB;QAExB,OAAO;YACL,wEAAwE;YACxE,MAAM,EAAE,CAAC,MAAc,EAAE,UAAc,EAAE,EAAE,CACzC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,EAAE,WAAW,EAAE,CAAC,EAAE,UAAU,CAAC;SACzC,CAAC;IACpB,CAAC;IAED,WAAW,CACT,IAAY,EACZ,QAAwB;QAExB,OAAO;YACL,MAAM,EAAE,CAAC,KAAa,EAAE,UAAc,EAAE,EAAE,CACxC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,CAAC;SAC7C,CAAC;IAChB,CAAC;IAED,qBAAqB;QACnB,OAAO,cAAc,EAAsB,CAAC;IAC9C,CAAC;IACD,uBAAuB;QACrB,OAAO,cAAc,EAAwB,CAAC;IAChD,CAAC;IACD,6BAA6B;QAG3B,OAAO,cAAc,EAA8B,CAAC;IACtD,CAAC;IACD,0BAA0B,CACxB,SAAqC,EACrC,YAA6B,IACtB,CAAC;IACV,6BAA6B,CAC3B,SAAqC,EACrC,YAA6B,IACtB,CAAC;CACX;AAID;;;GAGG;AACH,MAAM,UAAU,2BAA2B;IACzC,MAAM,QAAQ,GAAG,IAAI,QAAQ,EAAE,CAAC;IAChC,MAAM,KAAK,GAAG,IAAI,aAAa,CAAC,QAAQ,CAAC,CAAC;IAC1C,OAAO;QACL,QAAQ,CAAC,KAAa,EAAE,QAAiB,EAAE,QAAuB;YAChE,OAAO,KAAK,CAAC;QACf,CAAC;QACD,QAAQ,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,QAAQ,EAAE;QACnC,KAAK,EAAE,CAAC,IAAI,EAAE,UAAU,EAAE,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,EAAE,UAAU,CAAC;QAC7D,KAAK,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC;QACrC,KAAK,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,KAAK,EAAE;KAC9B,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shapes the observability consumers expose for inspection.
|
|
3
|
+
*
|
|
4
|
+
* These exist so that "what did the server report?" is answerable as DATA — by
|
|
5
|
+
* a test, by `/api/status`, by a future exporter — rather than by scraping
|
|
6
|
+
* stdout. Every consumer in this directory implements a standard OpenTelemetry
|
|
7
|
+
* provider interface, so swapping one for another is an argument change and
|
|
8
|
+
* never a change to instrumentation.
|
|
9
|
+
*/
|
|
10
|
+
import type { Attributes } from "@opentelemetry/api";
|
|
11
|
+
import type { LogRecord } from "@opentelemetry/api-logs";
|
|
12
|
+
/**
|
|
13
|
+
* Attributes as this package retains them.
|
|
14
|
+
*
|
|
15
|
+
* Log attributes upstream are `AnyValue` — nested maps, arrays, null. Metric
|
|
16
|
+
* attributes are narrower. Both are normalized to scalars on capture so one
|
|
17
|
+
* query shape works over either, `/api/status` stays JSON-safe, and a
|
|
18
|
+
* formatted line never prints `[object Object]`.
|
|
19
|
+
*/
|
|
20
|
+
export type FlatAttributes = Record<string, string | number | boolean>;
|
|
21
|
+
/** Coerce either attribute flavour to scalars, dropping empty values. */
|
|
22
|
+
export declare function flattenAttributes(attributes: unknown): FlatAttributes;
|
|
23
|
+
/** Severities this codebase uses, ordered. Anything below the configured
|
|
24
|
+
* threshold is dropped by the console consumer before it is formatted. */
|
|
25
|
+
export declare const SEVERITIES: readonly ["TRACE", "DEBUG", "INFO", "WARN", "ERROR", "FATAL"];
|
|
26
|
+
export type Severity = (typeof SEVERITIES)[number];
|
|
27
|
+
export declare function severityRank(severity: string | undefined): number;
|
|
28
|
+
/** One emitted record, with the scope that produced it. */
|
|
29
|
+
export interface CapturedLog {
|
|
30
|
+
/** Instrumentation scope — the `[ws]` / `[auth]` prefix this repo already uses. */
|
|
31
|
+
scope: string;
|
|
32
|
+
severity: Severity;
|
|
33
|
+
body: string;
|
|
34
|
+
attributes: FlatAttributes;
|
|
35
|
+
/** Wall clock at emit, in ms. */
|
|
36
|
+
timestamp: number;
|
|
37
|
+
}
|
|
38
|
+
/** A query over captured logs. Every field is an AND, every field optional. */
|
|
39
|
+
export interface LogQuery {
|
|
40
|
+
scope?: string;
|
|
41
|
+
severity?: Severity;
|
|
42
|
+
/** Minimum severity, inclusive. */
|
|
43
|
+
minSeverity?: Severity;
|
|
44
|
+
/** Substring match against the body. */
|
|
45
|
+
body?: string;
|
|
46
|
+
/** Every listed attribute must be present and equal. */
|
|
47
|
+
attributes?: FlatAttributes;
|
|
48
|
+
}
|
|
49
|
+
/** One counter/histogram series: an instrument plus one attribute combination. */
|
|
50
|
+
export interface MetricPoint {
|
|
51
|
+
name: string;
|
|
52
|
+
/** The attribute set that identifies this series. */
|
|
53
|
+
attributes: FlatAttributes;
|
|
54
|
+
/** Sum for counters, count of observations for histograms. */
|
|
55
|
+
value: number;
|
|
56
|
+
kind: "counter" | "updowncounter" | "histogram" | "gauge";
|
|
57
|
+
}
|
|
58
|
+
/** Serializable metric state, for `/api/status` and for assertions. */
|
|
59
|
+
export type MetricSnapshot = MetricPoint[];
|
|
60
|
+
/**
|
|
61
|
+
* Stable key for a series. Attributes are sorted, so two calls with the same
|
|
62
|
+
* attributes in a different literal order land on the same series.
|
|
63
|
+
*/
|
|
64
|
+
export declare function seriesKey(name: string, attributes: Attributes | undefined): string;
|
|
65
|
+
/** Convert a `LogRecord` body — typed as unknown-ish upstream — to a string. */
|
|
66
|
+
export declare function bodyToString(body: LogRecord["body"]): string;
|
|
67
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/observability/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAEzD;;;;;;;GAOG;AACH,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC;AAEvE,yEAAyE;AACzE,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,OAAO,GAAG,cAAc,CAYrE;AAED;2EAC2E;AAC3E,eAAO,MAAM,UAAU,+DAAgE,CAAC;AACxF,MAAM,MAAM,QAAQ,GAAG,CAAC,OAAO,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC;AAEnD,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAGjE;AAED,2DAA2D;AAC3D,MAAM,WAAW,WAAW;IAC1B,mFAAmF;IACnF,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,QAAQ,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,cAAc,CAAC;IAC3B,iCAAiC;IACjC,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,+EAA+E;AAC/E,MAAM,WAAW,QAAQ;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,mCAAmC;IACnC,WAAW,CAAC,EAAE,QAAQ,CAAC;IACvB,wCAAwC;IACxC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,wDAAwD;IACxD,UAAU,CAAC,EAAE,cAAc,CAAC;CAC7B;AAED,kFAAkF;AAClF,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,qDAAqD;IACrD,UAAU,EAAE,cAAc,CAAC;IAC3B,8DAA8D;IAC9D,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,SAAS,GAAG,eAAe,GAAG,WAAW,GAAG,OAAO,CAAC;CAC3D;AAED,uEAAuE;AACvE,MAAM,MAAM,cAAc,GAAG,WAAW,EAAE,CAAC;AAE3C;;;GAGG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,GAAG,SAAS,GAAG,MAAM,CAMlF;AAED,gFAAgF;AAChF,wBAAgB,YAAY,CAAC,IAAI,EAAE,SAAS,CAAC,MAAM,CAAC,GAAG,MAAM,CAI5D"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/** Coerce either attribute flavour to scalars, dropping empty values. */
|
|
2
|
+
export function flattenAttributes(attributes) {
|
|
3
|
+
const flat = {};
|
|
4
|
+
if (!attributes || typeof attributes !== "object")
|
|
5
|
+
return flat;
|
|
6
|
+
for (const [key, value] of Object.entries(attributes)) {
|
|
7
|
+
if (value === undefined || value === null)
|
|
8
|
+
continue;
|
|
9
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
10
|
+
flat[key] = value;
|
|
11
|
+
}
|
|
12
|
+
else {
|
|
13
|
+
flat[key] = JSON.stringify(value);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return flat;
|
|
17
|
+
}
|
|
18
|
+
/** Severities this codebase uses, ordered. Anything below the configured
|
|
19
|
+
* threshold is dropped by the console consumer before it is formatted. */
|
|
20
|
+
export const SEVERITIES = ["TRACE", "DEBUG", "INFO", "WARN", "ERROR", "FATAL"];
|
|
21
|
+
export function severityRank(severity) {
|
|
22
|
+
const index = SEVERITIES.indexOf((severity ?? "INFO"));
|
|
23
|
+
return index === -1 ? SEVERITIES.indexOf("INFO") : index;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Stable key for a series. Attributes are sorted, so two calls with the same
|
|
27
|
+
* attributes in a different literal order land on the same series.
|
|
28
|
+
*/
|
|
29
|
+
export function seriesKey(name, attributes) {
|
|
30
|
+
const entries = Object.entries(attributes ?? {})
|
|
31
|
+
.filter(([, v]) => v !== undefined)
|
|
32
|
+
.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
|
|
33
|
+
.map(([k, v]) => `${k}=${String(v)}`);
|
|
34
|
+
return entries.length ? `${name}|${entries.join(",")}` : name;
|
|
35
|
+
}
|
|
36
|
+
/** Convert a `LogRecord` body — typed as unknown-ish upstream — to a string. */
|
|
37
|
+
export function bodyToString(body) {
|
|
38
|
+
if (typeof body === "string")
|
|
39
|
+
return body;
|
|
40
|
+
if (body === undefined || body === null)
|
|
41
|
+
return "";
|
|
42
|
+
return typeof body === "object" ? JSON.stringify(body) : String(body);
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/observability/types.ts"],"names":[],"mappings":"AAsBA,yEAAyE;AACzE,MAAM,UAAU,iBAAiB,CAAC,UAAmB;IACnD,MAAM,IAAI,GAAmB,EAAE,CAAC;IAChC,IAAI,CAAC,UAAU,IAAI,OAAO,UAAU,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IAC/D,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,UAAqC,CAAC,EAAE,CAAC;QACjF,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI;YAAE,SAAS;QACpD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,OAAO,KAAK,KAAK,SAAS,EAAE,CAAC;YACzF,IAAI,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;QACpB,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QACpC,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;2EAC2E;AAC3E,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAU,CAAC;AAGxF,MAAM,UAAU,YAAY,CAAC,QAA4B;IACvD,MAAM,KAAK,GAAG,UAAU,CAAC,OAAO,CAAC,CAAC,QAAQ,IAAI,MAAM,CAAa,CAAC,CAAC;IACnE,OAAO,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;AAC3D,CAAC;AAsCD;;;GAGG;AACH,MAAM,UAAU,SAAS,CAAC,IAAY,EAAE,UAAkC;IACxE,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,UAAU,IAAI,EAAE,CAAC;SAC7C,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,SAAS,CAAC;SAClC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;SAChD,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACxC,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;AAChE,CAAC;AAED,gFAAgF;AAChF,MAAM,UAAU,YAAY,CAAC,IAAuB;IAClD,IAAI,OAAO,IAAI,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IAC1C,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,IAAI;QAAE,OAAO,EAAE,CAAC;IACnD,OAAO,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;AACxE,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"brain.d.ts","sourceRoot":"","sources":["../../src/routes/brain.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEtD,OAAO,EAGL,KAAK,eAAe,EACrB,MAAM,6BAA6B,CAAC;AAIrC,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,WAAW,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,eAAe,CAAC;CAC3B;AAqBD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,eAAe,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"brain.d.ts","sourceRoot":"","sources":["../../src/routes/brain.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEtD,OAAO,EAGL,KAAK,eAAe,EACrB,MAAM,6BAA6B,CAAC;AAIrC,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,WAAW,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,eAAe,CAAC;CAC3B;AAqBD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,eAAe,GAAG,IAAI,CAqR7D"}
|
package/dist/routes/brain.js
CHANGED
|
@@ -89,6 +89,14 @@ export function createBrainRoutes(deps) {
|
|
|
89
89
|
stderr: "pipe",
|
|
90
90
|
env: subprocessEnv({ NO_COLOR: "1" }),
|
|
91
91
|
});
|
|
92
|
+
// Start draining stderr NOW, not after the process exits.
|
|
93
|
+
//
|
|
94
|
+
// The stdout loop below can run for minutes. Meanwhile stderr fills an
|
|
95
|
+
// OS pipe buffer that nothing is reading — and once it is full the
|
|
96
|
+
// child blocks on write, never exits, and `await proc.exited` never
|
|
97
|
+
// resolves. A chatty run deadlocked the request; a quiet one looked
|
|
98
|
+
// fine, which is why this survived.
|
|
99
|
+
const stderrText = new Response(proc.stderr).text();
|
|
92
100
|
const reader = proc.stdout.getReader();
|
|
93
101
|
const decoder = new TextDecoder();
|
|
94
102
|
let buffer = "";
|
|
@@ -110,7 +118,7 @@ export function createBrainRoutes(deps) {
|
|
|
110
118
|
}
|
|
111
119
|
catch { }
|
|
112
120
|
const exitCode = await proc.exited;
|
|
113
|
-
const stderr = await
|
|
121
|
+
const stderr = await stderrText;
|
|
114
122
|
if (stderr.trim()) {
|
|
115
123
|
for (const line of stderr.split("\n")) {
|
|
116
124
|
if (line.trim())
|