LAB429/ Cheetah product page ↗

Cheetah / Cheetah documentation

Client telemetry

Client runtimes know about stages that server inspection cannot see directly: receipt of a command, local authorization, lease failure, capture work, handler completion, startup rollback, and runtime shutdown. ITelemetrySink is the replaceable boundary for those structured events.

When no sink is supplied, the core runtime uses ConsoleTelemetrySink if logging is enabled and NullTelemetrySink when it is disabled. Neither choice posts events to the server. Important progress, results, and errors still belong in the authenticated response and history path rather than in best-effort telemetry.

Use the HTTP sink as a bounded courier

import { HttpTelemetrySink } from '@cheetah/core';

const telemetrySink = new HttpTelemetrySink({
  endpoint: 'https://telemetry.example.com/v1/client-spans',
  authToken,
  clientId,
  transportSecurityMode: 'strict',
  minLevel: 'info',
  maxBufferSize: 50,
  flushIntervalMs: 5_000,
  timeoutMs: 10_000,
});

telemetrySink.start();

The sink converts accepted structured log entries into one-millisecond span-shaped records and batches them for HTTP POST. Strict transport requires HTTPS; debug_insecure permits HTTP only for an explicitly insecure development environment.

Buffering is intentionally lossy under sustained failure. With no endpoint, the sink retains at most twice maxBufferSize and drops the oldest observations. A failed batch is requeued only while space remains up to maxBufferSize. This prevents telemetry pressure from growing client memory without bound, but it means the sink is not durable evidence.

start() owns the periodic flush timer. The core runtime calls flush() during a graceful stop, after recording runtime_stopped, but it does not call the concrete sink's stop() method. The host should stop the timer after the runtime has flushed:

await runtime.stop();
telemetrySink.stop();

A browser service worker or mobile process can still disappear without graceful finalization.

Endpoint negotiation is applied, not operated

On every accepted handshake, the core runtime validates known server-config URLs. If the selected sink implements setNegotiatedEndpoint, the runtime supplies the current telemetry_endpoint or undefined when the field is absent. HttpTelemetrySink then uses the negotiated value, while an absent value restores its construction-time endpoint.

This update is best effort: a sink failure is logged through the telemetry boundary and does not make the WebSocket unready. Negotiation does not construct the sink, start its timer, provide its bearer token, or decide whether the application trusts the advertised receiver. The host still owns those choices.

Resolve the same stable client_id that the runtime advertises before constructing the sink. Command-related events can carry trace_id and parent_span_id; startup and other client-initiated events may form independent traces.

The reusable server does not mount a receiver

servercheetah has no general client-telemetry ingestion route. A receiving application must:

  1. authenticate the caller and derive trusted user or tenant scope;
  2. bound the HTTP body, span count, field sizes, and request rate;
  3. validate a versioned payload;
  4. map accepted spans into its tracer or observability backend;
  5. count accepted, dropped, and failed observations.

Product01 demonstrates one composition with bearer authentication, a maximum of 100 spans per request, and a process-local limit of 1,000 spans per authenticated user per minute. Those values are sample-product choices, not reusable Cheetah defaults or a horizontally shared quota.

OpenTelemetryTracer.record_client_span() can preserve client trace, span, parent, timestamp, attributes, and status fields. That method is specific to the supplied OpenTelemetry implementation; it is not part of ITracer. A receiver using another tracer needs an explicit mapping and may be able to preserve only a diagnostic event rather than the original client span.

Verify the pipeline one boundary at a time

Begin with LoggingTracer and one known RPC. Confirm that server dispatch and returned-message ingestion share a trace identity. Next attach an explicit exporter and query the backend for the service and trace. Test the client receiver directly with one authenticated batch before enabling the HTTP sink. Only then verify that command-related client events join the expected server trace.

Exercise failure as well: stop the collector, reject invalid credentials and oversized batches, remove the negotiated endpoint, fill the client buffer, and terminate the runtime abruptly. Command execution must remain independent while telemetry loss stays visible through receiver and exporter health signals.

Sampling, TLS, credentials, redaction, retention, deletion, backend access, alerting, and cost control all belong to the deployment. Treat error strings as potentially sensitive and exclude tokens, command payloads, captured page content, and uncontrolled user text from ordinary attributes.

For server event parentage and exporter ownership, read Server tracing. For domain evidence alongside traces, read Live and retained evidence.