Skip to content

Telemetry (server-telemetry) ​

The Telemetry service handles log aggregation via VictoriaLogs and event tracking via a database. It provides query and write APIs for structured logs.

Running ​

bash
# Development
npm run dev --workspace=apps/server-telemetry

# CLI
npm run cli --workspace=apps/server-telemetry -- start

# Docker
docker run -e ... privateaim/hub telemetry cli start

Dependencies ​

  • Database — MySQL, PostgreSQL, or SQLite (for event entities)
  • Authup — OAuth2 identity provider
  • VictoriaLogs (optional) — log storage backend
  • RabbitMQ — AMQP message bus

Environment Variables ​

Service-Specific ​

VariableDefaultDescription
VICTORIA_LOGS_URL—VictoriaLogs base URL
VICTORIA_LOGS_INGESTOR_URL—VictoriaLogs ingest endpoint (overrides base URL)
VICTORIA_LOGS_QUERIER_URL—VictoriaLogs query endpoint (overrides base URL)
EVENT_RETENTION_DAYS7Days a bus-ingested event row is kept before the sweep drops it. Stamped at ingest when the publisher supplied no expiresAt; 0 keeps rows forever. Events created directly via POST /events are never stamped.

All VictoriaLogs variables are optional. When unset, an in-memory log store is used as a fallback.

Inherited ​

See Shared Configuration and Database Configuration.

Key Endpoints ​

MethodEndpointDescription
GET/eventsList events
GET/events/:idGet event
POST/eventsCreate event
DELETE/events/:idDelete event
GET/logsQuery logs (VictoriaLogs)
POST/logsWrite logs
DELETE/logsDelete logs
GET/docsSwagger/OpenAPI documentation

Response Shapes ​

Event and log endpoints answer with a { data, meta } envelope — the record (or the record array) under data. GET /events and GET /events/:id advertise the endpoint's queryable vocabulary at meta.schema; POST /events, DELETE /events/:id and POST /logs carry meta: {}.

The event vocabulary is deliberately narrow: filterable on scope, name, refType, refId and realmId; sortable on createdAt only, with createdAt DESC as the default — a sort parameter that decodes to nothing falls back to newest-first rather than to no ordering at all. createdAt and updatedAt are not filterable — both are datetime columns behind a read-side transformer, so a timestamp filter would compare an ISO string against the database's native storage format and match wrong rows silently. An expression-dialect filter on them answers 400; the legacy bracket form drops silently. Events are append-only, so updatedAt always equals createdAt and adds no ordering.

Two surfaces stay outside that contract:

EndpointShape
GET /{ version, timestamp } — service metadata, flat
DELETE /logsnull (202), flat

GET /logs is deliberately schemaless

The log collection is decoded as an open query: its filters are dynamic VictoriaLogs labels rather than a declared rapiq vocabulary, so there is nothing to describe. It is the one query endpoint in Hub that carries no meta.schema — meta holds only total, limit and offset.

See API Reference for the full contract and the meta.schema reading rules.

Event data is sanitized on write

Every event row is written through one validator, on both the POST /events path and the AMQP ingest path. Keys matching /(password|secret|hash|token|credential)/i are dropped, diff survives only as a one-level map of { next, previous } scalar pairs (an entry whose previous is absent is dropped — that is the signature of a write-only credential column) and everything else is discarded. Audit diffs therefore never contain registry.accountSecret or registryProject.accountSecret; the denylist also — deliberately, fail-closed — suppresses the non-secret bucketFile.hash, analysis.buildHash and masterImage.buildHash columns.

Architecture ​

  • LogStore port (core/services/log-store/types.ts) — defines query, write, delete operations
  • VictoriaLogsLogStore — production implementation with query injection protection
  • MemoryLogStore — in-memory fallback for startup and testing
  • EventComponent / LogComponent — AMQP consumers for async event and log ingestion. EventComponent owns retention at both ends. It stamps the window on ingest from EVENT_RETENTION_DAYS (publishers no longer stamp; an explicit expiresAt, or expiring: false, from the publisher still wins), and it runs the sweep: expiring events are dropped once at start and daily at 01:00 on a fixed, non-configurable schedule, in bounded batches so a matured retention window never becomes one long-running delete.

WARNING

The telemetry service is the log writer itself, so its own logger cannot use the log component caller (would be circular). It uses a MemoryLogStore fallback internally.