Class: Woods::SessionTracer::Middleware

Inherits:
Object
  • Object
show all
Defined in:
lib/woods/session_tracer/middleware.rb

Overview

Rack middleware that captures request metadata for session tracing.

Wraps ‘@app.call(env)`, records after response. Extracts controller/action from `env`. Session ID from `X-Trace-Session` header first, falls back to `request.session.id`.

Fire-and-forget writes — ‘rescue StandardError` on recording, never breaks the request.

Examples:

Inserting into a Rails middleware stack

app.middleware.insert_after ActionDispatch::Session::CookieStore,
                            Woods::SessionTracer::Middleware

Constant Summary collapse

FULL_STORE_INTERFACE =

Full Store interface every backend (FileStore, RedisStore, SolidCacheStore) implements. Middleware itself only calls #record — the read-side methods are used by the session_trace MCP tool and other consumers. Surfaced as a constant so operators can assert the full interface eagerly when they want:

missing = Woods::SessionTracer::Middleware::FULL_STORE_INTERFACE
             .reject { |m| store.respond_to?(m) }
raise "incomplete store: #{missing}" unless missing.empty?
%i[record read sessions clear clear_all].freeze
REQUIRED_STORE_METHODS =

Methods the middleware actually calls at request time. Validated at init so a half-configured store fails loudly at boot.

%i[record].freeze

Instance Method Summary collapse

Constructor Details

#initialize(app, store:, session_id_proc: nil, exclude_paths: []) ⇒ Middleware

Returns a new instance of Middleware.

Parameters:

  • app (#call)

    The downstream Rack application

  • store (Store)

    Session trace store backend. Must respond to ‘#record` (called by this middleware). Consumers that use the read-side (FULL_STORE_INTERFACE) should assert on their own contract; middleware does not enforce it to stay backward- compatible with minimal `#record`-only implementations.

  • session_id_proc (Proc, nil) (defaults to: nil)

    Custom session ID extraction (receives env)

  • exclude_paths (Array<String>) (defaults to: [])

    Path prefixes to skip

Raises:

  • (ArgumentError)

    if the store is nil or does not implement ‘:record`. Boot-time validation is preferable to the fire-and- forget rescue in #call silently swallowing every request trace when the store has the wrong shape.



47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/woods/session_tracer/middleware.rb', line 47

def initialize(app, store:, session_id_proc: nil, exclude_paths: [])
  raise ArgumentError, 'session tracer middleware requires a store' if store.nil?

  missing = REQUIRED_STORE_METHODS.reject { |m| store.respond_to?(m) }
  unless missing.empty?
    raise ArgumentError,
          'session tracer store is missing required methods ' \
          "#{missing.inspect} (got #{store.class}). " \
          "Required: #{REQUIRED_STORE_METHODS.inspect}. " \
          "Full interface: #{FULL_STORE_INTERFACE.inspect}."
  end

  @app = app
  @store = store
  @session_id_proc = session_id_proc
  @exclude_paths = exclude_paths
end

Instance Method Details

#call(env) ⇒ Array

Returns Rack response triple [status, headers, body].

Parameters:

  • env (Hash)

    Rack environment

Returns:

  • (Array)

    Rack response triple [status, headers, body]



67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/woods/session_tracer/middleware.rb', line 67

def call(env)
  start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
  status, headers, body = @app.call(env)
  duration_ms = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond) - start_time

  begin
    record_request(env, status, duration_ms)
  rescue StandardError
    # Fire-and-forget — recording failures never break the request
  end

  [status, headers, body]
end