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/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

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.8.0"

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.



178
179
180
# File 'lib/closeyourit-ruby.rb', line 178

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.



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

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).



286
287
288
289
290
# File 'lib/closeyourit-ruby.rb', line 286

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

.capture_event(event) ⇒ Object

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



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

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.



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

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")



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

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



161
162
163
# File 'lib/closeyourit-ruby.rb', line 161

def clear_scope
  Scope.reset!
end

.configurationObject



80
81
82
# File 'lib/closeyourit-ruby.rb', line 80

def configuration
  @configuration ||= Configuration.new
end

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

Yields:



157
158
159
# File 'lib/closeyourit-ruby.rb', line 157

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

.configured?Boolean

Returns:

  • (Boolean)


84
85
86
# File 'lib/closeyourit-ruby.rb', line 84

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.



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

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)


88
89
90
# File 'lib/closeyourit-ruby.rb', line 88

def enabled?
  configuration.enabled?
end

.flush_logsObject

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



255
256
257
258
# File 'lib/closeyourit-ruby.rb', line 255

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)


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

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:



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

def init
  shutdown
  @configuration = Configuration.new
  @client = nil
  @log_buffer = 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)



197
198
199
# File 'lib/closeyourit-ruby.rb', line 197

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)



186
187
188
# File 'lib/closeyourit-ruby.rb', line 186

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)


219
220
221
# File 'lib/closeyourit-ruby.rb', line 219

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") { ... }



130
131
132
# File 'lib/closeyourit-ruby.rb', line 130

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).



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

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



149
150
151
# File 'lib/closeyourit-ruby.rb', line 149

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

.set_extra(key, value) ⇒ Object



153
154
155
# File 'lib/closeyourit-ruby.rb', line 153

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

.set_tag(key, value) ⇒ Object



141
142
143
# File 'lib/closeyourit-ruby.rb', line 141

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

.set_tags(attributes) ⇒ Object



145
146
147
# File 'lib/closeyourit-ruby.rb', line 145

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.



137
138
139
# File 'lib/closeyourit-ruby.rb', line 137

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).



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

def shutdown
  # Ordine critico: prima il buffer (accoda l'ultimo batch nel worker), poi il worker (lo drena).
  @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: … }



294
295
296
# File 'lib/closeyourit-ruby.rb', line 294

def stats
  @stats ||= Stats.new
end