Class: RailsOtelContext::BodyCapture

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

Overview

Rack middleware that captures request and response bodies as OpenTelemetry span attributes. Works with any OTel Rack instrumentation — the middleware must sit inside (after) the span-creating middleware so current_span is set.

Usage:

# config/application.rb (or an initializer after OTel is configured)
config.middleware.use RailsOtelContext::BodyCapture

# With options:
config.middleware.use RailsOtelContext::BodyCapture,
  on_error_only: true,
  max_bytes:     4096

Span attributes set:

http.request.body  — captured request body (when content type matches)
http.response.body — captured response body (when content type matches)

Instance Method Summary collapse

Constructor Details

#initialize(app, capture_request: true, capture_response: true, max_bytes: DEFAULT_MAX_BYTES, on_error_only: false, content_types: DEFAULT_CONTENT_TYPES, include_paths: [], exclude_paths: DEFAULT_EXCLUDE_PATHS) ⇒ BodyCapture

rubocop:disable Metrics/ParameterLists



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/rails_otel_context/body_capture.rb', line 28

def initialize(app, # rubocop:disable Metrics/ParameterLists
               capture_request:  true,
               capture_response: true,
               max_bytes:        DEFAULT_MAX_BYTES,
               on_error_only:    false,
               content_types:    DEFAULT_CONTENT_TYPES,
               include_paths:    [],
               exclude_paths:    DEFAULT_EXCLUDE_PATHS)
  @app              = app
  @capture_request  = capture_request
  @capture_response = capture_response
  @max_bytes        = max_bytes
  @on_error_only    = on_error_only
  @content_types    = content_types
  @include_paths    = include_paths
  @exclude_paths    = exclude_paths
end

Instance Method Details

#call(env) ⇒ Object



46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/rails_otel_context/body_capture.rb', line 46

def call(env)
  return @app.call(env) unless should_capture?(env['PATH_INFO'])

  request_str           = read_request(env)
  status, headers, body = @app.call(env)

  # With on_error_only, skip body drain on successful responses — no buffering
  # overhead on the happy path. (Synchronous Rack means we know status before
  # deciding to drain, unlike async frameworks that must always buffer.)
  should_record = !@on_error_only || status >= 400

  if should_record && @capture_response
    body, response_str = drain_response(body, headers)
  end

  set_span_attributes(request_str, response_str) if should_record

  [status, headers, body]
end