Manual instrumentation gives you fine-grained control when automatic instrumentation alone cannot express important business operations. Use it to capture steps that matter for debugging, to attach business-specific attributes, or to guarantee that failures surface with the right context in SigNoz.
Prerequisites
- Finish the core Ruby setup in the Ruby OpenTelemetry instrumentation guide so exporters, tracer provider, and SDK are configured.
- The
opentelemetry-sdkgem installed in your application. - Tested with Ruby 3.3.0 and OpenTelemetry Ruby SDK v1.10.0.
Step 1. Create manual spans
Use the SDK tracer to wrap important work inside custom spans:
app/services/order_service.rb
class OrderService
def process_order(order_id)
tracer = OpenTelemetry.tracer_provider.tracer('order-service')
tracer.in_span('process-order') do |span|
span.set_attribute('order.id', order_id)
span.set_attribute('order.status', 'processing')
# Your order processing logic here
validate_inventory(order_id)
charge_payment(order_id)
end
end
end
Tips:
- Reuse tracer instances instead of creating a new one for each request.
- Start spans with descriptive names that match business steps (
checkout,fetch-user, etc.). - The
in_spanblock automatically ends the span when the block completes.
Step 2. Add attributes and events
Attributes show up as key-value pairs in SigNoz so you can filter and aggregate spans. Events capture notable moments inside a span.
app/services/payment_service.rb
class PaymentService
def handle_payment(amount, currency)
span = OpenTelemetry::Trace.current_span
span.set_attribute('payment.amount', amount)
span.set_attribute('payment.currency', currency)
span.set_attribute('payment.method', 'credit_card')
# Process the payment
result = process_charge(amount, currency)
span.add_event('payment.processed', attributes: {
'status' => 'success',
'transaction.id' => result.transaction_id
})
end
end
- Keep attribute keys consistent (use semantic conventions when possible).
- Use events to mark retries, cache hits/misses, queue waits, and similar milestones.
Step 3. Record errors
Flag failures on the span so they are easy to query in SigNoz.
app/services/risky_service.rb
class RiskyService
def risky_operation
span = OpenTelemetry::Trace.current_span
begin
do_something_risky
span.status = OpenTelemetry::Trace::Status.ok
rescue StandardError => e
span.record_exception(e)
span.status = OpenTelemetry::Trace::Status.error(e.message)
raise
end
end
end
record_exceptionattaches stack trace and message details.- Setting status to
errorsurfaces the span in SigNoz error views and alerts. - Continue propagating the error upstream so calling code can respond.
Validate
- Trigger the code paths that emit manual spans.
- In SigNoz Traces, filter by
service.nameor your span name. - Open a trace and verify attributes, events, and error status.
- Check the Errors view to confirm failures show up with recorded exceptions.
Troubleshooting
Why don't I see my custom spans in SigNoz?
- Confirm the OpenTelemetry initializer runs before your application handlers.
- Check sampler configuration. Using a low sampling rate in development may drop most manual spans.
- Verify traffic hits the functions where you inserted spans.
Why are child spans missing even though I create them?
- Ensure you use
in_spanblocks or properly manage span context for nested spans. - When calling external services or databases, the instrumentation must propagate context.
Why don't attributes or events appear on the span?
- Attribute values must be strings, integers, floats, booleans, or arrays of these types.
- Call
set_attributeoradd_eventbefore the span ends. Post-end mutations are ignored. - Avoid reusing finished spans—always create a new span for each invocation.
Next steps
- Set up alerts for your Ruby application
- Explore the OpenTelemetry Ruby instrumentation registry for additional library instrumentations