Class: Dispatch::Rails::Configuration

Inherits:
Object
  • Object
show all
Defined in:
lib/dispatch/rails/configuration.rb

Constant Summary collapse

MODES =

Install mode. :widget is the default (a UI app embeds the bug-report button); :errors_only is for API-only / headless apps that have no UI to render into — server-side exception capture and manual reporting still work, but the widget and browser-error tags become no-ops.

%i[widget errors_only].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeConfiguration

Returns a new instance of Configuration.



34
35
36
37
38
39
40
41
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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/dispatch/rails/configuration.rb', line 34

def initialize
  @api_key = nil
  @endpoint = "https://dispatchit.app/api/v1/tickets"
  @user = ->(_ctx) { nil }
  @metadata = ->(_ctx) { {} }
  @capture_console = false
  @capture_clicks = true # track the last few clicks as a "user path" for context
  @button_position = "bottom-right"
  # Opt-in: hide the widget on requests with no resolved user (the `user`
  # lambda returned nil/blank). Lets a host app keep the widget off
  # public/unauthenticated pages — a home page, EULA, privacy policy —
  # where anyone could otherwise submit a report. Off by default because
  # the stock `user` resolver returns nil, so defaulting it on would hide
  # the widget for every app that hasn't wired up identity.
  @require_authenticated_user = false

  # Mode + API-only context
  @mode = :widget
  @context = ->(_ctx) { {} } # extra tags resolved from the controller (API key user, headers, …)
  @send_default_params = false # opt-in: include Rails' filtered_parameters in the event

  # Exception tracking defaults
  @capture_exceptions = true
  @capture_browser_errors = true
  @error_endpoint = nil # derived from endpoint when nil
  @environment = nil    # derived from Rails.env when nil
  @release = nil        # e.g. ENV["GIT_SHA"]
  @enabled_environments = %w[production staging]
  @error_sample_rate = 1.0
  @before_send = nil    # ->(event) { event or nil to drop }

  # Browser security/reporting capture. Both opt-in: a permissive or
  # report-only CSP can emit these in bulk, and capture_browser_reports also
  # makes the gem own a URL path. Pick ONE CSP mechanism — the JS
  # securitypolicyviolation listener (capture_csp_violations) OR the native
  # report endpoint (capture_browser_reports) — or a violation reported
  # through both is counted twice.
  @capture_csp_violations = false   # JS: listen for SecurityPolicyViolationEvent
  @capture_browser_reports = false  # Server: accept native browser report POSTs
  @reporting_endpoint_path = "/dispatch/reports"

  # Process lifecycle. Report the exception killing the process (a crash
  # during boot, a dying runner) and drain the send queue before exit so
  # deploys/restarts don't drop captured events.
  @capture_at_exit = true
  @shutdown_timeout = 3 # seconds to wait for the queue to drain at exit; 0 skips the flush

  # SolidQueue infrastructure capture. On by default in enabled environments
  # (no-op unless SolidQueue is actually running, so apps that don't use it
  # pay nothing). These failures — a worker pruned after a missed heartbeat,
  # jobs orphaned by a dead process, a recurring task that didn't enqueue —
  # bypass Rails.error entirely, so without this they never reach Dispatch.
  @capture_solid_queue = true

  # Structured error responses (off by default — opt-in so we never alter a
  # host app's error contract without being asked).
  @structured_error_responses = false
  @annotate_error_body = false # headers-only unless explicitly enabled
  @report_base_url = nil       # derived from endpoint host when nil

  # Traffic heartbeats. On by default in enabled environments: aggregate
  # counts only (one small POST per flush window, regardless of request
  # volume), so the cost is negligible and the confound guard works out of
  # the box. Sampling is independent of error_sample_rate.
  @capture_traffic = true
  @traffic_sample_rate = 1.0
  @heartbeat_flush_seconds = 60
  @heartbeat_endpoint = nil # derived from endpoint when nil
end

Instance Attribute Details

#annotate_error_bodyObject

Structured error responses (API-only)



28
29
30
# File 'lib/dispatch/rails/configuration.rb', line 28

def annotate_error_body
  @annotate_error_body
end

#api_keyObject

Bug-report widget



13
14
15
# File 'lib/dispatch/rails/configuration.rb', line 13

def api_key
  @api_key
end

#before_sendObject

Exception tracking



18
19
20
# File 'lib/dispatch/rails/configuration.rb', line 18

def before_send
  @before_send
end

#button_positionObject

Bug-report widget



13
14
15
# File 'lib/dispatch/rails/configuration.rb', line 13

def button_position
  @button_position
end

#capture_at_exitObject

Process lifecycle (crash-at-exit capture, rake failures, shutdown flush)



23
24
25
# File 'lib/dispatch/rails/configuration.rb', line 23

def capture_at_exit
  @capture_at_exit
end

#capture_browser_errorsObject

Exception tracking



18
19
20
# File 'lib/dispatch/rails/configuration.rb', line 18

def capture_browser_errors
  @capture_browser_errors
end

#capture_browser_reportsObject

Browser security/reporting capture (both opt-in; high-volume + noise-prone)



21
22
23
# File 'lib/dispatch/rails/configuration.rb', line 21

def capture_browser_reports
  @capture_browser_reports
end

#capture_clicksObject

Bug-report widget



13
14
15
# File 'lib/dispatch/rails/configuration.rb', line 13

def capture_clicks
  @capture_clicks
end

#capture_consoleObject

Bug-report widget



13
14
15
# File 'lib/dispatch/rails/configuration.rb', line 13

def capture_console
  @capture_console
end

#capture_csp_violationsObject

Browser security/reporting capture (both opt-in; high-volume + noise-prone)



21
22
23
# File 'lib/dispatch/rails/configuration.rb', line 21

def capture_csp_violations
  @capture_csp_violations
end

#capture_exceptionsObject

Exception tracking



18
19
20
# File 'lib/dispatch/rails/configuration.rb', line 18

def capture_exceptions
  @capture_exceptions
end

#capture_solid_queueObject

SolidQueue infrastructure failures (pruned/orphaned jobs, thread crashes, recurring-enqueue misses) that never flow through Rails.error/ActiveJob.



26
27
28
# File 'lib/dispatch/rails/configuration.rb', line 26

def capture_solid_queue
  @capture_solid_queue
end

#capture_trafficObject

Traffic heartbeats — per-transaction success counts that let the Dispatch server tell “the error stopped because we fixed it” from “…because the path went quiet” (the confound guard behind fix verification).



32
33
34
# File 'lib/dispatch/rails/configuration.rb', line 32

def capture_traffic
  @capture_traffic
end

#contextObject

Mode + API-only context



16
17
18
# File 'lib/dispatch/rails/configuration.rb', line 16

def context
  @context
end

#enabled_environmentsObject

Exception tracking



18
19
20
# File 'lib/dispatch/rails/configuration.rb', line 18

def enabled_environments
  @enabled_environments
end

#endpointObject

Bug-report widget



13
14
15
# File 'lib/dispatch/rails/configuration.rb', line 13

def endpoint
  @endpoint
end

#environmentObject

Exception tracking



18
19
20
# File 'lib/dispatch/rails/configuration.rb', line 18

def environment
  @environment
end

#error_endpointObject

Exception tracking



18
19
20
# File 'lib/dispatch/rails/configuration.rb', line 18

def error_endpoint
  @error_endpoint
end

#error_sample_rateObject

Exception tracking



18
19
20
# File 'lib/dispatch/rails/configuration.rb', line 18

def error_sample_rate
  @error_sample_rate
end

#heartbeat_endpointObject

Traffic heartbeats — per-transaction success counts that let the Dispatch server tell “the error stopped because we fixed it” from “…because the path went quiet” (the confound guard behind fix verification).



32
33
34
# File 'lib/dispatch/rails/configuration.rb', line 32

def heartbeat_endpoint
  @heartbeat_endpoint
end

#heartbeat_flush_secondsObject

Traffic heartbeats — per-transaction success counts that let the Dispatch server tell “the error stopped because we fixed it” from “…because the path went quiet” (the confound guard behind fix verification).



32
33
34
# File 'lib/dispatch/rails/configuration.rb', line 32

def heartbeat_flush_seconds
  @heartbeat_flush_seconds
end

#metadataObject

Bug-report widget



13
14
15
# File 'lib/dispatch/rails/configuration.rb', line 13

def 
  @metadata
end

#modeObject

Mode + API-only context



16
17
18
# File 'lib/dispatch/rails/configuration.rb', line 16

def mode
  @mode
end

#releaseObject

Exception tracking



18
19
20
# File 'lib/dispatch/rails/configuration.rb', line 18

def release
  @release
end

#report_base_urlObject

Structured error responses (API-only)



28
29
30
# File 'lib/dispatch/rails/configuration.rb', line 28

def report_base_url
  @report_base_url
end

#reporting_endpoint_pathObject

Browser security/reporting capture (both opt-in; high-volume + noise-prone)



21
22
23
# File 'lib/dispatch/rails/configuration.rb', line 21

def reporting_endpoint_path
  @reporting_endpoint_path
end

#require_authenticated_userObject

Bug-report widget



13
14
15
# File 'lib/dispatch/rails/configuration.rb', line 13

def require_authenticated_user
  @require_authenticated_user
end

#send_default_paramsObject

Mode + API-only context



16
17
18
# File 'lib/dispatch/rails/configuration.rb', line 16

def send_default_params
  @send_default_params
end

#shutdown_timeoutObject

Process lifecycle (crash-at-exit capture, rake failures, shutdown flush)



23
24
25
# File 'lib/dispatch/rails/configuration.rb', line 23

def shutdown_timeout
  @shutdown_timeout
end

#structured_error_responsesObject

Structured error responses (API-only)



28
29
30
# File 'lib/dispatch/rails/configuration.rb', line 28

def structured_error_responses
  @structured_error_responses
end

#traffic_sample_rateObject

Traffic heartbeats — per-transaction success counts that let the Dispatch server tell “the error stopped because we fixed it” from “…because the path went quiet” (the confound guard behind fix verification).



32
33
34
# File 'lib/dispatch/rails/configuration.rb', line 32

def traffic_sample_rate
  @traffic_sample_rate
end

#userObject

Bug-report widget



13
14
15
# File 'lib/dispatch/rails/configuration.rb', line 13

def user
  @user
end

Instance Method Details

#browser_reports_enabled?Boolean

Fast-path guard for ReportingEndpointMiddleware: own the reporting path only when the host opted in and we have credentials to deliver. Otherwise the middleware passes the request straight through (no surprise 204s).

Returns:

  • (Boolean)


156
157
158
# File 'lib/dispatch/rails/configuration.rb', line 156

def browser_reports_enabled?
  configured? && @capture_browser_reports
end

#configured?Boolean

Returns:

  • (Boolean)


104
105
106
# File 'lib/dispatch/rails/configuration.rb', line 104

def configured?
  api_key.present? && endpoint.present?
end

#effective_environmentObject



145
146
147
# File 'lib/dispatch/rails/configuration.rb', line 145

def effective_environment
  @environment.presence || (defined?(::Rails) && ::Rails.respond_to?(:env) ? ::Rails.env.to_s : "production")
end

#effective_error_endpointObject

Where exception events are posted. Defaults to the same host as the widget endpoint with the path swapped to the Sentry-compatible /store endpoint.



116
117
118
119
120
# File 'lib/dispatch/rails/configuration.rb', line 116

def effective_error_endpoint
  return @error_endpoint if @error_endpoint.present?

  endpoint.to_s.sub(%r{/[^/]+\z}, "/store")
end

#effective_heartbeat_endpointObject

Where per-transaction traffic rollups are posted. Defaults to the same host as the widget endpoint with the path swapped to /heartbeats.



139
140
141
142
143
# File 'lib/dispatch/rails/configuration.rb', line 139

def effective_heartbeat_endpoint
  return @heartbeat_endpoint if @heartbeat_endpoint.present?

  endpoint.to_s.sub(%r{/[^/]+\z}, "/heartbeats")
end

#effective_report_base_urlObject

The base URL (scheme + host) where the Dispatch tenant lives, used to build the human-facing report link surfaced in structured error responses. Falls back to the origin of ‘endpoint` (dispatchit.app).



125
126
127
128
129
130
131
132
133
134
135
# File 'lib/dispatch/rails/configuration.rb', line 125

def effective_report_base_url
  return @report_base_url.to_s.chomp("/") if @report_base_url.present?

  uri = URI.parse(endpoint.to_s)
  return nil if uri.host.nil?

  port = uri.port && ![80, 443].include?(uri.port) ? ":#{uri.port}" : ""
  "#{uri.scheme}://#{uri.host}#{port}"
rescue StandardError
  nil
end

#environment_enabled?Boolean

Returns:

  • (Boolean)


174
175
176
177
# File 'lib/dispatch/rails/configuration.rb', line 174

def environment_enabled?
  list = Array(@enabled_environments).map(&:to_s)
  list.empty? || list.include?(effective_environment)
end

#error_tracking_enabled?Boolean

Returns:

  • (Boolean)


149
150
151
# File 'lib/dispatch/rails/configuration.rb', line 149

def error_tracking_enabled?
  configured? && @capture_exceptions
end

#errors_only?Boolean

Returns:

  • (Boolean)


108
109
110
111
112
# File 'lib/dispatch/rails/configuration.rb', line 108

def errors_only?
  mode.to_sym == :errors_only
rescue StandardError
  false
end

#solid_queue_tracking_enabled?Boolean

SolidQueue capture shares error capture’s gating (credentials + the capture_exceptions master switch + enabled environments), plus its own toggle. The subscriber is wired unconditionally at boot and checks this at event time, so toggling it in an initializer always takes effect.

Returns:

  • (Boolean)


170
171
172
# File 'lib/dispatch/rails/configuration.rb', line 170

def solid_queue_tracking_enabled?
  @capture_solid_queue && error_tracking_enabled? && environment_enabled?
end

#traffic_tracking_enabled?Boolean

Heartbeats piggyback on the same gating as error capture, plus their own toggle. Off in non-enabled environments (so dev/test never phone home).

Returns:

  • (Boolean)


162
163
164
# File 'lib/dispatch/rails/configuration.rb', line 162

def traffic_tracking_enabled?
  @capture_traffic && error_tracking_enabled? && environment_enabled?
end