brainzlab 0.1.3 → 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.
@@ -0,0 +1,139 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BrainzLab
4
+ module Instrumentation
5
+ class RailsDeprecation
6
+ class << self
7
+ def install!
8
+ return unless defined?(::Rails)
9
+ return if @installed
10
+
11
+ install_deprecation_subscriber!
12
+
13
+ @installed = true
14
+ BrainzLab.debug_log('Rails deprecation instrumentation installed')
15
+ end
16
+
17
+ def installed?
18
+ @installed == true
19
+ end
20
+
21
+ private
22
+
23
+ # ============================================
24
+ # deprecation.rails
25
+ # Fired when a deprecated Rails API is used
26
+ # ============================================
27
+ def install_deprecation_subscriber!
28
+ ActiveSupport::Notifications.subscribe('deprecation.rails') do |*args|
29
+ event = ActiveSupport::Notifications::Event.new(*args)
30
+ handle_deprecation(event)
31
+ end
32
+ end
33
+
34
+ def handle_deprecation(event)
35
+ payload = event.payload
36
+
37
+ message = payload[:message]
38
+ callstack = payload[:callstack]
39
+ gem_name = payload[:gem_name]
40
+ deprecation_horizon = payload[:deprecation_horizon]
41
+
42
+ # Extract relevant caller info
43
+ caller_info = extract_caller_info(callstack)
44
+
45
+ # Record breadcrumb with warning level
46
+ if BrainzLab.configuration.reflex_effectively_enabled?
47
+ BrainzLab::Reflex.add_breadcrumb(
48
+ "Deprecation: #{truncate_message(message)}",
49
+ category: 'rails.deprecation',
50
+ level: :warning,
51
+ data: {
52
+ message: truncate_message(message, 500),
53
+ gem_name: gem_name,
54
+ deprecation_horizon: deprecation_horizon,
55
+ caller: caller_info
56
+ }.compact
57
+ )
58
+ end
59
+
60
+ # Log to Recall for tracking
61
+ if BrainzLab.configuration.recall_effectively_enabled?
62
+ BrainzLab::Recall.warn(
63
+ "Rails deprecation warning",
64
+ message: truncate_message(message, 500),
65
+ gem_name: gem_name,
66
+ deprecation_horizon: deprecation_horizon,
67
+ caller: caller_info,
68
+ callstack: truncate_callstack(callstack)
69
+ )
70
+ end
71
+
72
+ # Track deprecation count in Pulse metrics
73
+ record_deprecation_metric(gem_name, deprecation_horizon)
74
+ rescue StandardError => e
75
+ BrainzLab.debug_log("Rails deprecation instrumentation failed: #{e.message}")
76
+ end
77
+
78
+ # ============================================
79
+ # Helper Methods
80
+ # ============================================
81
+ def extract_caller_info(callstack)
82
+ return nil unless callstack.is_a?(Array) && callstack.any?
83
+
84
+ # Find the first non-Rails, non-gem caller
85
+ app_caller = callstack.find do |line|
86
+ line_str = line.to_s
87
+ !line_str.include?('/gems/') &&
88
+ !line_str.include?('/ruby/') &&
89
+ !line_str.include?('/bundler/')
90
+ end
91
+
92
+ (app_caller || callstack.first).to_s
93
+ end
94
+
95
+ def truncate_callstack(callstack, max_lines = 5)
96
+ return nil unless callstack.is_a?(Array)
97
+
98
+ callstack.first(max_lines).map(&:to_s)
99
+ end
100
+
101
+ def truncate_message(message, max_length = 200)
102
+ return 'unknown' unless message
103
+
104
+ msg_str = message.to_s
105
+ if msg_str.length > max_length
106
+ "#{msg_str[0, max_length - 3]}..."
107
+ else
108
+ msg_str
109
+ end
110
+ end
111
+
112
+ def record_deprecation_metric(gem_name, horizon)
113
+ return unless BrainzLab.configuration.pulse_effectively_enabled?
114
+
115
+ # If Pulse has a counter/metric API, use it here
116
+ # For now, we just add a span to track it
117
+ tracer = BrainzLab::Pulse.tracer
118
+ return unless tracer.current_trace
119
+
120
+ span_data = {
121
+ span_id: SecureRandom.uuid,
122
+ name: 'rails.deprecation',
123
+ kind: 'internal',
124
+ started_at: Time.now,
125
+ ended_at: Time.now,
126
+ duration_ms: 0,
127
+ error: false,
128
+ data: {
129
+ 'deprecation.gem_name' => gem_name,
130
+ 'deprecation.horizon' => horizon
131
+ }.compact
132
+ }
133
+
134
+ tracer.current_spans << span_data
135
+ end
136
+ end
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,134 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BrainzLab
4
+ module Instrumentation
5
+ class Railties
6
+ # Thresholds for slow initializers (in milliseconds)
7
+ SLOW_INITIALIZER_THRESHOLD = 100 # 100ms
8
+ VERY_SLOW_INITIALIZER_THRESHOLD = 500 # 500ms
9
+
10
+ class << self
11
+ def install!
12
+ return unless defined?(::Rails)
13
+ return if @installed
14
+
15
+ install_load_config_initializer_subscriber!
16
+
17
+ @installed = true
18
+ BrainzLab.debug_log('Railties instrumentation installed')
19
+ end
20
+
21
+ def installed?
22
+ @installed == true
23
+ end
24
+
25
+ private
26
+
27
+ # ============================================
28
+ # load_config_initializer.railties
29
+ # Fired when each config initializer is loaded
30
+ # ============================================
31
+ def install_load_config_initializer_subscriber!
32
+ ActiveSupport::Notifications.subscribe('load_config_initializer.railties') do |*args|
33
+ event = ActiveSupport::Notifications::Event.new(*args)
34
+ handle_load_config_initializer(event)
35
+ end
36
+ end
37
+
38
+ def handle_load_config_initializer(event)
39
+ payload = event.payload
40
+ duration = event.duration.round(2)
41
+
42
+ initializer = payload[:initializer]
43
+
44
+ # Extract just the filename for cleaner display
45
+ initializer_name = extract_initializer_name(initializer)
46
+
47
+ # Determine level based on duration
48
+ level = case duration
49
+ when 0...SLOW_INITIALIZER_THRESHOLD then :info
50
+ when SLOW_INITIALIZER_THRESHOLD...VERY_SLOW_INITIALIZER_THRESHOLD then :warning
51
+ else :error
52
+ end
53
+
54
+ # Record breadcrumb (only for slow initializers to reduce noise during boot)
55
+ if duration >= 10 && BrainzLab.configuration.reflex_effectively_enabled?
56
+ BrainzLab::Reflex.add_breadcrumb(
57
+ "Initializer loaded: #{initializer_name} (#{duration}ms)",
58
+ category: 'rails.initializer',
59
+ level: level,
60
+ data: {
61
+ initializer: initializer_name,
62
+ path: initializer,
63
+ duration_ms: duration
64
+ }.compact
65
+ )
66
+ end
67
+
68
+ # Add Pulse span for tracking boot time
69
+ record_initializer_span(event, initializer_name, initializer, duration)
70
+
71
+ # Log slow initializers
72
+ log_slow_initializer(initializer_name, initializer, duration) if duration >= SLOW_INITIALIZER_THRESHOLD
73
+ rescue StandardError => e
74
+ BrainzLab.debug_log("Railties load_config_initializer instrumentation failed: #{e.message}")
75
+ end
76
+
77
+ # ============================================
78
+ # Span Recording
79
+ # ============================================
80
+ def record_initializer_span(event, name, path, duration)
81
+ return unless BrainzLab.configuration.pulse_effectively_enabled?
82
+
83
+ tracer = BrainzLab::Pulse.tracer
84
+ return unless tracer.current_trace
85
+
86
+ span_data = {
87
+ span_id: SecureRandom.uuid,
88
+ name: "rails.initializer.#{name}",
89
+ kind: 'internal',
90
+ started_at: event.time,
91
+ ended_at: event.end,
92
+ duration_ms: duration,
93
+ error: false,
94
+ data: {
95
+ 'rails.initializer.name' => name,
96
+ 'rails.initializer.path' => path
97
+ }.compact
98
+ }
99
+
100
+ tracer.current_spans << span_data
101
+ end
102
+
103
+ # ============================================
104
+ # Logging
105
+ # ============================================
106
+ def log_slow_initializer(name, path, duration)
107
+ return unless BrainzLab.configuration.recall_effectively_enabled?
108
+
109
+ level = duration >= VERY_SLOW_INITIALIZER_THRESHOLD ? :error : :warn
110
+
111
+ BrainzLab::Recall.send(
112
+ level,
113
+ "Slow initializer: #{name} (#{duration}ms)",
114
+ initializer: name,
115
+ path: path,
116
+ duration_ms: duration,
117
+ threshold_exceeded: duration >= VERY_SLOW_INITIALIZER_THRESHOLD ? 'critical' : 'warning'
118
+ )
119
+ end
120
+
121
+ # ============================================
122
+ # Helper Methods
123
+ # ============================================
124
+ def extract_initializer_name(path)
125
+ return 'unknown' unless path
126
+
127
+ # Extract filename without extension from path
128
+ # e.g., "/app/config/initializers/devise.rb" -> "devise"
129
+ File.basename(path.to_s, '.*')
130
+ end
131
+ end
132
+ end
133
+ end
134
+ end
@@ -31,9 +31,17 @@ module BrainzLab
31
31
  # Elasticsearch instrumentation
32
32
  install_elasticsearch! if config.instrument_elasticsearch
33
33
 
34
- # ActionMailer instrumentation
34
+ # Rails MVC instrumentation
35
+ install_action_controller! if config.instrument_action_controller
36
+ install_action_view! if config.instrument_action_view
35
37
  install_action_mailer! if config.instrument_action_mailer
36
38
 
39
+ # ActiveJob instrumentation (covers all job backends)
40
+ install_active_job! if config.instrument_active_job
41
+
42
+ # ActiveSupport::Cache instrumentation
43
+ install_active_support_cache! if config.instrument_active_support_cache
44
+
37
45
  # Delayed::Job instrumentation
38
46
  install_delayed_job! if config.instrument_delayed_job
39
47
 
@@ -55,6 +63,18 @@ module BrainzLab
55
63
  # Cloud & Payment
56
64
  install_aws! if config.instrument_aws
57
65
  install_stripe! if config.instrument_stripe
66
+
67
+ # File storage
68
+ install_active_storage! if config.instrument_active_storage
69
+
70
+ # WebSocket
71
+ install_action_cable! if config.instrument_action_cable
72
+
73
+ # Rails framework events
74
+ install_action_dispatch! if config.instrument_action_dispatch
75
+ install_rails_deprecation! if config.instrument_rails_deprecation
76
+ install_action_mailbox! if config.instrument_action_mailbox
77
+ install_railties! if config.instrument_railties
58
78
  end
59
79
 
60
80
  def install_net_http!
@@ -116,6 +136,20 @@ module BrainzLab
116
136
  ElasticsearchInstrumentation.install!
117
137
  end
118
138
 
139
+ def install_action_controller!
140
+ return unless defined?(::ActionController)
141
+
142
+ require_relative 'instrumentation/action_controller'
143
+ ActionController.install!
144
+ end
145
+
146
+ def install_action_view!
147
+ return unless defined?(::ActionView)
148
+
149
+ require_relative 'instrumentation/action_view'
150
+ ActionView.install!
151
+ end
152
+
119
153
  def install_action_mailer!
120
154
  return unless defined?(::ActionMailer)
121
155
 
@@ -123,6 +157,20 @@ module BrainzLab
123
157
  ActionMailerInstrumentation.install!
124
158
  end
125
159
 
160
+ def install_active_job!
161
+ return unless defined?(::ActiveJob)
162
+
163
+ require_relative 'instrumentation/active_job'
164
+ ActiveJob.install!
165
+ end
166
+
167
+ def install_active_support_cache!
168
+ return unless defined?(::ActiveSupport::Cache)
169
+
170
+ require_relative 'instrumentation/active_support_cache'
171
+ ActiveSupportCache.install!
172
+ end
173
+
126
174
  def install_delayed_job!
127
175
  return unless defined?(::Delayed::Job) || defined?(::Delayed::Backend)
128
176
 
@@ -193,6 +241,48 @@ module BrainzLab
193
241
  StripeInstrumentation.install!
194
242
  end
195
243
 
244
+ def install_active_storage!
245
+ return unless defined?(::ActiveStorage)
246
+
247
+ require_relative 'instrumentation/active_storage'
248
+ ActiveStorage.install!
249
+ end
250
+
251
+ def install_action_cable!
252
+ return unless defined?(::ActionCable)
253
+
254
+ require_relative 'instrumentation/action_cable'
255
+ ActionCable.install!
256
+ end
257
+
258
+ def install_action_dispatch!
259
+ return unless defined?(::ActionDispatch)
260
+
261
+ require_relative 'instrumentation/action_dispatch'
262
+ ActionDispatch.install!
263
+ end
264
+
265
+ def install_rails_deprecation!
266
+ return unless defined?(::Rails)
267
+
268
+ require_relative 'instrumentation/rails_deprecation'
269
+ RailsDeprecation.install!
270
+ end
271
+
272
+ def install_action_mailbox!
273
+ return unless defined?(::ActionMailbox)
274
+
275
+ require_relative 'instrumentation/action_mailbox'
276
+ ActionMailbox.install!
277
+ end
278
+
279
+ def install_railties!
280
+ return unless defined?(::Rails)
281
+
282
+ require_relative 'instrumentation/railties'
283
+ Railties.install!
284
+ end
285
+
196
286
  # Manual installation methods for lazy-loaded libraries
197
287
  def install_http!
198
288
  install_net_http!
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module BrainzLab
4
- VERSION = '0.1.3'
4
+ VERSION = '0.1.4'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: brainzlab
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brainz Lab
@@ -123,8 +123,16 @@ files:
123
123
  - lib/brainzlab/flux/client.rb
124
124
  - lib/brainzlab/flux/provisioner.rb
125
125
  - lib/brainzlab/instrumentation.rb
126
+ - lib/brainzlab/instrumentation/action_cable.rb
127
+ - lib/brainzlab/instrumentation/action_controller.rb
128
+ - lib/brainzlab/instrumentation/action_dispatch.rb
129
+ - lib/brainzlab/instrumentation/action_mailbox.rb
126
130
  - lib/brainzlab/instrumentation/action_mailer.rb
131
+ - lib/brainzlab/instrumentation/action_view.rb
132
+ - lib/brainzlab/instrumentation/active_job.rb
127
133
  - lib/brainzlab/instrumentation/active_record.rb
134
+ - lib/brainzlab/instrumentation/active_storage.rb
135
+ - lib/brainzlab/instrumentation/active_support_cache.rb
128
136
  - lib/brainzlab/instrumentation/aws.rb
129
137
  - lib/brainzlab/instrumentation/dalli.rb
130
138
  - lib/brainzlab/instrumentation/delayed_job.rb
@@ -137,6 +145,8 @@ files:
137
145
  - lib/brainzlab/instrumentation/httparty.rb
138
146
  - lib/brainzlab/instrumentation/mongodb.rb
139
147
  - lib/brainzlab/instrumentation/net_http.rb
148
+ - lib/brainzlab/instrumentation/rails_deprecation.rb
149
+ - lib/brainzlab/instrumentation/railties.rb
140
150
  - lib/brainzlab/instrumentation/redis.rb
141
151
  - lib/brainzlab/instrumentation/resque.rb
142
152
  - lib/brainzlab/instrumentation/sidekiq.rb