fluyenta-ruby 0.1.14
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 +7 -0
- data/CHANGELOG.md +68 -0
- data/LICENSE +11 -0
- data/README.md +571 -0
- data/lib/brainzlab/beacon/client.rb +227 -0
- data/lib/brainzlab/beacon/provisioner.rb +44 -0
- data/lib/brainzlab/beacon.rb +215 -0
- data/lib/brainzlab/configuration.rb +676 -0
- data/lib/brainzlab/context.rb +90 -0
- data/lib/brainzlab/cortex/cache.rb +59 -0
- data/lib/brainzlab/cortex/client.rb +159 -0
- data/lib/brainzlab/cortex/provisioner.rb +49 -0
- data/lib/brainzlab/cortex.rb +223 -0
- data/lib/brainzlab/debug.rb +305 -0
- data/lib/brainzlab/dendrite/client.rb +250 -0
- data/lib/brainzlab/dendrite/provisioner.rb +44 -0
- data/lib/brainzlab/dendrite.rb +195 -0
- data/lib/brainzlab/development/logger.rb +150 -0
- data/lib/brainzlab/development/store.rb +121 -0
- data/lib/brainzlab/development.rb +72 -0
- data/lib/brainzlab/devtools/assets/devtools.css +1329 -0
- data/lib/brainzlab/devtools/assets/devtools.js +396 -0
- data/lib/brainzlab/devtools/assets/logo.svg +6 -0
- data/lib/brainzlab/devtools/assets/templates/debug_panel.html.erb +511 -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/errors.rb +490 -0
- data/lib/brainzlab/flux/buffer.rb +96 -0
- data/lib/brainzlab/flux/client.rb +68 -0
- data/lib/brainzlab/flux/provisioner.rb +124 -0
- data/lib/brainzlab/flux.rb +184 -0
- data/lib/brainzlab/instrumentation/action_cable.rb +351 -0
- data/lib/brainzlab/instrumentation/action_controller.rb +649 -0
- data/lib/brainzlab/instrumentation/action_dispatch.rb +259 -0
- data/lib/brainzlab/instrumentation/action_mailbox.rb +197 -0
- data/lib/brainzlab/instrumentation/action_mailer.rb +182 -0
- data/lib/brainzlab/instrumentation/action_view.rb +380 -0
- data/lib/brainzlab/instrumentation/active_job.rb +569 -0
- data/lib/brainzlab/instrumentation/active_record.rb +559 -0
- data/lib/brainzlab/instrumentation/active_storage.rb +541 -0
- data/lib/brainzlab/instrumentation/active_support_cache.rb +730 -0
- data/lib/brainzlab/instrumentation/aws.rb +183 -0
- data/lib/brainzlab/instrumentation/dalli.rb +108 -0
- data/lib/brainzlab/instrumentation/delayed_job.rb +234 -0
- data/lib/brainzlab/instrumentation/elasticsearch.rb +209 -0
- data/lib/brainzlab/instrumentation/excon.rb +152 -0
- data/lib/brainzlab/instrumentation/faraday.rb +181 -0
- data/lib/brainzlab/instrumentation/good_job.rb +102 -0
- data/lib/brainzlab/instrumentation/grape.rb +293 -0
- data/lib/brainzlab/instrumentation/graphql.rb +252 -0
- data/lib/brainzlab/instrumentation/httparty.rb +193 -0
- data/lib/brainzlab/instrumentation/mongodb.rb +187 -0
- data/lib/brainzlab/instrumentation/net_http.rb +114 -0
- data/lib/brainzlab/instrumentation/rails_deprecation.rb +139 -0
- data/lib/brainzlab/instrumentation/railties.rb +134 -0
- data/lib/brainzlab/instrumentation/redis.rb +324 -0
- data/lib/brainzlab/instrumentation/resque.rb +114 -0
- data/lib/brainzlab/instrumentation/sidekiq.rb +265 -0
- 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 +360 -0
- data/lib/brainzlab/nerve/client.rb +235 -0
- data/lib/brainzlab/nerve/provisioner.rb +44 -0
- data/lib/brainzlab/nerve.rb +219 -0
- data/lib/brainzlab/pulse/client.rb +203 -0
- data/lib/brainzlab/pulse/instrumentation.rb +401 -0
- data/lib/brainzlab/pulse/propagation.rb +241 -0
- data/lib/brainzlab/pulse/provisioner.rb +114 -0
- data/lib/brainzlab/pulse/tracer.rb +111 -0
- data/lib/brainzlab/pulse.rb +294 -0
- data/lib/brainzlab/rails/log_formatter.rb +807 -0
- data/lib/brainzlab/rails/log_subscriber.rb +334 -0
- data/lib/brainzlab/rails/railtie.rb +606 -0
- data/lib/brainzlab/recall/buffer.rb +66 -0
- data/lib/brainzlab/recall/client.rb +158 -0
- data/lib/brainzlab/recall/logger.rb +116 -0
- data/lib/brainzlab/recall/provisioner.rb +130 -0
- data/lib/brainzlab/recall.rb +175 -0
- data/lib/brainzlab/reflex/breadcrumbs.rb +55 -0
- data/lib/brainzlab/reflex/client.rb +150 -0
- data/lib/brainzlab/reflex/provisioner.rb +116 -0
- data/lib/brainzlab/reflex.rb +421 -0
- data/lib/brainzlab/sentinel/client.rb +236 -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 +115 -0
- data/lib/brainzlab/signal.rb +136 -0
- data/lib/brainzlab/synapse/client.rb +308 -0
- data/lib/brainzlab/synapse/provisioner.rb +44 -0
- data/lib/brainzlab/synapse.rb +270 -0
- data/lib/brainzlab/testing/event_store.rb +377 -0
- data/lib/brainzlab/testing/helpers.rb +650 -0
- data/lib/brainzlab/testing/matchers.rb +391 -0
- data/lib/brainzlab/testing.rb +327 -0
- data/lib/brainzlab/utilities/circuit_breaker.rb +290 -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 +216 -0
- data/lib/brainzlab/vault/provisioner.rb +49 -0
- data/lib/brainzlab/vault.rb +262 -0
- data/lib/brainzlab/version.rb +5 -0
- data/lib/brainzlab/vision/client.rb +175 -0
- data/lib/brainzlab/vision/provisioner.rb +136 -0
- data/lib/brainzlab/vision.rb +155 -0
- data/lib/brainzlab-sdk.rb +3 -0
- data/lib/brainzlab.rb +306 -0
- data/lib/generators/brainzlab/install/install_generator.rb +63 -0
- data/lib/generators/brainzlab/install/templates/brainzlab.rb.tt +77 -0
- metadata +251 -0
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'net/http'
|
|
4
|
+
require 'json'
|
|
5
|
+
require 'uri'
|
|
6
|
+
require 'fileutils'
|
|
7
|
+
|
|
8
|
+
module BrainzLab
|
|
9
|
+
module Flux
|
|
10
|
+
class Provisioner
|
|
11
|
+
CACHE_DIR = ENV.fetch('BRAINZLAB_CACHE_DIR') { File.join(Dir.home, '.brainzlab') }
|
|
12
|
+
|
|
13
|
+
def initialize(config)
|
|
14
|
+
@config = config
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def ensure_project!
|
|
18
|
+
return unless should_provision?
|
|
19
|
+
|
|
20
|
+
# Try cached credentials first
|
|
21
|
+
if (cached = load_cached_credentials)
|
|
22
|
+
apply_credentials(cached)
|
|
23
|
+
return cached
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Provision new project
|
|
27
|
+
project = provision_project
|
|
28
|
+
return unless project
|
|
29
|
+
|
|
30
|
+
# Cache and apply credentials
|
|
31
|
+
cache_credentials(project)
|
|
32
|
+
apply_credentials(project)
|
|
33
|
+
|
|
34
|
+
project
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def should_provision?
|
|
40
|
+
# Already have credentials
|
|
41
|
+
return false if @config.flux_ingest_key.to_s.strip.length.positive?
|
|
42
|
+
return false if @config.flux_api_key.to_s.strip.length.positive?
|
|
43
|
+
|
|
44
|
+
# Need auto_provision enabled
|
|
45
|
+
return false unless @config.flux_auto_provision
|
|
46
|
+
|
|
47
|
+
# Need app_name for project name
|
|
48
|
+
return false unless @config.app_name.to_s.strip.length.positive?
|
|
49
|
+
|
|
50
|
+
# Need master key for provisioning
|
|
51
|
+
return false unless @config.flux_master_key.to_s.strip.length.positive?
|
|
52
|
+
|
|
53
|
+
# Need flux_url
|
|
54
|
+
return false unless @config.flux_url.to_s.strip.length.positive?
|
|
55
|
+
|
|
56
|
+
true
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def provision_project
|
|
60
|
+
BrainzLab.debug_log('[Flux] Auto-provisioning project...')
|
|
61
|
+
|
|
62
|
+
uri = URI.parse("#{@config.flux_url}/api/v1/projects/provision")
|
|
63
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
64
|
+
http.use_ssl = uri.scheme == 'https'
|
|
65
|
+
http.open_timeout = 5
|
|
66
|
+
http.read_timeout = 10
|
|
67
|
+
|
|
68
|
+
request = Net::HTTP::Post.new(uri.path)
|
|
69
|
+
request['Content-Type'] = 'application/json'
|
|
70
|
+
request['X-Master-Key'] = @config.flux_master_key
|
|
71
|
+
request['User-Agent'] = "brainzlab-sdk/#{BrainzLab::VERSION}"
|
|
72
|
+
request.body = {
|
|
73
|
+
name: @config.app_name,
|
|
74
|
+
environment: @config.environment
|
|
75
|
+
}.to_json
|
|
76
|
+
|
|
77
|
+
response = http.request(request)
|
|
78
|
+
|
|
79
|
+
if response.is_a?(Net::HTTPSuccess)
|
|
80
|
+
data = JSON.parse(response.body, symbolize_names: true)
|
|
81
|
+
BrainzLab.debug_log('[Flux] Project provisioned successfully')
|
|
82
|
+
data
|
|
83
|
+
else
|
|
84
|
+
BrainzLab.debug_log("[Flux] Provisioning failed: #{response.code} - #{response.body}")
|
|
85
|
+
nil
|
|
86
|
+
end
|
|
87
|
+
rescue StandardError => e
|
|
88
|
+
BrainzLab.debug_log("[Flux] Provisioning error: #{e.message}")
|
|
89
|
+
nil
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def load_cached_credentials
|
|
93
|
+
path = cache_file_path
|
|
94
|
+
return nil unless File.exist?(path)
|
|
95
|
+
|
|
96
|
+
data = JSON.parse(File.read(path), symbolize_names: true)
|
|
97
|
+
|
|
98
|
+
# Validate cached data has required keys
|
|
99
|
+
return nil unless data[:ingest_key]
|
|
100
|
+
|
|
101
|
+
data
|
|
102
|
+
rescue StandardError => e
|
|
103
|
+
BrainzLab.debug_log("[Flux] Failed to load cached credentials: #{e.message}")
|
|
104
|
+
nil
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def cache_credentials(project)
|
|
108
|
+
FileUtils.mkdir_p(CACHE_DIR)
|
|
109
|
+
File.write(cache_file_path, JSON.generate(project))
|
|
110
|
+
rescue StandardError => e
|
|
111
|
+
BrainzLab.debug_log("[Flux] Failed to cache credentials: #{e.message}")
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def cache_file_path
|
|
115
|
+
File.join(CACHE_DIR, "#{@config.app_name}.flux.json")
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def apply_credentials(project)
|
|
119
|
+
@config.flux_ingest_key = project[:ingest_key]
|
|
120
|
+
@config.flux_api_key = project[:api_key]
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
@@ -0,0 +1,184 @@
|
|
|
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
|
+
# Histogram: Alias for distribution (for compatibility with brainzlab-rails)
|
|
104
|
+
def histogram(name, value, tags: {})
|
|
105
|
+
distribution(name, value, tags: tags)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Timing: Record duration in milliseconds (alias for distribution)
|
|
109
|
+
def timing(name, value_ms, tags: {})
|
|
110
|
+
distribution(name, value_ms, tags: tags.merge(unit: 'ms'))
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Set: Unique count (cardinality)
|
|
114
|
+
def set(name, value, tags: {})
|
|
115
|
+
return unless enabled?
|
|
116
|
+
|
|
117
|
+
ensure_provisioned!
|
|
118
|
+
return unless BrainzLab.configuration.flux_valid?
|
|
119
|
+
|
|
120
|
+
metric = {
|
|
121
|
+
type: 'set',
|
|
122
|
+
name: name,
|
|
123
|
+
value: value.to_s,
|
|
124
|
+
tags: tags,
|
|
125
|
+
timestamp: Time.now.utc.iso8601(3)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
buffer.add(:metric, metric)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# === CONVENIENCE METHODS ===
|
|
132
|
+
|
|
133
|
+
# Time a block and record as distribution
|
|
134
|
+
def measure(name, tags: {})
|
|
135
|
+
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
136
|
+
begin
|
|
137
|
+
yield
|
|
138
|
+
ensure
|
|
139
|
+
duration_ms = (Process.clock_gettime(Process::CLOCK_MONOTONIC) - start) * 1000
|
|
140
|
+
distribution(name, duration_ms, tags: tags.merge(unit: 'ms'))
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Flush any buffered data immediately
|
|
145
|
+
def flush!
|
|
146
|
+
buffer.flush!
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# === INTERNAL ===
|
|
150
|
+
|
|
151
|
+
def ensure_provisioned!
|
|
152
|
+
return if @provisioned
|
|
153
|
+
|
|
154
|
+
@provisioned = true
|
|
155
|
+
provisioner.ensure_project!
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def provisioner
|
|
159
|
+
@provisioner ||= Provisioner.new(BrainzLab.configuration)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def client
|
|
163
|
+
@client ||= Client.new(BrainzLab.configuration)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def buffer
|
|
167
|
+
@buffer ||= Buffer.new(client)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def reset!
|
|
171
|
+
@client = nil
|
|
172
|
+
@buffer = nil
|
|
173
|
+
@provisioner = nil
|
|
174
|
+
@provisioned = false
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
private
|
|
178
|
+
|
|
179
|
+
def enabled?
|
|
180
|
+
BrainzLab.configuration.flux_effectively_enabled?
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
end
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BrainzLab
|
|
4
|
+
module Instrumentation
|
|
5
|
+
class ActionCable
|
|
6
|
+
# Thresholds for slow operations (in milliseconds)
|
|
7
|
+
SLOW_ACTION_THRESHOLD = 100
|
|
8
|
+
VERY_SLOW_ACTION_THRESHOLD = 500
|
|
9
|
+
|
|
10
|
+
class << self
|
|
11
|
+
def install!
|
|
12
|
+
return unless defined?(::ActionCable)
|
|
13
|
+
return if @installed
|
|
14
|
+
|
|
15
|
+
install_perform_action_subscriber!
|
|
16
|
+
install_transmit_subscriber!
|
|
17
|
+
install_transmit_subscription_confirmation_subscriber!
|
|
18
|
+
install_transmit_subscription_rejection_subscriber!
|
|
19
|
+
install_broadcast_subscriber!
|
|
20
|
+
|
|
21
|
+
@installed = true
|
|
22
|
+
BrainzLab.debug_log('ActionCable instrumentation installed')
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def installed?
|
|
26
|
+
@installed == true
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
# ============================================
|
|
32
|
+
# perform_action.action_cable
|
|
33
|
+
# ============================================
|
|
34
|
+
def install_perform_action_subscriber!
|
|
35
|
+
ActiveSupport::Notifications.subscribe('perform_action.action_cable') do |*args|
|
|
36
|
+
event = ActiveSupport::Notifications::Event.new(*args)
|
|
37
|
+
handle_perform_action(event)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def handle_perform_action(event)
|
|
42
|
+
payload = event.payload
|
|
43
|
+
duration = event.duration.round(2)
|
|
44
|
+
|
|
45
|
+
channel_class = payload[:channel_class]
|
|
46
|
+
action = payload[:action]
|
|
47
|
+
data = payload[:data]
|
|
48
|
+
|
|
49
|
+
# Determine level based on duration
|
|
50
|
+
level = case duration
|
|
51
|
+
when 0...SLOW_ACTION_THRESHOLD then :info
|
|
52
|
+
when SLOW_ACTION_THRESHOLD...VERY_SLOW_ACTION_THRESHOLD then :warning
|
|
53
|
+
else :error
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Record breadcrumb
|
|
57
|
+
if BrainzLab.configuration.reflex_effectively_enabled?
|
|
58
|
+
BrainzLab::Reflex.add_breadcrumb(
|
|
59
|
+
"Cable action: #{channel_class}##{action} (#{duration}ms)",
|
|
60
|
+
category: 'cable.action',
|
|
61
|
+
level: level,
|
|
62
|
+
data: {
|
|
63
|
+
channel: channel_class,
|
|
64
|
+
action: action,
|
|
65
|
+
duration_ms: duration
|
|
66
|
+
}.compact
|
|
67
|
+
)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Add Pulse span
|
|
71
|
+
record_action_span(event, channel_class, action, duration, data)
|
|
72
|
+
|
|
73
|
+
# Log slow actions
|
|
74
|
+
log_slow_action(channel_class, action, duration) if duration >= SLOW_ACTION_THRESHOLD
|
|
75
|
+
rescue StandardError => e
|
|
76
|
+
BrainzLab.debug_log("ActionCable perform_action instrumentation failed: #{e.message}")
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# ============================================
|
|
80
|
+
# transmit.action_cable
|
|
81
|
+
# ============================================
|
|
82
|
+
def install_transmit_subscriber!
|
|
83
|
+
ActiveSupport::Notifications.subscribe('transmit.action_cable') do |*args|
|
|
84
|
+
event = ActiveSupport::Notifications::Event.new(*args)
|
|
85
|
+
handle_transmit(event)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def handle_transmit(event)
|
|
90
|
+
payload = event.payload
|
|
91
|
+
duration = event.duration.round(2)
|
|
92
|
+
|
|
93
|
+
channel_class = payload[:channel_class]
|
|
94
|
+
data = payload[:data]
|
|
95
|
+
via = payload[:via]
|
|
96
|
+
|
|
97
|
+
# Record breadcrumb
|
|
98
|
+
if BrainzLab.configuration.reflex_effectively_enabled?
|
|
99
|
+
message = via ? "Cable transmit via #{via}" : 'Cable transmit'
|
|
100
|
+
BrainzLab::Reflex.add_breadcrumb(
|
|
101
|
+
"#{message}: #{channel_class} (#{duration}ms)",
|
|
102
|
+
category: 'cable.transmit',
|
|
103
|
+
level: :info,
|
|
104
|
+
data: {
|
|
105
|
+
channel: channel_class,
|
|
106
|
+
via: via,
|
|
107
|
+
duration_ms: duration
|
|
108
|
+
}.compact
|
|
109
|
+
)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Add Pulse span
|
|
113
|
+
record_transmit_span(event, channel_class, duration, via)
|
|
114
|
+
rescue StandardError => e
|
|
115
|
+
BrainzLab.debug_log("ActionCable transmit instrumentation failed: #{e.message}")
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# ============================================
|
|
119
|
+
# transmit_subscription_confirmation.action_cable
|
|
120
|
+
# ============================================
|
|
121
|
+
def install_transmit_subscription_confirmation_subscriber!
|
|
122
|
+
ActiveSupport::Notifications.subscribe('transmit_subscription_confirmation.action_cable') do |*args|
|
|
123
|
+
event = ActiveSupport::Notifications::Event.new(*args)
|
|
124
|
+
handle_subscription_confirmation(event)
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def handle_subscription_confirmation(event)
|
|
129
|
+
payload = event.payload
|
|
130
|
+
duration = event.duration.round(2)
|
|
131
|
+
|
|
132
|
+
channel_class = payload[:channel_class]
|
|
133
|
+
|
|
134
|
+
# Record breadcrumb
|
|
135
|
+
if BrainzLab.configuration.reflex_effectively_enabled?
|
|
136
|
+
BrainzLab::Reflex.add_breadcrumb(
|
|
137
|
+
"Cable subscribed: #{channel_class}",
|
|
138
|
+
category: 'cable.subscribe',
|
|
139
|
+
level: :info,
|
|
140
|
+
data: {
|
|
141
|
+
channel: channel_class,
|
|
142
|
+
status: 'confirmed',
|
|
143
|
+
duration_ms: duration
|
|
144
|
+
}.compact
|
|
145
|
+
)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Add Pulse span
|
|
149
|
+
record_subscription_span(event, channel_class, 'confirmed', duration)
|
|
150
|
+
rescue StandardError => e
|
|
151
|
+
BrainzLab.debug_log("ActionCable subscription confirmation instrumentation failed: #{e.message}")
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# ============================================
|
|
155
|
+
# transmit_subscription_rejection.action_cable
|
|
156
|
+
# ============================================
|
|
157
|
+
def install_transmit_subscription_rejection_subscriber!
|
|
158
|
+
ActiveSupport::Notifications.subscribe('transmit_subscription_rejection.action_cable') do |*args|
|
|
159
|
+
event = ActiveSupport::Notifications::Event.new(*args)
|
|
160
|
+
handle_subscription_rejection(event)
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def handle_subscription_rejection(event)
|
|
165
|
+
payload = event.payload
|
|
166
|
+
duration = event.duration.round(2)
|
|
167
|
+
|
|
168
|
+
channel_class = payload[:channel_class]
|
|
169
|
+
|
|
170
|
+
# Record breadcrumb - rejection is a warning
|
|
171
|
+
if BrainzLab.configuration.reflex_effectively_enabled?
|
|
172
|
+
BrainzLab::Reflex.add_breadcrumb(
|
|
173
|
+
"Cable subscription rejected: #{channel_class}",
|
|
174
|
+
category: 'cable.subscribe',
|
|
175
|
+
level: :warning,
|
|
176
|
+
data: {
|
|
177
|
+
channel: channel_class,
|
|
178
|
+
status: 'rejected',
|
|
179
|
+
duration_ms: duration
|
|
180
|
+
}.compact
|
|
181
|
+
)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# Add Pulse span
|
|
185
|
+
record_subscription_span(event, channel_class, 'rejected', duration)
|
|
186
|
+
|
|
187
|
+
# Log rejection to Recall
|
|
188
|
+
if BrainzLab.configuration.recall_effectively_enabled?
|
|
189
|
+
BrainzLab::Recall.warn(
|
|
190
|
+
"ActionCable subscription rejected",
|
|
191
|
+
channel: channel_class
|
|
192
|
+
)
|
|
193
|
+
end
|
|
194
|
+
rescue StandardError => e
|
|
195
|
+
BrainzLab.debug_log("ActionCable subscription rejection instrumentation failed: #{e.message}")
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# ============================================
|
|
199
|
+
# broadcast.action_cable
|
|
200
|
+
# ============================================
|
|
201
|
+
def install_broadcast_subscriber!
|
|
202
|
+
ActiveSupport::Notifications.subscribe('broadcast.action_cable') do |*args|
|
|
203
|
+
event = ActiveSupport::Notifications::Event.new(*args)
|
|
204
|
+
handle_broadcast(event)
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def handle_broadcast(event)
|
|
209
|
+
payload = event.payload
|
|
210
|
+
duration = event.duration.round(2)
|
|
211
|
+
|
|
212
|
+
broadcasting = payload[:broadcasting]
|
|
213
|
+
message = payload[:message]
|
|
214
|
+
coder = payload[:coder]
|
|
215
|
+
|
|
216
|
+
# Record breadcrumb
|
|
217
|
+
if BrainzLab.configuration.reflex_effectively_enabled?
|
|
218
|
+
BrainzLab::Reflex.add_breadcrumb(
|
|
219
|
+
"Cable broadcast: #{broadcasting} (#{duration}ms)",
|
|
220
|
+
category: 'cable.broadcast',
|
|
221
|
+
level: :info,
|
|
222
|
+
data: {
|
|
223
|
+
broadcasting: broadcasting,
|
|
224
|
+
coder: coder&.to_s,
|
|
225
|
+
duration_ms: duration
|
|
226
|
+
}.compact
|
|
227
|
+
)
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# Add Pulse span
|
|
231
|
+
record_broadcast_span(event, broadcasting, duration, coder)
|
|
232
|
+
rescue StandardError => e
|
|
233
|
+
BrainzLab.debug_log("ActionCable broadcast instrumentation failed: #{e.message}")
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
# ============================================
|
|
237
|
+
# Span Recording Helpers
|
|
238
|
+
# ============================================
|
|
239
|
+
def record_action_span(event, channel_class, action, duration, data)
|
|
240
|
+
return unless BrainzLab.configuration.pulse_effectively_enabled?
|
|
241
|
+
|
|
242
|
+
tracer = BrainzLab::Pulse.tracer
|
|
243
|
+
return unless tracer.current_trace
|
|
244
|
+
|
|
245
|
+
span_data = {
|
|
246
|
+
span_id: SecureRandom.uuid,
|
|
247
|
+
name: "cable.action.#{action}",
|
|
248
|
+
kind: 'websocket',
|
|
249
|
+
started_at: event.time,
|
|
250
|
+
ended_at: event.end,
|
|
251
|
+
duration_ms: duration,
|
|
252
|
+
error: false,
|
|
253
|
+
data: {
|
|
254
|
+
'cable.channel' => channel_class,
|
|
255
|
+
'cable.action' => action
|
|
256
|
+
}.compact
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
tracer.current_spans << span_data
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def record_transmit_span(event, channel_class, duration, via)
|
|
263
|
+
return unless BrainzLab.configuration.pulse_effectively_enabled?
|
|
264
|
+
|
|
265
|
+
tracer = BrainzLab::Pulse.tracer
|
|
266
|
+
return unless tracer.current_trace
|
|
267
|
+
|
|
268
|
+
span_data = {
|
|
269
|
+
span_id: SecureRandom.uuid,
|
|
270
|
+
name: 'cable.transmit',
|
|
271
|
+
kind: 'websocket',
|
|
272
|
+
started_at: event.time,
|
|
273
|
+
ended_at: event.end,
|
|
274
|
+
duration_ms: duration,
|
|
275
|
+
error: false,
|
|
276
|
+
data: {
|
|
277
|
+
'cable.channel' => channel_class,
|
|
278
|
+
'cable.via' => via
|
|
279
|
+
}.compact
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
tracer.current_spans << span_data
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
def record_subscription_span(event, channel_class, status, duration)
|
|
286
|
+
return unless BrainzLab.configuration.pulse_effectively_enabled?
|
|
287
|
+
|
|
288
|
+
tracer = BrainzLab::Pulse.tracer
|
|
289
|
+
return unless tracer.current_trace
|
|
290
|
+
|
|
291
|
+
span_data = {
|
|
292
|
+
span_id: SecureRandom.uuid,
|
|
293
|
+
name: 'cable.subscribe',
|
|
294
|
+
kind: 'websocket',
|
|
295
|
+
started_at: event.time,
|
|
296
|
+
ended_at: event.end,
|
|
297
|
+
duration_ms: duration,
|
|
298
|
+
error: status == 'rejected',
|
|
299
|
+
data: {
|
|
300
|
+
'cable.channel' => channel_class,
|
|
301
|
+
'cable.subscription_status' => status
|
|
302
|
+
}.compact
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
tracer.current_spans << span_data
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
def record_broadcast_span(event, broadcasting, duration, coder)
|
|
309
|
+
return unless BrainzLab.configuration.pulse_effectively_enabled?
|
|
310
|
+
|
|
311
|
+
tracer = BrainzLab::Pulse.tracer
|
|
312
|
+
return unless tracer.current_trace
|
|
313
|
+
|
|
314
|
+
span_data = {
|
|
315
|
+
span_id: SecureRandom.uuid,
|
|
316
|
+
name: 'cable.broadcast',
|
|
317
|
+
kind: 'websocket',
|
|
318
|
+
started_at: event.time,
|
|
319
|
+
ended_at: event.end,
|
|
320
|
+
duration_ms: duration,
|
|
321
|
+
error: false,
|
|
322
|
+
data: {
|
|
323
|
+
'cable.broadcasting' => broadcasting,
|
|
324
|
+
'cable.coder' => coder&.to_s
|
|
325
|
+
}.compact
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
tracer.current_spans << span_data
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
# ============================================
|
|
332
|
+
# Logging Helpers
|
|
333
|
+
# ============================================
|
|
334
|
+
def log_slow_action(channel_class, action, duration)
|
|
335
|
+
return unless BrainzLab.configuration.recall_effectively_enabled?
|
|
336
|
+
|
|
337
|
+
level = duration >= VERY_SLOW_ACTION_THRESHOLD ? :error : :warn
|
|
338
|
+
|
|
339
|
+
BrainzLab::Recall.send(
|
|
340
|
+
level,
|
|
341
|
+
"Slow ActionCable action: #{channel_class}##{action} (#{duration}ms)",
|
|
342
|
+
channel: channel_class,
|
|
343
|
+
action: action,
|
|
344
|
+
duration_ms: duration,
|
|
345
|
+
threshold_exceeded: duration >= VERY_SLOW_ACTION_THRESHOLD ? 'critical' : 'warning'
|
|
346
|
+
)
|
|
347
|
+
end
|
|
348
|
+
end
|
|
349
|
+
end
|
|
350
|
+
end
|
|
351
|
+
end
|