The Telemetry Package
vantacloud-telemetry is a naming layer over the plain OpenTelemetry SDK of
your language. It writes the names, units, labels and boundaries
Telemetry Conventions
states, so your code names its operations and the package does the rest. It
installs no framework, owns no exporter of its own and wraps nothing but the
SDK, so every other OpenTelemetry instrument and library your code uses works
beside it unchanged.
| Language | Package | Installed from, once released publicly |
|---|---|---|
| Rust | vantacloud-telemetry | crates.io |
| TypeScript and JavaScript on Node.js | vantacloud-telemetry | npm |
| Python | vantacloud-telemetry | PyPI |
The packages are published to these registries from the first public release on; before it, they are available to the organization’s own projects from its package feed, and the plain-SDK path below needs no package at all.
Each package names the conventions version it writes, 1 today, as a
constant it exports and in the vantacloud.conventions.version attribute it
sets on every record; a package’s own version says nothing of the conventions,
and a release that moves to a new conventions version says so in its notes.
Every package is open source under the Apache-2.0 OR MIT license, so you read,
vendor or fork it as you like. From the first public release on, the source is
the public mirror github.com/TheClockwyrks/VantaCloudTelemetry, which the
packages name as their repository.
What it does
Section titled “What it does”init sets up the SDK’s tracer, meter and logger providers over the OTLP
exporter, with the resource attributes service.name, service.version and
vantacloud.conventions.version. It takes the service’s name and version and
nothing else, and it applies the histogram boundaries of the conventions to
every duration histogram.
init reads its environment from the standard OpenTelemetry variables alone,
OTEL_EXPORTER_OTLP_ENDPOINT for where it exports and
OTEL_RESOURCE_ATTRIBUTES for the resource attributes the platform adds, and
from no configuration key of your project. The platform sets both on your
workload’s environment. The endpoint is an http:// or an https:// URL, the
second reached over TLS with the collector’s certificate verified against the
system’s trust store, so a collector outside the cluster is named as readily as
the platform’s own. init sets service.instance.id to the pod’s name, read
from HOSTNAME, unless OTEL_RESOURCE_ATTRIBUTES names one, so each replica of
your workload exports its own series of every metric.
init registers the process gauges: process.memory.usage, and the
allocator gauges where the process’s allocator offers them. In Rust they
are read from jemalloc on Linux targets, so a Rust service sets
tikv-jemallocator as its global allocator under cfg(target_os = "linux")
and enables the package’s jemalloc feature, which exports the allocator’s
statistics on Linux alone and does nothing on any other target. A Node.js or
Python process reports process.memory.usage alone.
operation wraps one operation. It takes the operation’s name, whether
callers wait on it beyond its acknowledgement and, optionally, the instant
the operation started, opens a span named for the operation, and records
vantacloud.operation.ack when your code marks the acknowledgement and
vantacloud.operation.duration when the operation ends, each with the
outcome class your code’s result maps to. Both count from the start instant,
the moment operation is called when none is given. Work claimed from a
queue passes the enqueue time stored with its job, so its ack counts from
the job’s enqueueing as the conventions state.
An HTTP middleware for the common servers of each language wraps every route this way, naming each by its method and route template. It records the ack at the response’s first byte. For a route your code marks waited on, it wraps the response’s body and records the duration when the body ends, so a streamed answer’s duration runs to its last byte.
The db module records the database series of a process holding its own
connection pool. The pool reports each event to it as the event happens:
| Event | Carries | Records |
|---|---|---|
| A connection taken | The operation it was taken for and how long the caller waited | vantacloud.db.pool.wait |
| A connection released | How long it was held | vantacloud.db.connection.held |
| A statement finished | Its schema, its command and its duration | vantacloud.db.statement.duration |
| An error | Its kind | vantacloud.db.errors |
Your pool’s wiring calls the module, in Rust from the pool’s acquire and
release hooks. The module also samples the engine every 15 seconds for the
lock waits of the pool’s database role, recording vantacloud.db.lock.waits
and vantacloud.db.lock.wait by pool and database alone.
error emits an error log record in the shape
Error Logging states, inside the
current span, from the error, its kind and the operation.
use vantacloud_telemetry::{Outcome, Telemetry};
#[cfg(target_os = "linux")]#[global_allocator]static ALLOCATOR: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc;
let telemetry = Telemetry::init("shop-api", env!("CARGO_PKG_VERSION"))?;
let job = queue.claim().await?;let settled = telemetry .operation("orders.settle") .waited_on() .started_at(job.due_at.max(job.enqueued_at)) .run(|op| async move { op.acknowledge(); settle(job.order).await }) .await;The outcome class is read from the error’s code: invalid-argument,
not-found, conflict, refused, limit-exceeded and permission-denied
are client-error, and every other code is server-error.
TypeScript
Section titled “TypeScript”import { init } from "vantacloud-telemetry";
const telemetry = init({ service: "shop-api", version: "1.4.2" });
const job = await queue.claim();await telemetry.operation( "orders.settle", { waitedOn: true, startedAt: max(job.dueAt, job.enqueuedAt) }, async (op) => { op.acknowledge(); await settle(job.order); },);Python
Section titled “Python”from vantacloud_telemetry import init
telemetry = init(service="shop-api", version="1.4.2")
job = await queue.claim()async with telemetry.operation( "orders.settle", waited_on=True, started_at=max(job.due_at, job.enqueued_at)) as op: op.acknowledge() await settle(job.order)Without the package
Section titled “Without the package”The package is a convenience. Code that sets the resource attributes, registers the instruments under the names, units, labels and boundaries the conventions state and logs errors in their shape, through the plain OpenTelemetry SDK of any language, is conformant in the same way, and its dashboards and alerts are the same.