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
-
.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.
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 (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).
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 (, 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
161 162 163 |
# File 'lib/closeyourit-ruby.rb', line 161 def clear_scope Scope.reset! end |
.configuration ⇒ Object
80 81 82 |
# File 'lib/closeyourit-ruby.rb', line 80 def configuration @configuration ||= Configuration.new end |
.configure_scope {|Scope.current| ... } ⇒ Object
157 158 159 |
# File 'lib/closeyourit-ruby.rb', line 157 def configure_scope yield(Scope.current) if block_given? end |
.configured? ⇒ 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, , 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
88 89 90 |
# File 'lib/closeyourit-ruby.rb', line 88 def enabled? configuration.enabled? end |
.flush_logs ⇒ Object
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).
237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 |
# File 'lib/closeyourit-ruby.rb', line 237 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.
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 (: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)
197 198 199 |
# File 'lib/closeyourit-ruby.rb', line 197 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)
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.
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.}") 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 (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.
137 138 139 |
# File 'lib/closeyourit-ruby.rb', line 137 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).
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 |