- Robot-client: TelemetryReporter collects system metrics (CPU, RAM, disk, WiFi) and sends them to backend every 60s via WebSocket - Robot-client: LogForwarder buffers Pino logs and flushes them in batches every 5s to the backend via WebSocket - Backend: HealthReport entity + HealthTelemetryService with alert thresholds (CPU >80°C, RAM >90%, disk >90%, load >3.0, heap >85%) - Backend: DeviceLog entity + LogIngestionService with EventEmitter2 for SSE - Backend: REST endpoints GET /devices/:id/health/reports and /alerts - Backend: REST endpoint GET /devices/:id/logs with filtering (level, logger, search) - Backend: SSE endpoint GET /admin/logs/stream for real-time log streaming - Migrations for health_reports and device_logs tables with proper indexes Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
41 lines
1.4 KiB
TypeScript
41 lines
1.4 KiB
TypeScript
import { MigrationInterface, QueryRunner } from 'typeorm';
|
|
|
|
export class AddDeviceLogs1744540100000 implements MigrationInterface {
|
|
name = 'AddDeviceLogs1744540100000';
|
|
|
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
|
await queryRunner.query(`
|
|
CREATE TABLE "device_logs" (
|
|
"id" UUID DEFAULT uuid_generate_v4() NOT NULL,
|
|
"device_id" UUID NOT NULL,
|
|
"level" SMALLINT NOT NULL,
|
|
"msg" TEXT NOT NULL,
|
|
"logger_name" VARCHAR(64),
|
|
"context" JSONB,
|
|
"logged_at" TIMESTAMPTZ NOT NULL,
|
|
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
|
|
CONSTRAINT "PK_device_logs" PRIMARY KEY ("id"),
|
|
CONSTRAINT "FK_device_logs_device"
|
|
FOREIGN KEY ("device_id") REFERENCES "devices"("id") ON DELETE CASCADE
|
|
);
|
|
|
|
-- Primary query pattern: recent logs for a device
|
|
CREATE INDEX "IDX_device_logs_device_logged"
|
|
ON "device_logs" ("device_id", "logged_at" DESC);
|
|
|
|
-- Filter by level (e.g. show only errors)
|
|
CREATE INDEX "IDX_device_logs_device_level"
|
|
ON "device_logs" ("device_id", "level");
|
|
|
|
-- Purge old logs
|
|
CREATE INDEX "IDX_device_logs_logged"
|
|
ON "device_logs" ("logged_at");
|
|
`);
|
|
}
|
|
|
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
|
await queryRunner.query(`DROP TABLE IF EXISTS "device_logs";`);
|
|
}
|
|
}
|