closeyourit-ruby 0.3.1 → 0.3.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/background_worker.rb +2 -2
- data/lib/closeyourit/client.rb +14 -0
- data/lib/closeyourit/configuration.rb +15 -4
- data/lib/closeyourit/events/error_event.rb +2 -0
- data/lib/closeyourit/events/log_event.rb +65 -0
- data/lib/closeyourit/log_buffer.rb +63 -0
- data/lib/closeyourit/log_device.rb +39 -0
- data/lib/closeyourit/rails/log_broadcast.rb +30 -0
- data/lib/closeyourit/rails/railtie.rb +10 -0
- data/lib/closeyourit/rails/request_context.rb +17 -3
- data/lib/closeyourit/scope.rb +2 -1
- data/lib/closeyourit/transport.rb +2 -2
- data/lib/closeyourit/version.rb +1 -1
- data/lib/closeyourit-ruby.rb +71 -3
- metadata +5 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0e319cd48ab53d0933f14344dc8ea2ac503ed7537b8111789fd540cf6bc3d65f
|
|
4
|
+
data.tar.gz: 2f7d6223045d8c9aea2ad4d1b9b6bac4cd1420cbc8abe2bd91622855cb45dfae
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4d37a064316618dcbee1b2a82626f950dc62e20dc22db3f7b8c6d14373ae1d307a20570ead272959536a1a337cceb3a13375e87edb5c2af23c1eeceae14fbee1
|
|
7
|
+
data.tar.gz: 937873c344d55dd765cedae07d1b7d05fb531012c22b616f4e22d6a8130fd26059a65cc8e949a1336b3ab97788d8e930118e451a13e1a51c2e4c6bccdcdb0efb
|
|
@@ -20,12 +20,12 @@ module CloseYourIt
|
|
|
20
20
|
block.call
|
|
21
21
|
rescue Exception => e # rubocop:disable Lint/RescueException
|
|
22
22
|
# Mai propagare: la telemetria non deve poter crashare l'app ospite.
|
|
23
|
-
CloseYourIt.
|
|
23
|
+
CloseYourIt.internal_logger.error("CloseYourIt background worker: #{e.class}: #{e.message}")
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
unless accepted
|
|
27
27
|
CloseYourIt.stats.increment(:dropped)
|
|
28
|
-
CloseYourIt.
|
|
28
|
+
CloseYourIt.internal_logger.warn("CloseYourIt background worker: coda piena, evento scartato")
|
|
29
29
|
end
|
|
30
30
|
|
|
31
31
|
accepted
|
data/lib/closeyourit/client.rb
CHANGED
|
@@ -24,6 +24,20 @@ module CloseYourIt
|
|
|
24
24
|
payload
|
|
25
25
|
end
|
|
26
26
|
|
|
27
|
+
# Invia un batch di log come ARRAY a /logs (l'endpoint accetta singolo o array). before_send è
|
|
28
|
+
# applicato a ciascun payload; quelli scartati (nil) non vengono inviati.
|
|
29
|
+
def flush_logs(events)
|
|
30
|
+
return nil if events.nil? || events.empty?
|
|
31
|
+
|
|
32
|
+
payloads = events.map(&:to_h)
|
|
33
|
+
payloads = payloads.filter_map { |payload| @configuration.before_send.call(payload) } if @configuration.before_send
|
|
34
|
+
return nil if payloads.empty?
|
|
35
|
+
|
|
36
|
+
path = events.first.ingest_path(@configuration.project_id)
|
|
37
|
+
@worker.perform { @transport.send_event(payloads, path: path) }
|
|
38
|
+
payloads
|
|
39
|
+
end
|
|
40
|
+
|
|
27
41
|
def shutdown
|
|
28
42
|
@worker.shutdown
|
|
29
43
|
end
|
|
@@ -22,7 +22,9 @@ module CloseYourIt
|
|
|
22
22
|
:capture_query_bindings, :capture_method_arguments,
|
|
23
23
|
:capture_request, :request_header_allowlist,
|
|
24
24
|
:breadcrumbs_enabled, :max_breadcrumbs, :sample_rate,
|
|
25
|
-
:capture_handled_errors, :report_active_job_errors
|
|
25
|
+
:capture_handled_errors, :report_active_job_errors,
|
|
26
|
+
:logs_enabled, :logs_sample_rate, :logs_batch_size, :logs_flush_interval,
|
|
27
|
+
:capture_rails_logs, :logs_min_level
|
|
26
28
|
attr_writer :release
|
|
27
29
|
attr_reader :excluded_exceptions, :filter_parameters, :scrub_message_patterns
|
|
28
30
|
|
|
@@ -65,6 +67,15 @@ module CloseYourIt
|
|
|
65
67
|
@capture_query_bindings = false
|
|
66
68
|
@capture_method_arguments = false
|
|
67
69
|
|
|
70
|
+
# Log strutturati (CloseYourIt.log / .logger). Master switch + sampling + batching dedicati.
|
|
71
|
+
@logs_enabled = true
|
|
72
|
+
@logs_sample_rate = 1.0
|
|
73
|
+
@logs_batch_size = 50
|
|
74
|
+
@logs_flush_interval = 5
|
|
75
|
+
# Broadcast opt-in di Rails.logger → CloseYourIt.log (default OFF; spedisce solo ≥ soglia).
|
|
76
|
+
@capture_rails_logs = false
|
|
77
|
+
@logs_min_level = :info
|
|
78
|
+
|
|
68
79
|
@filter_parameters = []
|
|
69
80
|
@scrub_message_patterns = []
|
|
70
81
|
end
|
|
@@ -98,9 +109,9 @@ module CloseYourIt
|
|
|
98
109
|
# Logga i warning di configurazione (es. endpoint http://, project_id/endpoint malformati).
|
|
99
110
|
# Non solleva mai: coerente con la filosofia no-op del client. Chiamata da `CloseYourIt.init`.
|
|
100
111
|
def validate!
|
|
101
|
-
CloseYourIt.
|
|
102
|
-
CloseYourIt.
|
|
103
|
-
CloseYourIt.
|
|
112
|
+
CloseYourIt.internal_logger.warn(insecure_endpoint_message) if insecure_endpoint?
|
|
113
|
+
CloseYourIt.internal_logger.warn(malformed_project_id_message) if malformed_project_id?
|
|
114
|
+
CloseYourIt.internal_logger.warn(malformed_endpoint_message) if malformed_endpoint?
|
|
104
115
|
self
|
|
105
116
|
end
|
|
106
117
|
|
|
@@ -29,6 +29,8 @@ module CloseYourIt
|
|
|
29
29
|
"timestamp" => @occurred_at,
|
|
30
30
|
"platform" => "ruby",
|
|
31
31
|
"level" => @level,
|
|
32
|
+
# Correlazione log↔errori: stesso trace_id dei log della medesima richiesta (popolato dallo Scope).
|
|
33
|
+
"trace_id" => CloseYourIt::Scope.current.trace_id,
|
|
32
34
|
"environment" => environment,
|
|
33
35
|
"release" => @configuration.release,
|
|
34
36
|
"server_name" => server_name,
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "securerandom"
|
|
4
|
+
|
|
5
|
+
module CloseYourIt
|
|
6
|
+
# Voce di log strutturata spedita all'ingest /logs (NON formato Sentry: i log sono uno stream con
|
|
7
|
+
# message/level/attributes/logger). Gli `attributes` passano dallo Scrubber (denylist). `trace_id`
|
|
8
|
+
# è preso dallo Scope corrente (popolato per richiesta) → correlazione log↔errori della stessa request.
|
|
9
|
+
class LogEvent < Event
|
|
10
|
+
# Livelli canonici del backend (enum) + alias dei nomi stile ::Logger. Normalizzati QUI (fonte
|
|
11
|
+
# unica) così ogni costruzione — via CloseYourIt.log, .logger o diretta — produce un livello valido.
|
|
12
|
+
LEVELS = %w[debug info warning error fatal].freeze
|
|
13
|
+
LEVEL_ALIASES = { "warn" => "warning", "err" => "error", "unknown" => "fatal" }.freeze
|
|
14
|
+
|
|
15
|
+
def initialize(message, level:, attributes:, configuration:, logger: nil)
|
|
16
|
+
super(configuration)
|
|
17
|
+
@message = message
|
|
18
|
+
@level = normalize_level(level)
|
|
19
|
+
@attributes = attributes || {}
|
|
20
|
+
@logger = logger
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def to_h
|
|
24
|
+
compact(
|
|
25
|
+
"event_id" => SecureRandom.uuid.delete("-"),
|
|
26
|
+
"timestamp" => @occurred_at,
|
|
27
|
+
"level" => @level,
|
|
28
|
+
"message" => @message.to_s,
|
|
29
|
+
"attributes" => scrubbed_attributes,
|
|
30
|
+
"logger" => @logger,
|
|
31
|
+
"trace_id" => CloseYourIt::Scope.current.trace_id,
|
|
32
|
+
"environment" => environment,
|
|
33
|
+
"release" => @configuration.release,
|
|
34
|
+
"sdk" => sdk
|
|
35
|
+
)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def ingest_path(project_id)
|
|
39
|
+
"/api/v1/projects/#{project_id}/logs"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def normalize_level(level)
|
|
45
|
+
value = level.to_s.downcase
|
|
46
|
+
value = LEVEL_ALIASES.fetch(value, value)
|
|
47
|
+
LEVELS.include?(value) ? value : "info"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def scrubbed_attributes
|
|
51
|
+
return {} if @attributes.nil? || @attributes.empty?
|
|
52
|
+
|
|
53
|
+
Scrubber.new(@configuration).filter_params(deep_stringify_keys(@attributes))
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Chiavi sempre stringa (anche annidate): coerenza col payload JSON e con la denylist dello Scrubber.
|
|
57
|
+
def deep_stringify_keys(value)
|
|
58
|
+
case value
|
|
59
|
+
when Hash then value.each_with_object({}) { |(key, val), acc| acc[key.to_s] = deep_stringify_keys(val) }
|
|
60
|
+
when Array then value.map { |item| deep_stringify_keys(item) }
|
|
61
|
+
else value
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "concurrent"
|
|
4
|
+
|
|
5
|
+
module CloseYourIt
|
|
6
|
+
# Buffer in-memory thread-safe dei log: accumula i LogEvent e li flusha in batch verso /logs quando
|
|
7
|
+
# raggiungono `logs_batch_size`, allo scadere di `logs_flush_interval` (timer), o allo shutdown.
|
|
8
|
+
# Riduce le richieste HTTP — i log sono alto-volume, a differenza di errori/metriche uno-a-uno.
|
|
9
|
+
class LogBuffer
|
|
10
|
+
attr_reader :timer # esposto per i test (verifica dell'intervallo configurato)
|
|
11
|
+
|
|
12
|
+
def initialize(client:, configuration:)
|
|
13
|
+
@client = client
|
|
14
|
+
@configuration = configuration
|
|
15
|
+
@mutex = Mutex.new
|
|
16
|
+
@events = []
|
|
17
|
+
@timer = nil
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def add(event)
|
|
21
|
+
reached_batch = false
|
|
22
|
+
@mutex.synchronize do
|
|
23
|
+
@events << event
|
|
24
|
+
ensure_timer
|
|
25
|
+
reached_batch = @events.size >= @configuration.logs_batch_size.to_i
|
|
26
|
+
end
|
|
27
|
+
flush if reached_batch
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def flush
|
|
31
|
+
batch = drain
|
|
32
|
+
return if batch.empty?
|
|
33
|
+
|
|
34
|
+
@client.flush_logs(batch)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def shutdown
|
|
38
|
+
@timer&.shutdown
|
|
39
|
+
flush
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def drain
|
|
45
|
+
@mutex.synchronize do
|
|
46
|
+
events = @events
|
|
47
|
+
@events = []
|
|
48
|
+
events
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Avvia il timer di flush periodico alla prima voce (una sola volta).
|
|
53
|
+
def ensure_timer
|
|
54
|
+
return if @timer
|
|
55
|
+
|
|
56
|
+
interval = @configuration.logs_flush_interval.to_f
|
|
57
|
+
return if interval <= 0
|
|
58
|
+
|
|
59
|
+
@timer = Concurrent::TimerTask.new(execution_interval: interval) { flush }
|
|
60
|
+
@timer.execute
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CloseYourIt
|
|
4
|
+
# Oggetto Logger-compatibile esposto come `CloseYourIt.logger`: ogni chiamata inoltra a
|
|
5
|
+
# `CloseYourIt.log` (→ ingest /logs). Usabile come logger esplicito dell'app, anche con attributes:
|
|
6
|
+
# CloseYourIt.logger.warn("disco quasi pieno", disk: "sda1")
|
|
7
|
+
# `warn` mappa sul livello `warning` (enum backend); supporta block e `::Logger#add` per drop-in.
|
|
8
|
+
class LogDevice
|
|
9
|
+
# Severità numeriche ::Logger → livelli CloseYourIt (UNKNOWN→fatal).
|
|
10
|
+
SEVERITY_LEVELS = { 0 => "debug", 1 => "info", 2 => "warning", 3 => "error", 4 => "fatal", 5 => "fatal" }.freeze
|
|
11
|
+
|
|
12
|
+
def debug(message = nil, **attributes, &block) = write("debug", message, attributes, &block)
|
|
13
|
+
def info(message = nil, **attributes, &block) = write("info", message, attributes, &block)
|
|
14
|
+
def warn(message = nil, **attributes, &block) = write("warning", message, attributes, &block)
|
|
15
|
+
def error(message = nil, **attributes, &block) = write("error", message, attributes, &block)
|
|
16
|
+
def fatal(message = nil, **attributes, &block) = write("fatal", message, attributes, &block)
|
|
17
|
+
|
|
18
|
+
def <<(message)
|
|
19
|
+
write("info", message, {})
|
|
20
|
+
message
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Compat con ::Logger#add(severity, message = nil, progname = nil).
|
|
24
|
+
def add(severity, message = nil, progname = nil, &block)
|
|
25
|
+
write(SEVERITY_LEVELS.fetch(severity.to_i, "info"), message || progname, {}, &block)
|
|
26
|
+
end
|
|
27
|
+
alias log add
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def write(level, message, attributes, &block)
|
|
32
|
+
# Gate PRIMA del block: `logger.debug { dump_costoso }` non valuta il block se i log sono spenti.
|
|
33
|
+
return unless CloseYourIt.logs_active?
|
|
34
|
+
|
|
35
|
+
message = block.call if block
|
|
36
|
+
CloseYourIt.log(level, message, **attributes)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "logger"
|
|
4
|
+
|
|
5
|
+
module CloseYourIt
|
|
6
|
+
module Rails
|
|
7
|
+
# Sink Logger-compatibile agganciato a Rails.logger (broadcast opt-in, `config.capture_rails_logs`):
|
|
8
|
+
# ogni log dell'app ≥ soglia (`logs_min_level`) viene re-inoltrato a CloseYourIt.log → ingest /logs.
|
|
9
|
+
# Sotto soglia: no-op (nessun invio). Non scrive su alcun device (inoltra soltanto).
|
|
10
|
+
class LogBroadcast < ::Logger
|
|
11
|
+
SEVERITY_LEVELS = { 0 => "debug", 1 => "info", 2 => "warning", 3 => "error", 4 => "fatal", 5 => "fatal" }.freeze
|
|
12
|
+
LEVEL_BY_SYMBOL = { debug: 0, info: 1, warn: 2, warning: 2, error: 3, fatal: 4 }.freeze
|
|
13
|
+
|
|
14
|
+
def initialize(min_level = :info)
|
|
15
|
+
super(nil) # nessun device: inoltra soltanto
|
|
16
|
+
self.level = LEVEL_BY_SYMBOL.fetch(min_level.to_sym, 1)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Sovrascrive il punto unico di ::Logger: filtra per soglia e re-inoltra a CloseYourIt.log.
|
|
20
|
+
def add(severity, message = nil, progname = nil)
|
|
21
|
+
severity ||= ::Logger::UNKNOWN
|
|
22
|
+
return true if severity < level
|
|
23
|
+
|
|
24
|
+
text = message || (block_given? ? yield : nil) || progname
|
|
25
|
+
CloseYourIt.log(SEVERITY_LEVELS.fetch(severity, "info"), text) unless text.nil?
|
|
26
|
+
true
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -6,6 +6,7 @@ require_relative "active_job_extension"
|
|
|
6
6
|
require_relative "error_subscriber"
|
|
7
7
|
require_relative "../sidekiq/error_handler"
|
|
8
8
|
require_relative "query_source"
|
|
9
|
+
require_relative "log_broadcast"
|
|
9
10
|
require_relative "../subscribers/slow_query"
|
|
10
11
|
|
|
11
12
|
module CloseYourIt
|
|
@@ -61,6 +62,15 @@ module CloseYourIt
|
|
|
61
62
|
end
|
|
62
63
|
end
|
|
63
64
|
|
|
65
|
+
# Broadcast opt-in di Rails.logger → CloseYourIt.log (config.capture_rails_logs, default OFF).
|
|
66
|
+
# Spedisce solo i log dell'app ≥ logs_min_level. Richiede BroadcastLogger (Rails 7.1+).
|
|
67
|
+
initializer "closeyourit.capture_rails_logs" do
|
|
68
|
+
config = CloseYourIt.configuration
|
|
69
|
+
if config.capture_rails_logs && ::Rails.logger.respond_to?(:broadcast_to)
|
|
70
|
+
::Rails.logger.broadcast_to(CloseYourIt::Rails::LogBroadcast.new(config.logs_min_level))
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
64
74
|
# Cattura gli errori dei job Sidekiq (solo se Sidekiq è presente).
|
|
65
75
|
initializer "closeyourit.sidekiq" do
|
|
66
76
|
if defined?(::Sidekiq) && ::Sidekiq.respond_to?(:configure_server)
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "securerandom"
|
|
4
|
+
|
|
3
5
|
module CloseYourIt
|
|
4
6
|
module Rails
|
|
5
7
|
# Rack middleware: popola lo Scope con il contesto HTTP della richiesta (method/url/header)
|
|
@@ -15,7 +17,11 @@ module CloseYourIt
|
|
|
15
17
|
end
|
|
16
18
|
|
|
17
19
|
def call(env)
|
|
18
|
-
|
|
20
|
+
if enabled?
|
|
21
|
+
# trace_id sempre (correlazione log↔errori), anche con capture_request OFF.
|
|
22
|
+
CloseYourIt::Scope.current.trace_id = trace_id_for(env)
|
|
23
|
+
CloseYourIt::Scope.current.request = build_request(env) if CloseYourIt.configuration.capture_request
|
|
24
|
+
end
|
|
19
25
|
@app.call(env)
|
|
20
26
|
ensure
|
|
21
27
|
CloseYourIt::Scope.reset!
|
|
@@ -23,12 +29,20 @@ module CloseYourIt
|
|
|
23
29
|
|
|
24
30
|
private
|
|
25
31
|
|
|
26
|
-
def
|
|
27
|
-
CloseYourIt.enabled?
|
|
32
|
+
def enabled?
|
|
33
|
+
CloseYourIt.enabled?
|
|
28
34
|
rescue StandardError
|
|
29
35
|
false
|
|
30
36
|
end
|
|
31
37
|
|
|
38
|
+
# Riusa il request id di Rails/Rack se presente (stessa correlazione dei log applicativi),
|
|
39
|
+
# altrimenti ne genera uno.
|
|
40
|
+
def trace_id_for(env)
|
|
41
|
+
env["action_dispatch.request_id"] ||
|
|
42
|
+
env["HTTP_X_REQUEST_ID"].to_s.split(",").first&.strip.then { |id| id&.empty? ? nil : id } ||
|
|
43
|
+
SecureRandom.uuid
|
|
44
|
+
end
|
|
45
|
+
|
|
32
46
|
# Forma `request` Sentry. URL senza query string; header solo dall'allowlist (mai
|
|
33
47
|
# Authorization/Cookie). query_string + IP solo con send_pii (opt-in).
|
|
34
48
|
def build_request(env)
|
data/lib/closeyourit/scope.rb
CHANGED
|
@@ -34,7 +34,7 @@ module CloseYourIt
|
|
|
34
34
|
end
|
|
35
35
|
end
|
|
36
36
|
|
|
37
|
-
attr_accessor :request
|
|
37
|
+
attr_accessor :request, :trace_id
|
|
38
38
|
attr_reader :user, :tags, :extra, :contexts, :breadcrumbs
|
|
39
39
|
|
|
40
40
|
def initialize
|
|
@@ -71,6 +71,7 @@ module CloseYourIt
|
|
|
71
71
|
@extra = {}
|
|
72
72
|
@contexts = {}
|
|
73
73
|
@request = nil
|
|
74
|
+
@trace_id = nil
|
|
74
75
|
@breadcrumbs = BreadcrumbBuffer.new(CloseYourIt.configuration.max_breadcrumbs)
|
|
75
76
|
end
|
|
76
77
|
|
|
@@ -24,12 +24,12 @@ module CloseYourIt
|
|
|
24
24
|
CloseYourIt.stats.increment(:sent)
|
|
25
25
|
else
|
|
26
26
|
CloseYourIt.stats.increment(:failed)
|
|
27
|
-
CloseYourIt.
|
|
27
|
+
CloseYourIt.internal_logger.warn("CloseYourIt transport: HTTP #{response.code} su #{path}")
|
|
28
28
|
end
|
|
29
29
|
response
|
|
30
30
|
rescue StandardError => e
|
|
31
31
|
CloseYourIt.stats.increment(:failed)
|
|
32
|
-
CloseYourIt.
|
|
32
|
+
CloseYourIt.internal_logger.error("CloseYourIt transport: #{e.class}: #{e.message}")
|
|
33
33
|
nil
|
|
34
34
|
end
|
|
35
35
|
|
data/lib/closeyourit/version.rb
CHANGED
data/lib/closeyourit-ruby.rb
CHANGED
|
@@ -15,12 +15,16 @@ require_relative "closeyourit/events/error_event"
|
|
|
15
15
|
require_relative "closeyourit/events/message_event"
|
|
16
16
|
require_relative "closeyourit/events/slow_query_event"
|
|
17
17
|
require_relative "closeyourit/events/slow_method_event"
|
|
18
|
+
require_relative "closeyourit/events/log_event"
|
|
19
|
+
require_relative "closeyourit/log_device"
|
|
20
|
+
require_relative "closeyourit/log_buffer"
|
|
18
21
|
require_relative "closeyourit/subscribers/slow_query"
|
|
19
22
|
require_relative "closeyourit/instrumenter"
|
|
20
23
|
require_relative "closeyourit/monitor"
|
|
21
24
|
require_relative "closeyourit/client"
|
|
22
25
|
require_relative "closeyourit/rails/capture_exceptions"
|
|
23
26
|
require_relative "closeyourit/rails/request_context"
|
|
27
|
+
require_relative "closeyourit/rails/log_broadcast"
|
|
24
28
|
require_relative "closeyourit/rails/active_job_extension"
|
|
25
29
|
require_relative "closeyourit/rails/error_subscriber"
|
|
26
30
|
require_relative "closeyourit/sidekiq/error_handler"
|
|
@@ -41,8 +45,10 @@ module CloseYourIt
|
|
|
41
45
|
def init
|
|
42
46
|
@configuration = Configuration.new
|
|
43
47
|
@client = nil
|
|
48
|
+
@log_buffer = nil
|
|
44
49
|
yield(@configuration) if block_given?
|
|
45
50
|
@configuration.validate!
|
|
51
|
+
register_shutdown_flush
|
|
46
52
|
@configuration
|
|
47
53
|
end
|
|
48
54
|
|
|
@@ -139,11 +145,46 @@ module CloseYourIt
|
|
|
139
145
|
)
|
|
140
146
|
end
|
|
141
147
|
|
|
148
|
+
# Logger interno della gemma (warning/errori diagnostici su stdout). NON è il logging applicativo:
|
|
149
|
+
# per spedire log strutturati a CloseYourIt usa `CloseYourIt.log` / `CloseYourIt.logger`.
|
|
150
|
+
def internal_logger
|
|
151
|
+
@internal_logger ||= default_internal_logger
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
attr_writer :internal_logger
|
|
155
|
+
|
|
156
|
+
# Logger applicativo Logger-compatibile: inoltra ogni messaggio a `CloseYourIt.log` (→ ingest /logs).
|
|
157
|
+
# CloseYourIt.logger.info("ordine creato", order_id: 1)
|
|
142
158
|
def logger
|
|
143
|
-
@
|
|
159
|
+
@app_logger ||= LogDevice.new
|
|
144
160
|
end
|
|
145
161
|
|
|
146
|
-
|
|
162
|
+
# Costruisce e bufferizza una voce di log strutturata (batch verso /logs, fire-and-forget). Il
|
|
163
|
+
# `level` è normalizzato ai livelli canonici (`:warn`→`warning`, downcase; ignoto→`info`). `logger`
|
|
164
|
+
# opzionale = nome della sorgente del log.
|
|
165
|
+
# CloseYourIt.log(:info, "ordine creato", order_id: 1)
|
|
166
|
+
# CloseYourIt.log(:warn, "retry", logger: "payments", attempt: 3)
|
|
167
|
+
def log(level, message, logger: nil, **attributes)
|
|
168
|
+
return nil unless logs_enabled?
|
|
169
|
+
return nil unless logs_sampled?
|
|
170
|
+
|
|
171
|
+
event = LogEvent.new(message, level: level, attributes: attributes,
|
|
172
|
+
logger: logger, configuration: configuration)
|
|
173
|
+
log_buffer.add(event)
|
|
174
|
+
nil
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Vero se i log sono attivi (master switch + flag): usato da LogDevice per NON valutare i block
|
|
178
|
+
# costosi (`logger.debug { dump }`) quando il logging è spento.
|
|
179
|
+
def logs_active?
|
|
180
|
+
logs_enabled?
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# Forza l'invio dei log bufferizzati (chiamato anche allo shutdown del processo).
|
|
184
|
+
def flush_logs
|
|
185
|
+
@log_buffer&.flush
|
|
186
|
+
nil
|
|
187
|
+
end
|
|
147
188
|
|
|
148
189
|
# Contatori diagnostici del client (accodati/scartati/spediti/falliti).
|
|
149
190
|
# CloseYourIt.stats.to_h # => { enqueued: …, dropped: …, sent: …, failed: … }
|
|
@@ -157,6 +198,33 @@ module CloseYourIt
|
|
|
157
198
|
@client ||= Client.new(configuration)
|
|
158
199
|
end
|
|
159
200
|
|
|
201
|
+
def log_buffer
|
|
202
|
+
@log_buffer ||= LogBuffer.new(client: client, configuration: configuration)
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# I log seguono il master switch del client + il proprio flag dedicato.
|
|
206
|
+
def logs_enabled?
|
|
207
|
+
enabled? && configuration.logs_enabled
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
# Sampling indipendente dei log (1.0 = tutti, 0.0 = nessuno).
|
|
211
|
+
def logs_sampled?
|
|
212
|
+
rate = configuration.logs_sample_rate.to_f
|
|
213
|
+
return true if rate >= 1.0
|
|
214
|
+
return false if rate <= 0.0
|
|
215
|
+
|
|
216
|
+
Random.rand < rate
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
# Allo shutdown del processo svuota i log bufferizzati e ferma il timer (una sola registrazione).
|
|
220
|
+
# Senza, i log sotto-batch dei processi brevi (rake/CLI) andrebbero persi all'uscita.
|
|
221
|
+
def register_shutdown_flush
|
|
222
|
+
return if @shutdown_registered
|
|
223
|
+
|
|
224
|
+
@shutdown_registered = true
|
|
225
|
+
at_exit { @log_buffer&.shutdown }
|
|
226
|
+
end
|
|
227
|
+
|
|
160
228
|
def ignored_exception?(exception)
|
|
161
229
|
return true if exception.is_a?(CloseYourIt::Error)
|
|
162
230
|
|
|
@@ -187,7 +255,7 @@ module CloseYourIt
|
|
|
187
255
|
exception.instance_variable_set(CAPTURED_FLAG, true)
|
|
188
256
|
end
|
|
189
257
|
|
|
190
|
-
def
|
|
258
|
+
def default_internal_logger
|
|
191
259
|
::Logger.new($stdout).tap do |l|
|
|
192
260
|
l.level = ::Logger::WARN
|
|
193
261
|
l.progname = "CloseYourIt"
|
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.3.
|
|
4
|
+
version: 0.3.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Alessio Bussolari
|
|
@@ -41,14 +41,18 @@ files:
|
|
|
41
41
|
- lib/closeyourit/configuration.rb
|
|
42
42
|
- lib/closeyourit/event.rb
|
|
43
43
|
- lib/closeyourit/events/error_event.rb
|
|
44
|
+
- lib/closeyourit/events/log_event.rb
|
|
44
45
|
- lib/closeyourit/events/message_event.rb
|
|
45
46
|
- lib/closeyourit/events/slow_method_event.rb
|
|
46
47
|
- lib/closeyourit/events/slow_query_event.rb
|
|
47
48
|
- lib/closeyourit/instrumenter.rb
|
|
49
|
+
- lib/closeyourit/log_buffer.rb
|
|
50
|
+
- lib/closeyourit/log_device.rb
|
|
48
51
|
- lib/closeyourit/monitor.rb
|
|
49
52
|
- lib/closeyourit/rails/active_job_extension.rb
|
|
50
53
|
- lib/closeyourit/rails/capture_exceptions.rb
|
|
51
54
|
- lib/closeyourit/rails/error_subscriber.rb
|
|
55
|
+
- lib/closeyourit/rails/log_broadcast.rb
|
|
52
56
|
- lib/closeyourit/rails/query_source.rb
|
|
53
57
|
- lib/closeyourit/rails/railtie.rb
|
|
54
58
|
- lib/closeyourit/rails/request_context.rb
|