closeyourit-ruby 0.2.0 → 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 +4 -4
- data/README.md +22 -1
- data/lib/closeyourit/background_worker.rb +10 -1
- data/lib/closeyourit/client.rb +2 -1
- data/lib/closeyourit/configuration.rb +28 -1
- data/lib/closeyourit/stats.rb +37 -0
- data/lib/closeyourit/transport.rb +33 -2
- data/lib/closeyourit/version.rb +1 -1
- data/lib/closeyourit-ruby.rb +7 -0
- 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: d98365053459eaded9732014e987b01e3fcf2ac60a21db83026243d076bc2dbf
|
|
4
|
+
data.tar.gz: 4cb2024f4681e57d415097b8af237156f3cc0c8b764a2d14ea958bb8824e1738
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: de2f19a59df8c78825e0070e5c48c97424a7481d314219da675543da1ab71171ecfe7d5cd09cc0d81d48323be6d50435027e8a6bf901f121c6f5f3435b419b0d
|
|
7
|
+
data.tar.gz: 75ab998c2d1049bdcc047a96a54a01fe92cb50b820e81d1eac3795f4695416d7076956aac3defd82c520daaffd2dccf3726bb585cc189fb76090601928b3447f
|
data/README.md
CHANGED
|
@@ -45,7 +45,7 @@ In CloseYourIt, area **Member → Project → tokens**, crea un token: ottieni i
|
|
|
45
45
|
```ruby
|
|
46
46
|
# config/initializers/closeyourit.rb
|
|
47
47
|
CloseYourIt.init do |c|
|
|
48
|
-
c.endpoint_url = ENV["CLOSEYOURIT_ENDPOINT_URL"] # es. https://closeyour.it
|
|
48
|
+
c.endpoint_url = ENV["CLOSEYOURIT_ENDPOINT_URL"] # es. https://www.closeyour.it (host canonico)
|
|
49
49
|
c.token = ENV["CLOSEYOURIT_TOKEN"] # Bearer secret del Projects::Token
|
|
50
50
|
c.project_id = ENV["CLOSEYOURIT_PROJECT_ID"] # UUID del progetto su CloseYourIt
|
|
51
51
|
c.environment = Rails.env
|
|
@@ -55,6 +55,10 @@ end
|
|
|
55
55
|
**Senza `endpoint_url` / `token` / `project_id` la gemma è no-op** (nessun invio, nessun overhead). In
|
|
56
56
|
`production` un `endpoint_url` `http://` viene rifiutato (no-op + warning): il token viaggerebbe in chiaro.
|
|
57
57
|
|
|
58
|
+
> **Endpoint:** usa l'host canonico **`https://www.closeyour.it`**. La gemma segue fino a 2 redirect su POST
|
|
59
|
+
> (preservando metodo e body), quindi anche l'apex `https://closeyour.it` (301 → www) funziona — ma puntare
|
|
60
|
+
> direttamente a www risparmia un hop per ogni evento.
|
|
61
|
+
|
|
58
62
|
### Opzioni
|
|
59
63
|
|
|
60
64
|
| Opzione | Default | Descrizione |
|
|
@@ -167,6 +171,23 @@ Privacy-by-default (`send_pii = false`). In sintesi:
|
|
|
167
171
|
> Rischio residuo: `exception.value` e i nomi tabella/colonna nello SQL possono contenere dati di
|
|
168
172
|
> dominio. Usa `before_send`/`scrub_message_patterns` per azzerarli. Dettaglio in [`PDR.md` §9](PDR.md).
|
|
169
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
|
+
|
|
170
191
|
## Sviluppo
|
|
171
192
|
|
|
172
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)
|
data/lib/closeyourit/client.rb
CHANGED
|
@@ -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
|
|
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,14 +10,25 @@ module CloseYourIt
|
|
|
10
10
|
class Transport
|
|
11
11
|
OPEN_TIMEOUT = 2
|
|
12
12
|
READ_TIMEOUT = 3
|
|
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
|
+
MAX_REDIRECTS = 2
|
|
13
16
|
|
|
14
17
|
def initialize(configuration)
|
|
15
18
|
@configuration = configuration
|
|
16
19
|
end
|
|
17
20
|
|
|
18
21
|
def send_event(payload, path:)
|
|
19
|
-
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
|
|
20
30
|
rescue StandardError => e
|
|
31
|
+
CloseYourIt.stats.increment(:failed)
|
|
21
32
|
CloseYourIt.logger.error("CloseYourIt transport: #{e.class}: #{e.message}")
|
|
22
33
|
nil
|
|
23
34
|
end
|
|
@@ -25,7 +36,21 @@ module CloseYourIt
|
|
|
25
36
|
private
|
|
26
37
|
|
|
27
38
|
def post(payload, path)
|
|
39
|
+
body = JSON.generate(payload)
|
|
28
40
|
uri = URI.parse("#{base_url}#{path}")
|
|
41
|
+
redirects = 0
|
|
42
|
+
|
|
43
|
+
loop do
|
|
44
|
+
response = post_once(uri, body)
|
|
45
|
+
location = response["location"] if response.is_a?(Net::HTTPRedirection)
|
|
46
|
+
return response unless location && redirects < MAX_REDIRECTS
|
|
47
|
+
|
|
48
|
+
redirects += 1
|
|
49
|
+
uri = redirect_uri(uri, location)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def post_once(uri, body)
|
|
29
54
|
http = Net::HTTP.new(uri.host, uri.port)
|
|
30
55
|
http.use_ssl = uri.scheme == "https"
|
|
31
56
|
http.open_timeout = OPEN_TIMEOUT
|
|
@@ -35,11 +60,17 @@ module CloseYourIt
|
|
|
35
60
|
request["Authorization"] = "Bearer #{@configuration.token}"
|
|
36
61
|
request["Content-Type"] = "application/json"
|
|
37
62
|
request["User-Agent"] = "closeyourit-ruby/#{VERSION}"
|
|
38
|
-
request.body =
|
|
63
|
+
request.body = body
|
|
39
64
|
|
|
40
65
|
http.request(request)
|
|
41
66
|
end
|
|
42
67
|
|
|
68
|
+
# Location può essere assoluto (https://www.…) o relativo (/api/…): risolvilo sull'URI corrente.
|
|
69
|
+
def redirect_uri(current, location)
|
|
70
|
+
target = URI.parse(location)
|
|
71
|
+
target.relative? ? current + target : target
|
|
72
|
+
end
|
|
73
|
+
|
|
43
74
|
def base_url
|
|
44
75
|
@configuration.endpoint_url.to_s.chomp("/")
|
|
45
76
|
end
|
data/lib/closeyourit/version.rb
CHANGED
data/lib/closeyourit-ruby.rb
CHANGED
|
@@ -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.
|
|
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
|