feat(analytics): instance census, full capture, richer error context (#511)

Add a once-per-boot instance_started event (arch, os, deploy_mode,
gpu_present) so the fleet architecture mix is measurable. It reuses the
existing per-instance instance_id and is exempt from the volume sample
rate, since a census that fires once per boot must not be thinned.

Restore useful capture depth now that the sponsored plan removes the
quota pressure behind the earlier hardening:

- PostHog sample rate 0.1 to 1.0 (full analytics when enabled); the
  property allowlist still blocks file data.
- Sentry per-instance ceiling 20 to 500/hr, breadcrumb trail restored
  (sanitized: urls/paths redacted, data payloads dropped), full stack
  paths kept; local vars, request bodies, and PII still dropped. Both
  api and web.

Honor ANALYTICS_ENABLED=false as an opt-out alias: it was documented on
the Docker Hub README but never wired in 2.x, so anyone who set it was
still tracked.

All capture stays behind the analytics opt-out gate.
This commit is contained in:
SnapOtter
2026-07-13 14:23:16 +08:00
committed by GitHub
parent b6fdabaea8
commit e1b8c24e5d
22 changed files with 378 additions and 106 deletions
+8 -2
View File
@@ -5,7 +5,7 @@ import cors from "@fastify/cors";
import rateLimit from "@fastify/rate-limit";
import { trace } from "@opentelemetry/api";
import { getDispatcherStatus, initDispatcher, isGpuAvailable } from "@snapotter/ai";
import { APP_VERSION, SafeError } from "@snapotter/shared";
import { ANALYTICS_EVENTS, APP_VERSION, SafeError } from "@snapotter/shared";
import { eq, sql } from "drizzle-orm";
import Fastify from "fastify";
import { env } from "./config.js";
@@ -17,7 +17,7 @@ import { closeFlowProducer, closeQueueEvents, warmQueueEvents } from "./jobs/enq
import { closeQueues, perPoolHealth, queueCounts } from "./jobs/queues.js";
import { enqueueSystemJob, SYSTEM_JOBS, scheduleSystemJobs } from "./jobs/system-jobs.js";
import { closeWorkers, startWorkers } from "./jobs/worker.js";
import { initAnalytics, shutdownAnalytics } from "./lib/analytics.js";
import { initAnalytics, shutdownAnalytics, trackEvent } from "./lib/analytics.js";
import { shouldRunStartupCleanup } from "./lib/cleanup.js";
import { buildCsp } from "./lib/csp.js";
import { reportError } from "./lib/error-report.js";
@@ -27,6 +27,7 @@ import { logger } from "./lib/logger.js";
import { requestDuration } from "./lib/metrics.js";
import { getSettingString } from "./lib/settings-helpers.js";
import { assertStorageWritable } from "./lib/storage-writable.js";
import { gatherSystemProperties } from "./lib/system-info.js";
import { requirePermission } from "./permissions.js";
import {
authMiddleware,
@@ -183,6 +184,11 @@ if (!env.COOKIE_SECRET) {
await initAnalytics();
const { primeAnalyticsGate } = await import("./lib/analytics-gate.js");
await primeAnalyticsGate();
// ignoreSampleRate: this once-per-boot census must not be thinned by the
// volume sample rate that exists to throttle high-frequency usage events.
await trackEvent(ANALYTICS_EVENTS.INSTANCE_STARTED, { ...gatherSystemProperties() }, undefined, {
ignoreSampleRate: true,
});
// Enterprise features (license-gated)
let enterpriseLicense: { org: string; plan: string } | null = null;