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/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, 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.7.0"
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).
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.
177 178 179 |
# File 'lib/closeyourit-ruby.rb', line 177 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.
166 167 168 169 170 171 172 173 |
# File 'lib/closeyourit-ruby.rb', line 166 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).
285 286 287 288 289 |
# File 'lib/closeyourit-ruby.rb', line 285 def after_fork discard_inherited_client! @pid = Process.pid nil end |
.capture_event(event) ⇒ Object
Spedisce un evento già costruito (slow_query/slow_method).
109 110 111 112 113 114 |
# File 'lib/closeyourit-ruby.rb', line 109 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.
93 94 95 96 97 98 99 100 101 102 103 104 105 106 |
# File 'lib/closeyourit-ruby.rb', line 93 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")
118 119 120 121 122 123 124 125 |
# File 'lib/closeyourit-ruby.rb', line 118 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
160 161 162 |
# File 'lib/closeyourit-ruby.rb', line 160 def clear_scope Scope.reset! end |
.configuration ⇒ Object
79 80 81 |
# File 'lib/closeyourit-ruby.rb', line 79 def configuration @configuration ||= Configuration.new end |
.configure_scope {|Scope.current| ... } ⇒ Object
156 157 158 |
# File 'lib/closeyourit-ruby.rb', line 156 def configure_scope yield(Scope.current) if block_given? end |
.configured? ⇒ Boolean
83 84 85 |
# File 'lib/closeyourit-ruby.rb', line 83 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.
204 205 206 207 208 209 210 211 212 213 214 |
# File 'lib/closeyourit-ruby.rb', line 204 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
87 88 89 |
# File 'lib/closeyourit-ruby.rb', line 87 def enabled? configuration.enabled? end |
.flush_logs ⇒ Object
Forza l'invio dei log bufferizzati (chiamato anche allo shutdown del processo).
254 255 256 257 |
# File 'lib/closeyourit-ruby.rb', line 254 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).
236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 |
# File 'lib/closeyourit-ruby.rb', line 236 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.
67 68 69 70 71 72 73 74 75 76 77 |
# File 'lib/closeyourit-ruby.rb', line 67 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 (: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)
196 197 198 |
# File 'lib/closeyourit-ruby.rb', line 196 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)
185 186 187 |
# File 'lib/closeyourit-ruby.rb', line 185 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.
218 219 220 |
# File 'lib/closeyourit-ruby.rb', line 218 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") { ... }
129 130 131 |
# File 'lib/closeyourit-ruby.rb', line 129 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).
309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 |
# File 'lib/closeyourit-ruby.rb', line 309 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
148 149 150 |
# File 'lib/closeyourit-ruby.rb', line 148 def set_context(key, attributes) Scope.current.set_context(key, attributes) end |
.set_extra(key, value) ⇒ Object
152 153 154 |
# File 'lib/closeyourit-ruby.rb', line 152 def set_extra(key, value) Scope.current.set_extra(key, value) end |
.set_tag(key, value) ⇒ Object
140 141 142 |
# File 'lib/closeyourit-ruby.rb', line 140 def set_tag(key, value) Scope.current.set_tag(key, value) end |
.set_tags(attributes) ⇒ Object
144 145 146 |
# File 'lib/closeyourit-ruby.rb', line 144 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.
136 137 138 |
# File 'lib/closeyourit-ruby.rb', line 136 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).
265 266 267 268 269 270 271 272 273 274 275 276 277 278 |
# File 'lib/closeyourit-ruby.rb', line 265 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 |