brainzlab 0.1.2 → 0.1.4

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.
Files changed (104) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +6 -21
  3. data/README.md +16 -2
  4. data/lib/brainzlab/beacon/client.rb +38 -40
  5. data/lib/brainzlab/beacon/provisioner.rb +1 -1
  6. data/lib/brainzlab/beacon.rb +15 -15
  7. data/lib/brainzlab/configuration.rb +112 -90
  8. data/lib/brainzlab/context.rb +2 -3
  9. data/lib/brainzlab/cortex/client.rb +29 -31
  10. data/lib/brainzlab/cortex/provisioner.rb +1 -1
  11. data/lib/brainzlab/cortex.rb +7 -11
  12. data/lib/brainzlab/dendrite/client.rb +42 -44
  13. data/lib/brainzlab/dendrite/provisioner.rb +1 -1
  14. data/lib/brainzlab/dendrite.rb +4 -4
  15. data/lib/brainzlab/devtools/data/collector.rb +22 -22
  16. data/lib/brainzlab/devtools/middleware/asset_server.rb +14 -14
  17. data/lib/brainzlab/devtools/middleware/database_handler.rb +52 -55
  18. data/lib/brainzlab/devtools/middleware/debug_panel.rb +19 -19
  19. data/lib/brainzlab/devtools/middleware/error_page.rb +45 -44
  20. data/lib/brainzlab/devtools/renderers/debug_panel_renderer.rb +39 -35
  21. data/lib/brainzlab/devtools/renderers/error_page_renderer.rb +13 -9
  22. data/lib/brainzlab/devtools.rb +11 -11
  23. data/lib/brainzlab/flux/buffer.rb +3 -3
  24. data/lib/brainzlab/flux/client.rb +14 -16
  25. data/lib/brainzlab/flux/provisioner.rb +13 -13
  26. data/lib/brainzlab/flux.rb +8 -8
  27. data/lib/brainzlab/instrumentation/action_cable.rb +351 -0
  28. data/lib/brainzlab/instrumentation/action_controller.rb +649 -0
  29. data/lib/brainzlab/instrumentation/action_dispatch.rb +259 -0
  30. data/lib/brainzlab/instrumentation/action_mailbox.rb +197 -0
  31. data/lib/brainzlab/instrumentation/action_mailer.rb +14 -13
  32. data/lib/brainzlab/instrumentation/action_view.rb +380 -0
  33. data/lib/brainzlab/instrumentation/active_job.rb +569 -0
  34. data/lib/brainzlab/instrumentation/active_record.rb +467 -36
  35. data/lib/brainzlab/instrumentation/active_storage.rb +541 -0
  36. data/lib/brainzlab/instrumentation/active_support_cache.rb +700 -0
  37. data/lib/brainzlab/instrumentation/aws.rb +43 -39
  38. data/lib/brainzlab/instrumentation/dalli.rb +20 -20
  39. data/lib/brainzlab/instrumentation/delayed_job.rb +27 -29
  40. data/lib/brainzlab/instrumentation/elasticsearch.rb +23 -24
  41. data/lib/brainzlab/instrumentation/excon.rb +27 -27
  42. data/lib/brainzlab/instrumentation/faraday.rb +3 -4
  43. data/lib/brainzlab/instrumentation/good_job.rb +28 -28
  44. data/lib/brainzlab/instrumentation/grape.rb +24 -24
  45. data/lib/brainzlab/instrumentation/graphql.rb +24 -23
  46. data/lib/brainzlab/instrumentation/httparty.rb +13 -14
  47. data/lib/brainzlab/instrumentation/mongodb.rb +7 -7
  48. data/lib/brainzlab/instrumentation/net_http.rb +6 -6
  49. data/lib/brainzlab/instrumentation/rails_deprecation.rb +139 -0
  50. data/lib/brainzlab/instrumentation/railties.rb +134 -0
  51. data/lib/brainzlab/instrumentation/redis.rb +14 -21
  52. data/lib/brainzlab/instrumentation/resque.rb +23 -24
  53. data/lib/brainzlab/instrumentation/sidekiq.rb +29 -28
  54. data/lib/brainzlab/instrumentation/solid_queue.rb +37 -41
  55. data/lib/brainzlab/instrumentation/stripe.rb +36 -37
  56. data/lib/brainzlab/instrumentation/typhoeus.rb +19 -17
  57. data/lib/brainzlab/instrumentation.rb +111 -21
  58. data/lib/brainzlab/nerve/client.rb +38 -40
  59. data/lib/brainzlab/nerve/provisioner.rb +1 -1
  60. data/lib/brainzlab/nerve.rb +6 -6
  61. data/lib/brainzlab/pulse/client.rb +15 -11
  62. data/lib/brainzlab/pulse/instrumentation.rb +61 -57
  63. data/lib/brainzlab/pulse/propagation.rb +28 -28
  64. data/lib/brainzlab/pulse/provisioner.rb +12 -12
  65. data/lib/brainzlab/pulse/tracer.rb +3 -3
  66. data/lib/brainzlab/pulse.rb +13 -13
  67. data/lib/brainzlab/rails/log_formatter.rb +127 -121
  68. data/lib/brainzlab/rails/log_subscriber.rb +70 -76
  69. data/lib/brainzlab/rails/railtie.rb +66 -89
  70. data/lib/brainzlab/recall/buffer.rb +1 -1
  71. data/lib/brainzlab/recall/client.rb +14 -10
  72. data/lib/brainzlab/recall/logger.rb +16 -18
  73. data/lib/brainzlab/recall/provisioner.rb +16 -16
  74. data/lib/brainzlab/recall.rb +11 -13
  75. data/lib/brainzlab/reflex/breadcrumbs.rb +2 -2
  76. data/lib/brainzlab/reflex/client.rb +14 -10
  77. data/lib/brainzlab/reflex/provisioner.rb +12 -12
  78. data/lib/brainzlab/reflex.rb +29 -29
  79. data/lib/brainzlab/sentinel/client.rb +40 -42
  80. data/lib/brainzlab/sentinel/provisioner.rb +1 -1
  81. data/lib/brainzlab/sentinel.rb +5 -5
  82. data/lib/brainzlab/signal/client.rb +12 -14
  83. data/lib/brainzlab/signal/provisioner.rb +12 -12
  84. data/lib/brainzlab/signal.rb +7 -7
  85. data/lib/brainzlab/synapse/client.rb +42 -44
  86. data/lib/brainzlab/synapse/provisioner.rb +1 -1
  87. data/lib/brainzlab/synapse.rb +6 -6
  88. data/lib/brainzlab/utilities/circuit_breaker.rb +37 -41
  89. data/lib/brainzlab/utilities/health_check.rb +53 -55
  90. data/lib/brainzlab/utilities/log_formatter.rb +38 -40
  91. data/lib/brainzlab/utilities/rate_limiter.rb +5 -5
  92. data/lib/brainzlab/utilities.rb +4 -4
  93. data/lib/brainzlab/vault/cache.rb +1 -1
  94. data/lib/brainzlab/vault/client.rb +39 -41
  95. data/lib/brainzlab/vault/provisioner.rb +1 -1
  96. data/lib/brainzlab/vault.rb +19 -25
  97. data/lib/brainzlab/version.rb +1 -1
  98. data/lib/brainzlab/vision/client.rb +20 -20
  99. data/lib/brainzlab/vision/provisioner.rb +21 -21
  100. data/lib/brainzlab/vision.rb +17 -19
  101. data/lib/brainzlab-sdk.rb +1 -1
  102. data/lib/brainzlab.rb +22 -24
  103. data/lib/generators/brainzlab/install/install_generator.rb +29 -27
  104. metadata +11 -1
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "net/http"
4
- require "json"
5
- require "uri"
3
+ require 'net/http'
4
+ require 'json'
5
+ require 'uri'
6
6
 
7
7
  module BrainzLab
8
8
  module Flux
@@ -12,23 +12,23 @@ module BrainzLab
12
12
  end
13
13
 
14
14
  def send_event(event)
15
- post("/api/v1/events", event)
15
+ post('/api/v1/events', event)
16
16
  end
17
17
 
18
18
  def send_events(events)
19
- post("/api/v1/events/batch", { events: events })
19
+ post('/api/v1/events/batch', { events: events })
20
20
  end
21
21
 
22
22
  def send_metric(metric)
23
- post("/api/v1/metrics", metric)
23
+ post('/api/v1/metrics', metric)
24
24
  end
25
25
 
26
26
  def send_metrics(metrics)
27
- post("/api/v1/metrics/batch", { metrics: metrics })
27
+ post('/api/v1/metrics/batch', { metrics: metrics })
28
28
  end
29
29
 
30
30
  def send_batch(events:, metrics:)
31
- post("/api/v1/flux/batch", { events: events, metrics: metrics })
31
+ post('/api/v1/flux/batch', { events: events, metrics: metrics })
32
32
  end
33
33
 
34
34
  private
@@ -36,24 +36,22 @@ module BrainzLab
36
36
  def post(path, body)
37
37
  uri = URI.parse("#{base_url}#{path}")
38
38
  http = Net::HTTP.new(uri.host, uri.port)
39
- http.use_ssl = uri.scheme == "https"
39
+ http.use_ssl = uri.scheme == 'https'
40
40
  http.open_timeout = 5
41
41
  http.read_timeout = 10
42
42
 
43
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}"
44
+ request['Content-Type'] = 'application/json'
45
+ request['Authorization'] = "Bearer #{api_key}"
46
+ request['User-Agent'] = "brainzlab-sdk/#{BrainzLab::VERSION}"
47
47
  request.body = body.to_json
48
48
 
49
49
  response = http.request(request)
50
50
 
51
- unless response.is_a?(Net::HTTPSuccess)
52
- BrainzLab.debug("[Flux] Request failed: #{response.code} - #{response.body}")
53
- end
51
+ BrainzLab.debug("[Flux] Request failed: #{response.code} - #{response.body}") unless response.is_a?(Net::HTTPSuccess)
54
52
 
55
53
  response
56
- rescue => e
54
+ rescue StandardError => e
57
55
  BrainzLab.debug("[Flux] Request error: #{e.message}")
58
56
  nil
59
57
  end
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "net/http"
4
- require "json"
5
- require "uri"
3
+ require 'net/http'
4
+ require 'json'
5
+ require 'uri'
6
6
 
7
7
  module BrainzLab
8
8
  module Flux
@@ -17,7 +17,7 @@ module BrainzLab
17
17
  return unless @config.flux_url && !@config.flux_url.to_s.empty?
18
18
  return unless @config.secret_key && !@config.secret_key.to_s.empty?
19
19
 
20
- BrainzLab.debug_log("[Flux] Auto-provisioning project...")
20
+ BrainzLab.debug_log('[Flux] Auto-provisioning project...')
21
21
  provision_project
22
22
  end
23
23
 
@@ -26,16 +26,16 @@ module BrainzLab
26
26
  def provision_project
27
27
  uri = URI.parse("#{@config.flux_url}/api/v1/projects/provision")
28
28
  http = Net::HTTP.new(uri.host, uri.port)
29
- http.use_ssl = uri.scheme == "https"
29
+ http.use_ssl = uri.scheme == 'https'
30
30
  http.open_timeout = 10
31
31
  http.read_timeout = 30
32
32
 
33
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}"
34
+ request['Content-Type'] = 'application/json'
35
+ request['Authorization'] = "Bearer #{@config.secret_key}"
36
+ request['User-Agent'] = "brainzlab-sdk/#{BrainzLab::VERSION}"
37
37
  request.body = {
38
- name: @config.service || "default",
38
+ name: @config.service || 'default',
39
39
  environment: @config.environment
40
40
  }.to_json
41
41
 
@@ -43,13 +43,13 @@ module BrainzLab
43
43
 
44
44
  if response.is_a?(Net::HTTPSuccess)
45
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")
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
49
  else
50
50
  BrainzLab.debug_log("[Flux] Provisioning failed: #{response.code} - #{response.body}")
51
51
  end
52
- rescue => e
52
+ rescue StandardError => e
53
53
  BrainzLab.debug_log("[Flux] Provisioning error: #{e.message}")
54
54
  end
55
55
  end
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "flux/client"
4
- require_relative "flux/buffer"
5
- require_relative "flux/provisioner"
3
+ require_relative 'flux/client'
4
+ require_relative 'flux/buffer'
5
+ require_relative 'flux/provisioner'
6
6
 
7
7
  module BrainzLab
8
8
  module Flux
@@ -49,7 +49,7 @@ module BrainzLab
49
49
  return unless BrainzLab.configuration.flux_valid?
50
50
 
51
51
  metric = {
52
- type: "gauge",
52
+ type: 'gauge',
53
53
  name: name,
54
54
  value: value,
55
55
  tags: tags,
@@ -67,7 +67,7 @@ module BrainzLab
67
67
  return unless BrainzLab.configuration.flux_valid?
68
68
 
69
69
  metric = {
70
- type: "counter",
70
+ type: 'counter',
71
71
  name: name,
72
72
  value: value,
73
73
  tags: tags,
@@ -90,7 +90,7 @@ module BrainzLab
90
90
  return unless BrainzLab.configuration.flux_valid?
91
91
 
92
92
  metric = {
93
- type: "distribution",
93
+ type: 'distribution',
94
94
  name: name,
95
95
  value: value,
96
96
  tags: tags,
@@ -108,7 +108,7 @@ module BrainzLab
108
108
  return unless BrainzLab.configuration.flux_valid?
109
109
 
110
110
  metric = {
111
- type: "set",
111
+ type: 'set',
112
112
  name: name,
113
113
  value: value.to_s,
114
114
  tags: tags,
@@ -127,7 +127,7 @@ module BrainzLab
127
127
  yield
128
128
  ensure
129
129
  duration_ms = (Process.clock_gettime(Process::CLOCK_MONOTONIC) - start) * 1000
130
- distribution(name, duration_ms, tags: tags.merge(unit: "ms"))
130
+ distribution(name, duration_ms, tags: tags.merge(unit: 'ms'))
131
131
  end
132
132
  end
133
133
 
@@ -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