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
-
.internal_logger ⇒ Object
Logger interno della gemma (warning/errori diagnostici su stdout).
Class Method Summary collapse
-
.add_breadcrumb(message: nil, category: nil, type: "default", level: "info", data: {}) ⇒ Object
Aggiunge una briciola di contesto (query, navigazione, evento custom) all'evento corrente.
-
.after_fork ⇒ Object
Ripristina le risorse di invio in un processo figlio dopo un fork.
-
.capture_event(event) ⇒ Object
Spedisce un evento già costruito (slow_query/slow_method).
-
.capture_exception(exception, handled: false, level: "error", contexts: nil) ⇒ Object
Cattura un'eccezione e la spedisce (fire-and-forget).
-
.capture_message(message, level: "info") ⇒ Object
Invia un messaggio diagnostico esplicito (non un'eccezione).
- .clear_scope ⇒ Object
- .configuration ⇒ Object
- .configure_scope {|Scope.current| ... } ⇒ Object
- .configured? ⇒ Boolean
-
.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
LogDeviceper trattare una chiaveloggercome attributo dati (mai come sorgente) e per impostare la sorgente solo via.named(child logger, parità dart/js — CYRB-8). - .enabled? ⇒ Boolean
-
.flush_logs ⇒ Object
Forza l'invio dei log bufferizzati (chiamato anche allo shutdown del processo).
-
.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 conexcluded_log_patterns. -
.init {|@configuration| ... } ⇒ Object
Configura il client.
-
.log(level, message, logger: nil, **attributes) ⇒ Object
API esplicita: costruisce e bufferizza una voce di log strutturata (batch verso /logs, fire-and-forget).
-
.logger ⇒ Object
Logger applicativo Logger-compatibile: inoltra ogni messaggio a
CloseYourIt.log(→ ingest /logs). -
.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. -
.measure(label, &block) ⇒ Object
Cronometra un blocco e invia un slow_method se supera la soglia.
-
.notify_diagnostic(event, **details) ⇒ Object
Notifica una tappa del ciclo di vita di un evento all'hook
on_diagnostic(se configurato). - .set_context(key, attributes) ⇒ Object
- .set_extra(key, value) ⇒ Object
- .set_tag(key, value) ⇒ Object
- .set_tags(attributes) ⇒ Object
-
.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.
-
.shutdown ⇒ Object
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.
-
.stats ⇒ Object
Contatori diagnostici del client (accodati/scartati/spediti/falliti/timeout).
-
.usage_registry ⇒ Object
CYSK-29 — il registro della telemetria d'uso (una lookup + increment sul percorso caldo).
-
.used(key) ⇒ Object
CYSK-29 — dichiara che un pezzo di codice è stato ESEGUITO.
Class Attribute Details
.internal_logger ⇒ Object
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 (message: nil, category: nil, type: "default", level: "info", data: {}) return nil unless configuration. scrubbed = data.nil? || data.empty? ? data : Scrubber.new(configuration).filter_params(data) Scope.current.( Breadcrumb.new(message: , category: category, type: type, level: level, data: scrubbed) ) end |
.after_fork ⇒ Object
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 (, level: "info") return nil if in_diagnostic? return nil unless enabled? return record_drop(:sampled) unless sampled? event = MessageEvent.new(, level: level, configuration: configuration) client.capture_event(event) end |
.clear_scope ⇒ Object
163 164 165 |
# File 'lib/closeyourit-ruby.rb', line 163 def clear_scope Scope.reset! end |
.configuration ⇒ Object
82 83 84 |
# File 'lib/closeyourit-ruby.rb', line 82 def configuration @configuration ||= Configuration.new end |
.configure_scope {|Scope.current| ... } ⇒ Object
159 160 161 |
# File 'lib/closeyourit-ruby.rb', line 159 def configure_scope yield(Scope.current) if block_given? end |
.configured? ⇒ 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, , 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(, level: level, attributes: attributes, logger: source, configuration: configuration) log_buffer.add(event) nil end |
.enabled? ⇒ Boolean
90 91 92 |
# File 'lib/closeyourit-ruby.rb', line 90 def enabled? configuration.enabled? end |
.flush_logs ⇒ Object
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).
239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 |
# File 'lib/closeyourit-ruby.rb', line 239 def (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.
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 (:warn→warning, 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, , logger: nil, **attributes) emit_log(level, , source: logger, attributes: attributes) end |
.logger ⇒ Object
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.
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.}") 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 (attributes) Scope.current.(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 |
.shutdown ⇒ Object
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 |
.stats ⇒ Object
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_registry ⇒ Object
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 |