Class: Sixty::Instrument::Rack

Inherits:
Object
  • Object
show all
Defined in:
lib/sixty/instrument/rack.rb

Overview

Inbound HTTP.

This is the root span every other span in a request hangs off, and it is what makes "which endpoint got slower" answerable at all. It is Rack middleware rather than a Rails hook so the same class covers Sinatra, Roda, Hanami and a bare Rack app — the Node agent patches http.Server for the same reason.

── Naming, and why the route matters so much ─────────────────────────────

Middleware runs before routing, so at span start all we have is the path. /users/42 and /users/43 are different paths and the same endpoint, and recording them separately would mint an operation per user id — the cardinality cap would be hit within minutes and every one of those operations would have too little traffic to compare against anything.

So the path is templated on the way in, and on the way out the real Rails route pattern replaces it if the router recorded one. The template is the fallback; the pattern is the truth.

Instance Method Summary collapse

Constructor Details

#initialize(app, config: nil) ⇒ Rack

Returns a new instance of Rack.



28
29
30
31
# File 'lib/sixty/instrument/rack.rb', line 28

def initialize(app, config: nil)
  @app = app
  @config = config
end

Instance Method Details

#call(env) ⇒ Object

── The rule this method is written to ────────────────────────────────

Nothing the agent does may change what the application returns, and nothing the agent gets wrong may stop it returning at all. So every line that belongs to us is inside a rescue, and the one line that belongs to the application — @app.call(env) — is not wrapped in anything that could swallow or alter it. A middleware that 500s a request because a measurement failed has done more damage than every finding it could ever produce.



42
43
44
45
46
47
48
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
# File 'lib/sixty/instrument/rack.rb', line 42

def call(env)
  span = begin
    start(env)
  rescue StandardError
    nil
  end

  return @app.call(env) unless span

  previous = Tracer.current
  Tracer.current = span
  begin
    status, headers, body = @app.call(env)
  rescue Exception => e # rubocop:disable Lint/RescueException
    Tracer.current = previous
    safely { span.attrs[:status] = 500 }
    safely { finish(span, env, e) }
    raise
  end

  Tracer.current = previous
  safely do
    span.attrs[:status] = status
    length = headers && (headers['content-length'] || headers['Content-Length'])
    span.attrs[:bytes] = length.to_i if length
    # A 5xx is an error whether or not anything was raised: the exception
    # may have been rescued into a rendered error page three layers down,
    # and from the outside those are the same failure.
    finish(span, env, status.to_i >= 500 ? StandardError.new("HTTP #{status}") : nil)
  end

  [status, headers, body]
end