closeyourit-ruby 0.8.0 → 0.9.2
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/lib/closeyourit/client.rb +16 -0
- data/lib/closeyourit/configuration.rb +10 -1
- data/lib/closeyourit/rails/railtie.rb +5 -0
- data/lib/closeyourit/usage_registry.rb +99 -0
- data/lib/closeyourit/version.rb +1 -1
- data/lib/closeyourit-ruby.rb +22 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a23acf258deb222638d8d9bd85f57ffc6fbf36429f9c87b50896814b0245e2e3
|
|
4
|
+
data.tar.gz: 7e0cb657fde07728331a131932242fb58685929af5ac88ffc7bfdf707ba3e301
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 964551d2a915c4c150dd586362dd110255f361bf8c93e20f8060de7f7c253f737d25d48f1776108f5556f6739e2c909af83dc71c4f1e568ad09797a99a1a755e
|
|
7
|
+
data.tar.gz: b371074aed62189a65b68f8ce3ad9725f96650b6f11fd871bfd228efdf30c3c2c6331fb93990869230c7e211d3191c3690f609f5873503cf1736a44e37bcc28c
|
data/lib/closeyourit/client.rb
CHANGED
|
@@ -78,6 +78,22 @@ module CloseYourIt
|
|
|
78
78
|
payloads
|
|
79
79
|
end
|
|
80
80
|
|
|
81
|
+
# CYSK-29 — il flush della telemetria d'uso: UNA POST per finestra, payload privo di dati utente
|
|
82
|
+
# per costruzione (route = Controller#action). Fire-and-forget via worker, come tutto il resto.
|
|
83
|
+
def flush_usage(symbols:, truncated:, window_started_at:, window_ended_at:)
|
|
84
|
+
payload = {
|
|
85
|
+
environment: @configuration.environment.to_s,
|
|
86
|
+
release: @configuration.release,
|
|
87
|
+
sdk: { name: "closeyourit-ruby", version: CloseYourIt::VERSION },
|
|
88
|
+
window_started_at: window_started_at, window_ended_at: window_ended_at,
|
|
89
|
+
truncated: truncated, symbols: symbols
|
|
90
|
+
}
|
|
91
|
+
path = "/api/v1/projects/#{@configuration.project_id}/usages"
|
|
92
|
+
accepted = @worker.perform { @transport.send_event(payload, path: path) }
|
|
93
|
+
CloseYourIt.notify_diagnostic(:enqueue, path: path, batch: symbols.size) if accepted
|
|
94
|
+
payload
|
|
95
|
+
end
|
|
96
|
+
|
|
81
97
|
def shutdown
|
|
82
98
|
@worker.shutdown
|
|
83
99
|
end
|
|
@@ -41,7 +41,8 @@ module CloseYourIt
|
|
|
41
41
|
:query_time_threshold_ms, :slow_request_threshold_ms, :slow_external_threshold_ms,
|
|
42
42
|
:capture_external_http, :trap_signals,
|
|
43
43
|
:monitor_jobs, :slow_job_threshold_ms, :job_queue_latency_threshold_ms,
|
|
44
|
-
:jobs_sample_rate, :propagate_trace_context
|
|
44
|
+
:jobs_sample_rate, :propagate_trace_context,
|
|
45
|
+
:usage_enabled, :usage_flush_interval, :usage_max_symbols
|
|
45
46
|
attr_writer :release, :project_root
|
|
46
47
|
attr_reader :excluded_exceptions, :excluded_log_patterns, :excluded_query_patterns,
|
|
47
48
|
:filter_parameters, :scrub_message_patterns, :trace_propagation_allowlist
|
|
@@ -112,6 +113,14 @@ module CloseYourIt
|
|
|
112
113
|
@logs_sample_rate = 1.0
|
|
113
114
|
@logs_batch_size = 50
|
|
114
115
|
@logs_flush_interval = 5
|
|
116
|
+
|
|
117
|
+
# CYSK-29 — telemetria d'uso: quali rotte/job/chiavi girano davvero. Il payload è privo di
|
|
118
|
+
# dati utente per costruzione (route = Controller#action, mai l'URL), quindi il default è ON:
|
|
119
|
+
# trenta giorni di raccolta facoltativa hanno insegnato che facoltativo significa mai.
|
|
120
|
+
# Il registro si svuota a ogni flush: il tetto limita i simboli DISTINTI per finestra.
|
|
121
|
+
@usage_enabled = true
|
|
122
|
+
@usage_flush_interval = 300
|
|
123
|
+
@usage_max_symbols = 2000
|
|
115
124
|
# Broadcast opt-in di Rails.logger → CloseYourIt.log (default OFF; spedisce solo ≥ soglia).
|
|
116
125
|
@capture_rails_logs = false
|
|
117
126
|
@logs_min_level = :info
|
|
@@ -68,6 +68,9 @@ module CloseYourIt
|
|
|
68
68
|
ActiveSupport::Notifications.subscribe("process_action.action_controller") do |*args|
|
|
69
69
|
event = ActiveSupport::Notifications::Event.new(*args)
|
|
70
70
|
payload = event.payload
|
|
71
|
+
# CYSK-29 — telemetria d'uso: la rotta è `Controller#action`, MAI l'URL. Il registro fa
|
|
72
|
+
# una lookup e un increment; il gate usage_enabled sta dentro #record.
|
|
73
|
+
CloseYourIt.usage_registry.record("route", "#{payload[:controller]}##{payload[:action]}") if CloseYourIt.enabled?
|
|
71
74
|
perf.record(
|
|
72
75
|
route: "#{payload[:controller]}##{payload[:action]}",
|
|
73
76
|
duration_ms: event.duration
|
|
@@ -104,6 +107,8 @@ module CloseYourIt
|
|
|
104
107
|
ActiveSupport::Notifications.subscribe("perform.active_job") do |*args|
|
|
105
108
|
event = ActiveSupport::Notifications::Event.new(*args)
|
|
106
109
|
job = event.payload[:job]
|
|
110
|
+
# CYSK-29 — anche i job dichiarano di essere girati: kind `job`, simbolo = la classe.
|
|
111
|
+
CloseYourIt.usage_registry.record("job", job.class.name) if job && CloseYourIt.enabled?
|
|
107
112
|
jobs.active_job_performed(job, event.duration) if job
|
|
108
113
|
end
|
|
109
114
|
end
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "concurrent"
|
|
4
|
+
|
|
5
|
+
module CloseYourIt
|
|
6
|
+
# CYSK-29 — la telemetria d'uso: registra QUALI simboli girano davvero (route come
|
|
7
|
+
# `Controller#action`, job, chiavi custom letterali) e li flusha in una POST ogni
|
|
8
|
+
# `usage_flush_interval` secondi per processo. Sul percorso caldo: una lookup e un increment.
|
|
9
|
+
#
|
|
10
|
+
# Le regole che rendono il sistema immune alle perdite:
|
|
11
|
+
# - i conteggi sono INDICATIVI, solo `last_seen_at` è portante: un flush perso costa una finestra
|
|
12
|
+
# su un simbolo che si rivede subito dopo;
|
|
13
|
+
# - il registro SI SVUOTA a ogni flush: il tetto (`usage_max_symbols`) limita i simboli distinti
|
|
14
|
+
# visti in una finestra — una grandezza legata al traffico, non alla dimensione del codice;
|
|
15
|
+
# - oltre il tetto il flush porta `truncated: true`, e lato scanner quel flag SQUALIFICA il kind;
|
|
16
|
+
# - nessun sampling, mai: un campionamento su una rotta chiamata 3 volte al mese fabbrica
|
|
17
|
+
# esattamente il falso «mai vista» che il sistema esiste per evitare;
|
|
18
|
+
# - `route` è `Controller#action`, MAI l'URL: niente path, parametri, user, IP.
|
|
19
|
+
class UsageRegistry
|
|
20
|
+
# I simboli vengono da payload di framework o da stringhe letterali: il pattern è il contratto.
|
|
21
|
+
SYMBOL_FORMAT = %r{\A[A-Za-z0-9_:#./-]{1,200}\z}
|
|
22
|
+
KINDS = %w[route job custom].freeze
|
|
23
|
+
|
|
24
|
+
attr_reader :timer # esposto per i test (verifica dell'intervallo configurato)
|
|
25
|
+
|
|
26
|
+
def initialize(client:, configuration:)
|
|
27
|
+
@client = client
|
|
28
|
+
@configuration = configuration
|
|
29
|
+
@mutex = Mutex.new
|
|
30
|
+
@symbols = {}
|
|
31
|
+
@truncated = false
|
|
32
|
+
@window_started_at = Time.now.utc
|
|
33
|
+
@timer = nil
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def record(kind, symbol)
|
|
37
|
+
return unless @configuration.usage_enabled
|
|
38
|
+
|
|
39
|
+
kind = kind.to_s
|
|
40
|
+
symbol = symbol.to_s
|
|
41
|
+
return unless KINDS.include?(kind) && symbol.match?(SYMBOL_FORMAT)
|
|
42
|
+
|
|
43
|
+
@mutex.synchronize do
|
|
44
|
+
key = [ kind, symbol ]
|
|
45
|
+
entry = @symbols[key]
|
|
46
|
+
if entry
|
|
47
|
+
entry[:count] += 1
|
|
48
|
+
entry[:last_seen_at] = Time.now.utc
|
|
49
|
+
elsif @symbols.size >= @configuration.usage_max_symbols.to_i
|
|
50
|
+
@truncated = true
|
|
51
|
+
else
|
|
52
|
+
@symbols[key] = { count: 1, last_seen_at: Time.now.utc }
|
|
53
|
+
end
|
|
54
|
+
ensure_timer
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def flush
|
|
59
|
+
payload = drain
|
|
60
|
+
return if payload[:symbols].empty? && !payload[:truncated]
|
|
61
|
+
|
|
62
|
+
@client.flush_usage(**payload)
|
|
63
|
+
rescue StandardError => e
|
|
64
|
+
# Il flush gira sul thread timer: un errore non deve propagare nell'app né uccidere il
|
|
65
|
+
# TimerTask. La semantica portante è last_seen_at: una finestra persa non falsifica niente.
|
|
66
|
+
CloseYourIt.internal_logger.error("CloseYourIt usage registry: #{e.class}: #{e.message}")
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def shutdown
|
|
70
|
+
@timer&.shutdown
|
|
71
|
+
flush
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
private
|
|
75
|
+
|
|
76
|
+
def drain
|
|
77
|
+
@mutex.synchronize do
|
|
78
|
+
symbols = @symbols.map do |(kind, symbol), entry|
|
|
79
|
+
{ kind: kind, symbol: symbol, count: entry[:count], last_seen_at: entry[:last_seen_at].iso8601 }
|
|
80
|
+
end
|
|
81
|
+
payload = { symbols: symbols, truncated: @truncated,
|
|
82
|
+
window_started_at: @window_started_at.iso8601, window_ended_at: Time.now.utc.iso8601 }
|
|
83
|
+
@symbols = {}
|
|
84
|
+
@truncated = false
|
|
85
|
+
@window_started_at = Time.now.utc
|
|
86
|
+
payload
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def ensure_timer
|
|
91
|
+
return if @timer
|
|
92
|
+
|
|
93
|
+
interval = @configuration.usage_flush_interval.to_i
|
|
94
|
+
interval = 300 if interval <= 0
|
|
95
|
+
@timer = Concurrent::TimerTask.new(execution_interval: interval) { flush }
|
|
96
|
+
@timer.execute
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
data/lib/closeyourit/version.rb
CHANGED
data/lib/closeyourit-ruby.rb
CHANGED
|
@@ -23,6 +23,7 @@ require_relative "closeyourit/performance/request_profile"
|
|
|
23
23
|
require_relative "closeyourit/performance/rollup"
|
|
24
24
|
require_relative "closeyourit/log_device"
|
|
25
25
|
require_relative "closeyourit/log_buffer"
|
|
26
|
+
require_relative "closeyourit/usage_registry"
|
|
26
27
|
require_relative "closeyourit/subscribers/slow_query"
|
|
27
28
|
require_relative "closeyourit/subscribers/request_performance"
|
|
28
29
|
require_relative "closeyourit/subscribers/job_performance"
|
|
@@ -70,6 +71,7 @@ module CloseYourIt
|
|
|
70
71
|
@configuration = Configuration.new
|
|
71
72
|
@client = nil
|
|
72
73
|
@log_buffer = nil
|
|
74
|
+
@usage_registry = nil
|
|
73
75
|
@shutdown_notified = false # nuova sessione: :shutdown potrà essere notificato di nuovo
|
|
74
76
|
yield(@configuration) if block_given?
|
|
75
77
|
@configuration.validate!
|
|
@@ -264,7 +266,8 @@ module CloseYourIt
|
|
|
264
266
|
# da CloseYourIt.init; con `config.trap_signals` viene raggiunto anche su SIGTERM. Idempotente:
|
|
265
267
|
# richiamarlo è sicuro (buffer già vuoto, worker già fermo → no-op).
|
|
266
268
|
def shutdown
|
|
267
|
-
# Ordine critico: prima
|
|
269
|
+
# Ordine critico: prima i buffer (accodano l'ultimo batch nel worker), poi il worker (li drena).
|
|
270
|
+
@usage_registry&.shutdown
|
|
268
271
|
@log_buffer&.shutdown
|
|
269
272
|
@client&.shutdown
|
|
270
273
|
# Riepilogo di fine-vita: l'app riceve lo snapshot dei contatori senza log rumorosi. Emesso una
|
|
@@ -323,6 +326,22 @@ module CloseYourIt
|
|
|
323
326
|
nil
|
|
324
327
|
end
|
|
325
328
|
|
|
329
|
+
# CYSK-29 — il registro della telemetria d'uso (una lookup + increment sul percorso caldo).
|
|
330
|
+
def usage_registry
|
|
331
|
+
ensure_current_process!
|
|
332
|
+
@usage_registry ||= UsageRegistry.new(client: client, configuration: configuration)
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
# CYSK-29 — dichiara che un pezzo di codice è stato ESEGUITO. `key` deve essere una stringa
|
|
336
|
+
# LETTERALE (mai interpolata con dati): è l'unico modo onesto di rispondere a «questo ramo viene
|
|
337
|
+
# mai preso?». No-op se la gemma non è configurata o usage_enabled è OFF.
|
|
338
|
+
def used(key)
|
|
339
|
+
return nil unless configured? && enabled?
|
|
340
|
+
|
|
341
|
+
usage_registry.record("custom", key)
|
|
342
|
+
nil
|
|
343
|
+
end
|
|
344
|
+
|
|
326
345
|
private
|
|
327
346
|
|
|
328
347
|
# Vero se il thread corrente sta eseguendo l'hook diagnostico: le API di telemetria diventano no-op
|
|
@@ -349,6 +368,7 @@ module CloseYourIt
|
|
|
349
368
|
@log_buffer ||= LogBuffer.new(client: client, configuration: configuration)
|
|
350
369
|
end
|
|
351
370
|
|
|
371
|
+
|
|
352
372
|
# Rileva un fork confrontando il PID del processo in cui @client/@log_buffer sono stati materializzati
|
|
353
373
|
# con quello corrente. In un figlio forkato i due oggetti sono ereditati dal padre, ma i loro thread
|
|
354
374
|
# — il worker pool di Client e il TimerTask di LogBuffer — vivono solo nel padre (il fork copia il
|
|
@@ -371,6 +391,7 @@ module CloseYourIt
|
|
|
371
391
|
def discard_inherited_client!
|
|
372
392
|
@client = nil
|
|
373
393
|
@log_buffer = nil
|
|
394
|
+
@usage_registry = nil
|
|
374
395
|
end
|
|
375
396
|
|
|
376
397
|
# I log seguono il master switch del client + il proprio flag dedicato.
|
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.9.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Alessio Bussolari
|
|
@@ -87,6 +87,7 @@ files:
|
|
|
87
87
|
- lib/closeyourit/subscribers/slow_query.rb
|
|
88
88
|
- lib/closeyourit/trace_context.rb
|
|
89
89
|
- lib/closeyourit/transport.rb
|
|
90
|
+
- lib/closeyourit/usage_registry.rb
|
|
90
91
|
- lib/closeyourit/version.rb
|
|
91
92
|
homepage: https://github.com/bussolabs/closeyourit-ruby
|
|
92
93
|
licenses:
|