feat: add structured backend logging
Collector failures, service-API errors, WS connection lifecycle, and config reload/discovery events were previously invisible outside the browser (or, for a few config/discovery cases, logged with an inconsistent ad-hoc console.error). Add a small scoped logger (lib/logger.ts, level via LOG_LEVEL) and wire it through server.ts, the scheduler, service collector, config loader/effective store, and the WS server so operators can see failures via `docker logs`. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+22
-4
@@ -6,6 +6,9 @@ import { effectiveConfigStore } from "@/lib/config/effective";
|
||||
import { toPublicConfig } from "@/lib/config/public";
|
||||
import type { Config } from "@/lib/config/schema";
|
||||
import type { WidgetResult } from "@/lib/types/widget-result";
|
||||
import { createLogger } from "@/lib/logger";
|
||||
|
||||
const log = createLogger("ws");
|
||||
|
||||
type Envelope =
|
||||
| { topic: string; type: "result"; ts: number; data: WidgetResult }
|
||||
@@ -22,6 +25,8 @@ export function attachWebSocketServer(httpServer: HttpServer): WebSocketServer {
|
||||
const wss = new WebSocketServer({ noServer: true });
|
||||
const subscriptions = new Map<string, Set<WebSocket>>();
|
||||
const allSockets = new Set<WebSocket>();
|
||||
const connectionIds = new WeakMap<WebSocket, number>();
|
||||
let nextConnectionId = 1;
|
||||
|
||||
function subscribe(socket: WebSocket, widgetId: string): void {
|
||||
let set = subscriptions.get(widgetId);
|
||||
@@ -46,12 +51,16 @@ export function attachWebSocketServer(httpServer: HttpServer): WebSocketServer {
|
||||
}
|
||||
|
||||
wss.on("connection", (socket) => {
|
||||
const connectionId = nextConnectionId++;
|
||||
connectionIds.set(socket, connectionId);
|
||||
allSockets.add(socket);
|
||||
log.info("client connected", { connectionId, clients: allSockets.size });
|
||||
|
||||
try {
|
||||
const config = effectiveConfigStore.get();
|
||||
send(socket, { topic: "config", type: "config:update", ts: Date.now(), data: toPublicConfig(config) });
|
||||
} catch {
|
||||
} catch (err) {
|
||||
log.debug("no valid config to send on connect", { connectionId, error: (err as Error).message });
|
||||
// no valid config loaded yet; client keeps its SSR-seeded config until one arrives
|
||||
}
|
||||
|
||||
@@ -59,18 +68,27 @@ export function attachWebSocketServer(httpServer: HttpServer): WebSocketServer {
|
||||
let message: { action?: string; widgetId?: string };
|
||||
try {
|
||||
message = JSON.parse(raw.toString());
|
||||
} catch {
|
||||
} catch (err) {
|
||||
log.warn("received malformed message", { connectionId, error: (err as Error).message });
|
||||
return;
|
||||
}
|
||||
if (message.action === "subscribe" && typeof message.widgetId === "string") {
|
||||
log.debug("client subscribed", { connectionId, widgetId: message.widgetId });
|
||||
subscribe(socket, message.widgetId);
|
||||
} else if (message.action === "unsubscribe" && typeof message.widgetId === "string") {
|
||||
log.debug("client unsubscribed", { connectionId, widgetId: message.widgetId });
|
||||
unsubscribe(socket, message.widgetId);
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("close", () => cleanupSocket(socket));
|
||||
socket.on("error", () => cleanupSocket(socket));
|
||||
socket.on("close", (code) => {
|
||||
log.info("client disconnected", { connectionId, code, clients: allSockets.size - 1 });
|
||||
cleanupSocket(socket);
|
||||
});
|
||||
socket.on("error", (err) => {
|
||||
log.error("socket error", { connectionId, error: err.message });
|
||||
cleanupSocket(socket);
|
||||
});
|
||||
});
|
||||
|
||||
collectorScheduler.onResult((widgetId, result) => {
|
||||
|
||||
Reference in New Issue
Block a user