brainzlab 0.1.0

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 (43) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +52 -0
  3. data/LICENSE +26 -0
  4. data/README.md +311 -0
  5. data/lib/brainzlab/configuration.rb +215 -0
  6. data/lib/brainzlab/context.rb +91 -0
  7. data/lib/brainzlab/instrumentation/action_mailer.rb +181 -0
  8. data/lib/brainzlab/instrumentation/active_record.rb +111 -0
  9. data/lib/brainzlab/instrumentation/delayed_job.rb +236 -0
  10. data/lib/brainzlab/instrumentation/elasticsearch.rb +210 -0
  11. data/lib/brainzlab/instrumentation/faraday.rb +182 -0
  12. data/lib/brainzlab/instrumentation/grape.rb +293 -0
  13. data/lib/brainzlab/instrumentation/graphql.rb +251 -0
  14. data/lib/brainzlab/instrumentation/httparty.rb +194 -0
  15. data/lib/brainzlab/instrumentation/mongodb.rb +187 -0
  16. data/lib/brainzlab/instrumentation/net_http.rb +109 -0
  17. data/lib/brainzlab/instrumentation/redis.rb +331 -0
  18. data/lib/brainzlab/instrumentation/sidekiq.rb +264 -0
  19. data/lib/brainzlab/instrumentation.rb +132 -0
  20. data/lib/brainzlab/pulse/client.rb +132 -0
  21. data/lib/brainzlab/pulse/instrumentation.rb +364 -0
  22. data/lib/brainzlab/pulse/propagation.rb +241 -0
  23. data/lib/brainzlab/pulse/provisioner.rb +114 -0
  24. data/lib/brainzlab/pulse/tracer.rb +111 -0
  25. data/lib/brainzlab/pulse.rb +224 -0
  26. data/lib/brainzlab/rails/log_formatter.rb +801 -0
  27. data/lib/brainzlab/rails/log_subscriber.rb +341 -0
  28. data/lib/brainzlab/rails/railtie.rb +590 -0
  29. data/lib/brainzlab/recall/buffer.rb +64 -0
  30. data/lib/brainzlab/recall/client.rb +86 -0
  31. data/lib/brainzlab/recall/logger.rb +118 -0
  32. data/lib/brainzlab/recall/provisioner.rb +113 -0
  33. data/lib/brainzlab/recall.rb +155 -0
  34. data/lib/brainzlab/reflex/breadcrumbs.rb +55 -0
  35. data/lib/brainzlab/reflex/client.rb +85 -0
  36. data/lib/brainzlab/reflex/provisioner.rb +116 -0
  37. data/lib/brainzlab/reflex.rb +374 -0
  38. data/lib/brainzlab/version.rb +5 -0
  39. data/lib/brainzlab-sdk.rb +3 -0
  40. data/lib/brainzlab.rb +140 -0
  41. data/lib/generators/brainzlab/install/install_generator.rb +61 -0
  42. data/lib/generators/brainzlab/install/templates/brainzlab.rb.tt +77 -0
  43. metadata +159 -0
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/http"
4
+ require "uri"
5
+ require "json"
6
+ require "fileutils"
7
+
8
+ module BrainzLab
9
+ module Reflex
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
+ return false unless @config.reflex_auto_provision
41
+ return false unless @config.app_name.to_s.strip.length > 0
42
+ # Only skip if reflex_api_key is already set (not secret_key, which may be for Recall)
43
+ return false if @config.reflex_api_key.to_s.strip.length > 0
44
+ return false unless @config.reflex_master_key.to_s.strip.length > 0
45
+
46
+ true
47
+ end
48
+
49
+ def provision_project
50
+ uri = URI.parse("#{@config.reflex_url}/api/v1/projects/provision")
51
+ request = Net::HTTP::Post.new(uri)
52
+ request["Content-Type"] = "application/json"
53
+ request["X-Master-Key"] = @config.reflex_master_key
54
+ request["User-Agent"] = "brainzlab-sdk-ruby/#{BrainzLab::VERSION}"
55
+ request.body = JSON.generate({ name: @config.app_name })
56
+
57
+ response = execute(uri, request)
58
+ return nil unless response.is_a?(Net::HTTPSuccess)
59
+
60
+ JSON.parse(response.body, symbolize_names: true)
61
+ rescue StandardError => e
62
+ log_error("Failed to provision Reflex project: #{e.message}")
63
+ nil
64
+ end
65
+
66
+ def load_cached_credentials
67
+ path = cache_file_path
68
+ return nil unless File.exist?(path)
69
+
70
+ data = JSON.parse(File.read(path), symbolize_names: true)
71
+
72
+ # Validate cached data has required keys
73
+ return nil unless data[:api_key]
74
+
75
+ data
76
+ rescue StandardError => e
77
+ log_error("Failed to load cached Reflex credentials: #{e.message}")
78
+ nil
79
+ end
80
+
81
+ def cache_credentials(project)
82
+ FileUtils.mkdir_p(CACHE_DIR)
83
+ File.write(cache_file_path, JSON.generate(project))
84
+ rescue StandardError => e
85
+ log_error("Failed to cache Reflex credentials: #{e.message}")
86
+ end
87
+
88
+ def cache_file_path
89
+ File.join(CACHE_DIR, "#{@config.app_name}.reflex.json")
90
+ end
91
+
92
+ def apply_credentials(project)
93
+ # Use reflex_api_key for Reflex if we have a separate key
94
+ # Otherwise fall back to shared secret_key
95
+ @config.reflex_api_key = project[:api_key]
96
+
97
+ # Also set service name from app_name if not already set
98
+ @config.service ||= @config.app_name
99
+ end
100
+
101
+ def execute(uri, request)
102
+ http = Net::HTTP.new(uri.host, uri.port)
103
+ http.use_ssl = uri.scheme == "https"
104
+ http.open_timeout = 5
105
+ http.read_timeout = 10
106
+ http.request(request)
107
+ end
108
+
109
+ def log_error(message)
110
+ return unless @config.logger
111
+
112
+ @config.logger.error("[BrainzLab::Reflex] #{message}")
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,374 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "reflex/client"
4
+ require_relative "reflex/breadcrumbs"
5
+ require_relative "reflex/provisioner"
6
+
7
+ module BrainzLab
8
+ module Reflex
9
+ FILTERED_PARAMS = %w[password password_confirmation token api_key secret credit_card cvv ssn].freeze
10
+
11
+ class << self
12
+ def capture(exception, **context)
13
+ return unless enabled?
14
+ return if capture_disabled?
15
+ return if excluded?(exception)
16
+ return if sampled_out?
17
+
18
+ # Auto-provision project on first capture if app_name is configured
19
+ ensure_provisioned!
20
+
21
+ return unless BrainzLab.configuration.reflex_valid?
22
+
23
+ payload = build_payload(exception, context)
24
+ payload = run_before_send(payload, exception)
25
+ return if payload.nil?
26
+
27
+ client.send_error(payload)
28
+ end
29
+
30
+ def capture_message(message, level: :error, **context)
31
+ return unless enabled?
32
+ return if capture_disabled?
33
+ return if sampled_out?
34
+
35
+ # Auto-provision project on first capture if app_name is configured
36
+ ensure_provisioned!
37
+
38
+ return unless BrainzLab.configuration.reflex_valid?
39
+
40
+ payload = build_message_payload(message, level, context)
41
+ payload = run_before_send(payload, nil)
42
+ return if payload.nil?
43
+
44
+ client.send_error(payload)
45
+ end
46
+
47
+ def ensure_provisioned!
48
+ return if @provisioned
49
+
50
+ @provisioned = true
51
+ provisioner.ensure_project!
52
+ end
53
+
54
+ def provisioner
55
+ @provisioner ||= Provisioner.new(BrainzLab.configuration)
56
+ end
57
+
58
+ # Temporarily disable capture within a block
59
+ def without_capture
60
+ previous = Thread.current[:brainzlab_capture_disabled]
61
+ Thread.current[:brainzlab_capture_disabled] = true
62
+ yield
63
+ ensure
64
+ Thread.current[:brainzlab_capture_disabled] = previous
65
+ end
66
+
67
+ def client
68
+ @client ||= Client.new(BrainzLab.configuration)
69
+ end
70
+
71
+ def reset!
72
+ @client = nil
73
+ @provisioner = nil
74
+ @provisioned = false
75
+ end
76
+
77
+ private
78
+
79
+ def enabled?
80
+ BrainzLab.configuration.reflex_enabled
81
+ end
82
+
83
+ def capture_disabled?
84
+ Thread.current[:brainzlab_capture_disabled] == true
85
+ end
86
+
87
+ def excluded?(exception)
88
+ config = BrainzLab.configuration
89
+ config.reflex_excluded_exceptions.any? do |excluded|
90
+ case excluded
91
+ when String
92
+ exception.class.name == excluded || exception.class.to_s == excluded
93
+ when Class
94
+ exception.is_a?(excluded)
95
+ when Regexp
96
+ exception.class.name =~ excluded
97
+ else
98
+ false
99
+ end
100
+ end
101
+ end
102
+
103
+ def sampled_out?
104
+ rate = BrainzLab.configuration.reflex_sample_rate
105
+ return false if rate.nil? || rate >= 1.0
106
+
107
+ rand > rate
108
+ end
109
+
110
+ def run_before_send(payload, exception)
111
+ hook = BrainzLab.configuration.reflex_before_send
112
+ return payload unless hook
113
+
114
+ hook.call(payload, exception)
115
+ end
116
+
117
+ def build_payload(exception, context)
118
+ config = BrainzLab.configuration
119
+ ctx = Context.current
120
+
121
+ payload = {
122
+ timestamp: Time.now.utc.iso8601(3),
123
+ error_class: exception.class.name,
124
+ message: exception.message,
125
+ backtrace: format_backtrace(exception.backtrace || []),
126
+
127
+ # Environment
128
+ environment: config.environment,
129
+ commit: config.commit,
130
+ branch: config.branch,
131
+ server_name: config.host,
132
+
133
+ # Request context
134
+ request_id: ctx.request_id
135
+ }
136
+
137
+ # Add request info if available
138
+ add_request_info(payload, ctx)
139
+
140
+ # Add user info
141
+ add_user_info(payload, ctx, context)
142
+
143
+ # Add context, tags, extra
144
+ add_context_data(payload, ctx, context)
145
+
146
+ # Add breadcrumbs
147
+ payload[:breadcrumbs] = ctx.breadcrumbs.to_a
148
+
149
+ # Add fingerprint for error grouping
150
+ payload[:fingerprint] = compute_fingerprint(exception, context, ctx)
151
+
152
+ payload
153
+ end
154
+
155
+ def build_message_payload(message, level, context)
156
+ config = BrainzLab.configuration
157
+ ctx = Context.current
158
+
159
+ payload = {
160
+ timestamp: Time.now.utc.iso8601(3),
161
+ error_class: "Message",
162
+ message: message.to_s,
163
+ level: level.to_s,
164
+
165
+ # Environment
166
+ environment: config.environment,
167
+ commit: config.commit,
168
+ branch: config.branch,
169
+ server_name: config.host,
170
+
171
+ # Request context
172
+ request_id: ctx.request_id
173
+ }
174
+
175
+ # Add request info if available
176
+ add_request_info(payload, ctx)
177
+
178
+ # Add user info
179
+ add_user_info(payload, ctx, context)
180
+
181
+ # Add context, tags, extra
182
+ add_context_data(payload, ctx, context)
183
+
184
+ # Add breadcrumbs
185
+ payload[:breadcrumbs] = ctx.breadcrumbs.to_a
186
+
187
+ payload
188
+ end
189
+
190
+ def add_request_info(payload, ctx)
191
+ return unless ctx.request_path
192
+
193
+ payload[:request] = {
194
+ method: ctx.request_method,
195
+ path: ctx.request_path,
196
+ url: ctx.request_url,
197
+ params: filter_params(ctx.request_params),
198
+ headers: ctx.request_headers,
199
+ controller: ctx.controller,
200
+ action: ctx.action
201
+ }.compact
202
+ end
203
+
204
+ def add_user_info(payload, ctx, context)
205
+ user = context[:user] || ctx.user
206
+ return if user.nil? || user.empty?
207
+
208
+ payload[:user] = {
209
+ id: user[:id]&.to_s,
210
+ email: user[:email],
211
+ name: user[:name]
212
+ }.compact
213
+
214
+ # Store additional user data
215
+ extra_user = user.except(:id, :email, :name)
216
+ payload[:user_data] = extra_user unless extra_user.empty?
217
+ end
218
+
219
+ def add_context_data(payload, ctx, context)
220
+ # Tags from context + provided tags
221
+ tags = ctx.tags.merge(context[:tags] || {})
222
+ payload[:tags] = tags unless tags.empty?
223
+
224
+ # Extra data from context + provided extra
225
+ extra = ctx.data_hash.merge(context[:extra] || {})
226
+ extra = extra.except(:user, :tags) # Remove user and tags as they're separate
227
+ payload[:extra] = extra unless extra.empty?
228
+
229
+ # General context
230
+ payload[:context] = context.except(:user, :tags, :extra) unless context.except(:user, :tags, :extra).empty?
231
+ end
232
+
233
+ def format_backtrace(backtrace)
234
+ backtrace.first(30).map do |line|
235
+ if line.is_a?(String)
236
+ parse_backtrace_line(line)
237
+ else
238
+ line
239
+ end
240
+ end
241
+ end
242
+
243
+ def parse_backtrace_line(line)
244
+ # Parse "path/to/file.rb:42:in `method_name'"
245
+ if line =~ /\A(.+):(\d+):in `(.+)'\z/
246
+ {
247
+ file: $1,
248
+ line: $2.to_i,
249
+ function: $3,
250
+ in_app: in_app_frame?($1)
251
+ }
252
+ else
253
+ { raw: line }
254
+ end
255
+ end
256
+
257
+ def in_app_frame?(path)
258
+ return false if path.nil?
259
+ return false if path.include?("vendor/")
260
+ return false if path.include?("/gems/")
261
+
262
+ path.start_with?("app/", "lib/", "./app/", "./lib/")
263
+ end
264
+
265
+ def filter_params(params)
266
+ return nil if params.nil?
267
+
268
+ scrub_fields = BrainzLab.configuration.scrub_fields + FILTERED_PARAMS.map(&:to_sym)
269
+ deep_filter(params, scrub_fields)
270
+ end
271
+
272
+ def deep_filter(obj, fields)
273
+ case obj
274
+ when Hash
275
+ obj.each_with_object({}) do |(key, value), result|
276
+ if should_filter?(key, fields)
277
+ result[key] = "[FILTERED]"
278
+ else
279
+ result[key] = deep_filter(value, fields)
280
+ end
281
+ end
282
+ when Array
283
+ obj.map { |item| deep_filter(item, fields) }
284
+ else
285
+ obj
286
+ end
287
+ end
288
+
289
+ def should_filter?(key, fields)
290
+ key_str = key.to_s.downcase
291
+ fields.any? do |field|
292
+ case field
293
+ when Regexp
294
+ key_str.match?(field)
295
+ else
296
+ key_str == field.to_s.downcase
297
+ end
298
+ end
299
+ end
300
+
301
+ # Compute fingerprint for error grouping
302
+ # Returns an array of strings that uniquely identify the error type
303
+ def compute_fingerprint(exception, context, ctx)
304
+ custom_callback = BrainzLab.configuration.reflex_fingerprint
305
+
306
+ if custom_callback
307
+ # Call user's custom fingerprint callback
308
+ result = custom_callback.call(exception, context, ctx)
309
+
310
+ # Normalize the result
311
+ case result
312
+ when Array
313
+ result.map(&:to_s)
314
+ when String
315
+ [result]
316
+ when nil
317
+ # nil means use default fingerprinting
318
+ default_fingerprint(exception)
319
+ else
320
+ [result.to_s]
321
+ end
322
+ else
323
+ default_fingerprint(exception)
324
+ end
325
+ rescue StandardError => e
326
+ BrainzLab.debug_log("Custom fingerprint callback failed: #{e.message}")
327
+ default_fingerprint(exception)
328
+ end
329
+
330
+ # Default fingerprint: error class + first in-app frame (or first frame)
331
+ def default_fingerprint(exception)
332
+ parts = [exception.class.name]
333
+
334
+ if exception.backtrace&.any?
335
+ # Try to find the first in-app frame
336
+ in_app_frame = exception.backtrace.find { |line| in_app_line?(line) }
337
+ frame = in_app_frame || exception.backtrace.first
338
+
339
+ if frame
340
+ # Normalize the frame (remove line numbers for consistent grouping)
341
+ normalized = normalize_frame_for_fingerprint(frame)
342
+ parts << normalized if normalized
343
+ end
344
+ end
345
+
346
+ parts
347
+ end
348
+
349
+ def in_app_line?(line)
350
+ return false if line.nil?
351
+ return false if line.include?("vendor/")
352
+ return false if line.include?("/gems/")
353
+
354
+ line.start_with?("app/", "lib/", "./app/", "./lib/") ||
355
+ line.include?("/app/") ||
356
+ line.include?("/lib/")
357
+ end
358
+
359
+ def normalize_frame_for_fingerprint(frame)
360
+ return nil unless frame.is_a?(String)
361
+
362
+ # Extract file and method, normalize out line numbers
363
+ # "app/models/user.rb:42:in `save'" -> "app/models/user.rb:in `save'"
364
+ if frame =~ /\A(.+):\d+:in `(.+)'\z/
365
+ "#{$1}:in `#{$2}'"
366
+ elsif frame =~ /\A(.+):\d+\z/
367
+ $1
368
+ else
369
+ frame
370
+ end
371
+ end
372
+ end
373
+ end
374
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BrainzLab
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "brainzlab"
data/lib/brainzlab.rb ADDED
@@ -0,0 +1,140 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "brainzlab/version"
4
+ require_relative "brainzlab/configuration"
5
+ require_relative "brainzlab/context"
6
+ require_relative "brainzlab/recall"
7
+ require_relative "brainzlab/reflex"
8
+ require_relative "brainzlab/pulse"
9
+ require_relative "brainzlab/instrumentation"
10
+
11
+ module BrainzLab
12
+ class << self
13
+ def configure
14
+ yield(configuration) if block_given?
15
+ configuration
16
+ end
17
+
18
+ def configuration
19
+ @configuration ||= Configuration.new
20
+ end
21
+
22
+ def reset_configuration!
23
+ @configuration = Configuration.new
24
+ Recall.reset!
25
+ Reflex.reset!
26
+ Pulse.reset!
27
+ end
28
+
29
+ # Context management
30
+ def set_user(id: nil, email: nil, name: nil, **extra)
31
+ Context.current.set_user(id: id, email: email, name: name, **extra)
32
+ end
33
+
34
+ def set_context(**data)
35
+ Context.current.set_context(**data)
36
+ end
37
+
38
+ def set_tags(**data)
39
+ Context.current.set_tags(**data)
40
+ end
41
+
42
+ def with_context(**data, &block)
43
+ Context.current.with_context(**data, &block)
44
+ end
45
+
46
+ def clear_context!
47
+ Context.clear!
48
+ end
49
+
50
+ # Breadcrumb helpers
51
+ def add_breadcrumb(message, category: "default", level: :info, data: nil)
52
+ Reflex.add_breadcrumb(message, category: category, level: level, data: data)
53
+ end
54
+
55
+ def clear_breadcrumbs!
56
+ Reflex.clear_breadcrumbs!
57
+ end
58
+
59
+ # Create a logger that can replace Rails.logger
60
+ # @param broadcast_to [Logger] Optional logger to also send logs to (e.g., original Rails.logger)
61
+ # @return [BrainzLab::Recall::Logger]
62
+ def logger(broadcast_to: nil)
63
+ Recall::Logger.new(nil, broadcast_to: broadcast_to)
64
+ end
65
+
66
+ # Debug logging helper
67
+ def debug_log(message)
68
+ configuration.debug_log(message)
69
+ end
70
+
71
+ # Check if debug mode is enabled
72
+ def debug?
73
+ configuration.debug?
74
+ end
75
+
76
+ # Health check - verifies connectivity to all enabled services
77
+ # @return [Hash] Status of each service
78
+ def health_check
79
+ results = { status: 'ok', services: {} }
80
+
81
+ # Check Recall
82
+ if configuration.recall_enabled
83
+ results[:services][:recall] = check_service_health(
84
+ url: configuration.recall_url,
85
+ name: 'Recall'
86
+ )
87
+ end
88
+
89
+ # Check Reflex
90
+ if configuration.reflex_enabled
91
+ results[:services][:reflex] = check_service_health(
92
+ url: configuration.reflex_url,
93
+ name: 'Reflex'
94
+ )
95
+ end
96
+
97
+ # Check Pulse
98
+ if configuration.pulse_enabled
99
+ results[:services][:pulse] = check_service_health(
100
+ url: configuration.pulse_url,
101
+ name: 'Pulse'
102
+ )
103
+ end
104
+
105
+ # Overall status
106
+ has_failure = results[:services].values.any? { |s| s[:status] == 'error' }
107
+ results[:status] = has_failure ? 'degraded' : 'ok'
108
+
109
+ results
110
+ end
111
+
112
+ private
113
+
114
+ def check_service_health(url:, name:)
115
+ require 'net/http'
116
+ require 'uri'
117
+
118
+ uri = URI.parse("#{url}/up")
119
+ http = Net::HTTP.new(uri.host, uri.port)
120
+ http.use_ssl = uri.scheme == 'https'
121
+ http.open_timeout = 5
122
+ http.read_timeout = 5
123
+
124
+ response = http.get(uri.request_uri)
125
+
126
+ if response.is_a?(Net::HTTPSuccess)
127
+ { status: 'ok', latency_ms: 0 }
128
+ else
129
+ { status: 'error', message: "HTTP #{response.code}" }
130
+ end
131
+ rescue StandardError => e
132
+ { status: 'error', message: e.message }
133
+ end
134
+ end
135
+ end
136
+
137
+ # Auto-load Rails integration if Rails is available
138
+ if defined?(Rails::Railtie)
139
+ require_relative "brainzlab/rails/railtie"
140
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators/base"
4
+
5
+ module Brainzlab
6
+ module Generators
7
+ class InstallGenerator < Rails::Generators::Base
8
+ source_root File.expand_path("templates", __dir__)
9
+
10
+ desc "Creates a BrainzLab initializer for your Rails application"
11
+
12
+ class_option :key, type: :string, desc: "Your BrainzLab secret key"
13
+ class_option :replace_logger, type: :boolean, default: false, desc: "Replace Rails.logger with BrainzLab logger"
14
+
15
+ def copy_initializer
16
+ template "brainzlab.rb.tt", "config/initializers/brainzlab.rb"
17
+ end
18
+
19
+ def show_post_install_message
20
+ say ""
21
+ say "BrainzLab SDK installed successfully!", :green
22
+ say ""
23
+ say "Next steps:"
24
+ say " 1. Set your environment variables:"
25
+ say " BRAINZLAB_SECRET_KEY - Your API key from https://brainzlab.ai/dashboard"
26
+ say ""
27
+ say " Or for auto-provisioning:"
28
+ say " RECALL_MASTER_KEY - Master key for Recall auto-provisioning"
29
+ say " REFLEX_MASTER_KEY - Master key for Reflex auto-provisioning"
30
+ say ""
31
+ say " 2. Start logging:"
32
+ say " BrainzLab::Recall.info('Hello from BrainzLab!')"
33
+ say ""
34
+ say " 3. Capture errors (automatic with Rails, or manual):"
35
+ say " BrainzLab::Reflex.capture(exception)"
36
+ say ""
37
+ if options[:replace_logger]
38
+ say " Rails.logger is now connected to Recall!", :yellow
39
+ else
40
+ say " To send all Rails logs to Recall, add to your initializer:"
41
+ say " Rails.logger = BrainzLab.logger(broadcast_to: Rails.logger)"
42
+ end
43
+ say ""
44
+ end
45
+
46
+ private
47
+
48
+ def secret_key_value
49
+ if options[:key].present?
50
+ %("#{options[:key]}")
51
+ else
52
+ 'ENV["BRAINZLAB_SECRET_KEY"]'
53
+ end
54
+ end
55
+
56
+ def app_name
57
+ Rails.application.class.module_parent_name.underscore rescue "my-app"
58
+ end
59
+ end
60
+ end
61
+ end