closeyourit-ruby 0.9.4 → 0.10.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +6 -0
  3. data/lib/closeyourit/background_worker.rb +28 -9
  4. data/lib/closeyourit/breadcrumb.rb +2 -2
  5. data/lib/closeyourit/breadcrumb_buffer.rb +2 -2
  6. data/lib/closeyourit/client.rb +42 -29
  7. data/lib/closeyourit/configuration.rb +105 -101
  8. data/lib/closeyourit/event.rb +6 -6
  9. data/lib/closeyourit/events/error_event.rb +16 -16
  10. data/lib/closeyourit/events/job_metric_event.rb +8 -8
  11. data/lib/closeyourit/events/log_event.rb +17 -17
  12. data/lib/closeyourit/events/message_event.rb +4 -4
  13. data/lib/closeyourit/events/performance_issue_event.rb +4 -4
  14. data/lib/closeyourit/events/slow_method_event.rb +6 -6
  15. data/lib/closeyourit/events/slow_query_event.rb +4 -4
  16. data/lib/closeyourit/instrumenter.rb +12 -4
  17. data/lib/closeyourit/line_cache.rb +4 -4
  18. data/lib/closeyourit/log_buffer.rb +10 -10
  19. data/lib/closeyourit/log_device.rb +18 -18
  20. data/lib/closeyourit/monitor.rb +2 -2
  21. data/lib/closeyourit/performance/request_profile.rb +5 -5
  22. data/lib/closeyourit/performance/rollup.rb +3 -3
  23. data/lib/closeyourit/rails/active_job_extension.rb +33 -31
  24. data/lib/closeyourit/rails/capture_exceptions.rb +3 -3
  25. data/lib/closeyourit/rails/error_subscriber.rb +4 -4
  26. data/lib/closeyourit/rails/log_broadcast.rb +12 -12
  27. data/lib/closeyourit/rails/net_http_patch.rb +22 -22
  28. data/lib/closeyourit/rails/query_source.rb +3 -3
  29. data/lib/closeyourit/rails/railtie.rb +32 -52
  30. data/lib/closeyourit/rails/request_body.rb +7 -7
  31. data/lib/closeyourit/rails/request_context.rb +27 -27
  32. data/lib/closeyourit/scope.rb +29 -29
  33. data/lib/closeyourit/scrubber.rb +47 -48
  34. data/lib/closeyourit/sidekiq/error_handler.rb +2 -2
  35. data/lib/closeyourit/sidekiq/job_metrics_middleware.rb +10 -11
  36. data/lib/closeyourit/stats.rb +8 -8
  37. data/lib/closeyourit/subscribers/job_performance.rb +30 -19
  38. data/lib/closeyourit/subscribers/request_performance.rb +4 -4
  39. data/lib/closeyourit/subscribers/slow_query.rb +42 -20
  40. data/lib/closeyourit/trace_context.rb +22 -22
  41. data/lib/closeyourit/transport.rb +25 -20
  42. data/lib/closeyourit/usage_registry.rb +17 -16
  43. data/lib/closeyourit/version.rb +1 -1
  44. data/lib/closeyourit-ruby.rb +125 -125
  45. metadata +1 -1
@@ -5,15 +5,15 @@ require_relative "../scope"
5
5
 
6
6
  module CloseYourIt
7
7
  module Rails
8
- # Prepended a Net::HTTP: cronometra ogni chiamata esterna e la spinge nel RequestProfile dello
9
- # Scope, così la finestra della richiesta può rilevare le HTTP esterne lente. Trasparente
10
- # (restituisce la risposta originale) e difensivo (no-op se la telemetria è off; mai solleva per
11
- # colpa del profiling). Esclude le chiamate verso l'endpoint CloseYourIt stesso (niente loop).
8
+ # Prepended to Net::HTTP: times every external call and pushes it into the Scope's RequestProfile,
9
+ # so the request window can detect slow external HTTP. Transparent (returns the original response)
10
+ # and defensive (no-op when telemetry is off; never raises because of profiling). Excludes calls to
11
+ # the CloseYourIt endpoint itself (no loop).
12
12
  module NetHTTPPatch
13
13
  def request(req, body = nil, &block)
14
14
  config = CloseYourIt.configuration
15
- # Propagazione W3C: indipendente dal profiling (ha il suo opt-in), va fatta PRIMA del round-trip
16
- # perché aggiunge header alla richiesta in uscita.
15
+ # W3C propagation: independent of profiling (it has its own opt-in), must happen BEFORE the
16
+ # round-trip because it adds headers to the outgoing request.
17
17
  inject_trace_context(config, req)
18
18
  return super unless config.detect_performance_issues && config.capture_external_http
19
19
 
@@ -28,12 +28,12 @@ module CloseYourIt
28
28
 
29
29
  private
30
30
 
31
- # Gestisce gli header di trace W3C sulla richiesta in uscita (solo con la propagazione opt-in ON).
32
- # Verso una destinazione autorizzata inietta traceparent/tracestate; verso qualsiasi altra li
33
- # RIMUOVE. La rimozione è la difesa contro il leak su redirect cross-host: se lo stesso oggetto
34
- # request viene riusato per seguire un redirect verso un host non in allowlist, gli header della
35
- # chiamata precedente non devono sopravvivere ("host esterni non ricevono header interni" — CYRB-15).
36
- # baggage non viene mai né letto né emesso. Difensivo: mai solleva per colpa della propagazione.
31
+ # Handles the W3C trace headers on the outgoing request (only with opt-in propagation ON).
32
+ # To an allowed destination it injects traceparent/tracestate; to any other it REMOVES them.
33
+ # Removal is the defense against leaks on cross-host redirects: if the same request object is
34
+ # reused to follow a redirect to a host not in the allowlist, the previous call's headers must not
35
+ # survive ("external hosts do not receive internal headers" — CYRB-15).
36
+ # baggage is never read nor emitted. Defensive: never raises because of propagation.
37
37
  def inject_trace_context(config, req)
38
38
  return unless config.propagate_trace_context
39
39
 
@@ -47,9 +47,9 @@ module CloseYourIt
47
47
  nil
48
48
  end
49
49
 
50
- # Il trace context da consegnare a QUESTA destinazione, o nil se non va propagato nulla: host
51
- # assente, endpoint CloseYourIt stesso (niente auto-propagazione), destinazione fuori allowlist,
52
- # o scope privo di contesto.
50
+ # The trace context to deliver to THIS destination, or nil if nothing must be propagated: missing
51
+ # host, the CloseYourIt endpoint itself (no self-propagation), destination outside the allowlist,
52
+ # or a scope without context.
53
53
  def deliverable_context(config)
54
54
  host = address
55
55
  return nil if host.nil? || own_endpoint?(config, host)
@@ -58,16 +58,16 @@ module CloseYourIt
58
58
  CloseYourIt::Scope.current.trace_context
59
59
  end
60
60
 
61
- # La destinazione è autorizzata a ricevere il trace context? String = host esatto (case-insensitive),
62
- # Regexp = pattern (sottodomini/famiglie). Lista vuota → sempre false (nessuna destinazione).
61
+ # Is the destination allowed to receive the trace context? String = exact host (case-insensitive),
62
+ # Regexp = pattern (subdomains/families). Empty list → always false (no destination).
63
63
  def destination_allowed?(config, host)
64
64
  config.trace_propagation_allowlist.any? do |pattern|
65
65
  pattern.is_a?(Regexp) ? pattern.match?(host) : pattern.to_s.casecmp?(host)
66
66
  end
67
67
  end
68
68
 
69
- # Rimuove gli header di trace W3C che una chiamata precedente sullo stesso oggetto request possa
70
- # aver lasciato. Solo i nostri header, mai altro; no-op se il request non li supporta.
69
+ # Removes the W3C trace headers a previous call on the same request object may have left.
70
+ # Only our headers, nothing else; no-op if the request does not support them.
71
71
  def strip_trace_headers(req)
72
72
  return unless req.respond_to?(:delete)
73
73
 
@@ -83,7 +83,7 @@ module CloseYourIt
83
83
  host: host, path: templatize_path(req), duration_ms: duration_ms
84
84
  )
85
85
  rescue StandardError
86
- # Il profiling non deve mai disturbare la chiamata ospite.
86
+ # Profiling must never disturb the host call.
87
87
  nil
88
88
  end
89
89
 
@@ -96,8 +96,8 @@ module CloseYourIt
96
96
  false
97
97
  end
98
98
 
99
- # Path senza query string, con uuid e run di ≥3 cifre → placeholder (stessa rotta = stessa
100
- # signature). La soglia ≥3 cifre preserva le versioni API tipo "/v1" e templatizza gli id reali
99
+ # Path without query string, with uuids and runs of ≥3 digits → placeholders (same route = same
100
+ # signature). The ≥3 digit threshold keeps API versions like "/v1" and templatizes real ids
101
101
  # ("/v1/charges/ch_12345" → "/v1/charges/ch_<n>", "/users/123" → "/users/<n>").
102
102
  def templatize_path(req)
103
103
  path = req.respond_to?(:path) ? req.path.to_s : ""
@@ -2,9 +2,9 @@
2
2
 
3
3
  module CloseYourIt
4
4
  module Rails
5
- # Call-site applicativo di una query lenta (privacy-safe → sempre inviato): primo frame della
6
- # backtrace ripulito da Rails.backtrace_cleaner (rimuove gem/framework, tiene il codice app),
7
- # senza il suffisso ":in '...'". Es. "app/models/order.rb:42".
5
+ # Application call site of a slow query (privacy-safe → always sent): first backtrace frame
6
+ # cleaned by Rails.backtrace_cleaner (removes gems/framework, keeps app code), without the
7
+ # ":in '...'" suffix. E.g. "app/models/order.rb:42".
8
8
  module QuerySource
9
9
  def self.from_caller(backtrace = caller)
10
10
  frame = ::Rails.backtrace_cleaner.clean(backtrace).first
@@ -15,13 +15,13 @@ require_relative "../sidekiq/job_metrics_middleware"
15
15
 
16
16
  module CloseYourIt
17
17
  module Rails
18
- # Aggancia il client a Rails: Rack middleware di cattura eccezioni +
19
- # subscriber `sql.active_record` per le query lente.
18
+ # Hooks the client into Rails: exception-capturing Rack middleware +
19
+ # `sql.active_record` subscriber for slow queries.
20
20
  class Railtie < ::Rails::Railtie
21
21
  initializer "closeyourit.use_rack_middleware" do |app|
22
22
  app.config.middleware.use CloseYourIt::Rails::CaptureExceptions
23
- # RequestContext deve AVVOLGERE CaptureExceptions: lo scope dev'essere già popolato
24
- # quando l'eccezione risale a CaptureExceptions.
23
+ # RequestContext must WRAP CaptureExceptions: the scope must already be populated
24
+ # when the exception bubbles up to CaptureExceptions.
25
25
  app.config.middleware.insert_before(
26
26
  CloseYourIt::Rails::CaptureExceptions,
27
27
  CloseYourIt::Rails::RequestContext
@@ -33,43 +33,22 @@ module CloseYourIt
33
33
 
34
34
  ActiveSupport::Notifications.subscribe("sql.active_record") do |*args|
35
35
  event = ActiveSupport::Notifications::Event.new(*args)
36
- subscriber.record(
37
- name: event.payload[:name],
38
- duration_ms: event.duration,
39
- sql: event.payload[:sql],
40
- cached: event.payload.fetch(:cached, false),
41
- connection: event.payload[:connection],
42
- binds: event.payload[:binds],
43
- type_casted_binds: event.payload[:type_casted_binds],
44
- source: CloseYourIt::Rails::QuerySource.from_caller
45
- )
46
- subscriber.breadcrumb(
47
- name: event.payload[:name],
48
- sql: event.payload[:sql],
49
- duration_ms: event.duration,
50
- cached: event.payload.fetch(:cached, false)
51
- )
52
- # Accumula la query nel profilo per-richiesta (detection N+1 a fine richiesta).
53
- subscriber.profile(
54
- name: event.payload[:name],
55
- sql: event.payload[:sql],
56
- duration_ms: event.duration,
57
- cached: event.payload.fetch(:cached, false),
58
- source: CloseYourIt::Rails::QuerySource.from_caller
59
- )
36
+ # Slow query, breadcrumb and per-request profile (N+1 detection). The call site is lazy:
37
+ # a backtrace for every query was the SDK's biggest cost on the host thread.
38
+ subscriber.notify(event.payload, event.duration, -> { CloseYourIt::Rails::QuerySource.from_caller })
60
39
  end
61
40
  end
62
41
 
63
- # A fine richiesta: trasforma il profilo accumulato in verdetti performance_issue (N+1, slow
64
- # request, HTTP esterne lente). Il subscriber è no-op se detect_performance_issues è OFF.
42
+ # At the end of the request: turns the collected profile into performance_issue verdicts (N+1,
43
+ # slow request, slow external HTTP). The subscriber is a no-op when detect_performance_issues is OFF.
65
44
  initializer "closeyourit.subscribe_request_performance" do
66
45
  perf = CloseYourIt::Subscribers::RequestPerformance.new
67
46
 
68
47
  ActiveSupport::Notifications.subscribe("process_action.action_controller") do |*args|
69
48
  event = ActiveSupport::Notifications::Event.new(*args)
70
49
  payload = event.payload
71
- # CYSK-29 — telemetria d'uso: la rotta è `Controller#action`, MAI l'URL. Il registro fa
72
- # una lookup e un increment; il gate usage_enabled sta dentro #record.
50
+ # CYSK-29 — usage telemetry: the route is `Controller#action`, NEVER the URL. The registry does
51
+ # one lookup and one increment; the usage_enabled gate lives inside #record.
73
52
  CloseYourIt.usage_registry.record("route", "#{payload[:controller]}##{payload[:action]}") if CloseYourIt.enabled?
74
53
  perf.record(
75
54
  route: "#{payload[:controller]}##{payload[:action]}",
@@ -78,24 +57,25 @@ module CloseYourIt
78
57
  end
79
58
  end
80
59
 
81
- # Strumenta Net::HTTP: rileva le chiamate esterne lente (solo con detect_performance_issues) e
82
- # propaga il trace context W3C (solo con propagate_trace_context). Prepend incondizionato — i due
83
- # opt-in sono valutati per-chiamata nel patch; con entrambi OFF è di fatto no-op (chiama super).
60
+ # Instruments Net::HTTP: detects slow external calls (only with detect_performance_issues) and
61
+ # propagates the W3C trace context (only with propagate_trace_context). Unconditional prepend —
62
+ # the two opt-ins are evaluated per call in the patch; with both OFF it is in effect a no-op (calls super).
84
63
  initializer "closeyourit.instrument_net_http" do
85
64
  require "net/http"
86
65
  ::Net::HTTP.prepend(CloseYourIt::Rails::NetHTTPPatch) unless ::Net::HTTP.ancestors.include?(CloseYourIt::Rails::NetHTTPPatch)
87
66
  end
88
67
 
89
- # Cattura gli errori di ActiveJob/Solid Queue (around_perform).
68
+ # Captures ActiveJob/Solid Queue errors (around_perform).
90
69
  initializer "closeyourit.active_job" do
91
70
  ActiveSupport.on_load(:active_job) do
92
71
  include CloseYourIt::Rails::ActiveJobExtension
93
72
  end
94
73
  end
95
74
 
96
- # Misura durata e attesa in coda dei job ActiveJob via notifiche ActiveSupport: `perform_start`
97
- # dà l'attesa (now - enqueued_at) appena il job parte, `perform` dà la durata dell'esecuzione a
98
- # fine job. Oltre soglia → metriche slow_job / job_queue_latency. No-op se monitor_jobs è OFF.
75
+ # Measures duration and queue wait of ActiveJob jobs via ActiveSupport notifications:
76
+ # `perform_start` gives the wait (now - enqueued_at) as soon as the job starts, `perform` gives
77
+ # the execution duration at the end. Beyond threshold → slow_job / job_queue_latency metrics.
78
+ # No-op when monitor_jobs is OFF.
99
79
  initializer "closeyourit.subscribe_active_job_performance" do
100
80
  jobs = CloseYourIt::Subscribers::JobPerformance.new
101
81
 
@@ -107,28 +87,28 @@ module CloseYourIt
107
87
  ActiveSupport::Notifications.subscribe("perform.active_job") do |*args|
108
88
  event = ActiveSupport::Notifications::Event.new(*args)
109
89
  job = event.payload[:job]
110
- # CYSK-29 — anche i job dichiarano di essere girati: kind `job`, simbolo = la classe.
90
+ # CYSK-29 — jobs also declare they ran: kind `job`, symbol = the class.
111
91
  CloseYourIt.usage_registry.record("job", job.class.name) if job && CloseYourIt.enabled?
112
92
  jobs.active_job_performed(job, event.duration) if job
113
93
  end
114
94
  end
115
95
 
116
- # Cattura gli errori HANDLED riportati via Rails.error.report (Rails 7+).
96
+ # Captures HANDLED errors reported via Rails.error.report (Rails 7+).
117
97
  initializer "closeyourit.error_reporter" do
118
98
  if ::Rails.respond_to?(:error) && ::Rails.error.respond_to?(:subscribe)
119
99
  ::Rails.error.subscribe(CloseYourIt::Rails::ErrorSubscriber.new)
120
100
  end
121
101
  end
122
102
 
123
- # Broadcast opt-in di Rails.logger → CloseYourIt.log (config.capture_rails_logs, default OFF).
124
- # Spedisce solo i log dell'app ≥ capture_rails_logs_min_level: soglia DEDICATA (default :warn),
125
- # distinta da logs_min_level, così il broadcast non inonda lo stream col rumore info del framework
126
- # (Started GET, Rendered, ... — una riga per richiesta ad alto traffico, CYRB-7). Richiede
103
+ # Opt-in Rails.logger → CloseYourIt.log broadcast (config.capture_rails_logs, default OFF).
104
+ # Sends only app logs ≥ capture_rails_logs_min_level: a DEDICATED threshold (default :warn),
105
+ # separate from logs_min_level, so the broadcast does not flood the stream with framework info
106
+ # noise (Started GET, Rendered, ... — one line per request under high traffic, CYRB-7). Requires
127
107
  # BroadcastLogger (Rails 7.1+).
128
- # `after: :load_config_initializers`: config.capture_rails_logs è impostato in
129
- # config/initializers/closeyourit.rb (CloseYourIt.init), che gira DOPO gli initializer dei
130
- # railtie. Senza questo `after:` il check leggerebbe il default (false) e il broadcast non
131
- # verrebbe mai agganciato → i log dell'app non arriverebbero a CloseYourIt.
108
+ # `after: :load_config_initializers`: config.capture_rails_logs is set in
109
+ # config/initializers/closeyourit.rb (CloseYourIt.init), which runs AFTER the railtie
110
+ # initializers. Without this `after:` the check would read the default (false) and the broadcast
111
+ # would never be attached → the app logs would never reach CloseYourIt.
132
112
  initializer "closeyourit.capture_rails_logs", after: :load_config_initializers do
133
113
  config = CloseYourIt.configuration
134
114
  if config.capture_rails_logs && ::Rails.logger.respond_to?(:broadcast_to)
@@ -136,9 +116,9 @@ module CloseYourIt
136
116
  end
137
117
  end
138
118
 
139
- # Cattura gli errori dei job Sidekiq + misura durata/attesa via server middleware (solo se
140
- # Sidekiq è presente). Il middleware è no-op effettivo se monitor_jobs è OFF (la decisione vive
141
- # in JobPerformance#record).
119
+ # Captures Sidekiq job errors + measures duration/wait via server middleware (only when Sidekiq
120
+ # is present). The middleware is effectively a no-op when monitor_jobs is OFF (the decision
121
+ # lives in JobPerformance#record).
142
122
  initializer "closeyourit.sidekiq" do
143
123
  if defined?(::Sidekiq) && ::Sidekiq.respond_to?(:configure_server)
144
124
  ::Sidekiq.configure_server do |sidekiq_config|
@@ -6,11 +6,11 @@ require_relative "../scrubber"
6
6
 
7
7
  module CloseYourIt
8
8
  module Rails
9
- # Estrae i params del body della richiesta (`request.data`) al momento dell'EVENTO — mai
10
- # eagerly a ogni richiesta (zero overhead sul percorso felice). Preferisce i params già
11
- # parsati da Rails/Rack presenti in env; ripiega sulla rilettura di rack.input (con rewind)
12
- # solo per JSON/form ≤ MAX_BODY_BYTES. Output sanitizzato (upload → "[FILE: …]", oggetti →
13
- # "[OBJECT: …]", stringhe troncate) e scrubbato con la stessa denylist del resto del client.
9
+ # Extracts the request body params (`request.data`) at EVENT time — never eagerly on every
10
+ # request (zero overhead on the happy path). Prefers the params already parsed by Rails/Rack in
11
+ # env; falls back to re-reading rack.input (with rewind) only for JSON/form ≤ MAX_BODY_BYTES.
12
+ # Sanitized output (uploads → "[FILE: …]", objects → "[OBJECT: …]", truncated strings) and
13
+ # scrubbed with the same denylist as the rest of the client.
14
14
  module RequestBody
15
15
  MAX_BODY_BYTES = 65_536
16
16
  MAX_DEPTH = 8
@@ -31,7 +31,7 @@ module CloseYourIt
31
31
 
32
32
  private
33
33
 
34
- # Params già parsati a monte (ActionDispatch o Rack): nessuna rilettura del body.
34
+ # Params already parsed upstream (ActionDispatch or Rack): no body re-read.
35
35
  def parsed_params(env)
36
36
  env["action_dispatch.request.request_parameters"] || env["rack.request.form_hash"]
37
37
  end
@@ -45,7 +45,7 @@ module CloseYourIt
45
45
  value = JSON.parse(raw)
46
46
  value.is_a?(Hash) ? value : { "_json" => value }
47
47
  when FORM_TYPE
48
- # Fallback flat (stdlib): il percorso reale con nesting passa dai params già parsati.
48
+ # Flat fallback (stdlib): the real path with nesting goes through the already parsed params.
49
49
  URI.decode_www_form(raw).to_h
50
50
  end
51
51
  rescue JSON::ParserError, ArgumentError
@@ -5,16 +5,16 @@ require_relative "../trace_context"
5
5
 
6
6
  module CloseYourIt
7
7
  module Rails
8
- # Rack middleware: popola lo Scope con il contesto HTTP della richiesta (method/url/header)
9
- # così l'evento d'errore catturato a valle sa "in quale pagina" è capitato. Deve avvolgere
10
- # `CaptureExceptions` (insert_before) per essere già popolato quando l'eccezione risale.
11
- # Rack puro (legge `env`, nessuna dipendenza da Rails) → testabile in isolamento.
8
+ # Rack middleware: populates the Scope with the request's HTTP context (method/url/headers)
9
+ # so the error event captured downstream knows "on which page" it happened. It must wrap
10
+ # `CaptureExceptions` (insert_before) to be already populated when the exception bubbles up.
11
+ # Pure Rack (reads `env`, no Rails dependency) → testable in isolation.
12
12
  class RequestContext
13
- # Header con prefisso non-HTTP_ in env Rack.
13
+ # Headers without the HTTP_ prefix in the Rack env.
14
14
  RAW_HEADERS = %w[CONTENT_TYPE CONTENT_LENGTH].freeze
15
15
 
16
- # Cookie di correlazione scritto dal browser SDK (session replay): id opaco della
17
- # sessione di replay, trasportato da ogni richiesta same-origin.
16
+ # Correlation cookie written by the browser SDK (session replay): opaque id of the
17
+ # replay session, carried by every same-origin request.
18
18
  REPLAY_COOKIE = "cyi_replay"
19
19
 
20
20
  def initialize(app)
@@ -23,19 +23,19 @@ module CloseYourIt
23
23
 
24
24
  def call(env)
25
25
  if enabled?
26
- # Contesto di trace W3C (solo con propagazione opt-in): adottato dall'header entrante o generato.
26
+ # W3C trace context (only with opt-in propagation): adopted from the incoming header or generated.
27
27
  context = trace_context_for(env)
28
28
  CloseYourIt::Scope.current.trace_context = context
29
- # trace_id sempre (correlazione log↔errori), anche con capture_request OFF. Con un trace context
30
- # W3C il trace_id degli eventi È il trace-id W3C → l'errore/metrica si allinea alla traccia
31
- # distribuita propagata a valle (mapping trace_id, CYRB-15); altrimenti request_id come prima.
29
+ # trace_id always (log↔error correlation), even with capture_request OFF. With a W3C trace
30
+ # context the events' trace_id IS the W3C trace-id → the error/metric lines up with the
31
+ # distributed trace propagated downstream (trace_id mapping, CYRB-15); otherwise request_id as before.
32
32
  CloseYourIt::Scope.current.trace_id = context ? context.trace_id : trace_id_for(env)
33
- # Correlazione errore server ↔ session replay: l'id dal cookie finisce sullo scope
34
- # → contexts.replay.replay_id dell'evento (stesso punto del percorso JS).
33
+ # Server error ↔ session replay correlation: the id from the cookie lands on the scope
34
+ # → the event's contexts.replay.replay_id (same spot as the JS path).
35
35
  CloseYourIt::Scope.current.replay_session_id = replay_session_id_for(env)
36
36
  if CloseYourIt.configuration.capture_request
37
37
  CloseYourIt::Scope.current.request = build_request(env)
38
- # Riferimento all'env per l'estrazione LAZY del body (request.data) a evento costruito.
38
+ # Reference to the env for LAZY body extraction (request.data) once the event is built.
39
39
  CloseYourIt::Scope.current.rack_env = env
40
40
  end
41
41
  end
@@ -52,10 +52,10 @@ module CloseYourIt
52
52
  false
53
53
  end
54
54
 
55
- # Contesto di trace W3C della richiesta, SOLO con la propagazione opt-in attiva (altrimenti nil →
56
- # comportamento storico invariato). Adotta un traceparent/tracestate entrante valido (correlazione
57
- # distribuita), altrimenti genera un root: così anche i servizi che ORIGINANO traffico partono con
58
- # un trace-id W3C propagabile a valle. Un traceparent malformato è ignorato → si genera un root.
55
+ # W3C trace context of the request, ONLY with opt-in propagation on (otherwise nil → historical
56
+ # behavior unchanged). Adopts a valid incoming traceparent/tracestate (distributed correlation),
57
+ # otherwise generates a root: so services that ORIGINATE traffic also start with a W3C trace-id
58
+ # that can be propagated downstream. A malformed traceparent is ignored → a root is generated.
59
59
  def trace_context_for(env)
60
60
  return nil unless CloseYourIt.configuration.propagate_trace_context
61
61
 
@@ -63,28 +63,28 @@ module CloseYourIt
63
63
  CloseYourIt::TraceContext.generate
64
64
  end
65
65
 
66
- # Riusa il request id di Rails/Rack se presente (stessa correlazione dei log applicativi),
67
- # altrimenti ne genera uno.
66
+ # Reuses the Rails/Rack request id when present (same correlation as the application logs),
67
+ # otherwise generates one.
68
68
  def trace_id_for(env)
69
69
  env["action_dispatch.request_id"] ||
70
70
  env["HTTP_X_REQUEST_ID"].to_s.split(",").first&.strip.then { |id| id&.empty? ? nil : id } ||
71
71
  SecureRandom.uuid
72
72
  end
73
73
 
74
- # Estrae `cyi_replay` dal cookie della richiesta (nil se assente). Rack puro.
74
+ # Extracts `cyi_replay` from the request cookie (nil if absent). Pure Rack.
75
75
  def replay_session_id_for(env)
76
76
  cookie = env["HTTP_COOKIE"]
77
77
  return nil if cookie.nil? || cookie.empty?
78
78
 
79
- # `rack/utils` caricato lazy: il middleware gira solo dentro un'app Rack (dove Rack c'è di
80
- # sicuro), così `require "closeyourit-ruby"` non forza Rack in app non-web (CLI/worker) e la
81
- # gemma resta installabile senza dichiarare `rack` tra le dipendenze (CYRB-13).
79
+ # `rack/utils` loaded lazily: the middleware runs only inside a Rack app (where Rack is surely
80
+ # present), so `require "closeyourit-ruby"` does not force Rack on non-web apps (CLI/workers)
81
+ # and the gem stays installable without declaring `rack` as a dependency (CYRB-13).
82
82
  require "rack/utils"
83
83
  Rack::Utils.parse_cookies_header(cookie)[REPLAY_COOKIE].to_s.then { |id| id.empty? ? nil : id }
84
84
  end
85
85
 
86
- # Forma `request` Sentry. URL senza query string; header solo dall'allowlist (mai
87
- # Authorization/Cookie). query_string + IP solo con send_pii (opt-in).
86
+ # Sentry `request` shape. URL without query string; headers only from the allowlist (never
87
+ # Authorization/Cookie). query_string + IP only with send_pii (opt-in).
88
88
  def build_request(env)
89
89
  request = {
90
90
  "method" => env["REQUEST_METHOD"],
@@ -101,7 +101,7 @@ module CloseYourIt
101
101
 
102
102
  request
103
103
  rescue StandardError
104
- # La telemetria non deve mai disturbare la richiesta ospite.
104
+ # Telemetry must never disturb the host request.
105
105
  nil
106
106
  end
107
107
 
@@ -4,29 +4,29 @@ require_relative "breadcrumb_buffer"
4
4
  require_relative "performance/request_profile"
5
5
 
6
6
  module CloseYourIt
7
- # Contesto per-richiesta (o per-job) isolato per execution-context (Fiber storage):
8
- # user/tags/extra/contexts/request. Letto da ErrorEvent#to_h sul thread chiamante (sincrono)
9
- # → il worker di invio non lo vede mai e lo scope non cola tra richieste.
7
+ # Per-request (or per-job) context isolated per execution context (Fiber storage):
8
+ # user/tags/extra/contexts/request. Read by ErrorEvent#to_h on the calling thread (synchronous)
9
+ # → the send worker never sees it and the scope does not leak between requests.
10
10
  class Scope
11
11
  STORAGE_KEY = :__closeyourit_scope
12
12
 
13
13
  class << self
14
- # Scope dell'execution-context corrente. Usa `ActiveSupport::IsolatedExecutionState` quando
15
- # presente (rispetta isolation_level: thread su Puma, fiber su Falcon), altrimenti
16
- # `Thread.current` (thread-local puro, NON ereditato dai thread figli → niente bleed).
14
+ # Scope of the current execution context. Uses `ActiveSupport::IsolatedExecutionState` when
15
+ # present (honors isolation_level: thread on Puma, fiber on Falcon), otherwise
16
+ # `Thread.current` (pure thread-local, NOT inherited by child threads → no bleed).
17
17
  def current
18
18
  store[STORAGE_KEY] ||= new
19
19
  end
20
20
 
21
- # Re-installa uno scope salvato in precedenza. Serve a `after_discard` (ActiveJob 7.1+) per
22
- # riprendere lo scope arricchito durante il `perform` — tag/contesti/breadcrumb, incluse le query —
23
- # dopo che il reset di fine `perform` l'ha sganciato dallo storage (CYRB-19). Simmetrico a `reset!`.
21
+ # Reinstalls a previously saved scope. Used by `after_discard` (ActiveJob 7.1+) to get back the
22
+ # scope enriched during `perform` — tags/contexts/breadcrumbs, queries included — after the
23
+ # end-of-`perform` reset detached it from storage (CYRB-19). Symmetric to `reset!`.
24
24
  def current=(scope)
25
25
  store[STORAGE_KEY] = scope
26
26
  end
27
27
 
28
- # Azzera lo scope corrente — chiamato in `ensure` da middleware e job (su Puma il
29
- # thread è riusato: senza reset lo scope colerebbe nella richiesta successiva).
28
+ # Clears the current scope — called in `ensure` by middleware and jobs (on Puma the
29
+ # thread is reused: without a reset the scope would leak into the next request).
30
30
  def reset!
31
31
  store[STORAGE_KEY] = nil
32
32
  end
@@ -73,8 +73,8 @@ module CloseYourIt
73
73
  @breadcrumbs.add(breadcrumb)
74
74
  end
75
75
 
76
- # Profilo di performance per-richiesta (query + HTTP esterne). Lazy: creato al primo accesso,
77
- # azzerato da #clear a fine richiesta. Il verdetto lo calcola Subscribers::RequestPerformance.
76
+ # Per-request performance profile (queries + external HTTP). Lazy: created on first access,
77
+ # reset by #clear at the end of the request. Subscribers::RequestPerformance computes the verdict.
78
78
  def performance_profile
79
79
  @performance_profile ||= Performance::RequestProfile.new
80
80
  end
@@ -88,17 +88,17 @@ module CloseYourIt
88
88
  @rack_env = nil
89
89
  @trace_id = nil
90
90
  @replay_session_id = nil
91
- # Contesto di trace W3C della richiesta (CloseYourIt::TraceContext): popolato solo con la
92
- # propagazione opt-in attiva, consumato dal patch Net::HTTP per gli header d'uscita.
91
+ # W3C trace context of the request (CloseYourIt::TraceContext): populated only when opt-in
92
+ # propagation is on, consumed by the Net::HTTP patch for outgoing headers.
93
93
  @trace_context = nil
94
94
  @breadcrumbs = BreadcrumbBuffer.new(CloseYourIt.configuration.max_breadcrumbs)
95
95
  @performance_profile = nil
96
96
  end
97
97
 
98
- # Sottoinsieme non vuoto in forma evento Sentry (user/tags/extra/contexts/request),
99
- # fuso nel payload da ErrorEvent#to_h. tags/extra/contexts passano dallo Scrubber (denylist
100
- # ricorsiva per chiave): il backend NON li ri-scruba (Errors::Ingest::Normalize li conserva
101
- # verbatim), quindi questa è l'unica rete di sicurezza contro le chiavi sensibili lì — R2.
98
+ # Non-empty subset in Sentry event shape (user/tags/extra/contexts/request), merged into the
99
+ # payload by ErrorEvent#to_h. tags/extra/contexts go through the Scrubber (recursive denylist by
100
+ # key): the backend does NOT scrub them again (Errors::Ingest::Normalize keeps them verbatim),
101
+ # so this is the only safety net against sensitive keys there — R2.
102
102
  def to_event_hash
103
103
  {
104
104
  "user" => serialize_user,
@@ -112,26 +112,26 @@ module CloseYourIt
112
112
 
113
113
  private
114
114
 
115
- # contexts utente + `replay.replay_id` (session replay) quando presente sullo scope: il
116
- # backend legge contexts.replay.replay_id per legare l'errore server al video (stesso punto
117
- # del percorso JS). replay/replay_id non sono chiavi sensibili → sopravvivono allo Scrubber.
115
+ # User contexts + `replay.replay_id` (session replay) when present on the scope: the backend
116
+ # reads contexts.replay.replay_id to link the server error to the video (same spot as the JS
117
+ # path). replay/replay_id are not sensitive keys → they survive the Scrubber.
118
118
  def contexts_payload
119
119
  merged = @contexts.dup
120
120
  merged["replay"] = { "replay_id" => @replay_session_id } if @replay_session_id
121
121
  scrub(presence(merged))
122
122
  end
123
123
 
124
- # Redige i valori delle chiavi sensibili preservando la struttura (es. contexts.runtime resta
125
- # intatto, solo i valori sotto chiavi sensibili diventano [FILTERED]). Riusa lo Scrubber della
126
- # configurazione, lo stesso percorso di breadcrumb.data e degli attributi di log.
124
+ # Redacts the values of sensitive keys preserving the structure (e.g. contexts.runtime stays
125
+ # intact, only values under sensitive keys become [FILTERED]). Reuses the configuration's
126
+ # Scrubber, the same path as breadcrumb.data and log attributes.
127
127
  def scrub(hash)
128
128
  return hash if hash.nil?
129
129
 
130
130
  Scrubber.new(CloseYourIt.configuration).filter_params(hash)
131
131
  end
132
132
 
133
- # Request context + body params (`request.data`) estratti LAZY qui — cioè solo quando un
134
- # evento viene davvero costruito, mai sul percorso felice della richiesta.
133
+ # Request context + body params (`request.data`) extracted LAZILY here — i.e. only when an
134
+ # event is actually built, never on the request's happy path.
135
135
  def request_payload
136
136
  return nil if @request.nil?
137
137
 
@@ -148,8 +148,8 @@ module CloseYourIt
148
148
  nil
149
149
  end
150
150
 
151
- # `user.id` sempre; email/ip_address/username solo con `send_pii` (il backend li strippa
152
- # comunque — difesa in profondità).
151
+ # `user.id` always; email/ip_address/username only with `send_pii` (the backend strips them
152
+ # anyway — defense in depth).
153
153
  def serialize_user
154
154
  return nil if @user.empty?
155
155
  return @user if CloseYourIt.configuration.send_pii