mirror of
https://github.com/alkimake/paperclip.git
synced 2026-06-15 18:30:39 +09:00
fix: add periodic flush and graceful shutdown for server-side telemetry
The TelemetryClient only flushed at 50 events, so the server silently lost all queued telemetry on restart. Add startPeriodicFlush/stop methods to TelemetryClient, wire up 60s periodic flush in server initTelemetry, and flush on SIGTERM/SIGINT before exit. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
parent
34044cdfce
commit
f16de6026d
4 changed files with 104 additions and 10 deletions
66
server/src/__tests__/telemetry-client-flush.test.ts
Normal file
66
server/src/__tests__/telemetry-client-flush.test.ts
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
import { describe, expect, it, vi, beforeEach, afterEach } from "vitest";
|
||||
import { TelemetryClient } from "../../../packages/shared/src/telemetry/client.js";
|
||||
import type { TelemetryConfig, TelemetryState } from "../../../packages/shared/src/telemetry/types.js";
|
||||
|
||||
function makeClient(config?: Partial<TelemetryConfig>) {
|
||||
const merged: TelemetryConfig = { enabled: true, endpoint: "http://localhost:9999/ingest", ...config };
|
||||
const state: TelemetryState = {
|
||||
installId: "test-install",
|
||||
salt: "test-salt",
|
||||
createdAt: "2026-01-01T00:00:00Z",
|
||||
firstSeenVersion: "0.0.0",
|
||||
};
|
||||
return new TelemetryClient(merged, () => state, "0.0.0-test");
|
||||
}
|
||||
|
||||
describe("TelemetryClient periodic flush", () => {
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers();
|
||||
vi.stubGlobal("fetch", vi.fn().mockResolvedValue({ ok: true }));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("flushes queued events on interval", async () => {
|
||||
const client = makeClient();
|
||||
client.startPeriodicFlush(1000);
|
||||
|
||||
client.track("install.started");
|
||||
expect(fetch).not.toHaveBeenCalled();
|
||||
|
||||
await vi.advanceTimersByTimeAsync(1000);
|
||||
expect(fetch).toHaveBeenCalledTimes(1);
|
||||
|
||||
// Second tick with no new events — no additional call
|
||||
await vi.advanceTimersByTimeAsync(1000);
|
||||
expect(fetch).toHaveBeenCalledTimes(1);
|
||||
|
||||
// New event gets flushed on next tick
|
||||
client.track("install.started");
|
||||
await vi.advanceTimersByTimeAsync(1000);
|
||||
expect(fetch).toHaveBeenCalledTimes(2);
|
||||
|
||||
client.stop();
|
||||
});
|
||||
|
||||
it("stop() prevents further flushes", async () => {
|
||||
const client = makeClient();
|
||||
client.startPeriodicFlush(1000);
|
||||
|
||||
client.track("install.started");
|
||||
client.stop();
|
||||
|
||||
await vi.advanceTimersByTimeAsync(2000);
|
||||
expect(fetch).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("startPeriodicFlush is idempotent", () => {
|
||||
const client = makeClient();
|
||||
client.startPeriodicFlush(1000);
|
||||
client.startPeriodicFlush(1000); // should not throw or double-fire
|
||||
client.stop();
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue