Instrumenting a Node.js service with @vectry/node
A practical guide to event design: namespaces, the operation grammar, change diffs, and capture patterns that never block your request path.
Instrumentation fails for two reasons: it’s too much work, or it produces events nobody can use later. This guide covers both — how to wire @vectry/node into a service in minutes, and how to design events that stay explainable a year from now.
Install and initialize
npm install @vectry/node
import { Vectry } from '@vectry/node';
const vectry = new Vectry({
apiKey: process.env.VECTRY_API_KEY,
});
One client per process. The SDK batches and ships events asynchronously — capture() resolves fast and never sits in your request path.
Design the namespace first
Every event carries a namespace following the grammar {domain}.{entity}.{operation}:
inventory.item.updatedauth.session.createdbilling.invoice.issued
Resist the temptation to invent free-form names. The namespace is how humans filter streams, how dashboards group signals, and how future-you finds anything. If you can’t name the domain and entity, you haven’t decided what the event is yet.
The operation is the contract
The operation object is what makes a Vectry event more than a log line:
await vectry.capture({
namespace: 'inventory.item.updated',
actor_id: 'usr-99992',
actor_type: 'user',
operation: {
type: 'updated',
system_domain: 'inventory',
system_entity: 'item',
system_entity_id: 'item-3339',
changes: {
original: { quantity: 3 },
updated: { quantity: 5 },
},
},
});
Three rules we follow internally:
- Always name the instance.
system_entity_idis what lets traces and threads assemble around a real thing. - Diff only what changed.
changesis a field-level diff, not a full snapshot — small, readable, auditable. - Non-mutating events still have a target. A
signaledorevaluatedoperation names the entity it evaluated, even though nothing changed.
Actors are never optional
actor_type accepts user, system, ai and device. The temptation is to default everything to system and move on — don’t. When an AI agent acts on your operation, actor_type: 'ai' is the difference between an auditable decision and an anonymous one. An event without a real actor is a bug, not a data point.
Where to capture
Put capture calls at the domain boundary, not in controllers: the service method that actually mutates the entity is the one place that knows the original and updated values. Instrument there once, and every caller — HTTP handler, queue consumer, cron job — emits the same well-formed event.
That’s the whole discipline. One grammar, captured where the truth lives, with the actor attached. Everything else — traces, threads, explanations, anomalies — Vectry builds from there.