Module: CloseYourIt

Defined in:
lib/closeyourit-ruby.rb,
lib/closeyourit/event.rb,
lib/closeyourit/scope.rb,
lib/closeyourit/stats.rb,
lib/closeyourit/client.rb,
lib/closeyourit/monitor.rb,
lib/closeyourit/version.rb,
lib/closeyourit/scrubber.rb,
lib/closeyourit/transport.rb,
lib/closeyourit/breadcrumb.rb,
lib/closeyourit/line_cache.rb,
lib/closeyourit/log_buffer.rb,
lib/closeyourit/log_device.rb,
lib/closeyourit/instrumenter.rb,
lib/closeyourit/configuration.rb,
lib/closeyourit/rails/railtie.rb,
lib/closeyourit/trace_context.rb,
lib/closeyourit/usage_registry.rb,
lib/closeyourit/events/log_event.rb,
lib/closeyourit/background_worker.rb,
lib/closeyourit/breadcrumb_buffer.rb,
lib/closeyourit/events/error_event.rb,
lib/closeyourit/performance/rollup.rb,
lib/closeyourit/rails/query_source.rb,
lib/closeyourit/rails/request_body.rb,
lib/closeyourit/rails/log_broadcast.rb,
lib/closeyourit/events/message_event.rb,
lib/closeyourit/rails/net_http_patch.rb,
lib/closeyourit/rails/request_context.rb,
lib/closeyourit/sidekiq/error_handler.rb,
lib/closeyourit/rails/error_subscriber.rb,
lib/closeyourit/subscribers/slow_query.rb,
lib/closeyourit/events/job_metric_event.rb,
lib/closeyourit/events/slow_query_event.rb,
lib/closeyourit/events/slow_method_event.rb,
lib/closeyourit/rails/capture_exceptions.rb,
lib/closeyourit/rails/active_job_extension.rb,
lib/closeyourit/performance/request_profile.rb,
lib/closeyourit/subscribers/job_performance.rb,
lib/closeyourit/events/performance_issue_event.rb,
lib/closeyourit/sidekiq/job_metrics_middleware.rb,
lib/closeyourit/subscribers/request_performance.rb

Overview

CloseYourIt — client di telemetria (errori + statistiche di query/metodi lenti) che invia gli eventi all'endpoint di ingest di CloseYourIt.

Entry point della gemma (file con trattino come sentry-ruby): require "closeyourit-ruby" carica il modulo CloseYourIt.

Defined Under Namespace

Modules: Instrumenter, LineCache, Monitor, Performance, Rails, Sidekiq, Subscribers Classes: BackgroundWorker, Breadcrumb, BreadcrumbBuffer, Client, Configuration, Error, ErrorEvent, Event, JobMetricEvent, LogBuffer, LogDevice, LogEvent, MessageEvent, PerformanceIssueEvent, Scope, Scrubber, SlowMethodEvent, SlowQueryEvent, Stats, TraceContext, Transport, UsageRegistry

Constant Summary collapse

CAPTURED_FLAG =
:@__closeyourit_captured
DIAGNOSTIC_GUARD =

Flag thread-local che segna "sono già dentro l'hook diagnostico": impedisce che una notifica emessa DENTRO l'hook (o da codice da esso invocato) rientri e riesegua l'hook → niente loop di auto-monitoraggio (CYRB-12). È per-thread perché le tappe girano su thread diversi (worker pool per send/timeout, thread chiamante per enqueue/drop).

:__closeyourit_in_diagnostic
VERSION =
"0.9.2"

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.internal_loggerObject

Logger interno della gemma (warning/errori diagnostici su stdout). NON è il logging applicativo: per spedire log strutturati a CloseYourIt usa CloseYourIt.log / CloseYourIt.logger.



180
181
182
# File 'lib/closeyourit-ruby.rb', line 180

def internal_logger
  @internal_logger ||= default_internal_logger
end

Class Method Details

.add_breadcrumb(message: nil, category: nil, type: "default", level: "info", data: {}) ⇒ Object

Aggiunge una briciola di contesto (query, navigazione, evento custom) all'evento corrente. No-op se breadcrumbs disabilitati; data viene scrubato (denylist) prima di essere salvato.



169
170
171
172
173
174
175
176
# File 'lib/closeyourit-ruby.rb', line 169

def add_breadcrumb(message: nil, category: nil, type: "default", level: "info", data: {})
  return nil unless configuration.breadcrumbs_enabled

  scrubbed = data.nil? || data.empty? ? data : Scrubber.new(configuration).filter_params(data)
  Scope.current.add_breadcrumb(
    Breadcrumb.new(message: message, category: category, type: type, level: level, data: scrubbed)
  )
end

.after_forkObject

Ripristina le risorse di invio in un processo figlio dopo un fork. Chiamalo dai worker hook dei server che forkano (Puma on_worker_boot, Sidekiq/Unicorn after_fork) per ricreare SUBITO worker pool e log buffer nel figlio, invece di attendere la rilevazione lazy al primo evento. Opzionale: la gemma rileva comunque il cambio PID da sé (vedi #ensure_current_process!). Idempotente e sicuro anche se client/buffer non sono ancora stati materializzati (→ no-op, verranno creati lazy).



289
290
291
292
293
# File 'lib/closeyourit-ruby.rb', line 289

def after_fork
  discard_inherited_client!
  @pid = Process.pid
  nil
end

.capture_event(event) ⇒ Object

Spedisce un evento già costruito (slow_query/slow_method).



112
113
114
115
116
117
# File 'lib/closeyourit-ruby.rb', line 112

def capture_event(event)
  return nil if in_diagnostic?
  return nil unless enabled?

  client.capture_event(event)
end

.capture_exception(exception, handled: false, level: "error", contexts: nil) ⇒ Object

Cattura un'eccezione e la spedisce (fire-and-forget). No-op se disabilitato, se l'eccezione è esclusa o già catturata.



96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/closeyourit-ruby.rb', line 96

def capture_exception(exception, handled: false, level: "error", contexts: nil)
  return nil if in_diagnostic?
  return nil unless enabled?
  return nil if ignored_exception?(exception)
  return nil if exception_captured?(exception)

  mark_captured(exception)
  return record_drop(:sampled) unless sampled?

  event = ErrorEvent.from_exception(
    exception, configuration: configuration, handled: handled, level: level, contexts: contexts
  )
  client.capture_event(event)
end

.capture_message(message, level: "info") ⇒ Object

Invia un messaggio diagnostico esplicito (non un'eccezione). Soggetto a sampling + scope. CloseYourIt.capture_message("cache miss storm", level: "warning")



121
122
123
124
125
126
127
128
# File 'lib/closeyourit-ruby.rb', line 121

def capture_message(message, level: "info")
  return nil if in_diagnostic?
  return nil unless enabled?
  return record_drop(:sampled) unless sampled?

  event = MessageEvent.new(message, level: level, configuration: configuration)
  client.capture_event(event)
end

.clear_scopeObject



163
164
165
# File 'lib/closeyourit-ruby.rb', line 163

def clear_scope
  Scope.reset!
end

.configurationObject



82
83
84
# File 'lib/closeyourit-ruby.rb', line 82

def configuration
  @configuration ||= Configuration.new
end

.configure_scope {|Scope.current| ... } ⇒ Object

Yields:



159
160
161
# File 'lib/closeyourit-ruby.rb', line 159

def configure_scope
  yield(Scope.current) if block_given?
end

.configured?Boolean

Returns:

  • (Boolean)


86
87
88
# File 'lib/closeyourit-ruby.rb', line 86

def configured?
  !@configuration.nil?
end

.emit_log(level, message, source: nil, attributes: {}) ⇒ Object

Percorso a basso livello (gating + costruzione + buffer) con sorgente e attributes SEPARATI e non-ambigui: usato da LogDevice per trattare una chiave logger come attributo dati (mai come sorgente) e per impostare la sorgente solo via .named (child logger, parità dart/js — CYRB-8). Le app usano CloseYourIt.log / CloseYourIt.logger.



207
208
209
210
211
212
213
214
215
216
217
# File 'lib/closeyourit-ruby.rb', line 207

def emit_log(level, message, source: nil, attributes: {})
  return nil if in_diagnostic?
  return nil unless logs_enabled?
  return nil if log_below_min_level?(level)
  return record_drop(:sampled) unless logs_sampled?

  event = LogEvent.new(message, level: level, attributes: attributes,
                                logger: source, configuration: configuration)
  log_buffer.add(event)
  nil
end

.enabled?Boolean

Returns:

  • (Boolean)


90
91
92
# File 'lib/closeyourit-ruby.rb', line 90

def enabled?
  configuration.enabled?
end

.flush_logsObject

Forza l'invio dei log bufferizzati (chiamato anche allo shutdown del processo).



257
258
259
260
# File 'lib/closeyourit-ruby.rb', line 257

def flush_logs
  @log_buffer&.flush
  nil
end

.ignored_log_message?(text) ⇒ Boolean

Vero se una riga del broadcast Rails.logger va scartata: nomina un'eccezione già presente in excluded_exceptions, oppure combacia con excluded_log_patterns.

Serve perché un'eccezione esclusa dal canale ERRORI rientrava da quello dei LOG: Rails la registra con logger.error, il broadcast inoltrava la riga senza guardarla, e il rumore che excluded_exceptions aveva appena scartato ricompariva come log-entry. Il 2026-07-30 erano 48.000 voci su 49.985 nello stream, quasi tutte ActionController::RoutingError da favicon mancanti e scansioni di bot — che è nella lista di default dalla prima riga (CYRB-17).

ignored_exception? non è applicabile: qui la classe arriva come TESTO dentro il messaggio, non come oggetto con ancestors da confrontare. Da cui il match per sottostringa sui matcher String.

Vale SOLO per il mirror automatico di Rails.logger: un CloseYourIt.log scritto di proposito dallo sviluppatore non si silenzia mai (chi lo scrive ha già deciso che vuole quella riga).

Returns:

  • (Boolean)


239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
# File 'lib/closeyourit-ruby.rb', line 239

def ignored_log_message?(text)
  text = text.to_s
  return false if text.empty?

  config = configuration
  named = config.excluded_exceptions.any? do |matcher|
    if matcher.is_a?(Regexp)
      matcher.match?(text)
    else
      # Un matcher vuoto combacerebbe con qualunque riga: mai silenziare tutto per una lista sporca.
      !matcher.empty? && text.include?(matcher)
    end
  end

  named || config.excluded_log_patterns.any? { |pattern| pattern.match?(text) }
end

.init {|@configuration| ... } ⇒ Object

Configura il client. Senza token/endpoint → no-op.

Una re-init SPEGNE prima il client e il log buffer della configurazione precedente (CYRB-10): azzerarli e basta lascerebbe orfani il thread del worker pool e il TimerTask del buffer, e gli eventi ancora in coda andrebbero persi o flushati fuori tempo dal timer orfano con la vecchia credenziale. Riusa la semantica di fine-vita di #shutdown (flush del buffer → drain del worker con timeout, CYRB-5) — così la coda precedente è svuotata in modo prevedibile con la sua config. Idempotente: alla prima init (o senza eventi catturati) @client/@log_buffer sono nil → no-op.

Yields:



69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/closeyourit-ruby.rb', line 69

def init
  shutdown
  @configuration = Configuration.new
  @client = nil
  @log_buffer = nil
  @usage_registry = nil
  @shutdown_notified = false # nuova sessione: :shutdown potrà essere notificato di nuovo
  yield(@configuration) if block_given?
  @configuration.validate!
  register_shutdown_flush
  @configuration
end

.log(level, message, logger: nil, **attributes) ⇒ Object

API esplicita: costruisce e bufferizza una voce di log strutturata (batch verso /logs, fire-and-forget). Il level è normalizzato ai livelli canonici (:warnwarning, downcase; ignoto→info). Il keyword logger: = nome della sorgente del log; ogni altra keyword è un attributo dati. I log sotto logs_min_level sono scartati con la stessa mappa numerica di dart/js (regola standardize, not adapt — CYRB-6): niente costruzione né invio. CloseYourIt.log(:info, "ordine creato", order_id: 1) CloseYourIt.log(:warn, "retry", logger: "payments", attempt: 3)



199
200
201
# File 'lib/closeyourit-ruby.rb', line 199

def log(level, message, logger: nil, **attributes)
  emit_log(level, message, source: logger, attributes: attributes)
end

.loggerObject

Logger applicativo Logger-compatibile: inoltra ogni messaggio a CloseYourIt.log (→ ingest /logs). CloseYourIt.logger.info("ordine creato", order_id: 1)



188
189
190
# File 'lib/closeyourit-ruby.rb', line 188

def logger
  @app_logger ||= LogDevice.new
end

.logs_active?Boolean

Vero se i log sono attivi (master switch + flag): usato da LogDevice per NON valutare i block costosi (logger.debug { dump }) quando il logging è spento.

Returns:

  • (Boolean)


221
222
223
# File 'lib/closeyourit-ruby.rb', line 221

def logs_active?
  logs_enabled?
end

.measure(label, &block) ⇒ Object

Cronometra un blocco e invia un slow_method se supera la soglia. CloseYourIt.measure("checkout.total") { ... }



132
133
134
# File 'lib/closeyourit-ruby.rb', line 132

def measure(label, &block)
  Instrumenter.measure(label, &block)
end

.notify_diagnostic(event, **details) ⇒ Object

Notifica una tappa del ciclo di vita di un evento all'hook on_diagnostic (se configurato). event è uno tra :enqueue, :send, :drop, :timeout, :shutdown; details un Hash privo di dati sensibili (es. { reason: :queue_full }, { status: 429 }). Chiamato da Client/Transport/ BackgroundWorker/LogBuffer. Garanzie (CYRB-12):

* NON invia telemetria: tocca solo l'hook dell'app e i contatori in-memory;
* NON innesca loop di auto-monitoraggio: durante l'hook il guard è alzato, e finché è alzato
sono soppressi SIA i `notify_diagnostic` annidati SIA le API di telemetria (`capture_*`/log,
vedi #in_diagnostic?). Poiché l'accodamento della telemetria è sincrono nel thread dell'hook
(solo l'invio HTTP è async), sopprimere l'accodamento chiude il loop anche cross-thread;
* NON solleva: un hook difettoso è isolato (logga su internal_logger) e mai propagato nell'app.

L'hook osserva sempre la configurazione CORRENTE: una notifica in volo che completa dopo una re-init raggiunge l'hook nuovo (best-effort, coerente col modello fire-and-forget).



313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
# File 'lib/closeyourit-ruby.rb', line 313

def notify_diagnostic(event, **details)
  hook = configuration.on_diagnostic
  return nil if hook.nil?
  return nil if Thread.current[DIAGNOSTIC_GUARD]

  Thread.current[DIAGNOSTIC_GUARD] = true
  begin
    hook.call(event, details)
  rescue StandardError => e
    internal_logger.error("CloseYourIt diagnostic hook: #{e.class}: #{e.message}")
  ensure
    Thread.current[DIAGNOSTIC_GUARD] = false
  end
  nil
end

.set_context(key, attributes) ⇒ Object



151
152
153
# File 'lib/closeyourit-ruby.rb', line 151

def set_context(key, attributes)
  Scope.current.set_context(key, attributes)
end

.set_extra(key, value) ⇒ Object



155
156
157
# File 'lib/closeyourit-ruby.rb', line 155

def set_extra(key, value)
  Scope.current.set_extra(key, value)
end

.set_tag(key, value) ⇒ Object



143
144
145
# File 'lib/closeyourit-ruby.rb', line 143

def set_tag(key, value)
  Scope.current.set_tag(key, value)
end

.set_tags(attributes) ⇒ Object



147
148
149
# File 'lib/closeyourit-ruby.rb', line 147

def set_tags(attributes)
  Scope.current.set_tags(attributes)
end

.set_user(attributes) ⇒ Object

--- Scope per-richiesta/job (user/tags/extra/contexts) --- Arricchiscono l'evento corrente; resettati a fine richiesta/job da middleware e estensioni.



139
140
141
# File 'lib/closeyourit-ruby.rb', line 139

def set_user(attributes)
  Scope.current.set_user(attributes)
end

.shutdownObject

Flush di fine-vita: svuota i log bufferizzati E drena il worker asincrono, attendendo (fino a un timeout breve) che gli invii in volo verso l'ingest completino. Solo flushare il buffer li accoderebbe nel worker che verrebbe poi ucciso alla terminazione → log persi (CYRB-5). Drena TUTTO il worker (log + errori + metriche fire-and-forget), non solo i log. Registrato su at_exit da CloseYourIt.init; con config.trap_signals viene raggiunto anche su SIGTERM. Idempotente: richiamarlo è sicuro (buffer già vuoto, worker già fermo → no-op).



268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
# File 'lib/closeyourit-ruby.rb', line 268

def shutdown
  # Ordine critico: prima i buffer (accodano l'ultimo batch nel worker), poi il worker (li drena).
  @usage_registry&.shutdown
  @log_buffer&.shutdown
  @client&.shutdown
  # Riepilogo di fine-vita: l'app riceve lo snapshot dei contatori senza log rumorosi. Emesso una
  # sola volta per sessione (uno shutdown esplicito seguito dall'at_exit non deve duplicarlo; il
  # flag è azzerato a ogni init). Lo snapshot è best-effort: eventuali invii ancora in volo oltre il
  # breve timeout di drain possono non esservi riflessi — il drain non blocca l'uscita (CYRB-5).
  unless @shutdown_notified
    @shutdown_notified = true
    notify_diagnostic(:shutdown, stats: stats.to_h)
  end
  nil
end

.statsObject

Contatori diagnostici del client (accodati/scartati/spediti/falliti/timeout). CloseYourIt.stats.to_h # => { enqueued: …, dropped: …, sent: …, failed: …, timeout: … }



297
298
299
# File 'lib/closeyourit-ruby.rb', line 297

def stats
  @stats ||= Stats.new
end

.usage_registryObject

CYSK-29 — il registro della telemetria d'uso (una lookup + increment sul percorso caldo).



330
331
332
333
# File 'lib/closeyourit-ruby.rb', line 330

def usage_registry
  ensure_current_process!
  @usage_registry ||= UsageRegistry.new(client: client, configuration: configuration)
end

.used(key) ⇒ Object

CYSK-29 — dichiara che un pezzo di codice è stato ESEGUITO. key deve essere una stringa LETTERALE (mai interpolata con dati): è l'unico modo onesto di rispondere a «questo ramo viene mai preso?». No-op se la gemma non è configurata o usage_enabled è OFF.



338
339
340
341
342
343
# File 'lib/closeyourit-ruby.rb', line 338

def used(key)
  return nil unless configured? && enabled?

  usage_registry.record("custom", key)
  nil
end