brainzlab 0.1.1 → 0.1.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/README.md +8 -0
- data/lib/brainzlab/beacon/client.rb +209 -0
- data/lib/brainzlab/beacon/provisioner.rb +44 -0
- data/lib/brainzlab/beacon.rb +215 -0
- data/lib/brainzlab/configuration.rb +341 -3
- data/lib/brainzlab/cortex/cache.rb +59 -0
- data/lib/brainzlab/cortex/client.rb +141 -0
- data/lib/brainzlab/cortex/provisioner.rb +49 -0
- data/lib/brainzlab/cortex.rb +227 -0
- data/lib/brainzlab/dendrite/client.rb +232 -0
- data/lib/brainzlab/dendrite/provisioner.rb +44 -0
- data/lib/brainzlab/dendrite.rb +195 -0
- data/lib/brainzlab/devtools/assets/devtools.css +1106 -0
- data/lib/brainzlab/devtools/assets/devtools.js +322 -0
- data/lib/brainzlab/devtools/assets/logo.svg +6 -0
- data/lib/brainzlab/devtools/assets/templates/debug_panel.html.erb +500 -0
- data/lib/brainzlab/devtools/assets/templates/error_page.html.erb +1086 -0
- data/lib/brainzlab/devtools/data/collector.rb +248 -0
- data/lib/brainzlab/devtools/middleware/asset_server.rb +63 -0
- data/lib/brainzlab/devtools/middleware/database_handler.rb +180 -0
- data/lib/brainzlab/devtools/middleware/debug_panel.rb +126 -0
- data/lib/brainzlab/devtools/middleware/error_page.rb +376 -0
- data/lib/brainzlab/devtools/renderers/debug_panel_renderer.rb +155 -0
- data/lib/brainzlab/devtools/renderers/error_page_renderer.rb +94 -0
- data/lib/brainzlab/devtools.rb +75 -0
- data/lib/brainzlab/flux/buffer.rb +96 -0
- data/lib/brainzlab/flux/client.rb +70 -0
- data/lib/brainzlab/flux/provisioner.rb +57 -0
- data/lib/brainzlab/flux.rb +174 -0
- data/lib/brainzlab/instrumentation/active_record.rb +18 -1
- data/lib/brainzlab/instrumentation/aws.rb +179 -0
- data/lib/brainzlab/instrumentation/dalli.rb +108 -0
- data/lib/brainzlab/instrumentation/excon.rb +152 -0
- data/lib/brainzlab/instrumentation/good_job.rb +102 -0
- data/lib/brainzlab/instrumentation/resque.rb +115 -0
- data/lib/brainzlab/instrumentation/solid_queue.rb +198 -0
- data/lib/brainzlab/instrumentation/stripe.rb +164 -0
- data/lib/brainzlab/instrumentation/typhoeus.rb +104 -0
- data/lib/brainzlab/instrumentation.rb +72 -0
- data/lib/brainzlab/nerve/client.rb +217 -0
- data/lib/brainzlab/nerve/provisioner.rb +44 -0
- data/lib/brainzlab/nerve.rb +219 -0
- data/lib/brainzlab/pulse/instrumentation.rb +35 -2
- data/lib/brainzlab/pulse/propagation.rb +1 -1
- data/lib/brainzlab/pulse/tracer.rb +1 -1
- data/lib/brainzlab/pulse.rb +1 -1
- data/lib/brainzlab/rails/log_subscriber.rb +1 -2
- data/lib/brainzlab/rails/railtie.rb +36 -3
- data/lib/brainzlab/recall/provisioner.rb +17 -0
- data/lib/brainzlab/recall.rb +6 -1
- data/lib/brainzlab/reflex.rb +2 -2
- data/lib/brainzlab/sentinel/client.rb +218 -0
- data/lib/brainzlab/sentinel/provisioner.rb +44 -0
- data/lib/brainzlab/sentinel.rb +165 -0
- data/lib/brainzlab/signal/client.rb +62 -0
- data/lib/brainzlab/signal/provisioner.rb +55 -0
- data/lib/brainzlab/signal.rb +136 -0
- data/lib/brainzlab/synapse/client.rb +290 -0
- data/lib/brainzlab/synapse/provisioner.rb +44 -0
- data/lib/brainzlab/synapse.rb +270 -0
- data/lib/brainzlab/utilities/circuit_breaker.rb +265 -0
- data/lib/brainzlab/utilities/health_check.rb +296 -0
- data/lib/brainzlab/utilities/log_formatter.rb +256 -0
- data/lib/brainzlab/utilities/rate_limiter.rb +230 -0
- data/lib/brainzlab/utilities.rb +17 -0
- data/lib/brainzlab/vault/cache.rb +80 -0
- data/lib/brainzlab/vault/client.rb +198 -0
- data/lib/brainzlab/vault/provisioner.rb +49 -0
- data/lib/brainzlab/vault.rb +268 -0
- data/lib/brainzlab/version.rb +1 -1
- data/lib/brainzlab/vision/client.rb +128 -0
- data/lib/brainzlab/vision/provisioner.rb +136 -0
- data/lib/brainzlab/vision.rb +157 -0
- data/lib/brainzlab.rb +101 -0
- metadata +60 -1
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BrainzLab
|
|
4
|
+
module Flux
|
|
5
|
+
class Buffer
|
|
6
|
+
MAX_EVENTS = 100
|
|
7
|
+
MAX_METRICS = 100
|
|
8
|
+
FLUSH_INTERVAL = 5 # seconds
|
|
9
|
+
|
|
10
|
+
def initialize(client)
|
|
11
|
+
@client = client
|
|
12
|
+
@events = []
|
|
13
|
+
@metrics = []
|
|
14
|
+
@mutex = Mutex.new
|
|
15
|
+
@last_flush = Time.now
|
|
16
|
+
|
|
17
|
+
start_flush_thread
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def add(type, data)
|
|
21
|
+
@mutex.synchronize do
|
|
22
|
+
case type
|
|
23
|
+
when :event
|
|
24
|
+
@events << data
|
|
25
|
+
when :metric
|
|
26
|
+
@metrics << data
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
flush_if_needed
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def flush!
|
|
34
|
+
events_to_send = nil
|
|
35
|
+
metrics_to_send = nil
|
|
36
|
+
|
|
37
|
+
@mutex.synchronize do
|
|
38
|
+
events_to_send = @events.dup
|
|
39
|
+
metrics_to_send = @metrics.dup
|
|
40
|
+
@events.clear
|
|
41
|
+
@metrics.clear
|
|
42
|
+
@last_flush = Time.now
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
send_batch(events_to_send, metrics_to_send)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def size
|
|
49
|
+
@mutex.synchronize { @events.size + @metrics.size }
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
def flush_if_needed
|
|
55
|
+
should_flush = @events.size >= MAX_EVENTS ||
|
|
56
|
+
@metrics.size >= MAX_METRICS ||
|
|
57
|
+
Time.now - @last_flush >= FLUSH_INTERVAL
|
|
58
|
+
|
|
59
|
+
flush_async if should_flush
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def flush_async
|
|
63
|
+
events_to_send = @events.dup
|
|
64
|
+
metrics_to_send = @metrics.dup
|
|
65
|
+
@events.clear
|
|
66
|
+
@metrics.clear
|
|
67
|
+
@last_flush = Time.now
|
|
68
|
+
|
|
69
|
+
Thread.new do
|
|
70
|
+
send_batch(events_to_send, metrics_to_send)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def send_batch(events, metrics)
|
|
75
|
+
return if events.empty? && metrics.empty?
|
|
76
|
+
|
|
77
|
+
@client.send_batch(events: events, metrics: metrics)
|
|
78
|
+
rescue => e
|
|
79
|
+
BrainzLab.debug("[Flux] Batch send failed: #{e.message}")
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def start_flush_thread
|
|
83
|
+
Thread.new do
|
|
84
|
+
loop do
|
|
85
|
+
sleep FLUSH_INTERVAL
|
|
86
|
+
begin
|
|
87
|
+
flush! if size > 0
|
|
88
|
+
rescue => e
|
|
89
|
+
BrainzLab.debug("[Flux] Flush thread error: #{e.message}")
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "net/http"
|
|
4
|
+
require "json"
|
|
5
|
+
require "uri"
|
|
6
|
+
|
|
7
|
+
module BrainzLab
|
|
8
|
+
module Flux
|
|
9
|
+
class Client
|
|
10
|
+
def initialize(config)
|
|
11
|
+
@config = config
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def send_event(event)
|
|
15
|
+
post("/api/v1/events", event)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def send_events(events)
|
|
19
|
+
post("/api/v1/events/batch", { events: events })
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def send_metric(metric)
|
|
23
|
+
post("/api/v1/metrics", metric)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def send_metrics(metrics)
|
|
27
|
+
post("/api/v1/metrics/batch", { metrics: metrics })
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def send_batch(events:, metrics:)
|
|
31
|
+
post("/api/v1/flux/batch", { events: events, metrics: metrics })
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def post(path, body)
|
|
37
|
+
uri = URI.parse("#{base_url}#{path}")
|
|
38
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
39
|
+
http.use_ssl = uri.scheme == "https"
|
|
40
|
+
http.open_timeout = 5
|
|
41
|
+
http.read_timeout = 10
|
|
42
|
+
|
|
43
|
+
request = Net::HTTP::Post.new(uri.path)
|
|
44
|
+
request["Content-Type"] = "application/json"
|
|
45
|
+
request["Authorization"] = "Bearer #{api_key}"
|
|
46
|
+
request["User-Agent"] = "brainzlab-sdk/#{BrainzLab::VERSION}"
|
|
47
|
+
request.body = body.to_json
|
|
48
|
+
|
|
49
|
+
response = http.request(request)
|
|
50
|
+
|
|
51
|
+
unless response.is_a?(Net::HTTPSuccess)
|
|
52
|
+
BrainzLab.debug("[Flux] Request failed: #{response.code} - #{response.body}")
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
response
|
|
56
|
+
rescue => e
|
|
57
|
+
BrainzLab.debug("[Flux] Request error: #{e.message}")
|
|
58
|
+
nil
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def base_url
|
|
62
|
+
@config.flux_url
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def api_key
|
|
66
|
+
@config.flux_ingest_key || @config.flux_api_key
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "net/http"
|
|
4
|
+
require "json"
|
|
5
|
+
require "uri"
|
|
6
|
+
|
|
7
|
+
module BrainzLab
|
|
8
|
+
module Flux
|
|
9
|
+
class Provisioner
|
|
10
|
+
def initialize(config)
|
|
11
|
+
@config = config
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def ensure_project!
|
|
15
|
+
return if (@config.flux_ingest_key && !@config.flux_ingest_key.to_s.empty?) ||
|
|
16
|
+
(@config.flux_api_key && !@config.flux_api_key.to_s.empty?)
|
|
17
|
+
return unless @config.flux_url && !@config.flux_url.to_s.empty?
|
|
18
|
+
return unless @config.secret_key && !@config.secret_key.to_s.empty?
|
|
19
|
+
|
|
20
|
+
BrainzLab.debug_log("[Flux] Auto-provisioning project...")
|
|
21
|
+
provision_project
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def provision_project
|
|
27
|
+
uri = URI.parse("#{@config.flux_url}/api/v1/projects/provision")
|
|
28
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
29
|
+
http.use_ssl = uri.scheme == "https"
|
|
30
|
+
http.open_timeout = 10
|
|
31
|
+
http.read_timeout = 30
|
|
32
|
+
|
|
33
|
+
request = Net::HTTP::Post.new(uri.path)
|
|
34
|
+
request["Content-Type"] = "application/json"
|
|
35
|
+
request["Authorization"] = "Bearer #{@config.secret_key}"
|
|
36
|
+
request["User-Agent"] = "brainzlab-sdk/#{BrainzLab::VERSION}"
|
|
37
|
+
request.body = {
|
|
38
|
+
name: @config.service || "default",
|
|
39
|
+
environment: @config.environment
|
|
40
|
+
}.to_json
|
|
41
|
+
|
|
42
|
+
response = http.request(request)
|
|
43
|
+
|
|
44
|
+
if response.is_a?(Net::HTTPSuccess)
|
|
45
|
+
data = JSON.parse(response.body)
|
|
46
|
+
@config.flux_ingest_key = data["ingest_key"]
|
|
47
|
+
@config.flux_api_key = data["api_key"]
|
|
48
|
+
BrainzLab.debug_log("[Flux] Project provisioned successfully")
|
|
49
|
+
else
|
|
50
|
+
BrainzLab.debug_log("[Flux] Provisioning failed: #{response.code} - #{response.body}")
|
|
51
|
+
end
|
|
52
|
+
rescue => e
|
|
53
|
+
BrainzLab.debug_log("[Flux] Provisioning error: #{e.message}")
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "flux/client"
|
|
4
|
+
require_relative "flux/buffer"
|
|
5
|
+
require_relative "flux/provisioner"
|
|
6
|
+
|
|
7
|
+
module BrainzLab
|
|
8
|
+
module Flux
|
|
9
|
+
class << self
|
|
10
|
+
# === EVENTS ===
|
|
11
|
+
|
|
12
|
+
# Track a custom event
|
|
13
|
+
# @param name [String] Event name (e.g., 'user.signup', 'order.completed')
|
|
14
|
+
# @param properties [Hash] Event properties
|
|
15
|
+
def track(name, properties = {})
|
|
16
|
+
return unless enabled?
|
|
17
|
+
|
|
18
|
+
ensure_provisioned!
|
|
19
|
+
return unless BrainzLab.configuration.flux_valid?
|
|
20
|
+
|
|
21
|
+
event = {
|
|
22
|
+
name: name,
|
|
23
|
+
timestamp: Time.now.utc.iso8601(3),
|
|
24
|
+
properties: properties.except(:user_id, :value, :tags, :session_id),
|
|
25
|
+
user_id: properties[:user_id],
|
|
26
|
+
session_id: properties[:session_id],
|
|
27
|
+
value: properties[:value],
|
|
28
|
+
tags: properties[:tags] || {},
|
|
29
|
+
environment: BrainzLab.configuration.environment,
|
|
30
|
+
service: BrainzLab.configuration.service
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
buffer.add(:event, event)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Track event for a specific user
|
|
37
|
+
def track_for_user(user, name, properties = {})
|
|
38
|
+
user_id = user.respond_to?(:id) ? user.id.to_s : user.to_s
|
|
39
|
+
track(name, properties.merge(user_id: user_id))
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# === METRICS ===
|
|
43
|
+
|
|
44
|
+
# Gauge: Current value (overwrites)
|
|
45
|
+
def gauge(name, value, tags: {})
|
|
46
|
+
return unless enabled?
|
|
47
|
+
|
|
48
|
+
ensure_provisioned!
|
|
49
|
+
return unless BrainzLab.configuration.flux_valid?
|
|
50
|
+
|
|
51
|
+
metric = {
|
|
52
|
+
type: "gauge",
|
|
53
|
+
name: name,
|
|
54
|
+
value: value,
|
|
55
|
+
tags: tags,
|
|
56
|
+
timestamp: Time.now.utc.iso8601(3)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
buffer.add(:metric, metric)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Counter: Increment value
|
|
63
|
+
def increment(name, value = 1, tags: {})
|
|
64
|
+
return unless enabled?
|
|
65
|
+
|
|
66
|
+
ensure_provisioned!
|
|
67
|
+
return unless BrainzLab.configuration.flux_valid?
|
|
68
|
+
|
|
69
|
+
metric = {
|
|
70
|
+
type: "counter",
|
|
71
|
+
name: name,
|
|
72
|
+
value: value,
|
|
73
|
+
tags: tags,
|
|
74
|
+
timestamp: Time.now.utc.iso8601(3)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
buffer.add(:metric, metric)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Counter: Decrement value
|
|
81
|
+
def decrement(name, value = 1, tags: {})
|
|
82
|
+
increment(name, -value, tags: tags)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Distribution: Statistical aggregation
|
|
86
|
+
def distribution(name, value, tags: {})
|
|
87
|
+
return unless enabled?
|
|
88
|
+
|
|
89
|
+
ensure_provisioned!
|
|
90
|
+
return unless BrainzLab.configuration.flux_valid?
|
|
91
|
+
|
|
92
|
+
metric = {
|
|
93
|
+
type: "distribution",
|
|
94
|
+
name: name,
|
|
95
|
+
value: value,
|
|
96
|
+
tags: tags,
|
|
97
|
+
timestamp: Time.now.utc.iso8601(3)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
buffer.add(:metric, metric)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Set: Unique count (cardinality)
|
|
104
|
+
def set(name, value, tags: {})
|
|
105
|
+
return unless enabled?
|
|
106
|
+
|
|
107
|
+
ensure_provisioned!
|
|
108
|
+
return unless BrainzLab.configuration.flux_valid?
|
|
109
|
+
|
|
110
|
+
metric = {
|
|
111
|
+
type: "set",
|
|
112
|
+
name: name,
|
|
113
|
+
value: value.to_s,
|
|
114
|
+
tags: tags,
|
|
115
|
+
timestamp: Time.now.utc.iso8601(3)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
buffer.add(:metric, metric)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# === CONVENIENCE METHODS ===
|
|
122
|
+
|
|
123
|
+
# Time a block and record as distribution
|
|
124
|
+
def measure(name, tags: {})
|
|
125
|
+
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
126
|
+
begin
|
|
127
|
+
yield
|
|
128
|
+
ensure
|
|
129
|
+
duration_ms = (Process.clock_gettime(Process::CLOCK_MONOTONIC) - start) * 1000
|
|
130
|
+
distribution(name, duration_ms, tags: tags.merge(unit: "ms"))
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Flush any buffered data immediately
|
|
135
|
+
def flush!
|
|
136
|
+
buffer.flush!
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# === INTERNAL ===
|
|
140
|
+
|
|
141
|
+
def ensure_provisioned!
|
|
142
|
+
return if @provisioned
|
|
143
|
+
|
|
144
|
+
@provisioned = true
|
|
145
|
+
provisioner.ensure_project!
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def provisioner
|
|
149
|
+
@provisioner ||= Provisioner.new(BrainzLab.configuration)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def client
|
|
153
|
+
@client ||= Client.new(BrainzLab.configuration)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def buffer
|
|
157
|
+
@buffer ||= Buffer.new(client)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def reset!
|
|
161
|
+
@client = nil
|
|
162
|
+
@buffer = nil
|
|
163
|
+
@provisioner = nil
|
|
164
|
+
@provisioned = false
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
private
|
|
168
|
+
|
|
169
|
+
def enabled?
|
|
170
|
+
BrainzLab.configuration.flux_effectively_enabled?
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
end
|
|
@@ -61,7 +61,7 @@ module BrainzLab
|
|
|
61
61
|
sql: truncate_sql(sql),
|
|
62
62
|
duration_ms: duration,
|
|
63
63
|
cached: payload[:cached] || false,
|
|
64
|
-
connection_name: payload[:connection]
|
|
64
|
+
connection_name: extract_connection_name(payload[:connection])
|
|
65
65
|
}.compact
|
|
66
66
|
)
|
|
67
67
|
rescue StandardError => e
|
|
@@ -95,6 +95,23 @@ module BrainzLab
|
|
|
95
95
|
false
|
|
96
96
|
end
|
|
97
97
|
|
|
98
|
+
def extract_connection_name(connection)
|
|
99
|
+
return nil unless connection
|
|
100
|
+
|
|
101
|
+
# Rails 8.1+ uses db_config.name on the pool
|
|
102
|
+
# Older versions used connection_class but that's removed in Rails 8.1
|
|
103
|
+
if connection.respond_to?(:pool)
|
|
104
|
+
pool = connection.pool
|
|
105
|
+
if pool.respond_to?(:db_config) && pool.db_config.respond_to?(:name)
|
|
106
|
+
pool.db_config.name
|
|
107
|
+
end
|
|
108
|
+
elsif connection.respond_to?(:db_config) && connection.db_config.respond_to?(:name)
|
|
109
|
+
connection.db_config.name
|
|
110
|
+
end
|
|
111
|
+
rescue StandardError
|
|
112
|
+
nil
|
|
113
|
+
end
|
|
114
|
+
|
|
98
115
|
def truncate_sql(sql)
|
|
99
116
|
return nil unless sql
|
|
100
117
|
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BrainzLab
|
|
4
|
+
module Instrumentation
|
|
5
|
+
module AWSInstrumentation
|
|
6
|
+
class << self
|
|
7
|
+
def install!
|
|
8
|
+
return unless defined?(::Aws)
|
|
9
|
+
|
|
10
|
+
install_plugin!
|
|
11
|
+
|
|
12
|
+
BrainzLab.debug_log("[Instrumentation] AWS SDK instrumentation installed")
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
def install_plugin!
|
|
18
|
+
# AWS SDK v3 uses plugins
|
|
19
|
+
if defined?(::Aws::Plugins)
|
|
20
|
+
::Aws.config[:plugins] ||= []
|
|
21
|
+
::Aws.config[:plugins] << BrainzLabPlugin unless ::Aws.config[:plugins].include?(BrainzLabPlugin)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Also hook into Seahorse for lower-level tracking
|
|
25
|
+
if defined?(::Seahorse::Client::Base)
|
|
26
|
+
install_seahorse_handler!
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def install_seahorse_handler!
|
|
31
|
+
handler_class = Class.new(::Seahorse::Client::Handler) do
|
|
32
|
+
def call(context)
|
|
33
|
+
started_at = Time.now
|
|
34
|
+
service = context.client.class.name.split("::")[1] || "AWS"
|
|
35
|
+
operation = context.operation_name.to_s
|
|
36
|
+
|
|
37
|
+
begin
|
|
38
|
+
response = @handler.call(context)
|
|
39
|
+
track_success(service, operation, started_at, context, response)
|
|
40
|
+
response
|
|
41
|
+
rescue StandardError => e
|
|
42
|
+
track_error(service, operation, started_at, context, e)
|
|
43
|
+
raise
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def track_success(service, operation, started_at, context, response)
|
|
50
|
+
duration_ms = ((Time.now - started_at) * 1000).round(2)
|
|
51
|
+
|
|
52
|
+
BrainzLab::Reflex.add_breadcrumb(
|
|
53
|
+
"AWS #{service}.#{operation}",
|
|
54
|
+
category: "aws",
|
|
55
|
+
level: :info,
|
|
56
|
+
data: {
|
|
57
|
+
service: service,
|
|
58
|
+
operation: operation,
|
|
59
|
+
region: context.config.region,
|
|
60
|
+
duration_ms: duration_ms
|
|
61
|
+
}
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
if BrainzLab.configuration.flux_effectively_enabled?
|
|
65
|
+
tags = { service: service, operation: operation, region: context.config.region }
|
|
66
|
+
BrainzLab::Flux.distribution("aws.duration_ms", duration_ms, tags: tags)
|
|
67
|
+
BrainzLab::Flux.increment("aws.requests", tags: tags)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def track_error(service, operation, started_at, context, error)
|
|
72
|
+
duration_ms = ((Time.now - started_at) * 1000).round(2)
|
|
73
|
+
|
|
74
|
+
BrainzLab::Reflex.add_breadcrumb(
|
|
75
|
+
"AWS #{service}.#{operation} failed: #{error.message}",
|
|
76
|
+
category: "aws",
|
|
77
|
+
level: :error,
|
|
78
|
+
data: {
|
|
79
|
+
service: service,
|
|
80
|
+
operation: operation,
|
|
81
|
+
error: error.class.name
|
|
82
|
+
}
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
if BrainzLab.configuration.flux_effectively_enabled?
|
|
86
|
+
tags = { service: service, operation: operation, error_class: error.class.name }
|
|
87
|
+
BrainzLab::Flux.increment("aws.errors", tags: tags)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
::Seahorse::Client::Base.add_plugin(
|
|
93
|
+
Class.new(::Seahorse::Client::Plugin) do
|
|
94
|
+
define_method(:add_handlers) do |handlers, config|
|
|
95
|
+
handlers.add(handler_class, step: :validate, priority: 0)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
)
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Aws SDK Plugin
|
|
103
|
+
class BrainzLabPlugin
|
|
104
|
+
def self.add_handlers(handlers, config)
|
|
105
|
+
handlers.add(Handler, step: :validate, priority: 0)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
class Handler
|
|
109
|
+
def initialize(handler)
|
|
110
|
+
@handler = handler
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def call(context)
|
|
114
|
+
started_at = Time.now
|
|
115
|
+
service = extract_service(context)
|
|
116
|
+
operation = context.operation_name.to_s
|
|
117
|
+
|
|
118
|
+
begin
|
|
119
|
+
response = @handler.call(context)
|
|
120
|
+
track_request(service, operation, started_at, context, response)
|
|
121
|
+
response
|
|
122
|
+
rescue StandardError => e
|
|
123
|
+
track_error(service, operation, started_at, context, e)
|
|
124
|
+
raise
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
private
|
|
129
|
+
|
|
130
|
+
def extract_service(context)
|
|
131
|
+
context.client.class.name.to_s.split("::")[1] || "AWS"
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def track_request(service, operation, started_at, context, response)
|
|
135
|
+
duration_ms = ((Time.now - started_at) * 1000).round(2)
|
|
136
|
+
region = context.config.region rescue "unknown"
|
|
137
|
+
|
|
138
|
+
BrainzLab::Reflex.add_breadcrumb(
|
|
139
|
+
"AWS #{service}.#{operation}",
|
|
140
|
+
category: "aws",
|
|
141
|
+
level: :info,
|
|
142
|
+
data: {
|
|
143
|
+
service: service,
|
|
144
|
+
operation: operation,
|
|
145
|
+
region: region,
|
|
146
|
+
duration_ms: duration_ms,
|
|
147
|
+
retries: context.retries
|
|
148
|
+
}
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
if BrainzLab.configuration.flux_effectively_enabled?
|
|
152
|
+
tags = { service: service, operation: operation, region: region }
|
|
153
|
+
BrainzLab::Flux.distribution("aws.duration_ms", duration_ms, tags: tags)
|
|
154
|
+
BrainzLab::Flux.increment("aws.requests", tags: tags)
|
|
155
|
+
|
|
156
|
+
if context.retries > 0
|
|
157
|
+
BrainzLab::Flux.increment("aws.retries", value: context.retries, tags: tags)
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def track_error(service, operation, started_at, context, error)
|
|
163
|
+
BrainzLab::Reflex.add_breadcrumb(
|
|
164
|
+
"AWS #{service}.#{operation} failed",
|
|
165
|
+
category: "aws",
|
|
166
|
+
level: :error,
|
|
167
|
+
data: { service: service, operation: operation, error: error.class.name }
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
if BrainzLab.configuration.flux_effectively_enabled?
|
|
171
|
+
tags = { service: service, operation: operation, error_class: error.class.name }
|
|
172
|
+
BrainzLab::Flux.increment("aws.errors", tags: tags)
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
end
|