Send Spans from a Product
You need to record spans from within a product. A span records what happened, how long it took, and whether it succeeded. You also need to trust that the service stores those spans and keeps them queryable. This page covers one bounded task. Connect to the span service. Build a span and send it. Query it back to confirm the round trip.
See Collect Spans from Any Product for the full setup with architecture context, the query interface, and tree reconstruction.
Prerequisites
-
Completed the
Collect Spans from Any Product
guide. You installed
@forwardimpact/librpcand@forwardimpact/libtype. The span service runs.
Connect
import { createClient } from "@forwardimpact/librpc";
import { createLogger } from "@forwardimpact/libtelemetry";
import { span } from "@forwardimpact/libtype";
const logger = createLogger("my-product");
const spanClient = await createClient("span", logger);
Send a span
Build a span.SpanItem and call RecordSpan.
Every span needs a trace_id and a span_id.
The trace_id groups related spans. The
span_id is unique to this span:
const record = span.SpanItem.fromObject({
trace_id: "job-run-042",
span_id: "step-01",
name: "generate-output",
kind: 1, // INTERNAL
start_time_unix_nano: BigInt(Date.now()) * 1_000_000n,
end_time_unix_nano: BigInt(Date.now() + 2000) * 1_000_000n,
attributes: {
"operation.name": "output-pipeline",
"step.type": "generation",
},
status: { code: 1, message: "" }, // OK
resource: {
attributes: { "service.name": "my-product" },
},
});
const result = await spanClient.RecordSpan(record);
console.log("Sent:", result.success);
Expected output:
Sent: true
Send a child span
Link a child span to its parent with parent_span_id:
const childSpan = span.SpanItem.fromObject({
trace_id: "job-run-042",
span_id: "step-02",
parent_span_id: "step-01",
name: "verify-output",
kind: 1,
start_time_unix_nano: BigInt(Date.now()) * 1_000_000n,
end_time_unix_nano: BigInt(Date.now() + 800) * 1_000_000n,
attributes: {
"operation.name": "output-pipeline",
"step.type": "verification",
"verdict": "pass",
},
status: { code: 1, message: "" },
resource: {
attributes: { "service.name": "my-product" },
},
});
await spanClient.RecordSpan(childSpan);
Send an error span
When a step fails, set the status code to ERROR (2)
with a message:
const errorSpan = span.SpanItem.fromObject({
trace_id: "job-run-042",
span_id: "step-03",
parent_span_id: "step-01",
name: "publish-results",
kind: 1,
start_time_unix_nano: BigInt(Date.now()) * 1_000_000n,
end_time_unix_nano: BigInt(Date.now() + 500) * 1_000_000n,
attributes: {
"operation.name": "output-pipeline",
},
status: { code: 2, message: "Connection refused on port 3005" },
resource: {
attributes: { "service.name": "my-product" },
},
});
await spanClient.RecordSpan(errorSpan);
Query to confirm
After you send spans, query them back by trace ID to confirm that the service stored them:
const query = span.QueryRequest.fromObject({
filter: { trace_id: "job-run-042" },
});
const result = await spanClient.QuerySpans(query);
console.log("Stored spans:", result.spans?.length ?? 0);
for (const s of result.spans ?? []) {
const status = s.status?.code === 2 ? "ERROR" : "OK";
console.log(` ${s.name} [${status}]`);
}
Expected output:
Stored spans: 3
generate-output [OK]
verify-output [OK]
publish-results [ERROR]
Handle send failures
RecordSpan validates that trace_id and
span_id are present. If you omit either one,
RecordSpan produces a gRPC error:
try {
const bad = span.SpanItem.fromObject({
trace_id: "",
span_id: "orphan",
name: "missing-trace-id",
});
await spanClient.RecordSpan(bad);
} catch (err) {
console.error(err.message);
// "trace_id is required"
}
If the span service is unreachable, the client retries up to 10 times with exponential backoff. The base delay is 1 second. The delay doubles on each attempt and includes jitter. Every attempt shares one absolute deadline of 60 seconds, so the retries stop when the call spends that budget. The client then reports the connection error.
Verify
You reach the outcome of this guide when:
-
RecordSpanwith a validtrace_idandspan_idreturns{ success: true }. - Child spans reference their parent and appear in the same trace query.
- Error spans preserve their status code and message.
-
QuerySpansreturns all spans sent under the sametrace_id.