closeyourit-ruby 0.2.1 → 0.3.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b015462d6b0e563ef202410b5f2bc33b11ee10f985d984ac84a874836f67c82e
4
- data.tar.gz: 41f3e3ca878dbfbfd818cd4963b238d1d78400344e0e8bbb95cfd025853ebf82
3
+ metadata.gz: d98365053459eaded9732014e987b01e3fcf2ac60a21db83026243d076bc2dbf
4
+ data.tar.gz: 4cb2024f4681e57d415097b8af237156f3cc0c8b764a2d14ea958bb8824e1738
5
5
  SHA512:
6
- metadata.gz: 16d70f8ef7436397d881a8f67664f24aaabfe3ae976d5b23276149242bf8f539d1c5dc1d27f9d7c8a2c72a080ab42b2b56c11ec8af1e0fae108e06f5bf8cfbdc
7
- data.tar.gz: '02853ab18aad59772dd4aa0cc497c32983e4e49f2926d2b42d28654552dabad1c0f9dacd589fc0423c920cbb1ea903457a35767365130777dc4d0317282e7225'
6
+ metadata.gz: de2f19a59df8c78825e0070e5c48c97424a7481d314219da675543da1ab71171ecfe7d5cd09cc0d81d48323be6d50435027e8a6bf901f121c6f5f3435b419b0d
7
+ data.tar.gz: 75ab998c2d1049bdcc047a96a54a01fe92cb50b820e81d1eac3795f4695416d7076956aac3defd82c520daaffd2dccf3726bb585cc189fb76090601928b3447f
data/README.md CHANGED
@@ -171,6 +171,23 @@ Privacy-by-default (`send_pii = false`). In sintesi:
171
171
  > Rischio residuo: `exception.value` e i nomi tabella/colonna nello SQL possono contenere dati di
172
172
  > dominio. Usa `before_send`/`scrub_message_patterns` per azzerarli. Dettaglio in [`PDR.md` §9](PDR.md).
173
173
 
174
+ ## Diagnostica
175
+
176
+ Il trasporto è fire-and-forget: non solleva mai e non blocca la request. Per non lasciare
177
+ fallimenti silenziosi, ogni risposta HTTP non-2xx (es. `401` token errato, `404` progetto
178
+ inesistente) viene loggata a `warn`, così come gli eventi scartati a coda piena. I contatori
179
+ sono ispezionabili a runtime:
180
+
181
+ ```ruby
182
+ CloseYourIt.stats.to_h
183
+ # => { enqueued: 128, dropped: 0, sent: 126, failed: 2 }
184
+ ```
185
+
186
+ - `enqueued` — eventi accettati per l'invio
187
+ - `dropped` — scartati perché la coda async era piena (mai backpressure)
188
+ - `sent` — risposta HTTP 2xx
189
+ - `failed` — errore di rete o status non-2xx (vedi i log a `warn`)
190
+
174
191
  ## Sviluppo
175
192
 
176
193
  ```bash
@@ -13,13 +13,22 @@ module CloseYourIt
13
13
  @executor = build_executor(threads.to_i, max_queue)
14
14
  end
15
15
 
16
+ # Ritorna true se l'evento è stato accettato (o eseguito sincrono), false se scartato
17
+ # perché la coda era piena (`fallback_policy: :discard`). Mai backpressure sulla request.
16
18
  def perform(&block)
17
- @executor.post do
19
+ accepted = @executor.post do
18
20
  block.call
19
21
  rescue Exception => e # rubocop:disable Lint/RescueException
20
22
  # Mai propagare: la telemetria non deve poter crashare l'app ospite.
21
23
  CloseYourIt.logger.error("CloseYourIt background worker: #{e.class}: #{e.message}")
22
24
  end
25
+
26
+ unless accepted
27
+ CloseYourIt.stats.increment(:dropped)
28
+ CloseYourIt.logger.warn("CloseYourIt background worker: coda piena, evento scartato")
29
+ end
30
+
31
+ accepted
23
32
  end
24
33
 
25
34
  def shutdown(timeout = 1)
@@ -19,7 +19,8 @@ module CloseYourIt
19
19
  return nil if payload.nil?
20
20
 
21
21
  path = event.ingest_path(@configuration.project_id)
22
- @worker.perform { @transport.send_event(payload, path: path) }
22
+ accepted = @worker.perform { @transport.send_event(payload, path: path) }
23
+ CloseYourIt.stats.increment(:enqueued) if accepted
23
24
  payload
24
25
  end
25
26
 
@@ -95,9 +95,12 @@ module CloseYourIt
95
95
  true
96
96
  end
97
97
 
98
- # Logga i warning di configurazione (es. endpoint http://). Chiamata da `CloseYourIt.init`.
98
+ # Logga i warning di configurazione (es. endpoint http://, project_id/endpoint malformati).
99
+ # Non solleva mai: coerente con la filosofia no-op del client. Chiamata da `CloseYourIt.init`.
99
100
  def validate!
100
101
  CloseYourIt.logger.warn(insecure_endpoint_message) if insecure_endpoint?
102
+ CloseYourIt.logger.warn(malformed_project_id_message) if malformed_project_id?
103
+ CloseYourIt.logger.warn(malformed_endpoint_message) if malformed_endpoint?
101
104
  self
102
105
  end
103
106
 
@@ -133,11 +136,35 @@ module CloseYourIt
133
136
  nil
134
137
  end
135
138
 
139
+ UUID_FORMAT = /\A[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\z/i
140
+
136
141
  def insecure_endpoint?
137
142
  uri = parsed_endpoint
138
143
  !uri.nil? && uri.scheme != "https"
139
144
  end
140
145
 
146
+ # Avvisa se il project_id è valorizzato ma non sembra uno UUID (l'errore tipico è incollare
147
+ # uno slug/nome al posto dell'id). Non blocca: il server è l'autorità sulla validità.
148
+ def malformed_project_id?
149
+ !blank?(project_id) && !UUID_FORMAT.match?(project_id.to_s)
150
+ end
151
+
152
+ def malformed_project_id_message
153
+ "CloseYourIt: project_id (#{project_id}) non ha forma UUID — verifica di aver copiato l'id corretto."
154
+ end
155
+
156
+ # Avvisa se endpoint_url è valorizzato ma non parsabile o privo di host.
157
+ def malformed_endpoint?
158
+ return false if blank?(endpoint_url)
159
+
160
+ uri = parsed_endpoint
161
+ uri.nil? || blank?(uri.host)
162
+ end
163
+
164
+ def malformed_endpoint_message
165
+ "CloseYourIt: endpoint_url (#{endpoint_url}) non è un URL valido (host mancante)."
166
+ end
167
+
141
168
  def insecure_endpoint_message
142
169
  tail = production? ? "Telemetria DISABILITATA in production." : "Consentito solo in sviluppo."
143
170
  "CloseYourIt: endpoint_url usa http:// non sicuro (#{endpoint_url}) — il token viaggerebbe in chiaro. #{tail}"
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "concurrent"
4
+
5
+ module CloseYourIt
6
+ # Contatori diagnostici thread-safe del client: quanti eventi sono stati accodati,
7
+ # scartati (coda piena), spediti con successo o falliti (rete o status non-2xx).
8
+ # Servono a rendere visibili i fallimenti silenziosi del trasporto fire-and-forget.
9
+ #
10
+ # CloseYourIt.stats.to_h # => { enqueued: 12, dropped: 0, sent: 11, failed: 1 }
11
+ class Stats
12
+ COUNTERS = %i[enqueued dropped sent failed].freeze
13
+
14
+ def initialize
15
+ @counters = COUNTERS.to_h { |name| [ name, Concurrent::AtomicFixnum.new(0) ] }
16
+ end
17
+
18
+ def increment(name)
19
+ counter = @counters.fetch(name)
20
+ counter.increment
21
+ counter.value
22
+ end
23
+
24
+ def [](name)
25
+ @counters.fetch(name).value
26
+ end
27
+
28
+ def to_h
29
+ @counters.transform_values(&:value)
30
+ end
31
+
32
+ def reset!
33
+ @counters.each_value { |counter| counter.value = 0 }
34
+ self
35
+ end
36
+ end
37
+ end
@@ -10,8 +10,8 @@ module CloseYourIt
10
10
  class Transport
11
11
  OPEN_TIMEOUT = 2
12
12
  READ_TIMEOUT = 3
13
- # Net::HTTP non segue i redirect da solo. L'host canonico può rispondere 301 (es. apex → www):
14
- # ri-POSTiamo a Location preservando metodo + body, così l'evento non si perde in silenzio.
13
+ # Net::HTTP non segue i redirect: l'host canonico può rispondere 301 (es. apex → www).
14
+ # Ri-POSTiamo a Location preservando metodo + body, così l'evento non si perde in silenzio.
15
15
  MAX_REDIRECTS = 2
16
16
 
17
17
  def initialize(configuration)
@@ -19,8 +19,16 @@ module CloseYourIt
19
19
  end
20
20
 
21
21
  def send_event(payload, path:)
22
- post(payload, path)
22
+ response = post(payload, path)
23
+ if response.is_a?(Net::HTTPSuccess)
24
+ CloseYourIt.stats.increment(:sent)
25
+ else
26
+ CloseYourIt.stats.increment(:failed)
27
+ CloseYourIt.logger.warn("CloseYourIt transport: HTTP #{response.code} su #{path}")
28
+ end
29
+ response
23
30
  rescue StandardError => e
31
+ CloseYourIt.stats.increment(:failed)
24
32
  CloseYourIt.logger.error("CloseYourIt transport: #{e.class}: #{e.message}")
25
33
  nil
26
34
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CloseYourIt
4
- VERSION = "0.2.1"
4
+ VERSION = "0.3.1"
5
5
  end
@@ -7,6 +7,7 @@ require_relative "closeyourit/configuration"
7
7
  require_relative "closeyourit/breadcrumb"
8
8
  require_relative "closeyourit/scope"
9
9
  require_relative "closeyourit/scrubber"
10
+ require_relative "closeyourit/stats"
10
11
  require_relative "closeyourit/background_worker"
11
12
  require_relative "closeyourit/transport"
12
13
  require_relative "closeyourit/event"
@@ -144,6 +145,12 @@ module CloseYourIt
144
145
 
145
146
  attr_writer :logger
146
147
 
148
+ # Contatori diagnostici del client (accodati/scartati/spediti/falliti).
149
+ # CloseYourIt.stats.to_h # => { enqueued: …, dropped: …, sent: …, failed: … }
150
+ def stats
151
+ @stats ||= Stats.new
152
+ end
153
+
147
154
  private
148
155
 
149
156
  def client
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.2.1
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alessio Bussolari
@@ -55,6 +55,7 @@ files:
55
55
  - lib/closeyourit/scope.rb
56
56
  - lib/closeyourit/scrubber.rb
57
57
  - lib/closeyourit/sidekiq/error_handler.rb
58
+ - lib/closeyourit/stats.rb
58
59
  - lib/closeyourit/subscribers/slow_query.rb
59
60
  - lib/closeyourit/transport.rb
60
61
  - lib/closeyourit/version.rb