closeyourit-ruby 0.10.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +16 -1
- data/lib/closeyourit/background_worker.rb +14 -14
- data/lib/closeyourit/breadcrumb.rb +2 -2
- data/lib/closeyourit/breadcrumb_buffer.rb +2 -2
- data/lib/closeyourit/client.rb +24 -24
- data/lib/closeyourit/configuration.rb +104 -103
- data/lib/closeyourit/event.rb +6 -6
- data/lib/closeyourit/events/error_event.rb +16 -16
- data/lib/closeyourit/events/job_metric_event.rb +11 -8
- data/lib/closeyourit/events/log_event.rb +17 -17
- data/lib/closeyourit/events/message_event.rb +4 -4
- data/lib/closeyourit/events/performance_issue_event.rb +4 -4
- data/lib/closeyourit/events/slow_method_event.rb +6 -6
- data/lib/closeyourit/events/slow_query_event.rb +4 -4
- data/lib/closeyourit/instrumenter.rb +6 -6
- data/lib/closeyourit/job_context.rb +61 -0
- data/lib/closeyourit/line_cache.rb +4 -4
- data/lib/closeyourit/log_buffer.rb +10 -10
- data/lib/closeyourit/log_device.rb +18 -18
- data/lib/closeyourit/monitor.rb +2 -2
- data/lib/closeyourit/performance/request_profile.rb +5 -5
- data/lib/closeyourit/performance/rollup.rb +3 -3
- data/lib/closeyourit/rails/active_job_extension.rb +36 -31
- data/lib/closeyourit/rails/capture_exceptions.rb +3 -3
- data/lib/closeyourit/rails/error_subscriber.rb +4 -4
- data/lib/closeyourit/rails/log_broadcast.rb +12 -12
- data/lib/closeyourit/rails/net_http_patch.rb +22 -22
- data/lib/closeyourit/rails/query_source.rb +3 -3
- data/lib/closeyourit/rails/railtie.rb +29 -65
- data/lib/closeyourit/rails/request_body.rb +7 -7
- data/lib/closeyourit/rails/request_context.rb +27 -27
- data/lib/closeyourit/scope.rb +29 -29
- data/lib/closeyourit/scrubber.rb +50 -50
- data/lib/closeyourit/sidekiq/error_handler.rb +2 -2
- data/lib/closeyourit/sidekiq/job_metrics_middleware.rb +39 -17
- data/lib/closeyourit/stats.rb +8 -8
- data/lib/closeyourit/subscribers/job_performance.rb +102 -24
- data/lib/closeyourit/subscribers/request_performance.rb +4 -4
- data/lib/closeyourit/subscribers/slow_query.rb +42 -20
- data/lib/closeyourit/trace_context.rb +22 -22
- data/lib/closeyourit/transport.rb +22 -22
- data/lib/closeyourit/usage_registry.rb +17 -16
- data/lib/closeyourit/version.rb +1 -1
- data/lib/closeyourit-ruby.rb +125 -125
- metadata +2 -1
data/lib/closeyourit-ruby.rb
CHANGED
|
@@ -40,39 +40,39 @@ require_relative "closeyourit/rails/error_subscriber"
|
|
|
40
40
|
require_relative "closeyourit/sidekiq/error_handler"
|
|
41
41
|
require_relative "closeyourit/sidekiq/job_metrics_middleware"
|
|
42
42
|
|
|
43
|
-
# CloseYourIt — client
|
|
44
|
-
#
|
|
43
|
+
# CloseYourIt — telemetry client (errors + slow query/method statistics)
|
|
44
|
+
# that sends events to the CloseYourIt ingest endpoint.
|
|
45
45
|
#
|
|
46
|
-
#
|
|
47
|
-
# `require "closeyourit-ruby"`
|
|
46
|
+
# Gem entry point (hyphenated file name, like `sentry-ruby`):
|
|
47
|
+
# `require "closeyourit-ruby"` loads the `CloseYourIt` module.
|
|
48
48
|
module CloseYourIt
|
|
49
|
-
#
|
|
49
|
+
# Internal base exception: used to avoid loops (our own exceptions are never captured).
|
|
50
50
|
class Error < StandardError; end
|
|
51
51
|
|
|
52
52
|
CAPTURED_FLAG = :@__closeyourit_captured
|
|
53
53
|
|
|
54
|
-
#
|
|
55
|
-
#
|
|
56
|
-
#
|
|
57
|
-
#
|
|
54
|
+
# Thread-local flag meaning "already inside the diagnostic hook": it stops a notification emitted
|
|
55
|
+
# INSIDE the hook (or by code it calls) from re-entering and re-running the hook → no
|
|
56
|
+
# self-monitoring loop (CYRB-12). It is per-thread because the stages run on different threads
|
|
57
|
+
# (worker pool for send/timeout, calling thread for enqueue/drop).
|
|
58
58
|
DIAGNOSTIC_GUARD = :__closeyourit_in_diagnostic
|
|
59
59
|
|
|
60
60
|
class << self
|
|
61
|
-
#
|
|
61
|
+
# Configures the client. Without token/endpoint → no-op.
|
|
62
62
|
#
|
|
63
|
-
#
|
|
64
|
-
#
|
|
65
|
-
#
|
|
66
|
-
#
|
|
67
|
-
#
|
|
68
|
-
#
|
|
63
|
+
# A re-init first SHUTS DOWN the client and log buffer of the previous configuration (CYRB-10):
|
|
64
|
+
# just clearing them would orphan the worker pool thread and the buffer's TimerTask, and events
|
|
65
|
+
# still queued would be lost or flushed late by the orphan timer with the old credential. It
|
|
66
|
+
# reuses the end-of-life semantics of #shutdown (buffer flush → worker drain with timeout,
|
|
67
|
+
# CYRB-5), so the previous queue is drained predictably with its own config.
|
|
68
|
+
# Idempotent: on the first init (or with no captured events) @client/@log_buffer are nil → no-op.
|
|
69
69
|
def init
|
|
70
70
|
shutdown
|
|
71
71
|
@configuration = Configuration.new
|
|
72
72
|
@client = nil
|
|
73
73
|
@log_buffer = nil
|
|
74
74
|
@usage_registry = nil
|
|
75
|
-
@shutdown_notified = false #
|
|
75
|
+
@shutdown_notified = false # new session: :shutdown can be notified again
|
|
76
76
|
yield(@configuration) if block_given?
|
|
77
77
|
@configuration.validate!
|
|
78
78
|
register_shutdown_flush
|
|
@@ -91,8 +91,8 @@ module CloseYourIt
|
|
|
91
91
|
configuration.enabled?
|
|
92
92
|
end
|
|
93
93
|
|
|
94
|
-
#
|
|
95
|
-
#
|
|
94
|
+
# Captures an exception and sends it (fire-and-forget). No-op when disabled,
|
|
95
|
+
# or when the exception is excluded or already captured.
|
|
96
96
|
def capture_exception(exception, handled: false, level: "error", contexts: nil)
|
|
97
97
|
return nil if in_diagnostic?
|
|
98
98
|
return nil unless enabled?
|
|
@@ -108,7 +108,7 @@ module CloseYourIt
|
|
|
108
108
|
client.capture_event(event)
|
|
109
109
|
end
|
|
110
110
|
|
|
111
|
-
#
|
|
111
|
+
# Sends an already built event (slow_query/slow_method).
|
|
112
112
|
def capture_event(event)
|
|
113
113
|
return nil if in_diagnostic?
|
|
114
114
|
return nil unless enabled?
|
|
@@ -116,7 +116,7 @@ module CloseYourIt
|
|
|
116
116
|
client.capture_event(event)
|
|
117
117
|
end
|
|
118
118
|
|
|
119
|
-
#
|
|
119
|
+
# Sends an explicit diagnostic message (not an exception). Subject to sampling + scope.
|
|
120
120
|
# CloseYourIt.capture_message("cache miss storm", level: "warning")
|
|
121
121
|
def capture_message(message, level: "info")
|
|
122
122
|
return nil if in_diagnostic?
|
|
@@ -127,14 +127,14 @@ module CloseYourIt
|
|
|
127
127
|
client.capture_event(event)
|
|
128
128
|
end
|
|
129
129
|
|
|
130
|
-
#
|
|
130
|
+
# Times a block and sends a slow_method if it exceeds the threshold.
|
|
131
131
|
# CloseYourIt.measure("checkout.total") { ... }
|
|
132
132
|
def measure(label, &block)
|
|
133
133
|
Instrumenter.measure(label, &block)
|
|
134
134
|
end
|
|
135
135
|
|
|
136
|
-
# ---
|
|
137
|
-
#
|
|
136
|
+
# --- Per-request/job scope (user/tags/extra/contexts) ---
|
|
137
|
+
# They enrich the current event; reset at the end of the request/job by middleware and extensions.
|
|
138
138
|
|
|
139
139
|
def set_user(attributes)
|
|
140
140
|
Scope.current.set_user(attributes)
|
|
@@ -164,8 +164,8 @@ module CloseYourIt
|
|
|
164
164
|
Scope.reset!
|
|
165
165
|
end
|
|
166
166
|
|
|
167
|
-
#
|
|
168
|
-
# No-op
|
|
167
|
+
# Adds a context breadcrumb (query, navigation, custom event) to the current event.
|
|
168
|
+
# No-op when breadcrumbs are disabled; `data` is scrubbed (denylist) before being stored.
|
|
169
169
|
def add_breadcrumb(message: nil, category: nil, type: "default", level: "info", data: {})
|
|
170
170
|
return nil unless configuration.breadcrumbs_enabled
|
|
171
171
|
|
|
@@ -175,35 +175,35 @@ module CloseYourIt
|
|
|
175
175
|
)
|
|
176
176
|
end
|
|
177
177
|
|
|
178
|
-
#
|
|
179
|
-
#
|
|
178
|
+
# The gem's internal logger (diagnostic warnings/errors on stdout). It is NOT application logging:
|
|
179
|
+
# to send structured logs to CloseYourIt use `CloseYourIt.log` / `CloseYourIt.logger`.
|
|
180
180
|
def internal_logger
|
|
181
181
|
@internal_logger ||= default_internal_logger
|
|
182
182
|
end
|
|
183
183
|
|
|
184
184
|
attr_writer :internal_logger
|
|
185
185
|
|
|
186
|
-
# Logger
|
|
186
|
+
# Logger-compatible application logger: forwards every message to `CloseYourIt.log` (→ ingest /logs).
|
|
187
187
|
# CloseYourIt.logger.info("ordine creato", order_id: 1)
|
|
188
188
|
def logger
|
|
189
189
|
@app_logger ||= LogDevice.new
|
|
190
190
|
end
|
|
191
191
|
|
|
192
|
-
# API
|
|
193
|
-
#
|
|
194
|
-
#
|
|
195
|
-
#
|
|
196
|
-
#
|
|
192
|
+
# Explicit API: builds and buffers a structured log entry (batched to /logs, fire-and-forget).
|
|
193
|
+
# `level` is normalized to the canonical levels (`:warn`→`warning`, downcase; unknown→`info`).
|
|
194
|
+
# The `logger:` keyword = name of the log source; every other keyword is a data attribute.
|
|
195
|
+
# Logs below `logs_min_level` are dropped with the same numeric map as dart/js
|
|
196
|
+
# (standardize, not adapt rule — CYRB-6): nothing is built or sent.
|
|
197
197
|
# CloseYourIt.log(:info, "ordine creato", order_id: 1)
|
|
198
198
|
# CloseYourIt.log(:warn, "retry", logger: "payments", attempt: 3)
|
|
199
199
|
def log(level, message, logger: nil, **attributes)
|
|
200
200
|
emit_log(level, message, source: logger, attributes: attributes)
|
|
201
201
|
end
|
|
202
202
|
|
|
203
|
-
#
|
|
204
|
-
#
|
|
205
|
-
#
|
|
206
|
-
#
|
|
203
|
+
# Low-level path (gating + building + buffer) with SEPARATE, unambiguous source and attributes:
|
|
204
|
+
# used by `LogDevice` to treat a `logger` key as a data attribute (never as the source) and to set
|
|
205
|
+
# the source only via `.named` (child logger, dart/js parity — CYRB-8).
|
|
206
|
+
# Apps use `CloseYourIt.log` / `CloseYourIt.logger`.
|
|
207
207
|
def emit_log(level, message, source: nil, attributes: {})
|
|
208
208
|
return nil if in_diagnostic?
|
|
209
209
|
return nil unless logs_enabled?
|
|
@@ -216,26 +216,26 @@ module CloseYourIt
|
|
|
216
216
|
nil
|
|
217
217
|
end
|
|
218
218
|
|
|
219
|
-
#
|
|
220
|
-
#
|
|
219
|
+
# True when logs are active (master switch + flag): used by LogDevice to NOT evaluate expensive
|
|
220
|
+
# blocks (`logger.debug { dump }`) when logging is off.
|
|
221
221
|
def logs_active?
|
|
222
222
|
logs_enabled?
|
|
223
223
|
end
|
|
224
224
|
|
|
225
|
-
#
|
|
226
|
-
# `excluded_exceptions`,
|
|
225
|
+
# True when a line of the Rails.logger broadcast must be dropped: it names an exception already
|
|
226
|
+
# listed in `excluded_exceptions`, or it matches `excluded_log_patterns`.
|
|
227
227
|
#
|
|
228
|
-
#
|
|
229
|
-
#
|
|
230
|
-
# `excluded_exceptions`
|
|
231
|
-
# 48
|
|
232
|
-
#
|
|
228
|
+
# Needed because an exception excluded from the ERRORS channel came back through the LOGS one:
|
|
229
|
+
# Rails records it with `logger.error`, the broadcast forwarded the line without looking at it,
|
|
230
|
+
# and the noise `excluded_exceptions` had just dropped reappeared as a log entry. On 2026-07-30
|
|
231
|
+
# it was 48,000 entries out of 49,985 in the stream, almost all `ActionController::RoutingError`
|
|
232
|
+
# from missing favicons and bot scans — which has been in the default list from day one (CYRB-17).
|
|
233
233
|
#
|
|
234
|
-
# `ignored_exception?`
|
|
235
|
-
#
|
|
234
|
+
# `ignored_exception?` does not apply: here the class arrives as TEXT inside the message, not as
|
|
235
|
+
# an object with `ancestors` to compare. Hence the substring match on String matchers.
|
|
236
236
|
#
|
|
237
|
-
#
|
|
238
|
-
#
|
|
237
|
+
# It applies ONLY to the automatic Rails.logger mirror: a `CloseYourIt.log` written on purpose by
|
|
238
|
+
# the developer is never silenced (whoever writes it has already decided they want that line).
|
|
239
239
|
def ignored_log_message?(text)
|
|
240
240
|
text = text.to_s
|
|
241
241
|
return false if text.empty?
|
|
@@ -245,7 +245,7 @@ module CloseYourIt
|
|
|
245
245
|
if matcher.is_a?(Regexp)
|
|
246
246
|
matcher.match?(text)
|
|
247
247
|
else
|
|
248
|
-
#
|
|
248
|
+
# An empty matcher would match any line: never silence everything because of a dirty list.
|
|
249
249
|
!matcher.empty? && text.include?(matcher)
|
|
250
250
|
end
|
|
251
251
|
end
|
|
@@ -253,27 +253,27 @@ module CloseYourIt
|
|
|
253
253
|
named || config.excluded_log_patterns.any? { |pattern| pattern.match?(text) }
|
|
254
254
|
end
|
|
255
255
|
|
|
256
|
-
#
|
|
256
|
+
# Forces sending of buffered logs (also called at process shutdown).
|
|
257
257
|
def flush_logs
|
|
258
258
|
@log_buffer&.flush
|
|
259
259
|
nil
|
|
260
260
|
end
|
|
261
261
|
|
|
262
|
-
#
|
|
263
|
-
# timeout
|
|
264
|
-
#
|
|
265
|
-
#
|
|
266
|
-
#
|
|
267
|
-
#
|
|
262
|
+
# End-of-life flush: empties the buffered logs AND drains the async worker, waiting (up to a short
|
|
263
|
+
# timeout) for in-flight sends to the ingest to complete. Only flushing the buffer would queue
|
|
264
|
+
# them in the worker, which is then killed on termination → lost logs (CYRB-5). It drains the
|
|
265
|
+
# WHOLE worker (logs + errors + fire-and-forget metrics), not only logs. Registered on at_exit by
|
|
266
|
+
# CloseYourIt.init; with `config.trap_signals` it is also reached on SIGTERM. Idempotent: calling
|
|
267
|
+
# it again is safe (buffer already empty, worker already stopped → no-op).
|
|
268
268
|
def shutdown
|
|
269
|
-
#
|
|
269
|
+
# Order matters: buffers first (they queue the last batch in the worker), then the worker (drains them).
|
|
270
270
|
@usage_registry&.shutdown
|
|
271
271
|
@log_buffer&.shutdown
|
|
272
272
|
@client&.shutdown
|
|
273
|
-
#
|
|
274
|
-
#
|
|
275
|
-
#
|
|
276
|
-
#
|
|
273
|
+
# End-of-life summary: the app receives the counters snapshot without noisy logs. Emitted once
|
|
274
|
+
# per session (an explicit shutdown followed by the at_exit must not duplicate it; the flag is
|
|
275
|
+
# reset on every init). The snapshot is best-effort: sends still in flight beyond the short
|
|
276
|
+
# drain timeout may not be reflected — the drain does not block exit (CYRB-5).
|
|
277
277
|
unless @shutdown_notified
|
|
278
278
|
@shutdown_notified = true
|
|
279
279
|
notify_diagnostic(:shutdown, stats: stats.to_h)
|
|
@@ -281,35 +281,35 @@ module CloseYourIt
|
|
|
281
281
|
nil
|
|
282
282
|
end
|
|
283
283
|
|
|
284
|
-
#
|
|
285
|
-
#
|
|
286
|
-
#
|
|
287
|
-
#
|
|
288
|
-
#
|
|
284
|
+
# Restores the sending resources in a child process after a fork. Call it from the worker hooks of
|
|
285
|
+
# forking servers (Puma `on_worker_boot`, Sidekiq/Unicorn `after_fork`) to recreate the worker pool
|
|
286
|
+
# and log buffer in the child RIGHT AWAY, instead of waiting for lazy detection on the first event.
|
|
287
|
+
# Optional: the gem detects the PID change on its own anyway (see #ensure_current_process!).
|
|
288
|
+
# Idempotent and safe even if client/buffer were not materialized yet (→ no-op, created lazily).
|
|
289
289
|
def after_fork
|
|
290
290
|
discard_inherited_client!
|
|
291
291
|
@pid = Process.pid
|
|
292
292
|
nil
|
|
293
293
|
end
|
|
294
294
|
|
|
295
|
-
#
|
|
295
|
+
# Client diagnostic counters (enqueued/dropped/sent/failed/timeout).
|
|
296
296
|
# CloseYourIt.stats.to_h # => { enqueued: …, dropped: …, sent: …, failed: …, timeout: … }
|
|
297
297
|
def stats
|
|
298
298
|
@stats ||= Stats.new
|
|
299
299
|
end
|
|
300
300
|
|
|
301
|
-
#
|
|
302
|
-
# `event`
|
|
303
|
-
#
|
|
304
|
-
# BackgroundWorker/LogBuffer.
|
|
305
|
-
# *
|
|
306
|
-
# *
|
|
307
|
-
#
|
|
308
|
-
#
|
|
309
|
-
# (
|
|
310
|
-
# *
|
|
311
|
-
#
|
|
312
|
-
# re-init
|
|
301
|
+
# Notifies a stage of an event's lifecycle to the `on_diagnostic` hook (if configured).
|
|
302
|
+
# `event` is one of :enqueue, :send, :drop, :timeout, :shutdown; `details` a Hash free of
|
|
303
|
+
# sensitive data (e.g. `{ reason: :queue_full }`, `{ status: 429 }`). Called by Client/Transport/
|
|
304
|
+
# BackgroundWorker/LogBuffer. Guarantees (CYRB-12):
|
|
305
|
+
# * it does NOT send telemetry: it only touches the app hook and the in-memory counters;
|
|
306
|
+
# * it does NOT trigger self-monitoring loops: during the hook the guard is raised, and while it
|
|
307
|
+
# is raised BOTH nested `notify_diagnostic` calls AND the telemetry APIs (`capture_*`/log, see
|
|
308
|
+
# #in_diagnostic?) are suppressed. Since telemetry enqueueing is synchronous on the hook's
|
|
309
|
+
# thread (only the HTTP send is async), suppressing enqueueing closes the loop cross-thread too;
|
|
310
|
+
# * it does NOT raise: a faulty hook is isolated (logs to internal_logger), never propagated.
|
|
311
|
+
# The hook always sees the CURRENT configuration: an in-flight notification completing after a
|
|
312
|
+
# re-init reaches the new hook (best-effort, consistent with the fire-and-forget model).
|
|
313
313
|
def notify_diagnostic(event, **details)
|
|
314
314
|
hook = configuration.on_diagnostic
|
|
315
315
|
return nil if hook.nil?
|
|
@@ -326,15 +326,15 @@ module CloseYourIt
|
|
|
326
326
|
nil
|
|
327
327
|
end
|
|
328
328
|
|
|
329
|
-
# CYSK-29 —
|
|
329
|
+
# CYSK-29 — the usage telemetry registry (one lookup + increment on the hot path).
|
|
330
330
|
def usage_registry
|
|
331
331
|
ensure_current_process!
|
|
332
332
|
@usage_registry ||= UsageRegistry.new(client: client, configuration: configuration)
|
|
333
333
|
end
|
|
334
334
|
|
|
335
|
-
# CYSK-29 —
|
|
336
|
-
#
|
|
337
|
-
#
|
|
335
|
+
# CYSK-29 — declares that a piece of code was EXECUTED. `key` must be a LITERAL string (never
|
|
336
|
+
# interpolated with data): it is the only honest way to answer "is this branch ever taken?".
|
|
337
|
+
# No-op when the gem is not configured or usage_enabled is OFF.
|
|
338
338
|
def used(key)
|
|
339
339
|
return nil unless configured? && enabled?
|
|
340
340
|
|
|
@@ -344,14 +344,14 @@ module CloseYourIt
|
|
|
344
344
|
|
|
345
345
|
private
|
|
346
346
|
|
|
347
|
-
#
|
|
348
|
-
#
|
|
347
|
+
# True when the current thread is running the diagnostic hook: the telemetry APIs become no-ops
|
|
348
|
+
# so a (badly written) hook cannot generate more telemetry and trigger self-monitoring.
|
|
349
349
|
def in_diagnostic?
|
|
350
350
|
Thread.current[DIAGNOSTIC_GUARD] == true
|
|
351
351
|
end
|
|
352
352
|
|
|
353
|
-
#
|
|
354
|
-
# (queue_full/before_send/sampled/error).
|
|
353
|
+
# Records a drop: increments the aggregate `dropped` counter and notifies `:drop` with the reason
|
|
354
|
+
# (queue_full/before_send/sampled/error). A single place keeps snapshot and hook aligned.
|
|
355
355
|
def record_drop(reason, **details)
|
|
356
356
|
stats.increment(:dropped)
|
|
357
357
|
notify_diagnostic(:drop, reason: reason, **details)
|
|
@@ -369,43 +369,43 @@ module CloseYourIt
|
|
|
369
369
|
end
|
|
370
370
|
|
|
371
371
|
|
|
372
|
-
#
|
|
373
|
-
#
|
|
374
|
-
# —
|
|
375
|
-
#
|
|
376
|
-
#
|
|
377
|
-
# (
|
|
378
|
-
#
|
|
372
|
+
# Detects a fork by comparing the PID of the process where @client/@log_buffer were materialized
|
|
373
|
+
# with the current one. In a forked child both objects are inherited from the parent, but their
|
|
374
|
+
# threads — the Client worker pool and the LogBuffer TimerTask — live only in the parent (fork
|
|
375
|
+
# copies only the calling thread): queued events would never leave, being left to threads that
|
|
376
|
+
# exist only in the parent. On a PID change we drop the inherited references WITHOUT #shutdown
|
|
377
|
+
# (no join is possible on the parent's threads, and a buffer flush would resend the parent's
|
|
378
|
+
# events) → the getters recreate them lazily under the current process. The parent is unchanged.
|
|
379
379
|
def ensure_current_process!
|
|
380
380
|
pid = Process.pid
|
|
381
381
|
return if @pid == pid
|
|
382
382
|
|
|
383
|
-
discard_inherited_client! if @pid #
|
|
383
|
+
discard_inherited_client! if @pid # not on first materialization (@pid nil): nothing to drop
|
|
384
384
|
@pid = pid
|
|
385
385
|
end
|
|
386
386
|
|
|
387
|
-
#
|
|
388
|
-
#
|
|
389
|
-
#
|
|
390
|
-
#
|
|
387
|
+
# Releases the client and log buffer references without draining them. Used both by lazy fork
|
|
388
|
+
# detection and by #after_fork: in the child the underlying threads do not exist, so there is
|
|
389
|
+
# nothing to join and nothing must be flushed (it would resend the parent's events). GC collects
|
|
390
|
+
# the old objects; the getters create new ones on the next access.
|
|
391
391
|
def discard_inherited_client!
|
|
392
392
|
@client = nil
|
|
393
393
|
@log_buffer = nil
|
|
394
394
|
@usage_registry = nil
|
|
395
395
|
end
|
|
396
396
|
|
|
397
|
-
#
|
|
397
|
+
# Logs follow the client master switch + their own dedicated flag.
|
|
398
398
|
def logs_enabled?
|
|
399
399
|
enabled? && configuration.logs_enabled
|
|
400
400
|
end
|
|
401
401
|
|
|
402
|
-
#
|
|
403
|
-
#
|
|
402
|
+
# Threshold gating: the log is dropped when its severity is below `logs_min_level`. Severity is
|
|
403
|
+
# LogEvent's numeric map (debug=0 … fatal=4), identical across SDKs → filters like dart/js.
|
|
404
404
|
def log_below_min_level?(level)
|
|
405
405
|
LogEvent.severity(level) < LogEvent.severity(configuration.logs_min_level)
|
|
406
406
|
end
|
|
407
407
|
|
|
408
|
-
#
|
|
408
|
+
# Independent log sampling (1.0 = all, 0.0 = none).
|
|
409
409
|
def logs_sampled?
|
|
410
410
|
rate = configuration.logs_sample_rate.to_f
|
|
411
411
|
return true if rate >= 1.0
|
|
@@ -414,16 +414,16 @@ module CloseYourIt
|
|
|
414
414
|
Random.rand < rate
|
|
415
415
|
end
|
|
416
416
|
|
|
417
|
-
#
|
|
418
|
-
#
|
|
419
|
-
#
|
|
420
|
-
#
|
|
417
|
+
# At process shutdown, empties the buffered logs and drains the async worker. Without it, the
|
|
418
|
+
# sub-batch logs of short processes (rake/CLI) and the last queued events would be lost on exit.
|
|
419
|
+
# With `config.trap_signals` it also intercepts SIGTERM, which skips at_exit by default
|
|
420
|
+
# (deploy/Kamal would lose the flush). See #shutdown (CYRB-5).
|
|
421
421
|
#
|
|
422
|
-
#
|
|
423
|
-
#
|
|
424
|
-
#
|
|
425
|
-
#
|
|
426
|
-
#
|
|
422
|
+
# The at_exit is registered once per process (@shutdown_registered avoids N flushes from multiple
|
|
423
|
+
# inits), but the SIGTERM trap is evaluated BEFORE the guard and on EVERY init: it must follow the
|
|
424
|
+
# CURRENT config, otherwise an init enabling `trap_signals` after a first one with the flag OFF
|
|
425
|
+
# would never install it (the idempotency guard would return first). `Signal.trap` is idempotent
|
|
426
|
+
# → reinstalling the same handler is harmless.
|
|
427
427
|
def register_shutdown_flush
|
|
428
428
|
install_term_trap if configuration.trap_signals
|
|
429
429
|
|
|
@@ -433,17 +433,17 @@ module CloseYourIt
|
|
|
433
433
|
at_exit { shutdown }
|
|
434
434
|
end
|
|
435
435
|
|
|
436
|
-
# SIGTERM
|
|
437
|
-
#
|
|
438
|
-
#
|
|
439
|
-
# (Mutex/wait_for_termination in trap context
|
|
440
|
-
#
|
|
436
|
+
# SIGTERM ends the process WITHOUT running at_exit → the end-of-life flush would not run.
|
|
437
|
+
# We turn TERM into a clean exit: `exit` raises SystemExit on the main thread, which RUNS the
|
|
438
|
+
# at_exit hooks (including #shutdown) in normal context — no heavy work in the trap handler
|
|
439
|
+
# (Mutex/wait_for_termination in trap context would raise ThreadError). Opt-in: it overrides any
|
|
440
|
+
# TERM handler of the host app.
|
|
441
441
|
def install_term_trap
|
|
442
442
|
Signal.trap("TERM") { exit }
|
|
443
443
|
rescue ArgumentError, RuntimeError => e
|
|
444
|
-
#
|
|
445
|
-
#
|
|
446
|
-
internal_logger.warn("CloseYourIt: trap
|
|
444
|
+
# Contexts where TERM cannot be trapped (platform without TERM, non-main thread): not fatal,
|
|
445
|
+
# the at_exit still covers normal exits.
|
|
446
|
+
internal_logger.warn("CloseYourIt: SIGTERM trap not installed (#{e.class}: #{e.message})")
|
|
447
447
|
end
|
|
448
448
|
|
|
449
449
|
def ignored_exception?(exception)
|
|
@@ -459,7 +459,7 @@ module CloseYourIt
|
|
|
459
459
|
end
|
|
460
460
|
end
|
|
461
461
|
|
|
462
|
-
#
|
|
462
|
+
# Probabilistic sampling: 1.0 always sends, 0.0 never, in between via Random.rand.
|
|
463
463
|
def sampled?
|
|
464
464
|
rate = configuration.sample_rate.to_f
|
|
465
465
|
return true if rate >= 1.0
|
|
@@ -485,5 +485,5 @@ module CloseYourIt
|
|
|
485
485
|
end
|
|
486
486
|
end
|
|
487
487
|
|
|
488
|
-
#
|
|
488
|
+
# Automatic Rails integration (only when Rails is present).
|
|
489
489
|
require_relative "closeyourit/rails/railtie" if defined?(::Rails::Railtie)
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: closeyourit-ruby
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.11.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Alessio Bussolari
|
|
@@ -62,6 +62,7 @@ files:
|
|
|
62
62
|
- lib/closeyourit/events/slow_method_event.rb
|
|
63
63
|
- lib/closeyourit/events/slow_query_event.rb
|
|
64
64
|
- lib/closeyourit/instrumenter.rb
|
|
65
|
+
- lib/closeyourit/job_context.rb
|
|
65
66
|
- lib/closeyourit/line_cache.rb
|
|
66
67
|
- lib/closeyourit/log_buffer.rb
|
|
67
68
|
- lib/closeyourit/log_device.rb
|