Telemetry
The SDK exposes vendor-neutral helpers for OpenTelemetry traces, metrics, and
logs. Import them from @ovok/native/helpers or the package root
@ovok/native. The SDK does not configure an exporter or choose a telemetry
backend; the host app owns provider registration, export, retention, and access
policy.
import {
configureOvokTelemetry,
withOvokSpan,
recordOvokMetric,
} from "@ovok/native/helpers";
Configure providers
If the app has registered OpenTelemetry providers globally, the SDK uses those providers when the optional API packages are installed. Otherwise, pass the providers explicitly:
configureOvokTelemetry({
tracerProvider: appTracerProvider,
meterProvider: appMeterProvider,
loggerProvider: appLoggerProvider,
});
Each provider is optional. @opentelemetry/api and @opentelemetry/api-logs are
optional peer dependencies; an app that does not use OpenTelemetry can omit them.
With no provider available, telemetry helpers are no-ops and SDK operations
continue. Configure providers during startup before SDK features run.
OvokTelemetryOptions accepts three provider adapters:
| Option | Provider method | Used for |
|---|---|---|
tracerProvider | getTracer(name, version?) and optional getActiveSpan() | Spans and span events |
meterProvider | getMeter(name, version?) | Counters and histograms |
loggerProvider | getLogger(name, version?) | OpenTelemetry log records |
The provider interfaces accept the SDK's minimal OpenTelemetry-compatible surface; the app still chooses the concrete SDK, processors, exporters, and destinations.
Public helpers
| Export | Signature | Behavior |
|---|---|---|
configureOvokTelemetry | (options?: OvokTelemetryOptions) => void | Set explicit providers; an empty options object clears explicit providers. |
sanitizeOvokTelemetryAttributes | (attributes?: Record<string, unknown>) => OvokTelemetryAttributes | Keep allowlisted keys with string, number, or boolean values. |
startOvokSpan | (name: string, attributes?: Record<string, unknown>) => OvokSpan | undefined | Start a span when a tracer exists; end it with span.end() in finally. |
withOvokSpan | <T>(name, attributes, operation) => Promise<T> | Run an operation in a span when available, preserving its return value or error. |
recordOvokMetric | (name: string, value: number, attributes?: Record<string, unknown>) => void | Add a value to a named counter. |
recordOvokDuration | (name: string, durationMs: number, attributes?: Record<string, unknown>) => void | Record milliseconds in a named histogram. |
addOvokSpanEvent | (name: string, attributes?: Record<string, unknown>, span?: OvokSpan) => void | Add an event to the supplied span or current active span. |
emitOvokLog | (severityText: string, body: string, attributes?: Record<string, unknown>) => void | Emit an OpenTelemetry log record when a logger provider exists. |
The public telemetry types are OvokTelemetryAttribute (string | number | boolean), OvokTelemetryAttributes (a string-keyed record of those values),
OvokSpan, OvokTracerProvider, OvokMeterProvider, OvokLoggerProvider, and
OvokTelemetryOptions. OvokSpan exposes optional methods for setting attributes,
adding events, recording exceptions, and setting status; end() is required.
type OvokTelemetryAttribute = string | number | boolean;
type OvokTelemetryAttributes = Record<string, OvokTelemetryAttribute>;
interface OvokSpan {
setAttribute?: (name: string, value: OvokTelemetryAttribute) => void;
setAttributes?: (attributes: OvokTelemetryAttributes) => void;
addEvent?: (name: string, attributes?: OvokTelemetryAttributes) => void;
recordException?: (error: Error | string) => void;
setStatus?: (status: { code: number; message?: string }) => void;
end: () => void;
}
interface OvokTelemetryOptions {
tracerProvider?: OvokTracerProvider;
meterProvider?: OvokMeterProvider;
loggerProvider?: OvokLoggerProvider;
}
When a span is available, withOvokSpan sets success or error status, records
exceptions, ends the span, and adds one to ovok.errors when the operation throws.
It rethrows the original error. If no tracer is available, it still invokes and
awaits the operation. Provider failures do not change SDK control flow.
For direct span management, always end the returned span:
const span = startOvokSpan("app.measurement.persist", {
provider: "bluetooth",
measurementTypeKey: "body-weight",
});
try {
await persistMeasurement(measurement);
} finally {
span?.end();
}
Prefer withOvokSpan for app operations because it handles lifecycle and errors.
Attribute allowlist
The SDK filters attributes passed through its span, metric, duration, and span-event helpers. It keeps only these keys and only primitive string, number, or boolean values:
deviceId, deviceName, model, manufacturer, measurementTypeKey, platform,
status, reason, errorCode, retryCount, attempt, queueDepth, durationMs,
success, exhausted, willRetry, requestedMtu, provider, and sampleCount.
Unknown keys and object or array values are dropped. This is an operational allowlist, not a guarantee that every permitted value is safe for every app. Review device identifiers and other context under your data policy.
emitOvokLog does not call this sanitizer. The logger helpers also pass log
messages and extra values as supplied. Do not put patient identifiers, credentials,
measurement values, or raw device payloads in log records. See Logging
for the logger callback and slog behavior.
Built-in SDK signals
The SDK currently emits these signals through the helpers above:
| Signal | Instrument | Meaning |
|---|---|---|
| Span | bluetooth.connect | One Bluetooth connection attempt. |
| Span | health.import | One HealthKit or Health Connect import run. |
| Event | bluetooth.scan.started | Continuous Bluetooth scanning started. |
| Event | device.status_changed | A device status callback ran. |
| Event | reading.received | A measurement reading was received. |
| Event | pairing.failed | The device connection reports that pairing requires a code. |
| Counter | ovok.devices.found | A supported device was found. |
| Counter | ovok.readings.received | A reading was delivered by a device. |
| Counter | ovok.pairing.failures | A pairing-required event occurred. |
| Counter | ovok.queue.delivered | A queued result was delivered. |
| Counter | ovok.queue.retries | A queued delivery attempt failed and will be retried. |
| Counter | ovok.queue.exhausted | A queued result reached the retry limit. |
| Counter | ovok.health.samples.imported | Count of newly created observations in a health-import batch. |
| Counter | ovok.errors | A traced operation failed, or the logger received error/fatal. |
| Counter | ovok.queue.depth | The current queue length is added as a counter value when the queue is written; this is not a gauge. |
| Histogram | ovok.device.connection.duration | Bluetooth connection duration in milliseconds. |
| Histogram | ovok.measurement.save.duration | Health-import batch save duration in milliseconds. |
Metric attributes are limited by the sanitizer. Signal availability also depends on the feature running in the foreground or background; the SDK does not guarantee that an OS-scheduled task will run long enough to emit telemetry.
See Logs and telemetry for setup and integration guidance, or return to the Helpers reference.