closeyourit-ruby 0.11.0 → 0.13.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d77ef65ad83c3890320e2c3c3a002a6708ef52d223204da4b553ac75ec58c4b9
4
- data.tar.gz: 4bc0274665e54f19fe33d6e379270f31802a71317075ceae65fecdd4b8a6d66c
3
+ metadata.gz: c46a549b542395326541c9def86cc019d5cd9620fef9c8f4584c90543ca1b179
4
+ data.tar.gz: 5ac2011a99d7be1d09a751334832154cf1d23765fe8527333adecda9862f50c1
5
5
  SHA512:
6
- metadata.gz: cc89f3b0f20d8781b5a963b9fda04935b735500612db5883b29f86abc92bc560a452abc133db66859b919cdfcd8994ff6c4891fdcafe39a6c7878a960c74b627
7
- data.tar.gz: 5b55acc5e39e78e9fc875e6cbfd32dfce56bbb9e27d600ec30f86a6d27d12660c1c61195a28c32f32ac22b418c78ac9d41e83af8bbdbd26f96781d740bd6f1cd
6
+ metadata.gz: 95b4271a533b85cb3d9cf0bf718f08b07a87ce180cd00c03fcc82c30cf24047bc240d1ad572be09bac04402b00ecb728e87250905d28666d443fd22d65111981
7
+ data.tar.gz: 9210728ff21f26c59b49a31007a068f1f3dd334214c2f67f5eb7aa9ce7b418ecdec3f6c1c9cc6b2935e950cded2252ebc417183c3c4518b7d8a4ca130d9a9a44
data/README.md CHANGED
@@ -52,7 +52,7 @@ In CloseYourIt, area **Member → Project → tokens**, crea un token: ottieni i
52
52
  ```ruby
53
53
  # config/initializers/closeyourit.rb
54
54
  CloseYourIt.init do |c|
55
- c.endpoint_url = ENV["CLOSEYOURIT_ENDPOINT_URL"] # es. https://www.closeyour.it (host canonico)
55
+ c.endpoint_url = ENV["CLOSEYOURIT_ENDPOINT_URL"] # es. https://app.closeyour.it (host canonico)
56
56
  c.token = ENV["CLOSEYOURIT_TOKEN"] # Bearer secret del Projects::Token
57
57
  c.project_id = ENV["CLOSEYOURIT_PROJECT_ID"] # UUID del progetto su CloseYourIt
58
58
  c.environment = Rails.env
@@ -62,9 +62,8 @@ end
62
62
  **Senza `endpoint_url` / `token` / `project_id` la gemma è no-op** (nessun invio, nessun overhead). In
63
63
  `production` un `endpoint_url` `http://` viene rifiutato (no-op + warning): il token viaggerebbe in chiaro.
64
64
 
65
- > **Endpoint:** usa l'host canonico **`https://www.closeyour.it`**. La gemma segue fino a 2 redirect su POST
66
- > (preservando metodo e body), quindi anche l'apex `https://closeyour.it` (301 → www) funziona — ma puntare
67
- > direttamente a www risparmia un hop per ogni evento.
65
+ > **Endpoint:** usa l'host canonico **`https://app.closeyour.it`**. Le installazioni già configurate su
66
+ > `https://www.closeyour.it` continuano a funzionare: l'ingest su `www` resta inoltrato all'app.
68
67
 
69
68
  ### Opzioni
70
69
 
@@ -84,7 +83,9 @@ end
84
83
  | `slow_query_threshold_ms` | `100` | Soglia query lente |
85
84
  | `excluded_query_patterns` | `solid_queue_`, `solid_cache_`, `solid_cable_` | Query da NON misurare come rallentamento — **Regexp** (pattern) o **String** (testo letterale). Vale solo per la misura: breadcrumb e profiling N+1 vedono tutto |
86
85
  | `slow_method_threshold_ms` | `200` | Soglia metodi lenti |
86
+ | `capture_method_arguments` | `false` | Allega gli argomenti di `measure`/`monitor` ai `slow_method`. **Attenzione**: gli argomenti sono spesso dati personali; la gemma redige chiavi sensibili, credenziali e `scrub_message_patterns`, non il resto |
87
87
  | `capture_request` | `true` | Cattura il contesto HTTP della richiesta (method/url/header allowlist) |
88
+ | `capture_request_body` | `true` | Allega i parametri del body (`request.data`) all'errore, scrubbati. Vale **solo con `send_pii = true`**: con la privacy spenta il body non parte mai |
88
89
  | `request_header_allowlist` | `Accept`, `Content-Type`, `User-Agent`, `Referer` | Header inviati (mai Authorization/Cookie) |
89
90
  | `breadcrumbs_enabled` | `true` | Cronologia (query offuscate + `add_breadcrumb`) allegata all'errore |
90
91
  | `max_breadcrumbs` | `100` | Dimensione max del ring buffer breadcrumbs |
@@ -92,7 +93,7 @@ end
92
93
  | `report_active_job_errors` | `true` | Cattura gli errori dei job ActiveJob/Solid Queue/Sidekiq |
93
94
  | `send_pii` | `false` | Master switch PII |
94
95
  | `obfuscate_sql` | `true` | Maschera i literal nello SQL |
95
- | `filter_parameters` | `[]` | Chiavi extra da redarre (mergiate con quelle di Rails) |
96
+ | `filter_parameters` | `[]` | Chiavi extra da redarre, in aggiunta a `Rails.application.config.filter_parameters` (unite automaticamente dalla Railtie) |
96
97
  | `scrub_message_patterns` | `[]` | Regexp **aggiuntive** da redarre da messaggi d'eccezione **e da `message` dei log**; le credenziali `Bearer`/`Basic`/`Token` sono già redatte di serie |
97
98
  | `logs_enabled` | `true` | Master switch dello stream di log strutturato |
98
99
  | `logs_min_level` | `:info` | Soglia minima: i log sotto (es. `debug`) non vengono inviati |
@@ -155,7 +156,9 @@ end
155
156
 
156
157
  Un Rack middleware allega a ogni evento il contesto HTTP della richiesta: **method**, **url** (senza
157
158
  query string) e gli **header dell'allowlist** (`request_header_allowlist`, mai Authorization/Cookie).
158
- Query string e IP solo con `send_pii`. Lo scope è resettato a fine richiesta (niente bleed tra request).
159
+ Query string, IP e **parametri del body** (`request.data`, scrubbati, estratti solo quando scoppia
160
+ l'errore; `capture_request_body` li spegne) solo con `send_pii`. Lo scope è resettato a fine richiesta
161
+ (niente bleed tra request).
159
162
 
160
163
  ### Background job + errori handled (automatico, con Rails)
161
164
 
@@ -312,7 +315,10 @@ end
312
315
  ```
313
316
 
314
317
  Viene inviato un `slow_method` solo se la durata supera `slow_method_threshold_ms`. **Gli argomenti
315
- del metodo non vengono mai inviati** (solo label, durata, file:riga).
318
+ del metodo non vengono inviati** (solo label, durata, file:riga) salvo `capture_method_arguments = true`:
319
+ in quel caso partono con `inspect` troncato, redatti per chiave sensibile (kwargs), credenziali
320
+ `Bearer`/`Basic`/`Token` e `scrub_message_patterns`. Tutto il resto arriva in chiaro: accendilo solo
321
+ su metodi che non ricevono dati personali.
316
322
 
317
323
  ## Performance issues (opt-in)
318
324
 
@@ -395,6 +401,40 @@ ActiveJob dentro Sidekiq è osservato una sola volta quando il subscriber Active
395
401
  Sampling, disabilitazione o perdita del transport impediscono di considerare questi log un censimento.
396
402
  Il raggruppamento backend v1 separa framework, nome, coda e misura; i gruppi legacy restano intatti.
397
403
 
404
+ ## Open Dashboard (opt-in)
405
+
406
+ Il servizio mostra i suoi numeri (utenti, abbonamenti, incassi…) a chi ha la chiave, su
407
+ `GET /open-dashboard`. Lo standard completo: contratto `contracts/open-dashboard/v1`.
408
+
409
+ ```ruby
410
+ # config/initializers/open_dashboard.rb
411
+ CloseYourIt::OpenDashboard.configure do |od|
412
+ od.token = ENV["OPEN_DASHBOARD_TOKEN"] # without it the route does not exist
413
+ od.name = "scontino"
414
+ od.app = "scontino-rails"
415
+ od.url = "https://scontino.it"
416
+ od.locale = "it"
417
+
418
+ od.metric "users.registered", label: "Utenti registrati", group: "Utenti", type: :count do |since|
419
+ { value: User.count, previous: User.where(created_at: ...since).count }
420
+ end
421
+
422
+ od.metric "revenue.mrr", label: "Incasso mensile ricorrente", group: "Incassi",
423
+ type: :money, currency: "EUR" do
424
+ Subscription.active.sum(:amount_cents)
425
+ end
426
+ end
427
+ ```
428
+
429
+ - `since` è l'inizio del periodo di confronto (`period:`, default `"30d"`; anche `1d`, `7d`, `90d`, `365d`).
430
+ - Il blocco restituisce un numero, oppure `{ value:, previous: }`.
431
+ - Tipi: `count` e `money` (interi; `money` in centesimi con `currency`), `percent` (0–100), `duration` (secondi).
432
+ - `better: :down` per i numeri che vanno bene quando scendono.
433
+ - Una metrica che solleva arriva come «non disponibile» e l'errore finisce in CloseYourIt; le altre restano.
434
+ - I blocchi girano a ogni richiesta e chi legge aspetta al massimo 10 secondi: i numeri lenti vanno precalcolati.
435
+ - Una dichiarazione sbagliata (chiave doppia, tipo sconosciuto, `money` senza valuta) blocca l'avvio.
436
+ - Solo totali: mai dati di singole persone.
437
+
398
438
  ## Privacy & PII
399
439
 
400
440
  Privacy-by-default (`send_pii = false`). In sintesi:
@@ -406,8 +446,10 @@ Privacy-by-default (`send_pii = false`). In sintesi:
406
446
  (`Authorization: Bearer …`, `Basic …`, `Token …`) sono redatte **di serie**, senza configurazione:
407
447
  chiave e schema restano leggibili, sparisce il valore → `Authorization: Bearer [FILTERED]`. Il resto
408
448
  è redigibile con `scrub_message_patterns` / `before_send`.
409
- - **Mai inviati**: variabili locali dei frame, argomenti dei metodi, IP/cookie/Authorization, token (che
410
- viaggia solo nell'header su HTTPS).
449
+ - **Solo con `send_pii`**: query string, IP, email/username dell'utente e parametri del body
450
+ (`capture_request_body`). Con la privacy spenta non partono, qualunque sia il resto della configurazione.
451
+ - **Mai inviati**: variabili locali dei frame, cookie/Authorization, token (che viaggia solo nell'header
452
+ su HTTPS). Gli argomenti dei metodi solo con `capture_method_arguments` (opt-in, vedi sopra).
411
453
 
412
454
  > Rischio residuo: `exception.value` e i nomi tabella/colonna nello SQL possono contenere dati di
413
455
  > dominio. Usa `before_send`/`scrub_message_patterns` per azzerarli. Dettaglio in [`PDR.md` §9](PDR.md).
@@ -94,7 +94,8 @@ module CloseYourIt
94
94
 
95
95
  # Request body params in the event (`request.data`), extracted LAZILY only when the error
96
96
  # happens, sanitized and scrubbed (denylist + filter_parameters). The backend scrubs again
97
- # defensively. Uploads → placeholder, 64 KB cap.
97
+ # defensively. Uploads → placeholder, 64 KB cap. Gated by send_pii: with it OFF the body
98
+ # is never sent, whatever this flag says (CYRB-28).
98
99
  @capture_request_body = true
99
100
 
100
101
  # Breadcrumbs: history (obfuscated queries, custom events) attached to the error.
@@ -200,6 +201,13 @@ module CloseYourIt
200
201
  @filter_parameters = Array(list)
201
202
  end
202
203
 
204
+ # Adds the host app's `Rails.application.config.filter_parameters` (strings/symbols/regexps) to the
205
+ # gem list, without duplicates. Rails filter procs are dropped: they mutate values in place, while
206
+ # the Scrubber matches keys only (CYRB-28).
207
+ def merge_filter_parameters(list)
208
+ @filter_parameters |= Array(list).reject { |item| item.respond_to?(:call) }
209
+ end
210
+
203
211
  # Destinations allowed to receive the W3C trace headers. String = exact host (case-insensitive
204
212
  # match), Regexp = pattern (for subdomains/host families). Empty list = none.
205
213
  def trace_propagation_allowlist=(list)
@@ -42,7 +42,9 @@ module CloseYourIt
42
42
  private
43
43
 
44
44
  # Arguments — ONLY with capture_method_arguments (opt-in). Positional by index; kwargs by name with
45
- # sensitive key scrubbing (denylist password/token/…). Value = truncated inspect (JSON-safe).
45
+ # sensitive key scrubbing (denylist password/token/…). Value = truncated inspect (JSON-safe),
46
+ # passed through the message scrubber (auth schemes + scrub_message_patterns) like exception
47
+ # messages, so a token inside a positional argument never leaves the app (CYRB-28).
46
48
  def arguments
47
49
  return nil unless @configuration.capture_method_arguments
48
50
 
@@ -54,7 +56,7 @@ module CloseYourIt
54
56
  end
55
57
 
56
58
  def safe_value(value)
57
- inspected = value.inspect.to_s
59
+ inspected = @scrubber.scrub_message(value.inspect.to_s)
58
60
  inspected.length > 120 ? "#{inspected[0, 117]}…" : inspected
59
61
  end
60
62
  end
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CloseYourIt
4
+ module OpenDashboard
5
+ # Raised at boot when the service declares a metric the standard does not allow.
6
+ class ConfigurationError < StandardError; end
7
+
8
+ # One metric of the Open Dashboard standard. Rules are checked when the service boots, so a bad
9
+ # declaration fails the deploy instead of producing an invalid response in production.
10
+ class Metric
11
+ TYPES = %w[count money percent duration].freeze
12
+ INTEGER_TYPES = %w[count money].freeze
13
+ PERIODS = { "1d" => 1, "7d" => 7, "30d" => 30, "90d" => 90, "365d" => 365 }.freeze
14
+ BETTER = %w[up down].freeze
15
+ KEY_FORMAT = /\A[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)+\z/
16
+ CURRENCY_FORMAT = /\A[A-Z]{3}\z/
17
+ UNAVAILABLE = "unavailable"
18
+ SECONDS_PER_DAY = 86_400
19
+
20
+ attr_reader :key
21
+
22
+ def initialize(key, label:, group:, type:, currency: nil, period: "30d", better: nil, &block)
23
+ @key = key.to_s
24
+ @label = label.to_s
25
+ @group = group.to_s
26
+ @type = type.to_s
27
+ @currency = currency
28
+ @period = period.to_s
29
+ @better = better&.to_s
30
+ @block = block
31
+ validate!
32
+ end
33
+
34
+ # The block gets the start of the comparison period. A failure never breaks the other metrics:
35
+ # it is reported to CloseYourIt and the metric goes out as unavailable.
36
+ def to_h(now)
37
+ since = now - (PERIODS.fetch(@period) * SECONDS_PER_DAY)
38
+ identity.merge(values(@block.call(since)))
39
+ rescue StandardError => e
40
+ CloseYourIt.capture_exception(e, handled: true)
41
+ identity.merge(value: nil, error: UNAVAILABLE)
42
+ end
43
+
44
+ private
45
+
46
+ def identity
47
+ { key: @key, label: @label, group: @group, type: @type, currency: @currency, better: @better }.compact
48
+ end
49
+
50
+ def values(result)
51
+ return { value: number(result) } unless result.is_a?(Hash)
52
+
53
+ out = { value: number(result.fetch(:value)) }
54
+ out.merge!(previous: number(result[:previous]), period: @period) unless result[:previous].nil?
55
+ out
56
+ end
57
+
58
+ # Returns the value to emit: an Integer for count/money, a Float for percent/duration.
59
+ def number(value)
60
+ reject("value must be numeric") unless value.is_a?(Numeric)
61
+ reject("value must be finite") unless !value.respond_to?(:finite?) || value.finite?
62
+
63
+ normalised = INTEGER_TYPES.include?(@type) ? whole(value) : Float(value)
64
+ check_range(normalised)
65
+ normalised
66
+ end
67
+
68
+ def whole(value)
69
+ reject("#{@type} must be an integer") unless value == value.to_i
70
+
71
+ value.to_i
72
+ end
73
+
74
+ def check_range(value)
75
+ reject("count must not be negative") if @type == "count" && value.negative?
76
+ reject("percent must be between 0 and 100") if @type == "percent" && !value.between?(0, 100)
77
+ reject("duration must not be negative") if @type == "duration" && value.negative?
78
+ end
79
+
80
+ def reject(message)
81
+ raise TypeError, "open_dashboard metric #{@key}: #{message}"
82
+ end
83
+
84
+ def validate!
85
+ fail_with("key must be lowercase and dotted, e.g. users.registered") unless KEY_FORMAT.match?(@key)
86
+ fail_with("label is blank") if @label.strip.empty?
87
+ fail_with("group is blank") if @group.strip.empty?
88
+ fail_with("type must be one of #{TYPES.join(', ')}") unless TYPES.include?(@type)
89
+ validate_currency!
90
+ fail_with("period must be one of #{PERIODS.keys.join(', ')}") unless PERIODS.key?(@period)
91
+ fail_with("better must be up or down") if @better && !BETTER.include?(@better)
92
+ fail_with("a block computing the value is required") unless @block
93
+ end
94
+
95
+ def validate_currency!
96
+ if @type == "money"
97
+ fail_with("money needs an ISO 4217 currency, e.g. EUR") unless CURRENCY_FORMAT.match?(@currency.to_s)
98
+ elsif @currency
99
+ fail_with("currency is allowed only on money")
100
+ end
101
+ end
102
+
103
+ def fail_with(message)
104
+ raise ConfigurationError, "open_dashboard metric #{@key}: #{message}"
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "digest"
4
+ require "json"
5
+ require "openssl"
6
+
7
+ module CloseYourIt
8
+ module OpenDashboard
9
+ # Pure Rack: answers GET /open-dashboard and passes everything else to the app. Without a token
10
+ # the request also goes to the app, so the route simply does not exist (404 from the app).
11
+ class Middleware
12
+ JSON_HEADERS = { "content-type" => "application/json", "cache-control" => "no-store" }.freeze
13
+ BEARER = "Bearer "
14
+
15
+ def initialize(app)
16
+ @app = app
17
+ end
18
+
19
+ def call(env)
20
+ settings = OpenDashboard.settings
21
+ return @app.call(env) unless dashboard_request?(env) && settings&.enabled?
22
+ return [ 401, { "cache-control" => "no-store" }, [] ] unless authorized?(env["HTTP_AUTHORIZATION"], settings.token)
23
+
24
+ [ 200, JSON_HEADERS.dup, [ JSON.generate(Report.new(settings).to_h) ] ]
25
+ end
26
+
27
+ private
28
+
29
+ def dashboard_request?(env)
30
+ env["REQUEST_METHOD"] == "GET" && env["PATH_INFO"] == PATH
31
+ end
32
+
33
+ # Hashing both sides gives equal lengths, so the comparison time does not leak the token length.
34
+ def authorized?(header, token)
35
+ header = header.to_s
36
+ return false unless header.start_with?(BEARER)
37
+
38
+ OpenSSL.fixed_length_secure_compare(
39
+ Digest::SHA256.digest(header.delete_prefix(BEARER)),
40
+ Digest::SHA256.digest(token.to_s)
41
+ )
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "time"
4
+
5
+ module CloseYourIt
6
+ module OpenDashboard
7
+ # Builds the response body. Service identity comes from the same ENV as GET /version, so the
8
+ # dashboard and the deploy smoke test always agree on which build is live.
9
+ class Report
10
+ UNKNOWN = "unknown"
11
+
12
+ def initialize(settings, now: Time.now.utc)
13
+ @settings = settings
14
+ @now = now
15
+ end
16
+
17
+ def to_h
18
+ {
19
+ open_dashboard: SPEC_VERSION,
20
+ service: service,
21
+ locale: @settings.locale,
22
+ generated_at: @now.getutc.iso8601,
23
+ metrics: @settings.metrics.map { |metric| metric.to_h(@now) }
24
+ }
25
+ end
26
+
27
+ private
28
+
29
+ def service
30
+ {
31
+ name: @settings.name,
32
+ app: @settings.app,
33
+ version: present(ENV["APP_GIT_TAG"]) || UNKNOWN,
34
+ sha: present(ENV["APP_GIT_SHA"])&.slice(0, 7) || UNKNOWN,
35
+ environment: CloseYourIt.configuration.environment.to_s,
36
+ url: present(@settings.url)
37
+ }.compact
38
+ end
39
+
40
+ def present(value)
41
+ value.to_s.strip.empty? ? nil : value.to_s
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CloseYourIt
4
+ module OpenDashboard
5
+ # What the service declares in config/initializers/open_dashboard.rb.
6
+ class Settings
7
+ LOCALE_FORMAT = /\A[a-z]{2}(-[A-Z]{2})?\z/
8
+
9
+ attr_accessor :token, :name, :app, :url, :locale
10
+ attr_reader :metrics
11
+
12
+ def initialize
13
+ @metrics = []
14
+ end
15
+
16
+ def metric(key, **options, &block)
17
+ if @metrics.any? { |existing| existing.key == key.to_s }
18
+ raise ConfigurationError, "open_dashboard metric #{key}: duplicated key"
19
+ end
20
+
21
+ @metrics << Metric.new(key, **options, &block)
22
+ end
23
+
24
+ # No token means the feature is off: the route does not exist.
25
+ def enabled? = !token.to_s.strip.empty?
26
+
27
+ def validate!
28
+ %i[name app].each do |field|
29
+ raise ConfigurationError, "open_dashboard: #{field} is required" if public_send(field).to_s.strip.empty?
30
+ end
31
+ raise ConfigurationError, "open_dashboard: locale must look like it or en-US" unless LOCALE_FORMAT.match?(locale.to_s)
32
+ raise ConfigurationError, "open_dashboard: at most #{MAX_METRICS} metrics" if @metrics.size > MAX_METRICS
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CloseYourIt
4
+ # Open Dashboard standard: GET /open-dashboard returns who the service is and its business
5
+ # numbers, behind a per-service token. Contract: contracts/open-dashboard/v1.
6
+ module OpenDashboard
7
+ SPEC_VERSION = "1.0"
8
+ PATH = "/open-dashboard"
9
+ MAX_METRICS = 50
10
+
11
+ class << self
12
+ # Kept separate from CloseYourIt.init: init rebuilds its configuration on every call, and the
13
+ # dashboard declaration must not disappear when telemetry is reconfigured.
14
+ def configure
15
+ candidate = Settings.new
16
+ yield candidate
17
+ candidate.validate!
18
+ @settings = candidate
19
+ end
20
+
21
+ attr_reader :settings
22
+
23
+ def reset!
24
+ @settings = nil
25
+ end
26
+ end
27
+ end
28
+ end
29
+
30
+ require_relative "open_dashboard/metric"
31
+ require_relative "open_dashboard/settings"
32
+ require_relative "open_dashboard/report"
33
+ require_relative "open_dashboard/middleware"
@@ -28,6 +28,16 @@ module CloseYourIt
28
28
  )
29
29
  end
30
30
 
31
+ # Open Dashboard (GET /open-dashboard). Inside CaptureExceptions, so a crash while building the
32
+ # report is reported like any other request error. Inert until the app calls
33
+ # CloseYourIt::OpenDashboard.configure with a token.
34
+ initializer "closeyourit.open_dashboard" do |app|
35
+ app.config.middleware.insert_after(
36
+ CloseYourIt::Rails::CaptureExceptions,
37
+ CloseYourIt::OpenDashboard::Middleware
38
+ )
39
+ end
40
+
31
41
  initializer "closeyourit.subscribe_slow_queries" do
32
42
  subscriber = CloseYourIt::Subscribers::SlowQuery.new
33
43
 
@@ -100,6 +110,13 @@ module CloseYourIt
100
110
  end
101
111
  end
102
112
 
113
+ # Keys the app declared sensitive via config.filter_parameters are scrubbed by the gem too.
114
+ # `after: :load_config_initializers`: CloseYourIt.init (config/initializers/closeyourit.rb)
115
+ # replaces the configuration, so an earlier merge would be lost (CYRB-28).
116
+ initializer "closeyourit.merge_rails_filter_parameters", after: :load_config_initializers do |app|
117
+ CloseYourIt.configuration.merge_filter_parameters(app.config.filter_parameters)
118
+ end
119
+
103
120
  # Captures Sidekiq job errors + measures duration/wait via server middleware (only when Sidekiq
104
121
  # is present). The middleware is effectively a no-op when monitor_jobs is OFF (the decision
105
122
  # lives in JobPerformance#record).
@@ -139,8 +139,10 @@ module CloseYourIt
139
139
  data ? @request.merge("data" => data) : @request
140
140
  end
141
141
 
142
+ # Body params are user input (PII): sent only with send_pii AND capture_request_body (CYRB-28).
142
143
  def request_body_data
143
- return nil unless CloseYourIt.configuration.capture_request_body
144
+ config = CloseYourIt.configuration
145
+ return nil unless config.send_pii && config.capture_request_body
144
146
  return nil if @rack_env.nil?
145
147
 
146
148
  Rails::RequestBody.extract(@rack_env)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CloseYourIt
4
- VERSION = "0.11.0"
4
+ VERSION = "0.13.0"
5
5
  end
@@ -39,6 +39,7 @@ require_relative "closeyourit/rails/active_job_extension"
39
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
+ require_relative "closeyourit/open_dashboard"
42
43
 
43
44
  # CloseYourIt — telemetry client (errors + slow query/method statistics)
44
45
  # that sends events to the CloseYourIt ingest endpoint.
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.11.0
4
+ version: 0.13.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alessio Bussolari
@@ -67,6 +67,11 @@ files:
67
67
  - lib/closeyourit/log_buffer.rb
68
68
  - lib/closeyourit/log_device.rb
69
69
  - lib/closeyourit/monitor.rb
70
+ - lib/closeyourit/open_dashboard.rb
71
+ - lib/closeyourit/open_dashboard/metric.rb
72
+ - lib/closeyourit/open_dashboard/middleware.rb
73
+ - lib/closeyourit/open_dashboard/report.rb
74
+ - lib/closeyourit/open_dashboard/settings.rb
70
75
  - lib/closeyourit/performance/request_profile.rb
71
76
  - lib/closeyourit/performance/rollup.rb
72
77
  - lib/closeyourit/rails/active_job_extension.rb
@@ -90,12 +95,12 @@ files:
90
95
  - lib/closeyourit/transport.rb
91
96
  - lib/closeyourit/usage_registry.rb
92
97
  - lib/closeyourit/version.rb
93
- homepage: https://github.com/bussolabs/closeyourit-ruby
98
+ homepage: https://github.com/bussolabs/closeyourit-ruby-community
94
99
  licenses:
95
100
  - MIT
96
101
  metadata:
97
- homepage_uri: https://github.com/bussolabs/closeyourit-ruby
98
- source_code_uri: https://github.com/bussolabs/closeyourit-ruby
102
+ homepage_uri: https://github.com/bussolabs/closeyourit-ruby-community
103
+ source_code_uri: https://github.com/bussolabs/closeyourit-ruby-community
99
104
  rdoc_options: []
100
105
  require_paths:
101
106
  - lib