Class: Httrace::CaptureMiddleware

Inherits:
Object
  • Object
show all
Defined in:
lib/httrace.rb

Overview

── CaptureMiddleware ──────────────────────────────────────────────────────

Rack middleware — works with Rails, Sinatra, Grape, and any Rack-compatible app.

Rails (config/application.rb):

config.middleware.use Httrace::CaptureMiddleware, api_key: 'ht_...'

Sinatra:

use Httrace::CaptureMiddleware, api_key: 'ht_...'

Instance Method Summary collapse

Constructor Details

#initialize(app, api_key:, service: 'default', sample_rate: 0.1, exclude_paths: nil, endpoint: nil) ⇒ CaptureMiddleware

Returns a new instance of CaptureMiddleware.

Parameters:

  • app (#call)

    the Rack app to wrap

  • api_key (String)

    your Httrace API key

  • service (String) (defaults to: 'default')

    service label shown in the dashboard

  • sample_rate (Float) (defaults to: 0.1)

    fraction of requests to capture (0.0–1.0)

  • exclude_paths (Array) (defaults to: nil)

    paths to skip

  • endpoint (String) (defaults to: nil)

    override API endpoint (for self-hosted)



40
41
42
43
44
45
46
47
# File 'lib/httrace.rb', line 40

def initialize(app, api_key:, service: 'default', sample_rate: 0.1,
               exclude_paths: nil, endpoint: nil)
  @app          = app
  @service      = service
  @sample_rate  = sample_rate.to_f
  @exclude      = Set.new(exclude_paths || %w[/health /metrics /favicon.ico])
  @client       = Client.new(api_key, endpoint || DEFAULT_ENDPOINT)
end

Instance Method Details

#call(env) ⇒ Object



49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/httrace.rb', line 49

def call(env)
  path = env['PATH_INFO'] || '/'

  if @exclude.include?(path) || rand >= @sample_rate
    return @app.call(env)
  end

  # Buffer request body so we can read it AND so the app can read it too
  raw_input = env['rack.input'] || StringIO.new
  req_body  = raw_input.read
  env['rack.input'] = StringIO.new(req_body)

  t_start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
  status, headers, body_iter = @app.call(env)
  latency_ms = (Process.clock_gettime(Process::CLOCK_MONOTONIC) - t_start) * 1000.0

  # Collect response body without consuming it (Rack contract: body is enumerable)
  resp_parts = []
  body_iter.each { |chunk| resp_parts << chunk }
  resp_body_str = resp_parts.join

  Thread.new do
    record(env, req_body, status, headers, resp_body_str, latency_ms)
  rescue
    # Never crash
  end

  [status, headers, [resp_body_str]]
end