brainzlab 0.1.1 → 0.1.3
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/LICENSE +6 -21
- data/README.md +24 -2
- data/lib/brainzlab/beacon/client.rb +207 -0
- data/lib/brainzlab/beacon/provisioner.rb +44 -0
- data/lib/brainzlab/beacon.rb +215 -0
- data/lib/brainzlab/configuration.rb +372 -32
- data/lib/brainzlab/context.rb +2 -3
- data/lib/brainzlab/cortex/cache.rb +59 -0
- data/lib/brainzlab/cortex/client.rb +139 -0
- data/lib/brainzlab/cortex/provisioner.rb +49 -0
- data/lib/brainzlab/cortex.rb +223 -0
- data/lib/brainzlab/dendrite/client.rb +230 -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 +177 -0
- data/lib/brainzlab/devtools/middleware/debug_panel.rb +126 -0
- data/lib/brainzlab/devtools/middleware/error_page.rb +377 -0
- data/lib/brainzlab/devtools/renderers/debug_panel_renderer.rb +159 -0
- data/lib/brainzlab/devtools/renderers/error_page_renderer.rb +98 -0
- data/lib/brainzlab/devtools.rb +75 -0
- data/lib/brainzlab/flux/buffer.rb +96 -0
- data/lib/brainzlab/flux/client.rb +68 -0
- data/lib/brainzlab/flux/provisioner.rb +57 -0
- data/lib/brainzlab/flux.rb +174 -0
- data/lib/brainzlab/instrumentation/action_mailer.rb +14 -13
- data/lib/brainzlab/instrumentation/active_record.rb +28 -13
- data/lib/brainzlab/instrumentation/aws.rb +183 -0
- data/lib/brainzlab/instrumentation/dalli.rb +108 -0
- data/lib/brainzlab/instrumentation/delayed_job.rb +27 -29
- data/lib/brainzlab/instrumentation/elasticsearch.rb +23 -24
- data/lib/brainzlab/instrumentation/excon.rb +152 -0
- data/lib/brainzlab/instrumentation/faraday.rb +3 -4
- data/lib/brainzlab/instrumentation/good_job.rb +102 -0
- data/lib/brainzlab/instrumentation/grape.rb +24 -24
- data/lib/brainzlab/instrumentation/graphql.rb +24 -23
- data/lib/brainzlab/instrumentation/httparty.rb +13 -14
- data/lib/brainzlab/instrumentation/mongodb.rb +7 -7
- data/lib/brainzlab/instrumentation/net_http.rb +6 -6
- data/lib/brainzlab/instrumentation/redis.rb +14 -21
- data/lib/brainzlab/instrumentation/resque.rb +114 -0
- data/lib/brainzlab/instrumentation/sidekiq.rb +29 -28
- data/lib/brainzlab/instrumentation/solid_queue.rb +194 -0
- data/lib/brainzlab/instrumentation/stripe.rb +163 -0
- data/lib/brainzlab/instrumentation/typhoeus.rb +106 -0
- data/lib/brainzlab/instrumentation.rb +84 -12
- data/lib/brainzlab/nerve/client.rb +215 -0
- data/lib/brainzlab/nerve/provisioner.rb +44 -0
- data/lib/brainzlab/nerve.rb +219 -0
- data/lib/brainzlab/pulse/client.rb +15 -11
- data/lib/brainzlab/pulse/instrumentation.rb +90 -53
- data/lib/brainzlab/pulse/propagation.rb +29 -29
- data/lib/brainzlab/pulse/provisioner.rb +12 -12
- data/lib/brainzlab/pulse/tracer.rb +4 -4
- data/lib/brainzlab/pulse.rb +14 -14
- data/lib/brainzlab/rails/log_formatter.rb +127 -121
- data/lib/brainzlab/rails/log_subscriber.rb +70 -77
- data/lib/brainzlab/rails/railtie.rb +96 -86
- data/lib/brainzlab/recall/buffer.rb +1 -1
- data/lib/brainzlab/recall/client.rb +14 -10
- data/lib/brainzlab/recall/logger.rb +16 -18
- data/lib/brainzlab/recall/provisioner.rb +29 -12
- data/lib/brainzlab/recall.rb +14 -11
- data/lib/brainzlab/reflex/breadcrumbs.rb +2 -2
- data/lib/brainzlab/reflex/client.rb +14 -10
- data/lib/brainzlab/reflex/provisioner.rb +12 -12
- data/lib/brainzlab/reflex.rb +31 -31
- data/lib/brainzlab/sentinel/client.rb +216 -0
- data/lib/brainzlab/sentinel/provisioner.rb +44 -0
- data/lib/brainzlab/sentinel.rb +165 -0
- data/lib/brainzlab/signal/client.rb +60 -0
- data/lib/brainzlab/signal/provisioner.rb +55 -0
- data/lib/brainzlab/signal.rb +136 -0
- data/lib/brainzlab/synapse/client.rb +288 -0
- data/lib/brainzlab/synapse/provisioner.rb +44 -0
- data/lib/brainzlab/synapse.rb +270 -0
- data/lib/brainzlab/utilities/circuit_breaker.rb +261 -0
- data/lib/brainzlab/utilities/health_check.rb +294 -0
- data/lib/brainzlab/utilities/log_formatter.rb +254 -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 +196 -0
- data/lib/brainzlab/vault/provisioner.rb +49 -0
- data/lib/brainzlab/vault.rb +262 -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 +155 -0
- data/lib/brainzlab-sdk.rb +1 -1
- data/lib/brainzlab.rb +112 -13
- data/lib/generators/brainzlab/install/install_generator.rb +29 -27
- 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 StandardError => 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.positive?
|
|
88
|
+
rescue StandardError => 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,68 @@
|
|
|
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
|
+
BrainzLab.debug("[Flux] Request failed: #{response.code} - #{response.body}") unless response.is_a?(Net::HTTPSuccess)
|
|
52
|
+
|
|
53
|
+
response
|
|
54
|
+
rescue StandardError => e
|
|
55
|
+
BrainzLab.debug("[Flux] Request error: #{e.message}")
|
|
56
|
+
nil
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def base_url
|
|
60
|
+
@config.flux_url
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def api_key
|
|
64
|
+
@config.flux_ingest_key || @config.flux_api_key
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
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 StandardError => 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
|
|
@@ -11,19 +11,19 @@ module BrainzLab
|
|
|
11
11
|
return if @installed
|
|
12
12
|
|
|
13
13
|
# Subscribe to deliver notification
|
|
14
|
-
ActiveSupport::Notifications.subscribe(
|
|
14
|
+
ActiveSupport::Notifications.subscribe('deliver.action_mailer') do |*args|
|
|
15
15
|
event = ActiveSupport::Notifications::Event.new(*args)
|
|
16
16
|
record_delivery(event)
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
# Subscribe to process notification (when mail is being prepared)
|
|
20
|
-
ActiveSupport::Notifications.subscribe(
|
|
20
|
+
ActiveSupport::Notifications.subscribe('process.action_mailer') do |*args|
|
|
21
21
|
event = ActiveSupport::Notifications::Event.new(*args)
|
|
22
22
|
record_process(event)
|
|
23
23
|
end
|
|
24
24
|
|
|
25
25
|
@installed = true
|
|
26
|
-
BrainzLab.debug_log(
|
|
26
|
+
BrainzLab.debug_log('ActionMailer instrumentation installed')
|
|
27
27
|
end
|
|
28
28
|
|
|
29
29
|
def installed?
|
|
@@ -46,13 +46,13 @@ module BrainzLab
|
|
|
46
46
|
mail = payload[:mail]
|
|
47
47
|
to = sanitize_recipients(mail&.to)
|
|
48
48
|
subject = mail&.subject
|
|
49
|
-
delivery_method = payload[:perform_deliveries] ?
|
|
49
|
+
delivery_method = payload[:perform_deliveries] ? 'delivered' : 'skipped'
|
|
50
50
|
|
|
51
51
|
# Add breadcrumb for Reflex
|
|
52
52
|
if BrainzLab.configuration.reflex_enabled
|
|
53
53
|
BrainzLab::Reflex.add_breadcrumb(
|
|
54
54
|
"Mail #{delivery_method}: #{mailer}",
|
|
55
|
-
category:
|
|
55
|
+
category: 'mailer.deliver',
|
|
56
56
|
level: :info,
|
|
57
57
|
data: {
|
|
58
58
|
mailer: mailer,
|
|
@@ -67,13 +67,13 @@ module BrainzLab
|
|
|
67
67
|
# Record span for Pulse
|
|
68
68
|
record_span(
|
|
69
69
|
name: "Mail deliver #{mailer}",
|
|
70
|
-
kind:
|
|
70
|
+
kind: 'mailer',
|
|
71
71
|
started_at: event.time,
|
|
72
72
|
ended_at: event.end,
|
|
73
73
|
duration_ms: duration_ms,
|
|
74
74
|
data: {
|
|
75
75
|
mailer: mailer,
|
|
76
|
-
action:
|
|
76
|
+
action: 'deliver',
|
|
77
77
|
to: to,
|
|
78
78
|
subject: truncate_subject(subject),
|
|
79
79
|
message_id: message_id,
|
|
@@ -106,7 +106,7 @@ module BrainzLab
|
|
|
106
106
|
if BrainzLab.configuration.reflex_enabled
|
|
107
107
|
BrainzLab::Reflex.add_breadcrumb(
|
|
108
108
|
"Mail process: #{mailer}##{action}",
|
|
109
|
-
category:
|
|
109
|
+
category: 'mailer.process',
|
|
110
110
|
level: :info,
|
|
111
111
|
data: {
|
|
112
112
|
mailer: mailer,
|
|
@@ -119,7 +119,7 @@ module BrainzLab
|
|
|
119
119
|
# Record span for Pulse
|
|
120
120
|
record_span(
|
|
121
121
|
name: "Mail process #{mailer}##{action}",
|
|
122
|
-
kind:
|
|
122
|
+
kind: 'mailer',
|
|
123
123
|
started_at: event.time,
|
|
124
124
|
ended_at: event.end,
|
|
125
125
|
duration_ms: duration_ms,
|
|
@@ -152,27 +152,28 @@ module BrainzLab
|
|
|
152
152
|
|
|
153
153
|
case recipients
|
|
154
154
|
when Array
|
|
155
|
-
recipients.map { |r| mask_email(r) }.join(
|
|
155
|
+
recipients.map { |r| mask_email(r) }.join(', ')
|
|
156
156
|
else
|
|
157
157
|
mask_email(recipients.to_s)
|
|
158
158
|
end
|
|
159
159
|
end
|
|
160
160
|
|
|
161
161
|
def mask_email(email)
|
|
162
|
-
return email unless email.include?(
|
|
162
|
+
return email unless email.include?('@')
|
|
163
163
|
|
|
164
|
-
local, domain = email.split(
|
|
164
|
+
local, domain = email.split('@', 2)
|
|
165
165
|
if local.length > 2
|
|
166
166
|
"#{local[0..1]}***@#{domain}"
|
|
167
167
|
else
|
|
168
168
|
"***@#{domain}"
|
|
169
169
|
end
|
|
170
170
|
rescue StandardError
|
|
171
|
-
|
|
171
|
+
'[email]'
|
|
172
172
|
end
|
|
173
173
|
|
|
174
174
|
def truncate_subject(subject)
|
|
175
175
|
return nil unless subject
|
|
176
|
+
|
|
176
177
|
subject.to_s[0, 100]
|
|
177
178
|
end
|
|
178
179
|
end
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
module BrainzLab
|
|
4
4
|
module Instrumentation
|
|
5
5
|
class ActiveRecord
|
|
6
|
-
SCHEMA_QUERIES = [
|
|
6
|
+
SCHEMA_QUERIES = %w[SCHEMA EXPLAIN].freeze
|
|
7
7
|
INTERNAL_TABLES = %w[pg_ information_schema sqlite_ mysql.].freeze
|
|
8
8
|
|
|
9
9
|
class << self
|
|
@@ -11,7 +11,7 @@ module BrainzLab
|
|
|
11
11
|
return unless defined?(::ActiveRecord)
|
|
12
12
|
return if @installed
|
|
13
13
|
|
|
14
|
-
ActiveSupport::Notifications.subscribe(
|
|
14
|
+
ActiveSupport::Notifications.subscribe('sql.active_record') do |*args|
|
|
15
15
|
event = ActiveSupport::Notifications::Event.new(*args)
|
|
16
16
|
next if skip_query?(event.payload)
|
|
17
17
|
|
|
@@ -19,7 +19,7 @@ module BrainzLab
|
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
@installed = true
|
|
22
|
-
BrainzLab.debug_log(
|
|
22
|
+
BrainzLab.debug_log('ActiveRecord breadcrumbs installed')
|
|
23
23
|
end
|
|
24
24
|
|
|
25
25
|
def installed?
|
|
@@ -31,7 +31,7 @@ module BrainzLab
|
|
|
31
31
|
def record_breadcrumb(event)
|
|
32
32
|
payload = event.payload
|
|
33
33
|
sql = payload[:sql]
|
|
34
|
-
name = payload[:name] ||
|
|
34
|
+
name = payload[:name] || 'SQL'
|
|
35
35
|
duration = event.duration.round(2)
|
|
36
36
|
|
|
37
37
|
# Extract operation type from SQL
|
|
@@ -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
|
|
@@ -69,15 +69,15 @@ module BrainzLab
|
|
|
69
69
|
end
|
|
70
70
|
|
|
71
71
|
def extract_operation(sql)
|
|
72
|
-
return
|
|
72
|
+
return 'query' unless sql
|
|
73
73
|
|
|
74
74
|
case sql.to_s.strip.upcase
|
|
75
|
-
when /\ASELECT/i then
|
|
76
|
-
when /\AINSERT/i then
|
|
77
|
-
when /\AUPDATE/i then
|
|
78
|
-
when /\ADELETE/i then
|
|
79
|
-
when /\ABEGIN/i, /\ACOMMIT/i, /\AROLLBACK/i then
|
|
80
|
-
else
|
|
75
|
+
when /\ASELECT/i then 'select'
|
|
76
|
+
when /\AINSERT/i then 'insert'
|
|
77
|
+
when /\AUPDATE/i then 'update'
|
|
78
|
+
when /\ADELETE/i then 'delete'
|
|
79
|
+
when /\ABEGIN/i, /\ACOMMIT/i, /\AROLLBACK/i then 'transaction'
|
|
80
|
+
else 'query'
|
|
81
81
|
end
|
|
82
82
|
end
|
|
83
83
|
|
|
@@ -95,10 +95,25 @@ 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
|
+
pool.db_config.name if pool.respond_to?(:db_config) && pool.db_config.respond_to?(:name)
|
|
106
|
+
elsif connection.respond_to?(:db_config) && connection.db_config.respond_to?(:name)
|
|
107
|
+
connection.db_config.name
|
|
108
|
+
end
|
|
109
|
+
rescue StandardError
|
|
110
|
+
nil
|
|
111
|
+
end
|
|
112
|
+
|
|
98
113
|
def truncate_sql(sql)
|
|
99
114
|
return nil unless sql
|
|
100
115
|
|
|
101
|
-
truncated = sql.to_s.gsub(/\s+/,
|
|
116
|
+
truncated = sql.to_s.gsub(/\s+/, ' ').strip
|
|
102
117
|
if truncated.length > 500
|
|
103
118
|
"#{truncated[0, 497]}..."
|
|
104
119
|
else
|