Manual instrumentation lets you trace business operations that auto-instrumentation misses. Use it to capture checkout flows, payment processing, or any code path where you need visibility into what happened and why.
Prerequisites
- Complete the Node.js instrumentation guide so your tracer provider and exporters are configured.
- Install the API package:
npm install @opentelemetry/api - Tested with Node.js 18 and
@opentelemetry/apiv1.9.0.
Step 1. Create manual spans
Get a tracer and wrap operations in spans:
const { trace } = require('@opentelemetry/api')
const tracer = trace.getTracer('order-service')
async function processOrder(orderId) {
return tracer.startActiveSpan('process-order', async (span) => {
try {
span.setAttribute('order.id', orderId)
span.setAttribute('order.status', 'processing')
// Business logic here
// Child spans in called functions link automatically
await validateOrder(orderId)
return { success: true }
} finally {
span.end()
}
})
}For nested operations, child spans link to the parent automatically:
async function processOrder(orderId) {
return tracer.startActiveSpan('process-order', async (parentSpan) => {
try {
parentSpan.setAttribute('order.id', orderId)
// Child span tracks a sub-operation
await tracer.startActiveSpan('validate-inventory', async (childSpan) => {
try {
childSpan.setAttribute('warehouse.id', 'WH-001')
await checkInventory(orderId)
} finally {
childSpan.end()
}
})
} finally {
parentSpan.end()
}
})
}Tips:
- Reuse tracer instances instead of creating one per request.
- Use descriptive span names that match business steps (
checkout,fetch-user). - Always call
span.end()in afinallyblock.
Step 2. Add attributes and events
Attributes let you filter and aggregate spans in SigNoz. Events mark notable moments within a span.
const { trace } = require('@opentelemetry/api')
function handlePayment(amount, currency = 'USD') {
const span = trace.getActiveSpan()
if (!span) return
span.setAttribute('payment.amount', amount)
span.setAttribute('payment.currency', currency)
span.setAttribute('payment.method', 'credit_card')
// Events mark milestones
span.addEvent('payment.validated')
// Process payment...
span.addEvent('payment.processed', {
'status': 'success',
'transaction.id': 'txn_123456'
})
}For standard attribute names, use semantic conventions:
npm install @opentelemetry/semantic-conventionsconst { trace } = require('@opentelemetry/api')
const { ATTR_HTTP_REQUEST_METHOD, ATTR_URL_FULL } = require('@opentelemetry/semantic-conventions')
const span = trace.getActiveSpan()
if (span) {
span.setAttribute(ATTR_HTTP_REQUEST_METHOD, 'GET')
span.setAttribute(ATTR_URL_FULL, 'https://api.example.com/users')
}- Use semantic conventions when a standard attribute exists.
- Add events for retries, cache hits/misses, queue waits.
Step 3. Record errors
Mark failures so they show up in SigNoz error views:
const { trace, SpanStatusCode } = require('@opentelemetry/api')
async function riskyOperation() {
return tracer.startActiveSpan('risky-operation', async (span) => {
try {
const result = await doSomethingRisky()
span.setStatus({ code: SpanStatusCode.OK })
return result
} catch (error) {
span.recordException(error)
span.setStatus({ code: SpanStatusCode.ERROR, message: error.message })
throw error
} finally {
span.end()
}
})
}recordExceptionattaches the stack trace.SpanStatusCode.ERRORflags the span in SigNoz error views and alerts.- Re-throw so calling code can handle the failure.
Step 4. Add span links (optional)
Links connect causally related spans that aren't parent-child:
const { trace } = require('@opentelemetry/api')
const tracer = trace.getTracer('my-service')
// First operation - capture context
let linkContext
tracer.startActiveSpan('batch-job', (span) => {
linkContext = span.spanContext()
span.end()
})
// Later operation linked to the first
tracer.startActiveSpan('process-result', { links: [{ context: linkContext }] }, (span) => {
// Linked to batch-job but not a child
span.end()
})Validate
- Trigger code paths that create manual spans.
- In SigNoz Traces, filter by
service.nameor span name. - Open a trace and check attributes, events, and error status.
- Use
has_error = trueto find spans with recorded exceptions.
Troubleshooting
Still not seeing data in SigNoz? Work through Debug missing traces, logs, and metrics, which covers SDK diagnostics, Collector connectivity, and the common ingestion errors for all three signals.
Why don't I see my spans in SigNoz?
- Tracer provider must initialize before your app code runs.
- Check sampler settings. Ratio-based sampling may drop spans in dev.
- Confirm traffic actually hits the instrumented functions.
Why are child spans missing?
- Use
startActiveSpanwhich sets parent context automatically. - If using
startSpan, you must manage context manually. - Parent span can't end before children are created.
Why don't attributes appear?
- Values must be strings, booleans, numbers, or arrays of these.
- Set attributes before
span.end(). Post-end mutations are ignored.
Spans disconnected across async calls?
startActiveSpanhandles async context in most cases.- For complex flows, use
context.with()to propagate context explicitly.
Next steps
- Correlate traces with logs for faster debugging
- Return to the Node.js instrumentation guide
- Exclude HTTP endpoints such as health checks, etc.
If you need help with the steps in this topic, please reach out to us on SigNoz Community Slack. If you are a SigNoz Cloud user, please use in product chat support located at the bottom right corner of your SigNoz instance or contact us at cloud-support@signoz.io.