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.
Files changed (121) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +68 -0
  3. data/LICENSE +11 -0
  4. data/README.md +571 -0
  5. data/lib/brainzlab/beacon/client.rb +227 -0
  6. data/lib/brainzlab/beacon/provisioner.rb +44 -0
  7. data/lib/brainzlab/beacon.rb +215 -0
  8. data/lib/brainzlab/configuration.rb +676 -0
  9. data/lib/brainzlab/context.rb +90 -0
  10. data/lib/brainzlab/cortex/cache.rb +59 -0
  11. data/lib/brainzlab/cortex/client.rb +159 -0
  12. data/lib/brainzlab/cortex/provisioner.rb +49 -0
  13. data/lib/brainzlab/cortex.rb +223 -0
  14. data/lib/brainzlab/debug.rb +305 -0
  15. data/lib/brainzlab/dendrite/client.rb +250 -0
  16. data/lib/brainzlab/dendrite/provisioner.rb +44 -0
  17. data/lib/brainzlab/dendrite.rb +195 -0
  18. data/lib/brainzlab/development/logger.rb +150 -0
  19. data/lib/brainzlab/development/store.rb +121 -0
  20. data/lib/brainzlab/development.rb +72 -0
  21. data/lib/brainzlab/devtools/assets/devtools.css +1329 -0
  22. data/lib/brainzlab/devtools/assets/devtools.js +396 -0
  23. data/lib/brainzlab/devtools/assets/logo.svg +6 -0
  24. data/lib/brainzlab/devtools/assets/templates/debug_panel.html.erb +511 -0
  25. data/lib/brainzlab/devtools/assets/templates/error_page.html.erb +1086 -0
  26. data/lib/brainzlab/devtools/data/collector.rb +248 -0
  27. data/lib/brainzlab/devtools/middleware/asset_server.rb +63 -0
  28. data/lib/brainzlab/devtools/middleware/database_handler.rb +177 -0
  29. data/lib/brainzlab/devtools/middleware/debug_panel.rb +126 -0
  30. data/lib/brainzlab/devtools/middleware/error_page.rb +377 -0
  31. data/lib/brainzlab/devtools/renderers/debug_panel_renderer.rb +159 -0
  32. data/lib/brainzlab/devtools/renderers/error_page_renderer.rb +98 -0
  33. data/lib/brainzlab/devtools.rb +75 -0
  34. data/lib/brainzlab/errors.rb +490 -0
  35. data/lib/brainzlab/flux/buffer.rb +96 -0
  36. data/lib/brainzlab/flux/client.rb +68 -0
  37. data/lib/brainzlab/flux/provisioner.rb +124 -0
  38. data/lib/brainzlab/flux.rb +184 -0
  39. data/lib/brainzlab/instrumentation/action_cable.rb +351 -0
  40. data/lib/brainzlab/instrumentation/action_controller.rb +649 -0
  41. data/lib/brainzlab/instrumentation/action_dispatch.rb +259 -0
  42. data/lib/brainzlab/instrumentation/action_mailbox.rb +197 -0
  43. data/lib/brainzlab/instrumentation/action_mailer.rb +182 -0
  44. data/lib/brainzlab/instrumentation/action_view.rb +380 -0
  45. data/lib/brainzlab/instrumentation/active_job.rb +569 -0
  46. data/lib/brainzlab/instrumentation/active_record.rb +559 -0
  47. data/lib/brainzlab/instrumentation/active_storage.rb +541 -0
  48. data/lib/brainzlab/instrumentation/active_support_cache.rb +730 -0
  49. data/lib/brainzlab/instrumentation/aws.rb +183 -0
  50. data/lib/brainzlab/instrumentation/dalli.rb +108 -0
  51. data/lib/brainzlab/instrumentation/delayed_job.rb +234 -0
  52. data/lib/brainzlab/instrumentation/elasticsearch.rb +209 -0
  53. data/lib/brainzlab/instrumentation/excon.rb +152 -0
  54. data/lib/brainzlab/instrumentation/faraday.rb +181 -0
  55. data/lib/brainzlab/instrumentation/good_job.rb +102 -0
  56. data/lib/brainzlab/instrumentation/grape.rb +293 -0
  57. data/lib/brainzlab/instrumentation/graphql.rb +252 -0
  58. data/lib/brainzlab/instrumentation/httparty.rb +193 -0
  59. data/lib/brainzlab/instrumentation/mongodb.rb +187 -0
  60. data/lib/brainzlab/instrumentation/net_http.rb +114 -0
  61. data/lib/brainzlab/instrumentation/rails_deprecation.rb +139 -0
  62. data/lib/brainzlab/instrumentation/railties.rb +134 -0
  63. data/lib/brainzlab/instrumentation/redis.rb +324 -0
  64. data/lib/brainzlab/instrumentation/resque.rb +114 -0
  65. data/lib/brainzlab/instrumentation/sidekiq.rb +265 -0
  66. data/lib/brainzlab/instrumentation/solid_queue.rb +194 -0
  67. data/lib/brainzlab/instrumentation/stripe.rb +163 -0
  68. data/lib/brainzlab/instrumentation/typhoeus.rb +106 -0
  69. data/lib/brainzlab/instrumentation.rb +360 -0
  70. data/lib/brainzlab/nerve/client.rb +235 -0
  71. data/lib/brainzlab/nerve/provisioner.rb +44 -0
  72. data/lib/brainzlab/nerve.rb +219 -0
  73. data/lib/brainzlab/pulse/client.rb +203 -0
  74. data/lib/brainzlab/pulse/instrumentation.rb +401 -0
  75. data/lib/brainzlab/pulse/propagation.rb +241 -0
  76. data/lib/brainzlab/pulse/provisioner.rb +114 -0
  77. data/lib/brainzlab/pulse/tracer.rb +111 -0
  78. data/lib/brainzlab/pulse.rb +294 -0
  79. data/lib/brainzlab/rails/log_formatter.rb +807 -0
  80. data/lib/brainzlab/rails/log_subscriber.rb +334 -0
  81. data/lib/brainzlab/rails/railtie.rb +606 -0
  82. data/lib/brainzlab/recall/buffer.rb +66 -0
  83. data/lib/brainzlab/recall/client.rb +158 -0
  84. data/lib/brainzlab/recall/logger.rb +116 -0
  85. data/lib/brainzlab/recall/provisioner.rb +130 -0
  86. data/lib/brainzlab/recall.rb +175 -0
  87. data/lib/brainzlab/reflex/breadcrumbs.rb +55 -0
  88. data/lib/brainzlab/reflex/client.rb +150 -0
  89. data/lib/brainzlab/reflex/provisioner.rb +116 -0
  90. data/lib/brainzlab/reflex.rb +421 -0
  91. data/lib/brainzlab/sentinel/client.rb +236 -0
  92. data/lib/brainzlab/sentinel/provisioner.rb +44 -0
  93. data/lib/brainzlab/sentinel.rb +165 -0
  94. data/lib/brainzlab/signal/client.rb +60 -0
  95. data/lib/brainzlab/signal/provisioner.rb +115 -0
  96. data/lib/brainzlab/signal.rb +136 -0
  97. data/lib/brainzlab/synapse/client.rb +308 -0
  98. data/lib/brainzlab/synapse/provisioner.rb +44 -0
  99. data/lib/brainzlab/synapse.rb +270 -0
  100. data/lib/brainzlab/testing/event_store.rb +377 -0
  101. data/lib/brainzlab/testing/helpers.rb +650 -0
  102. data/lib/brainzlab/testing/matchers.rb +391 -0
  103. data/lib/brainzlab/testing.rb +327 -0
  104. data/lib/brainzlab/utilities/circuit_breaker.rb +290 -0
  105. data/lib/brainzlab/utilities/health_check.rb +294 -0
  106. data/lib/brainzlab/utilities/log_formatter.rb +254 -0
  107. data/lib/brainzlab/utilities/rate_limiter.rb +230 -0
  108. data/lib/brainzlab/utilities.rb +17 -0
  109. data/lib/brainzlab/vault/cache.rb +80 -0
  110. data/lib/brainzlab/vault/client.rb +216 -0
  111. data/lib/brainzlab/vault/provisioner.rb +49 -0
  112. data/lib/brainzlab/vault.rb +262 -0
  113. data/lib/brainzlab/version.rb +5 -0
  114. data/lib/brainzlab/vision/client.rb +175 -0
  115. data/lib/brainzlab/vision/provisioner.rb +136 -0
  116. data/lib/brainzlab/vision.rb +155 -0
  117. data/lib/brainzlab-sdk.rb +3 -0
  118. data/lib/brainzlab.rb +306 -0
  119. data/lib/generators/brainzlab/install/install_generator.rb +63 -0
  120. data/lib/generators/brainzlab/install/templates/brainzlab.rb.tt +77 -0
  121. metadata +251 -0
@@ -0,0 +1,294 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BrainzLab
4
+ module Utilities
5
+ # Health check utility for application health endpoints
6
+ # Provides checks for database, cache, queues, and external services
7
+ #
8
+ # @example Basic usage in Rails routes
9
+ # # config/routes.rb
10
+ # mount BrainzLab::Utilities::HealthCheck::Engine => "/health"
11
+ #
12
+ # @example Manual usage
13
+ # result = BrainzLab::Utilities::HealthCheck.run
14
+ # result[:status] # => "healthy" or "unhealthy"
15
+ # result[:checks] # => { database: { status: "ok", latency_ms: 5 }, ... }
16
+ #
17
+ class HealthCheck
18
+ CHECKS = %i[database redis cache queue memory disk].freeze
19
+
20
+ class << self
21
+ # Run all configured health checks
22
+ def run(checks: nil)
23
+ checks_to_run = checks || CHECKS
24
+ results = {}
25
+ overall_healthy = true
26
+
27
+ checks_to_run.each do |check|
28
+ result = send("check_#{check}")
29
+ results[check] = result
30
+ overall_healthy = false if result[:status] != 'ok'
31
+ rescue StandardError => e
32
+ results[check] = { status: 'error', message: e.message }
33
+ overall_healthy = false
34
+ end
35
+
36
+ {
37
+ status: overall_healthy ? 'healthy' : 'unhealthy',
38
+ timestamp: Time.now.utc.iso8601,
39
+ checks: results
40
+ }
41
+ end
42
+
43
+ # Quick check - just returns status
44
+ def healthy?
45
+ result = run
46
+ result[:status] == 'healthy'
47
+ end
48
+
49
+ # Database connectivity check
50
+ def check_database
51
+ return { status: 'skip', message: 'ActiveRecord not loaded' } unless defined?(ActiveRecord::Base)
52
+
53
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
54
+ ActiveRecord::Base.connection.execute('SELECT 1')
55
+ latency = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start) * 1000).round(2)
56
+
57
+ { status: 'ok', latency_ms: latency }
58
+ rescue StandardError => e
59
+ { status: 'error', message: e.message }
60
+ end
61
+
62
+ # Redis connectivity check
63
+ def check_redis
64
+ return { status: 'skip', message: 'Redis not configured' } unless defined?(Redis)
65
+
66
+ redis = find_redis_connection
67
+ return { status: 'skip', message: 'No Redis connection found' } unless redis
68
+
69
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
70
+ redis.ping
71
+ latency = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start) * 1000).round(2)
72
+
73
+ { status: 'ok', latency_ms: latency }
74
+ rescue StandardError => e
75
+ { status: 'error', message: e.message }
76
+ end
77
+
78
+ # Rails cache check
79
+ def check_cache
80
+ return { status: 'skip', message: 'Rails not loaded' } unless defined?(Rails)
81
+
82
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
83
+ key = "brainzlab_health_check_#{SecureRandom.hex(4)}"
84
+ Rails.cache.write(key, 'ok', expires_in: 10.seconds)
85
+ value = Rails.cache.read(key)
86
+ Rails.cache.delete(key)
87
+ latency = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start) * 1000).round(2)
88
+
89
+ if value == 'ok'
90
+ { status: 'ok', latency_ms: latency }
91
+ else
92
+ { status: 'error', message: 'Cache read/write failed' }
93
+ end
94
+ rescue StandardError => e
95
+ { status: 'error', message: e.message }
96
+ end
97
+
98
+ # Queue system check
99
+ def check_queue
100
+ if defined?(SolidQueue)
101
+ check_solid_queue
102
+ elsif defined?(Sidekiq)
103
+ check_sidekiq
104
+ elsif defined?(GoodJob)
105
+ check_good_job
106
+ else
107
+ { status: 'skip', message: 'No queue system detected' }
108
+ end
109
+ end
110
+
111
+ # Memory usage check
112
+ def check_memory
113
+ mem_info = memory_usage
114
+
115
+ status = if mem_info[:percentage] > 90
116
+ 'warning'
117
+ elsif mem_info[:percentage] > 95
118
+ 'error'
119
+ else
120
+ 'ok'
121
+ end
122
+
123
+ {
124
+ status: status,
125
+ used_mb: mem_info[:used_mb],
126
+ percentage: mem_info[:percentage]
127
+ }
128
+ rescue StandardError => e
129
+ { status: 'error', message: e.message }
130
+ end
131
+
132
+ # Disk space check
133
+ def check_disk
134
+ disk_info = disk_usage
135
+
136
+ status = if disk_info[:percentage] > 90
137
+ 'warning'
138
+ elsif disk_info[:percentage] > 95
139
+ 'error'
140
+ else
141
+ 'ok'
142
+ end
143
+
144
+ {
145
+ status: status,
146
+ used_gb: disk_info[:used_gb],
147
+ available_gb: disk_info[:available_gb],
148
+ percentage: disk_info[:percentage]
149
+ }
150
+ rescue StandardError => e
151
+ { status: 'error', message: e.message }
152
+ end
153
+
154
+ # Register a custom health check
155
+ def register(name, &block)
156
+ custom_checks[name.to_sym] = block
157
+ end
158
+
159
+ def custom_checks
160
+ @custom_checks ||= {}
161
+ end
162
+
163
+ private
164
+
165
+ def find_redis_connection
166
+ # Try common Redis connection sources
167
+ if defined?(Redis.current) && Redis.current
168
+ Redis.current
169
+ elsif defined?(Sidekiq) && Sidekiq.respond_to?(:redis)
170
+ Sidekiq.redis { |conn| return conn }
171
+ elsif defined?(Rails) && Rails.application.config.respond_to?(:redis)
172
+ Rails.application.config.redis
173
+ end
174
+ rescue StandardError
175
+ nil
176
+ end
177
+
178
+ def check_solid_queue
179
+ return { status: 'skip', message: 'SolidQueue not loaded' } unless defined?(SolidQueue)
180
+
181
+ # Check if processes are running
182
+ if defined?(SolidQueue::Process)
183
+ process_count = SolidQueue::Process.where('last_heartbeat_at > ?', 5.minutes.ago).count
184
+ {
185
+ status: process_count.positive? ? 'ok' : 'warning',
186
+ processes: process_count
187
+ }
188
+ else
189
+ { status: 'ok', message: 'SolidQueue configured' }
190
+ end
191
+ rescue StandardError => e
192
+ { status: 'error', message: e.message }
193
+ end
194
+
195
+ def check_sidekiq
196
+ return { status: 'skip', message: 'Sidekiq not loaded' } unless defined?(Sidekiq)
197
+
198
+ stats = Sidekiq::Stats.new
199
+ {
200
+ status: 'ok',
201
+ processed: stats.processed,
202
+ failed: stats.failed,
203
+ queues: stats.queues,
204
+ workers: stats.workers_size
205
+ }
206
+ rescue StandardError => e
207
+ { status: 'error', message: e.message }
208
+ end
209
+
210
+ def check_good_job
211
+ return { status: 'skip', message: 'GoodJob not loaded' } unless defined?(GoodJob)
212
+
213
+ {
214
+ status: 'ok',
215
+ pending: GoodJob::Job.where(performed_at: nil).count,
216
+ running: GoodJob::Job.running.count
217
+ }
218
+ rescue StandardError => e
219
+ { status: 'error', message: e.message }
220
+ end
221
+
222
+ def memory_usage
223
+ # Use /proc/self/status on Linux, ps on macOS
224
+ if File.exist?('/proc/self/status')
225
+ status = File.read('/proc/self/status')
226
+ vm_rss = status.match(/VmRSS:\s+(\d+)\s+kB/)&.captures&.first.to_i
227
+ used_mb = (vm_rss / 1024.0).round(2)
228
+ else
229
+ # macOS fallback
230
+ pid = Process.pid
231
+ output = `ps -o rss= -p #{pid}`.strip
232
+ used_mb = (output.to_i / 1024.0).round(2)
233
+ end
234
+
235
+ # Estimate percentage (based on typical container memory)
236
+ max_mb = ENV.fetch('MEMORY_LIMIT_MB', 512).to_i
237
+ percentage = ((used_mb / max_mb) * 100).round(2)
238
+
239
+ { used_mb: used_mb, percentage: percentage }
240
+ end
241
+
242
+ def disk_usage
243
+ output = `df -k /`.split("\n").last.split
244
+ total = output[1].to_i / 1024 / 1024.0
245
+ used = output[2].to_i / 1024 / 1024.0
246
+ available = output[3].to_i / 1024 / 1024.0
247
+ percentage = ((used / total) * 100).round(2)
248
+
249
+ {
250
+ used_gb: used.round(2),
251
+ available_gb: available.round(2),
252
+ percentage: percentage
253
+ }
254
+ end
255
+ end
256
+
257
+ # Rails Engine for mounting health endpoints
258
+ if defined?(::Rails::Engine)
259
+ class Engine < ::Rails::Engine
260
+ isolate_namespace BrainzLab::Utilities::HealthCheck
261
+
262
+ routes.draw do
263
+ get '/', to: 'health#show'
264
+ get '/live', to: 'health#live'
265
+ get '/ready', to: 'health#ready'
266
+ end
267
+ end
268
+ end
269
+
270
+ # Controller for health endpoints
271
+ if defined?(ActionController::API)
272
+ class HealthController < ActionController::API
273
+ def show
274
+ result = HealthCheck.run
275
+ status = result[:status] == 'healthy' ? :ok : :service_unavailable
276
+ render json: result, status: status
277
+ end
278
+
279
+ def live
280
+ # Liveness probe - just check if the app is running
281
+ render json: { status: 'ok', timestamp: Time.now.utc.iso8601 }
282
+ end
283
+
284
+ def ready
285
+ # Readiness probe - check critical dependencies
286
+ result = HealthCheck.run(checks: %i[database redis])
287
+ status = result[:status] == 'healthy' ? :ok : :service_unavailable
288
+ render json: result, status: status
289
+ end
290
+ end
291
+ end
292
+ end
293
+ end
294
+ end
@@ -0,0 +1,254 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BrainzLab
4
+ module Utilities
5
+ # Beautiful log formatter for Rails development
6
+ # Provides colorized, structured output with request timing
7
+ #
8
+ # @example Usage in Rails
9
+ # # config/environments/development.rb
10
+ # config.log_formatter = BrainzLab::Utilities::LogFormatter.new
11
+ #
12
+ # # Or use the Rails integration
13
+ # BrainzLab::Utilities::LogFormatter.install!
14
+ #
15
+ class LogFormatter < ::Logger::Formatter
16
+ COLORS = {
17
+ debug: "\e[36m", # Cyan
18
+ info: "\e[32m", # Green
19
+ warn: "\e[33m", # Yellow
20
+ error: "\e[31m", # Red
21
+ fatal: "\e[35m", # Magenta
22
+ reset: "\e[0m",
23
+ dim: "\e[2m",
24
+ bold: "\e[1m",
25
+ blue: "\e[34m",
26
+ gray: "\e[90m"
27
+ }.freeze
28
+
29
+ SEVERITY_ICONS = {
30
+ 'DEBUG' => '🔍',
31
+ 'INFO' => 'ℹ️ ',
32
+ 'WARN' => '⚠️ ',
33
+ 'ERROR' => '❌',
34
+ 'FATAL' => '💀'
35
+ }.freeze
36
+
37
+ HTTP_METHODS = {
38
+ 'GET' => "\e[32m", # Green
39
+ 'POST' => "\e[33m", # Yellow
40
+ 'PUT' => "\e[34m", # Blue
41
+ 'PATCH' => "\e[34m", # Blue
42
+ 'DELETE' => "\e[31m", # Red
43
+ 'HEAD' => "\e[36m", # Cyan
44
+ 'OPTIONS' => "\e[36m" # Cyan
45
+ }.freeze
46
+
47
+ def initialize(colorize: nil, show_timestamp: true, show_severity: true, compact: false)
48
+ super()
49
+ @colorize = colorize.nil? ? $stdout.tty? : colorize
50
+ @show_timestamp = show_timestamp
51
+ @show_severity = show_severity
52
+ @compact = compact
53
+ end
54
+
55
+ def call(severity, timestamp, progname, msg)
56
+ return '' if msg.nil? || msg.to_s.strip.empty?
57
+
58
+ message = format_message(msg)
59
+ return '' if skip_message?(message)
60
+
61
+ formatted = build_output(severity, timestamp, progname, message)
62
+ "#{formatted}\n"
63
+ end
64
+
65
+ # Install as Rails logger formatter
66
+ def self.install!
67
+ return unless defined?(Rails)
68
+
69
+ Rails.application.configure do
70
+ config.log_formatter = BrainzLab::Utilities::LogFormatter.new(
71
+ colorize: BrainzLab.configuration.log_formatter_colors,
72
+ compact: BrainzLab.configuration.log_formatter_compact_assets
73
+ )
74
+ end
75
+
76
+ # Also hook into ActiveSupport::TaggedLogging if present
77
+ return unless defined?(ActiveSupport::TaggedLogging) && Rails.logger.respond_to?(:formatter=)
78
+
79
+ Rails.logger.formatter = new
80
+ end
81
+
82
+ private
83
+
84
+ def format_message(msg)
85
+ case msg
86
+ when String
87
+ msg
88
+ when Exception
89
+ "#{msg.class}: #{msg.message}\n#{msg.backtrace&.first(10)&.join("\n")}"
90
+ else
91
+ msg.inspect
92
+ end
93
+ end
94
+
95
+ def skip_message?(message)
96
+ return false unless BrainzLab.configuration.log_formatter_hide_assets
97
+
98
+ # Skip asset pipeline noise
99
+ message.include?('/assets/') ||
100
+ message.include?('Asset pipeline') ||
101
+ message.match?(%r{Started GET "/assets/})
102
+ end
103
+
104
+ def build_output(severity, timestamp, _progname, message)
105
+ parts = []
106
+
107
+ if @show_timestamp
108
+ ts = colorize(timestamp.strftime('%H:%M:%S.%L'), :gray)
109
+ parts << ts
110
+ end
111
+
112
+ if @show_severity
113
+ sev = format_severity(severity)
114
+ parts << sev
115
+ end
116
+
117
+ parts << format_content(message, severity)
118
+
119
+ parts.join(' ')
120
+ end
121
+
122
+ def format_severity(severity)
123
+ icon = SEVERITY_ICONS[severity] || ''
124
+ text = severity.ljust(5)
125
+
126
+ if @colorize
127
+ color = severity_color(severity)
128
+ "#{icon}#{color}#{text}#{COLORS[:reset]}"
129
+ else
130
+ "#{icon}[#{text}]"
131
+ end
132
+ end
133
+
134
+ def severity_color(severity)
135
+ case severity
136
+ when 'DEBUG' then COLORS[:debug]
137
+ when 'INFO' then COLORS[:info]
138
+ when 'WARN' then COLORS[:warn]
139
+ when 'ERROR' then COLORS[:error]
140
+ when 'FATAL' then COLORS[:fatal]
141
+ else COLORS[:reset]
142
+ end
143
+ end
144
+
145
+ def format_content(message, severity)
146
+ # Handle Rails request log patterns
147
+ if (request_match = message.match(/Started (GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS) "([^"]+)"/))
148
+ format_request_started(request_match[1], request_match[2])
149
+ elsif (completed_match = message.match(/Completed (\d+) .+ in (\d+(?:\.\d+)?)ms/))
150
+ format_request_completed(completed_match[1].to_i, completed_match[2].to_f)
151
+ elsif message.include?('Processing by')
152
+ format_processing(message)
153
+ elsif message.include?('Parameters:')
154
+ format_parameters(message)
155
+ elsif message.include?('Rendering') || message.include?('Rendered')
156
+ format_rendering(message)
157
+ elsif %w[ERROR FATAL].include?(severity)
158
+ format_error(message)
159
+ else
160
+ message
161
+ end
162
+ end
163
+
164
+ def format_request_started(method, path)
165
+ method_color = HTTP_METHODS[method] || COLORS[:reset]
166
+
167
+ if @colorize
168
+ "#{COLORS[:bold]}→#{COLORS[:reset]} #{method_color}#{method}#{COLORS[:reset]} #{path}"
169
+ else
170
+ "→ #{method} #{path}"
171
+ end
172
+ end
173
+
174
+ def format_request_completed(status, duration)
175
+ status_color = case status
176
+ when 200..299 then COLORS[:info]
177
+ when 300..399 then COLORS[:blue]
178
+ when 400..499 then COLORS[:warn]
179
+ when 500..599 then COLORS[:error]
180
+ else COLORS[:reset]
181
+ end
182
+
183
+ duration_color = case duration
184
+ when 0..100 then COLORS[:info]
185
+ when 100..500 then COLORS[:warn]
186
+ else COLORS[:error]
187
+ end
188
+
189
+ if @colorize
190
+ "#{COLORS[:bold]}←#{COLORS[:reset]} #{status_color}#{status}#{COLORS[:reset]} #{duration_color}#{duration.round(1)}ms#{COLORS[:reset]}"
191
+ else
192
+ "← #{status} #{duration.round(1)}ms"
193
+ end
194
+ end
195
+
196
+ def format_processing(message)
197
+ if (match = message.match(/Processing by (\w+)#(\w+)/))
198
+ controller, action = match.captures
199
+ if @colorize
200
+ " #{COLORS[:dim]}#{controller}##{action}#{COLORS[:reset]}"
201
+ else
202
+ " #{controller}##{action}"
203
+ end
204
+ else
205
+ " #{message}"
206
+ end
207
+ end
208
+
209
+ def format_parameters(message)
210
+ return message unless BrainzLab.configuration.log_formatter_show_params
211
+
212
+ if @colorize
213
+ " #{COLORS[:dim]}#{message}#{COLORS[:reset]}"
214
+ else
215
+ " #{message}"
216
+ end
217
+ end
218
+
219
+ def format_rendering(message)
220
+ if @compact
221
+ # Compact: just show the template name
222
+ if (match = message.match(/Render(?:ed|ing) ([^\s]+)/))
223
+ template = match[1].split('/').last
224
+ if @colorize
225
+ " #{COLORS[:gray]}#{template}#{COLORS[:reset]}"
226
+ else
227
+ " #{template}"
228
+ end
229
+ else
230
+ ''
231
+ end
232
+ elsif @colorize
233
+ " #{COLORS[:dim]}#{message}#{COLORS[:reset]}"
234
+ else
235
+ " #{message}"
236
+ end
237
+ end
238
+
239
+ def format_error(message)
240
+ if @colorize
241
+ "#{COLORS[:error]}#{message}#{COLORS[:reset]}"
242
+ else
243
+ message
244
+ end
245
+ end
246
+
247
+ def colorize(text, color)
248
+ return text unless @colorize
249
+
250
+ "#{COLORS[color]}#{text}#{COLORS[:reset]}"
251
+ end
252
+ end
253
+ end
254
+ end