Skip to main content

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:
SourceSpan Details
Rails/RackRequest method, path, status, controller/action
Active RecordSQL query, table, duration
Net::HTTPURL, method, status
FaradayURL, method, status
HTTPartyURL, method, status
RedisCommand, key, database
SidekiqJob class, queue, wait time
GraphQLQuery name, operation type
MongoDBCommand, collection
ElasticsearchOperation, 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

Supported Propagation Formats

FormatHeaders
W3C Trace Contexttraceparent, tracestate
B3X-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)
Search traces with filters:
path:/orders/* duration:>500ms status:500
FilterExampleDescription
pathpath:/api/*Request path
durationduration:>1000msDuration threshold
statusstatus:500HTTP status code
methodmethod:POSTHTTP method
errorerror:trueOnly 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?