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
@@ -40,39 +40,39 @@ require_relative "closeyourit/rails/error_subscriber"
40
40
  require_relative "closeyourit/sidekiq/error_handler"
41
41
  require_relative "closeyourit/sidekiq/job_metrics_middleware"
42
42
 
43
- # CloseYourIt — client di telemetria (errori + statistiche di query/metodi lenti)
44
- # che invia gli eventi all'endpoint di ingest di CloseYourIt.
43
+ # CloseYourIt — telemetry client (errors + slow query/method statistics)
44
+ # that sends events to the CloseYourIt ingest endpoint.
45
45
  #
46
- # Entry point della gemma (file con trattino come `sentry-ruby`):
47
- # `require "closeyourit-ruby"` carica il modulo `CloseYourIt`.
46
+ # Gem entry point (hyphenated file name, like `sentry-ruby`):
47
+ # `require "closeyourit-ruby"` loads the `CloseYourIt` module.
48
48
  module CloseYourIt
49
- # Eccezione base interna: usata per evitare loop (le nostre eccezioni non vengono catturate).
49
+ # Internal base exception: used to avoid loops (our own exceptions are never captured).
50
50
  class Error < StandardError; end
51
51
 
52
52
  CAPTURED_FLAG = :@__closeyourit_captured
53
53
 
54
- # Flag thread-local che segna "sono già dentro l'hook diagnostico": impedisce che una notifica
55
- # emessa DENTRO l'hook (o da codice da esso invocato) rientri e riesegua l'hook → niente loop di
56
- # auto-monitoraggio (CYRB-12). È per-thread perché le tappe girano su thread diversi (worker pool
57
- # per send/timeout, thread chiamante per enqueue/drop).
54
+ # Thread-local flag meaning "already inside the diagnostic hook": it stops a notification emitted
55
+ # INSIDE the hook (or by code it calls) from re-entering and re-running the hook → no
56
+ # self-monitoring loop (CYRB-12). It is per-thread because the stages run on different threads
57
+ # (worker pool for send/timeout, calling thread for enqueue/drop).
58
58
  DIAGNOSTIC_GUARD = :__closeyourit_in_diagnostic
59
59
 
60
60
  class << self
61
- # Configura il client. Senza token/endpoint → no-op.
61
+ # Configures the client. Without token/endpoint → no-op.
62
62
  #
63
- # Una re-init SPEGNE prima il client e il log buffer della configurazione precedente (CYRB-10):
64
- # azzerarli e basta lascerebbe orfani il thread del worker pool e il TimerTask del buffer, e gli
65
- # eventi ancora in coda andrebbero persi o flushati fuori tempo dal timer orfano con la vecchia
66
- # credenziale. Riusa la semantica di fine-vita di #shutdown (flush del buffer → drain del worker
67
- # con timeout, CYRB-5) — così la coda precedente è svuotata in modo prevedibile con la sua config.
68
- # Idempotente: alla prima init (o senza eventi catturati) @client/@log_buffer sono nil → no-op.
63
+ # A re-init first SHUTS DOWN the client and log buffer of the previous configuration (CYRB-10):
64
+ # just clearing them would orphan the worker pool thread and the buffer's TimerTask, and events
65
+ # still queued would be lost or flushed late by the orphan timer with the old credential. It
66
+ # reuses the end-of-life semantics of #shutdown (buffer flush → worker drain with timeout,
67
+ # CYRB-5), so the previous queue is drained predictably with its own config.
68
+ # Idempotent: on the first init (or with no captured events) @client/@log_buffer are nil → no-op.
69
69
  def init
70
70
  shutdown
71
71
  @configuration = Configuration.new
72
72
  @client = nil
73
73
  @log_buffer = nil
74
74
  @usage_registry = nil
75
- @shutdown_notified = false # nuova sessione: :shutdown potrà essere notificato di nuovo
75
+ @shutdown_notified = false # new session: :shutdown can be notified again
76
76
  yield(@configuration) if block_given?
77
77
  @configuration.validate!
78
78
  register_shutdown_flush
@@ -91,8 +91,8 @@ module CloseYourIt
91
91
  configuration.enabled?
92
92
  end
93
93
 
94
- # Cattura un'eccezione e la spedisce (fire-and-forget). No-op se disabilitato,
95
- # se l'eccezione è esclusa o già catturata.
94
+ # Captures an exception and sends it (fire-and-forget). No-op when disabled,
95
+ # or when the exception is excluded or already captured.
96
96
  def capture_exception(exception, handled: false, level: "error", contexts: nil)
97
97
  return nil if in_diagnostic?
98
98
  return nil unless enabled?
@@ -108,7 +108,7 @@ module CloseYourIt
108
108
  client.capture_event(event)
109
109
  end
110
110
 
111
- # Spedisce un evento già costruito (slow_query/slow_method).
111
+ # Sends an already built event (slow_query/slow_method).
112
112
  def capture_event(event)
113
113
  return nil if in_diagnostic?
114
114
  return nil unless enabled?
@@ -116,7 +116,7 @@ module CloseYourIt
116
116
  client.capture_event(event)
117
117
  end
118
118
 
119
- # Invia un messaggio diagnostico esplicito (non un'eccezione). Soggetto a sampling + scope.
119
+ # Sends an explicit diagnostic message (not an exception). Subject to sampling + scope.
120
120
  # CloseYourIt.capture_message("cache miss storm", level: "warning")
121
121
  def capture_message(message, level: "info")
122
122
  return nil if in_diagnostic?
@@ -127,14 +127,14 @@ module CloseYourIt
127
127
  client.capture_event(event)
128
128
  end
129
129
 
130
- # Cronometra un blocco e invia un slow_method se supera la soglia.
130
+ # Times a block and sends a slow_method if it exceeds the threshold.
131
131
  # CloseYourIt.measure("checkout.total") { ... }
132
132
  def measure(label, &block)
133
133
  Instrumenter.measure(label, &block)
134
134
  end
135
135
 
136
- # --- Scope per-richiesta/job (user/tags/extra/contexts) ---
137
- # Arricchiscono l'evento corrente; resettati a fine richiesta/job da middleware e estensioni.
136
+ # --- Per-request/job scope (user/tags/extra/contexts) ---
137
+ # They enrich the current event; reset at the end of the request/job by middleware and extensions.
138
138
 
139
139
  def set_user(attributes)
140
140
  Scope.current.set_user(attributes)
@@ -164,8 +164,8 @@ module CloseYourIt
164
164
  Scope.reset!
165
165
  end
166
166
 
167
- # Aggiunge una briciola di contesto (query, navigazione, evento custom) all'evento corrente.
168
- # No-op se breadcrumbs disabilitati; `data` viene scrubato (denylist) prima di essere salvato.
167
+ # Adds a context breadcrumb (query, navigation, custom event) to the current event.
168
+ # No-op when breadcrumbs are disabled; `data` is scrubbed (denylist) before being stored.
169
169
  def add_breadcrumb(message: nil, category: nil, type: "default", level: "info", data: {})
170
170
  return nil unless configuration.breadcrumbs_enabled
171
171
 
@@ -175,35 +175,35 @@ module CloseYourIt
175
175
  )
176
176
  end
177
177
 
178
- # Logger interno della gemma (warning/errori diagnostici su stdout). NON è il logging applicativo:
179
- # per spedire log strutturati a CloseYourIt usa `CloseYourIt.log` / `CloseYourIt.logger`.
178
+ # The gem's internal logger (diagnostic warnings/errors on stdout). It is NOT application logging:
179
+ # to send structured logs to CloseYourIt use `CloseYourIt.log` / `CloseYourIt.logger`.
180
180
  def internal_logger
181
181
  @internal_logger ||= default_internal_logger
182
182
  end
183
183
 
184
184
  attr_writer :internal_logger
185
185
 
186
- # Logger applicativo Logger-compatibile: inoltra ogni messaggio a `CloseYourIt.log` (→ ingest /logs).
186
+ # Logger-compatible application logger: forwards every message to `CloseYourIt.log` (→ ingest /logs).
187
187
  # CloseYourIt.logger.info("ordine creato", order_id: 1)
188
188
  def logger
189
189
  @app_logger ||= LogDevice.new
190
190
  end
191
191
 
192
- # API esplicita: costruisce e bufferizza una voce di log strutturata (batch verso /logs,
193
- # fire-and-forget). Il `level` è normalizzato ai livelli canonici (`:warn`→`warning`, downcase;
194
- # ignoto→`info`). Il keyword `logger:` = nome della sorgente del log; ogni altra keyword è un
195
- # attributo dati. I log sotto `logs_min_level` sono scartati con la stessa mappa numerica di
196
- # dart/js (regola standardize, not adapt — CYRB-6): niente costruzione né invio.
192
+ # Explicit API: builds and buffers a structured log entry (batched to /logs, fire-and-forget).
193
+ # `level` is normalized to the canonical levels (`:warn`→`warning`, downcase; unknown→`info`).
194
+ # The `logger:` keyword = name of the log source; every other keyword is a data attribute.
195
+ # Logs below `logs_min_level` are dropped with the same numeric map as dart/js
196
+ # (standardize, not adapt rule — CYRB-6): nothing is built or sent.
197
197
  # CloseYourIt.log(:info, "ordine creato", order_id: 1)
198
198
  # CloseYourIt.log(:warn, "retry", logger: "payments", attempt: 3)
199
199
  def log(level, message, logger: nil, **attributes)
200
200
  emit_log(level, message, source: logger, attributes: attributes)
201
201
  end
202
202
 
203
- # Percorso a basso livello (gating + costruzione + buffer) con sorgente e attributes SEPARATI e
204
- # non-ambigui: usato da `LogDevice` per trattare una chiave `logger` come attributo dati (mai come
205
- # sorgente) e per impostare la sorgente solo via `.named` (child logger, parità dart/js — CYRB-8).
206
- # Le app usano `CloseYourIt.log` / `CloseYourIt.logger`.
203
+ # Low-level path (gating + building + buffer) with SEPARATE, unambiguous source and attributes:
204
+ # used by `LogDevice` to treat a `logger` key as a data attribute (never as the source) and to set
205
+ # the source only via `.named` (child logger, dart/js parity — CYRB-8).
206
+ # Apps use `CloseYourIt.log` / `CloseYourIt.logger`.
207
207
  def emit_log(level, message, source: nil, attributes: {})
208
208
  return nil if in_diagnostic?
209
209
  return nil unless logs_enabled?
@@ -216,26 +216,26 @@ module CloseYourIt
216
216
  nil
217
217
  end
218
218
 
219
- # Vero se i log sono attivi (master switch + flag): usato da LogDevice per NON valutare i block
220
- # costosi (`logger.debug { dump }`) quando il logging è spento.
219
+ # True when logs are active (master switch + flag): used by LogDevice to NOT evaluate expensive
220
+ # blocks (`logger.debug { dump }`) when logging is off.
221
221
  def logs_active?
222
222
  logs_enabled?
223
223
  end
224
224
 
225
- # Vero se una riga del broadcast Rails.logger va scartata: nomina un'eccezione già presente in
226
- # `excluded_exceptions`, oppure combacia con `excluded_log_patterns`.
225
+ # True when a line of the Rails.logger broadcast must be dropped: it names an exception already
226
+ # listed in `excluded_exceptions`, or it matches `excluded_log_patterns`.
227
227
  #
228
- # Serve perché un'eccezione esclusa dal canale ERRORI rientrava da quello dei LOG: Rails la
229
- # registra con `logger.error`, il broadcast inoltrava la riga senza guardarla, e il rumore che
230
- # `excluded_exceptions` aveva appena scartato ricompariva come log-entry. Il 2026-07-30 erano
231
- # 48.000 voci su 49.985 nello stream, quasi tutte `ActionController::RoutingError` da favicon
232
- # mancanti e scansioni di bot — che è nella lista di default dalla prima riga (CYRB-17).
228
+ # Needed because an exception excluded from the ERRORS channel came back through the LOGS one:
229
+ # Rails records it with `logger.error`, the broadcast forwarded the line without looking at it,
230
+ # and the noise `excluded_exceptions` had just dropped reappeared as a log entry. On 2026-07-30
231
+ # it was 48,000 entries out of 49,985 in the stream, almost all `ActionController::RoutingError`
232
+ # from missing favicons and bot scans — which has been in the default list from day one (CYRB-17).
233
233
  #
234
- # `ignored_exception?` non è applicabile: qui la classe arriva come TESTO dentro il messaggio, non
235
- # come oggetto con `ancestors` da confrontare. Da cui il match per sottostringa sui matcher String.
234
+ # `ignored_exception?` does not apply: here the class arrives as TEXT inside the message, not as
235
+ # an object with `ancestors` to compare. Hence the substring match on String matchers.
236
236
  #
237
- # Vale SOLO per il mirror automatico di Rails.logger: un `CloseYourIt.log` scritto di proposito
238
- # dallo sviluppatore non si silenzia mai (chi lo scrive ha già deciso che vuole quella riga).
237
+ # It applies ONLY to the automatic Rails.logger mirror: a `CloseYourIt.log` written on purpose by
238
+ # the developer is never silenced (whoever writes it has already decided they want that line).
239
239
  def ignored_log_message?(text)
240
240
  text = text.to_s
241
241
  return false if text.empty?
@@ -245,7 +245,7 @@ module CloseYourIt
245
245
  if matcher.is_a?(Regexp)
246
246
  matcher.match?(text)
247
247
  else
248
- # Un matcher vuoto combacerebbe con qualunque riga: mai silenziare tutto per una lista sporca.
248
+ # An empty matcher would match any line: never silence everything because of a dirty list.
249
249
  !matcher.empty? && text.include?(matcher)
250
250
  end
251
251
  end
@@ -253,27 +253,27 @@ module CloseYourIt
253
253
  named || config.excluded_log_patterns.any? { |pattern| pattern.match?(text) }
254
254
  end
255
255
 
256
- # Forza l'invio dei log bufferizzati (chiamato anche allo shutdown del processo).
256
+ # Forces sending of buffered logs (also called at process shutdown).
257
257
  def flush_logs
258
258
  @log_buffer&.flush
259
259
  nil
260
260
  end
261
261
 
262
- # Flush di fine-vita: svuota i log bufferizzati E drena il worker asincrono, attendendo (fino a un
263
- # timeout breve) che gli invii in volo verso l'ingest completino. Solo flushare il buffer li
264
- # accoderebbe nel worker che verrebbe poi ucciso alla terminazione → log persi (CYRB-5). Drena
265
- # TUTTO il worker (log + errori + metriche fire-and-forget), non solo i log. Registrato su at_exit
266
- # da CloseYourIt.init; con `config.trap_signals` viene raggiunto anche su SIGTERM. Idempotente:
267
- # richiamarlo è sicuro (buffer già vuoto, worker già fermo → no-op).
262
+ # End-of-life flush: empties the buffered logs AND drains the async worker, waiting (up to a short
263
+ # timeout) for in-flight sends to the ingest to complete. Only flushing the buffer would queue
264
+ # them in the worker, which is then killed on termination → lost logs (CYRB-5). It drains the
265
+ # WHOLE worker (logs + errors + fire-and-forget metrics), not only logs. Registered on at_exit by
266
+ # CloseYourIt.init; with `config.trap_signals` it is also reached on SIGTERM. Idempotent: calling
267
+ # it again is safe (buffer already empty, worker already stopped → no-op).
268
268
  def shutdown
269
- # Ordine critico: prima i buffer (accodano l'ultimo batch nel worker), poi il worker (li drena).
269
+ # Order matters: buffers first (they queue the last batch in the worker), then the worker (drains them).
270
270
  @usage_registry&.shutdown
271
271
  @log_buffer&.shutdown
272
272
  @client&.shutdown
273
- # Riepilogo di fine-vita: l'app riceve lo snapshot dei contatori senza log rumorosi. Emesso una
274
- # sola volta per sessione (uno shutdown esplicito seguito dall'at_exit non deve duplicarlo; il
275
- # flag è azzerato a ogni init). Lo snapshot è best-effort: eventuali invii ancora in volo oltre il
276
- # breve timeout di drain possono non esservi riflessi — il drain non blocca l'uscita (CYRB-5).
273
+ # End-of-life summary: the app receives the counters snapshot without noisy logs. Emitted once
274
+ # per session (an explicit shutdown followed by the at_exit must not duplicate it; the flag is
275
+ # reset on every init). The snapshot is best-effort: sends still in flight beyond the short
276
+ # drain timeout may not be reflected — the drain does not block exit (CYRB-5).
277
277
  unless @shutdown_notified
278
278
  @shutdown_notified = true
279
279
  notify_diagnostic(:shutdown, stats: stats.to_h)
@@ -281,35 +281,35 @@ module CloseYourIt
281
281
  nil
282
282
  end
283
283
 
284
- # Ripristina le risorse di invio in un processo figlio dopo un fork. Chiamalo dai worker hook dei
285
- # server che forkano (Puma `on_worker_boot`, Sidekiq/Unicorn `after_fork`) per ricreare SUBITO worker
286
- # pool e log buffer nel figlio, invece di attendere la rilevazione lazy al primo evento. Opzionale:
287
- # la gemma rileva comunque il cambio PID da sé (vedi #ensure_current_process!). Idempotente e sicuro
288
- # anche se client/buffer non sono ancora stati materializzati (→ no-op, verranno creati lazy).
284
+ # Restores the sending resources in a child process after a fork. Call it from the worker hooks of
285
+ # forking servers (Puma `on_worker_boot`, Sidekiq/Unicorn `after_fork`) to recreate the worker pool
286
+ # and log buffer in the child RIGHT AWAY, instead of waiting for lazy detection on the first event.
287
+ # Optional: the gem detects the PID change on its own anyway (see #ensure_current_process!).
288
+ # Idempotent and safe even if client/buffer were not materialized yet (→ no-op, created lazily).
289
289
  def after_fork
290
290
  discard_inherited_client!
291
291
  @pid = Process.pid
292
292
  nil
293
293
  end
294
294
 
295
- # Contatori diagnostici del client (accodati/scartati/spediti/falliti/timeout).
295
+ # Client diagnostic counters (enqueued/dropped/sent/failed/timeout).
296
296
  # CloseYourIt.stats.to_h # => { enqueued: …, dropped: …, sent: …, failed: …, timeout: … }
297
297
  def stats
298
298
  @stats ||= Stats.new
299
299
  end
300
300
 
301
- # Notifica una tappa del ciclo di vita di un evento all'hook `on_diagnostic` (se configurato).
302
- # `event` è uno tra :enqueue, :send, :drop, :timeout, :shutdown; `details` un Hash privo di dati
303
- # sensibili (es. `{ reason: :queue_full }`, `{ status: 429 }`). Chiamato da Client/Transport/
304
- # BackgroundWorker/LogBuffer. Garanzie (CYRB-12):
305
- # * NON invia telemetria: tocca solo l'hook dell'app e i contatori in-memory;
306
- # * NON innesca loop di auto-monitoraggio: durante l'hook il guard è alzato, e finché è alzato
307
- # sono soppressi SIA i `notify_diagnostic` annidati SIA le API di telemetria (`capture_*`/log,
308
- # vedi #in_diagnostic?). Poiché l'accodamento della telemetria è sincrono nel thread dell'hook
309
- # (solo l'invio HTTP è async), sopprimere l'accodamento chiude il loop anche cross-thread;
310
- # * NON solleva: un hook difettoso è isolato (logga su internal_logger) e mai propagato nell'app.
311
- # L'hook osserva sempre la configurazione CORRENTE: una notifica in volo che completa dopo una
312
- # re-init raggiunge l'hook nuovo (best-effort, coerente col modello fire-and-forget).
301
+ # Notifies a stage of an event's lifecycle to the `on_diagnostic` hook (if configured).
302
+ # `event` is one of :enqueue, :send, :drop, :timeout, :shutdown; `details` a Hash free of
303
+ # sensitive data (e.g. `{ reason: :queue_full }`, `{ status: 429 }`). Called by Client/Transport/
304
+ # BackgroundWorker/LogBuffer. Guarantees (CYRB-12):
305
+ # * it does NOT send telemetry: it only touches the app hook and the in-memory counters;
306
+ # * it does NOT trigger self-monitoring loops: during the hook the guard is raised, and while it
307
+ # is raised BOTH nested `notify_diagnostic` calls AND the telemetry APIs (`capture_*`/log, see
308
+ # #in_diagnostic?) are suppressed. Since telemetry enqueueing is synchronous on the hook's
309
+ # thread (only the HTTP send is async), suppressing enqueueing closes the loop cross-thread too;
310
+ # * it does NOT raise: a faulty hook is isolated (logs to internal_logger), never propagated.
311
+ # The hook always sees the CURRENT configuration: an in-flight notification completing after a
312
+ # re-init reaches the new hook (best-effort, consistent with the fire-and-forget model).
313
313
  def notify_diagnostic(event, **details)
314
314
  hook = configuration.on_diagnostic
315
315
  return nil if hook.nil?
@@ -326,15 +326,15 @@ module CloseYourIt
326
326
  nil
327
327
  end
328
328
 
329
- # CYSK-29 — il registro della telemetria d'uso (una lookup + increment sul percorso caldo).
329
+ # CYSK-29 — the usage telemetry registry (one lookup + increment on the hot path).
330
330
  def usage_registry
331
331
  ensure_current_process!
332
332
  @usage_registry ||= UsageRegistry.new(client: client, configuration: configuration)
333
333
  end
334
334
 
335
- # CYSK-29 — dichiara che un pezzo di codice è stato ESEGUITO. `key` deve essere una stringa
336
- # LETTERALE (mai interpolata con dati): è l'unico modo onesto di rispondere a «questo ramo viene
337
- # mai preso?». No-op se la gemma non è configurata o usage_enabled è OFF.
335
+ # CYSK-29 — declares that a piece of code was EXECUTED. `key` must be a LITERAL string (never
336
+ # interpolated with data): it is the only honest way to answer "is this branch ever taken?".
337
+ # No-op when the gem is not configured or usage_enabled is OFF.
338
338
  def used(key)
339
339
  return nil unless configured? && enabled?
340
340
 
@@ -344,14 +344,14 @@ module CloseYourIt
344
344
 
345
345
  private
346
346
 
347
- # Vero se il thread corrente sta eseguendo l'hook diagnostico: le API di telemetria diventano no-op
348
- # per impedire che un hook (mal scritto) generi altra telemetria e inneschi auto-monitoraggio.
347
+ # True when the current thread is running the diagnostic hook: the telemetry APIs become no-ops
348
+ # so a (badly written) hook cannot generate more telemetry and trigger self-monitoring.
349
349
  def in_diagnostic?
350
350
  Thread.current[DIAGNOSTIC_GUARD] == true
351
351
  end
352
352
 
353
- # Registra uno scarto: incrementa il contatore aggregato `dropped` e notifica `:drop` con il motivo
354
- # (queue_full/before_send/sampled/error). Un unico punto tiene allineati snapshot e hook.
353
+ # Records a drop: increments the aggregate `dropped` counter and notifies `:drop` with the reason
354
+ # (queue_full/before_send/sampled/error). A single place keeps snapshot and hook aligned.
355
355
  def record_drop(reason, **details)
356
356
  stats.increment(:dropped)
357
357
  notify_diagnostic(:drop, reason: reason, **details)
@@ -369,43 +369,43 @@ module CloseYourIt
369
369
  end
370
370
 
371
371
 
372
- # Rileva un fork confrontando il PID del processo in cui @client/@log_buffer sono stati materializzati
373
- # con quello corrente. In un figlio forkato i due oggetti sono ereditati dal padre, ma i loro thread
374
- # — il worker pool di Client e il TimerTask di LogBuffer — vivono solo nel padre (il fork copia il
375
- # solo thread chiamante): gli eventi accodati non partirebbero mai, restando affidati a thread che
376
- # esistono soltanto nel padre. Al cambio di PID abbandoniamo i riferimenti ereditati SENZA #shutdown
377
- # (nessun join possibile sui thread del padre, e un flush del buffer rispedirebbe eventi del padre) →
378
- # i getter li ricreano lazy sotto il processo corrente. Nel padre lo stato resta invariato.
372
+ # Detects a fork by comparing the PID of the process where @client/@log_buffer were materialized
373
+ # with the current one. In a forked child both objects are inherited from the parent, but their
374
+ # threads — the Client worker pool and the LogBuffer TimerTask — live only in the parent (fork
375
+ # copies only the calling thread): queued events would never leave, being left to threads that
376
+ # exist only in the parent. On a PID change we drop the inherited references WITHOUT #shutdown
377
+ # (no join is possible on the parent's threads, and a buffer flush would resend the parent's
378
+ # events) → the getters recreate them lazily under the current process. The parent is unchanged.
379
379
  def ensure_current_process!
380
380
  pid = Process.pid
381
381
  return if @pid == pid
382
382
 
383
- discard_inherited_client! if @pid # non alla prima materializzazione (@pid nil): nulla da abbandonare
383
+ discard_inherited_client! if @pid # not on first materialization (@pid nil): nothing to drop
384
384
  @pid = pid
385
385
  end
386
386
 
387
- # Sgancia i riferimenti a client e log buffer senza drenarli. Usato sia dalla rilevazione lazy del
388
- # fork sia da #after_fork: nel figlio i thread sottostanti non esistono, quindi non c'è nulla da
389
- # joinare e non si deve flushare (rispedirebbe gli eventi del padre). Il GC raccoglie i vecchi
390
- # oggetti; i getter ne creano di nuovi al prossimo accesso.
387
+ # Releases the client and log buffer references without draining them. Used both by lazy fork
388
+ # detection and by #after_fork: in the child the underlying threads do not exist, so there is
389
+ # nothing to join and nothing must be flushed (it would resend the parent's events). GC collects
390
+ # the old objects; the getters create new ones on the next access.
391
391
  def discard_inherited_client!
392
392
  @client = nil
393
393
  @log_buffer = nil
394
394
  @usage_registry = nil
395
395
  end
396
396
 
397
- # I log seguono il master switch del client + il proprio flag dedicato.
397
+ # Logs follow the client master switch + their own dedicated flag.
398
398
  def logs_enabled?
399
399
  enabled? && configuration.logs_enabled
400
400
  end
401
401
 
402
- # Gating per soglia: il log è scartato se la sua severità è sotto `logs_min_level`. La severità è
403
- # la mappa numerica di LogEvent (debug=0 … fatal=4), identica cross-SDK → filtra come dart/js.
402
+ # Threshold gating: the log is dropped when its severity is below `logs_min_level`. Severity is
403
+ # LogEvent's numeric map (debug=0 … fatal=4), identical across SDKs → filters like dart/js.
404
404
  def log_below_min_level?(level)
405
405
  LogEvent.severity(level) < LogEvent.severity(configuration.logs_min_level)
406
406
  end
407
407
 
408
- # Sampling indipendente dei log (1.0 = tutti, 0.0 = nessuno).
408
+ # Independent log sampling (1.0 = all, 0.0 = none).
409
409
  def logs_sampled?
410
410
  rate = configuration.logs_sample_rate.to_f
411
411
  return true if rate >= 1.0
@@ -414,16 +414,16 @@ module CloseYourIt
414
414
  Random.rand < rate
415
415
  end
416
416
 
417
- # Allo shutdown del processo svuota i log bufferizzati e drena il worker asincrono. Senza, i log
418
- # sotto-batch dei processi brevi (rake/CLI) e gli ultimi eventi accodati andrebbero persi all'uscita.
419
- # Con `config.trap_signals` intercetta anche SIGTERM, che di default salta gli at_exit (deploy/Kamal
420
- # perderebbero il flush). Vedi #shutdown (CYRB-5).
417
+ # At process shutdown, empties the buffered logs and drains the async worker. Without it, the
418
+ # sub-batch logs of short processes (rake/CLI) and the last queued events would be lost on exit.
419
+ # With `config.trap_signals` it also intercepts SIGTERM, which skips at_exit by default
420
+ # (deploy/Kamal would lose the flush). See #shutdown (CYRB-5).
421
421
  #
422
- # L'at_exit si registra una sola volta per processo (@shutdown_registered evita N flush da init
423
- # multiple), ma il trap SIGTERM è valutato PRIMA del guard e a OGNI init: deve seguire la config
424
- # CORRENTE, altrimenti una init che attiva `trap_signals` dopo una prima con la flag OFF non lo
425
- # installerebbe mai (il guard di idempotenza ritornerebbe prima). `Signal.trap` è idempotente →
426
- # reinstallare lo stesso handler è innocuo.
422
+ # The at_exit is registered once per process (@shutdown_registered avoids N flushes from multiple
423
+ # inits), but the SIGTERM trap is evaluated BEFORE the guard and on EVERY init: it must follow the
424
+ # CURRENT config, otherwise an init enabling `trap_signals` after a first one with the flag OFF
425
+ # would never install it (the idempotency guard would return first). `Signal.trap` is idempotent
426
+ # → reinstalling the same handler is harmless.
427
427
  def register_shutdown_flush
428
428
  install_term_trap if configuration.trap_signals
429
429
 
@@ -433,17 +433,17 @@ module CloseYourIt
433
433
  at_exit { shutdown }
434
434
  end
435
435
 
436
- # SIGTERM termina il processo SENZA eseguire gli at_exit → il flush di fine-vita non girerebbe.
437
- # Convertiamo TERM in un exit pulito: `exit` solleva SystemExit sul thread principale, che ESEGUE
438
- # gli at_exit (incluso #shutdown) in contesto normale — niente lavoro pesante nel trap handler
439
- # (Mutex/wait_for_termination in trap context solleverebbero ThreadError). Opt-in: sovrascrive un
440
- # eventuale handler TERM dell'app ospite.
436
+ # SIGTERM ends the process WITHOUT running at_exit → the end-of-life flush would not run.
437
+ # We turn TERM into a clean exit: `exit` raises SystemExit on the main thread, which RUNS the
438
+ # at_exit hooks (including #shutdown) in normal context — no heavy work in the trap handler
439
+ # (Mutex/wait_for_termination in trap context would raise ThreadError). Opt-in: it overrides any
440
+ # TERM handler of the host app.
441
441
  def install_term_trap
442
442
  Signal.trap("TERM") { exit }
443
443
  rescue ArgumentError, RuntimeError => e
444
- # Contesti dove TERM non è trap-abile (piattaforma senza TERM, thread non-main): non fatale,
445
- # resta comunque l'at_exit per le uscite normali.
446
- internal_logger.warn("CloseYourIt: trap SIGTERM non installato (#{e.class}: #{e.message})")
444
+ # Contexts where TERM cannot be trapped (platform without TERM, non-main thread): not fatal,
445
+ # the at_exit still covers normal exits.
446
+ internal_logger.warn("CloseYourIt: SIGTERM trap not installed (#{e.class}: #{e.message})")
447
447
  end
448
448
 
449
449
  def ignored_exception?(exception)
@@ -459,7 +459,7 @@ module CloseYourIt
459
459
  end
460
460
  end
461
461
 
462
- # Sampling probabilistico: 1.0 invia sempre, 0.0 mai, intermedio via Random.rand.
462
+ # Probabilistic sampling: 1.0 always sends, 0.0 never, in between via Random.rand.
463
463
  def sampled?
464
464
  rate = configuration.sample_rate.to_f
465
465
  return true if rate >= 1.0
@@ -485,5 +485,5 @@ module CloseYourIt
485
485
  end
486
486
  end
487
487
 
488
- # Integrazione Rails automatica (solo se Rails è presente).
488
+ # Automatic Rails integration (only when Rails is present).
489
489
  require_relative "closeyourit/rails/railtie" if defined?(::Rails::Railtie)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: closeyourit-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.4
4
+ version: 0.10.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alessio Bussolari