Traces
Traces show the complete journey of a request through your application, including all database queries, HTTP calls, and background jobs.
What is a Trace?
A trace is a tree of spans representing operations:
Request: GET /orders/123 (250ms)
├── OrdersController#show (248ms)
│ ├── SQL: SELECT * FROM orders (5ms)
│ ├── SQL: SELECT * FROM users (3ms)
│ ├── HTTP: GET api.stripe.com/charges (150ms)
│ └── View: orders/show.html.erb (80ms)
│ ├── Partial: _line_items (40ms)
│ └── Partial: _summary (30ms)
Each span contains:
- Name - What operation was performed
- Duration - How long it took
- Kind - Type (request, db, http, redis, etc.)
- Data - Operation-specific details
Automatic Tracing
Pulse automatically creates spans for:
| Source | Span Details |
|---|
| Rails/Rack | Request method, path, status, controller/action |
| Active Record | SQL query, table, duration |
| Net::HTTP | URL, method, status |
| Faraday | URL, method, status |
| HTTParty | URL, method, status |
| Redis | Command, key, database |
| Sidekiq | Job class, queue, wait time |
| GraphQL | Query name, operation type |
| MongoDB | Command, collection |
| Elasticsearch | Operation, index |
Custom Spans
Add custom spans for business logic:
def calculate_shipping
BrainzLab::Pulse.trace("shipping.calculate", kind: "business") do |span|
span[:data] = {
carrier: @carrier,
weight: @package.weight,
destination: @address.country
}
rate = ShippingCalculator.calculate(@package, @address)
span[:data][:rate] = rate
rate
end
end
Nested Spans
BrainzLab::Pulse.trace("checkout.process") do
BrainzLab::Pulse.trace("checkout.validate") do
validate_cart!
end
BrainzLab::Pulse.trace("checkout.charge") do
charge_payment!
end
BrainzLab::Pulse.trace("checkout.fulfill") do
create_shipment!
end
end
Distributed Tracing
Pulse automatically propagates trace context across services.
Web → Sidekiq
# In controller - creates trace
def create
@order = Order.create!(params)
ProcessOrderJob.perform_async(@order.id) # Context passed automatically
end
# In Sidekiq job - receives trace context
class ProcessOrderJob
include Sidekiq::Job
def perform(order_id)
# This job is linked to the web request trace
order = Order.find(order_id)
order.process!
end
end
Service → Service
HTTP requests automatically include trace headers:
# Service A makes request
response = Faraday.get("https://service-b.com/api/data")
# Headers include: traceparent, X-B3-TraceId, X-B3-SpanId
# Service B extracts context (automatic in Rails)
# The trace continues with Service B's spans linked to Service A
| Format | Headers |
|---|
| W3C Trace Context | traceparent, tracestate |
| B3 | X-B3-TraceId, X-B3-SpanId, X-B3-Sampled |
Finding Traces
By Request ID
Every request has a trace ID. Find it in:
- Response header:
X-Trace-Id
- Logs (if using Recall)
- Error reports (in Reflex)
By Search
Search traces with filters:
path:/orders/* duration:>500ms status:500
| Filter | Example | Description |
|---|
path | path:/api/* | Request path |
duration | duration:>1000ms | Duration threshold |
status | status:500 | HTTP status code |
method | method:POST | HTTP method |
error | error:true | Only errors |
Trace Details View
Click a trace to see:
Waterfall View
Visual timeline showing:
- Span hierarchy
- Duration bars
- Parallel vs sequential operations
Span Details
For each span:
- Name and kind
- Start time and duration
- Custom data/attributes
- Errors (if any)
- Logs - Recall logs with matching request_id
- Errors - Reflex errors from this request
- User - Who made the request
Sampling
For high-traffic apps, use sampling:
config.pulse_sample_rate = 0.1 # Sample 10% of traces
Errors are always captured regardless of sample rate.
What’s Next?