brainzlab 0.1.10 → 0.1.12

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.
@@ -84,20 +84,29 @@ module BrainzLab
84
84
 
85
85
  def post(path, body)
86
86
  uri = URI.join(@config.pulse_url, path)
87
+
88
+ # Call on_send callback if configured
89
+ invoke_on_send(:pulse, :post, path, body)
90
+
91
+ # Log debug output for request
92
+ log_debug_request(path, body)
93
+
87
94
  request = Net::HTTP::Post.new(uri)
88
95
  request['Content-Type'] = 'application/json'
89
96
  request['Authorization'] = "Bearer #{@config.pulse_auth_key}"
90
97
  request['User-Agent'] = "brainzlab-sdk-ruby/#{BrainzLab::VERSION}"
91
98
  request.body = JSON.generate(body)
92
99
 
93
- execute_with_retry(uri, request)
100
+ execute_with_retry(uri, request, path)
94
101
  rescue StandardError => e
95
- log_error("Failed to send to Pulse: #{e.message}")
102
+ handle_error(e, context: { path: path, body_size: body.to_s.length })
96
103
  nil
97
104
  end
98
105
 
99
- def execute_with_retry(uri, request)
106
+ def execute_with_retry(uri, request, path)
100
107
  retries = 0
108
+ start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
109
+
101
110
  begin
102
111
  http = Net::HTTP.new(uri.host, uri.port)
103
112
  http.use_ssl = uri.scheme == 'https'
@@ -105,6 +114,10 @@ module BrainzLab
105
114
  http.read_timeout = 10
106
115
 
107
116
  response = http.request(request)
117
+ duration_ms = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time) * 1000).round(2)
118
+
119
+ # Log debug output for response
120
+ log_debug_response(response.code.to_i, duration_ms)
108
121
 
109
122
  case response.code.to_i
110
123
  when 200..299
@@ -116,7 +129,10 @@ module BrainzLab
116
129
  when 429, 500..599
117
130
  raise RetryableError, "Server error: #{response.code}"
118
131
  else
119
- log_error("Pulse API error: #{response.code} - #{response.body}")
132
+ handle_error(
133
+ StandardError.new("Pulse API error: #{response.code}"),
134
+ context: { path: path, status: response.code, body: response.body }
135
+ )
120
136
  nil
121
137
  end
122
138
  rescue RetryableError, Net::OpenTimeout, Net::ReadTimeout => e
@@ -125,12 +141,57 @@ module BrainzLab
125
141
  sleep(RETRY_DELAY * retries)
126
142
  retry
127
143
  end
128
- log_error("Failed after #{MAX_RETRIES} retries: #{e.message}")
144
+ duration_ms = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time) * 1000).round(2)
145
+ log_debug_response(0, duration_ms, error: e.message)
146
+ handle_error(e, context: { path: path, retries: retries })
129
147
  nil
130
148
  end
131
149
  end
132
150
 
151
+ def log_debug_request(path, body)
152
+ return unless BrainzLab::Debug.enabled?
153
+
154
+ data = if body.is_a?(Hash) && body[:traces]
155
+ { count: body[:traces].size }
156
+ elsif body.is_a?(Hash) && body[:name]
157
+ { name: body[:name] }
158
+ else
159
+ {}
160
+ end
161
+
162
+ BrainzLab::Debug.log_request(:pulse, 'POST', path, data: data)
163
+ end
164
+
165
+ def log_debug_response(status, duration_ms, error: nil)
166
+ return unless BrainzLab::Debug.enabled?
167
+
168
+ BrainzLab::Debug.log_response(:pulse, status, duration_ms, error: error)
169
+ end
170
+
171
+ def invoke_on_send(service, method, path, payload)
172
+ return unless @config.on_send
173
+
174
+ @config.on_send.call(service, method, path, payload)
175
+ rescue StandardError => e
176
+ # Don't let callback errors break the SDK
177
+ log_error("on_send callback error: #{e.message}")
178
+ end
179
+
180
+ def handle_error(error, context: {})
181
+ log_error("#{error.message}")
182
+
183
+ # Call on_error callback if configured
184
+ return unless @config.on_error
185
+
186
+ @config.on_error.call(error, context.merge(service: :pulse))
187
+ rescue StandardError => e
188
+ # Don't let callback errors break the SDK
189
+ log_error("on_error callback error: #{e.message}")
190
+ end
191
+
133
192
  def log_error(message)
193
+ BrainzLab::Debug.log(message, level: :error) if BrainzLab::Debug.enabled?
194
+
134
195
  return unless @config.logger
135
196
 
136
197
  @config.logger.error("[BrainzLab::Pulse] #{message}")
@@ -47,10 +47,17 @@ module BrainzLab
47
47
  def record_trace(name, started_at:, ended_at:, kind: 'request', **attributes)
48
48
  return unless enabled?
49
49
 
50
+ payload = build_trace_payload(name, kind, started_at, ended_at, attributes)
51
+
52
+ # In development mode, log locally instead of sending to server
53
+ if BrainzLab.configuration.development_mode?
54
+ Development.record(service: :pulse, event_type: 'trace', payload: payload)
55
+ return
56
+ end
57
+
50
58
  ensure_provisioned!
51
59
  return unless BrainzLab.configuration.pulse_valid?
52
60
 
53
- payload = build_trace_payload(name, kind, started_at, ended_at, attributes)
54
61
  client.send_trace(payload)
55
62
  end
56
63
 
@@ -58,9 +65,6 @@ module BrainzLab
58
65
  def record_metric(name, value:, kind: 'gauge', tags: {})
59
66
  return unless enabled?
60
67
 
61
- ensure_provisioned!
62
- return unless BrainzLab.configuration.pulse_valid?
63
-
64
68
  payload = {
65
69
  name: name,
66
70
  value: value,
@@ -69,6 +73,15 @@ module BrainzLab
69
73
  tags: tags
70
74
  }
71
75
 
76
+ # In development mode, log locally instead of sending to server
77
+ if BrainzLab.configuration.development_mode?
78
+ Development.record(service: :pulse, event_type: 'metric', payload: payload)
79
+ return
80
+ end
81
+
82
+ ensure_provisioned!
83
+ return unless BrainzLab.configuration.pulse_valid?
84
+
72
85
  client.send_metric(payload)
73
86
  end
74
87
 
@@ -32,20 +32,29 @@ module BrainzLab
32
32
 
33
33
  def post(path, body)
34
34
  uri = URI.join(@config.recall_url, path)
35
+
36
+ # Call on_send callback if configured
37
+ invoke_on_send(:recall, :post, path, body)
38
+
39
+ # Log debug output for request
40
+ log_debug_request(path, body)
41
+
35
42
  request = Net::HTTP::Post.new(uri)
36
43
  request['Content-Type'] = 'application/json'
37
44
  request['Authorization'] = "Bearer #{@config.secret_key}"
38
45
  request['User-Agent'] = "brainzlab-sdk-ruby/#{BrainzLab::VERSION}"
39
46
  request.body = JSON.generate(body)
40
47
 
41
- execute_with_retry(uri, request)
48
+ execute_with_retry(uri, request, path)
42
49
  rescue StandardError => e
43
- log_error("Failed to send to Recall: #{e.message}")
50
+ handle_error(e, context: { path: path, body_size: body.to_s.length })
44
51
  nil
45
52
  end
46
53
 
47
- def execute_with_retry(uri, request)
54
+ def execute_with_retry(uri, request, path)
48
55
  retries = 0
56
+ start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
57
+
49
58
  begin
50
59
  http = Net::HTTP.new(uri.host, uri.port)
51
60
  http.use_ssl = uri.scheme == 'https'
@@ -53,6 +62,10 @@ module BrainzLab
53
62
  http.read_timeout = 10
54
63
 
55
64
  response = http.request(request)
65
+ duration_ms = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time) * 1000).round(2)
66
+
67
+ # Log debug output for response
68
+ log_debug_response(response.code.to_i, duration_ms)
56
69
 
57
70
  case response.code.to_i
58
71
  when 200..299
@@ -64,7 +77,10 @@ module BrainzLab
64
77
  when 429, 500..599
65
78
  raise RetryableError, "Server error: #{response.code}"
66
79
  else
67
- log_error("Recall API error: #{response.code} - #{response.body}")
80
+ handle_error(
81
+ StandardError.new("Recall API error: #{response.code}"),
82
+ context: { path: path, status: response.code, body: response.body }
83
+ )
68
84
  nil
69
85
  end
70
86
  rescue RetryableError, Net::OpenTimeout, Net::ReadTimeout => e
@@ -73,15 +89,67 @@ module BrainzLab
73
89
  sleep(RETRY_DELAY * retries)
74
90
  retry
75
91
  end
76
- log_error("Failed after #{MAX_RETRIES} retries: #{e.message}")
92
+ duration_ms = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time) * 1000).round(2)
93
+ log_debug_response(0, duration_ms, error: e.message)
94
+ handle_error(e, context: { path: path, retries: retries })
77
95
  nil
78
96
  end
79
97
  end
80
98
 
99
+ def log_debug_request(path, body)
100
+ return unless BrainzLab::Debug.enabled?
101
+
102
+ data = if body.is_a?(Hash) && body[:logs]
103
+ { count: body[:logs].size }
104
+ elsif body.is_a?(Hash) && body[:message]
105
+ { message: body[:message] }
106
+ else
107
+ {}
108
+ end
109
+
110
+ BrainzLab::Debug.log_request(:recall, 'POST', path, data: data)
111
+ end
112
+
113
+ def log_debug_response(status, duration_ms, error: nil)
114
+ return unless BrainzLab::Debug.enabled?
115
+
116
+ BrainzLab::Debug.log_response(:recall, status, duration_ms, error: error)
117
+ end
118
+
119
+ def invoke_on_send(service, method, path, payload)
120
+ return unless @config.on_send
121
+
122
+ @config.on_send.call(service, method, path, payload)
123
+ rescue StandardError => e
124
+ # Don't let callback errors break the SDK
125
+ log_error("on_send callback error: #{e.message}")
126
+ end
127
+
128
+ def handle_error(error, context: {})
129
+ # Wrap the error in a structured error if it's not already one
130
+ structured_error = if error.is_a?(BrainzLab::Error)
131
+ error
132
+ else
133
+ ErrorHandler.wrap(error, service: 'Recall', operation: context[:path] || 'unknown')
134
+ end
135
+
136
+ log_error(structured_error.message)
137
+
138
+ # Call on_error callback if configured
139
+ return unless @config.on_error
140
+
141
+ @config.on_error.call(structured_error, context.merge(service: :recall))
142
+ rescue StandardError => e
143
+ # Don't let callback errors break the SDK
144
+ log_error("on_error callback error: #{e.message}")
145
+ end
146
+
81
147
  def log_error(message)
148
+ BrainzLab::Debug.log(message, level: :error) if BrainzLab::Debug.enabled?
149
+
82
150
  return unless @config.logger
83
151
 
84
- @config.logger.error("[BrainzLab] #{message}")
152
+ @config.logger.error("[BrainzLab::Recall] #{message}")
85
153
  end
86
154
 
87
155
  class RetryableError < StandardError; end
@@ -31,14 +31,24 @@ module BrainzLab
31
31
  def log(level, message, **data)
32
32
  config = BrainzLab.configuration
33
33
  return unless config.recall_effectively_enabled?
34
+ return unless config.level_enabled?(level)
35
+
36
+ entry = build_entry(level, message, data)
37
+
38
+ # Log debug output for the operation
39
+ log_debug_operation(level, message, data)
40
+
41
+ # In development mode, log locally instead of sending to server
42
+ if config.development_mode?
43
+ Development.record(service: :recall, event_type: 'log', payload: entry)
44
+ return
45
+ end
34
46
 
35
47
  # Auto-provision project on first log if app_name is configured
36
48
  ensure_provisioned!
37
49
 
38
- return unless config.level_enabled?(level)
39
50
  return unless config.valid?
40
51
 
41
- entry = build_entry(level, message, data)
42
52
  buffer.push(entry)
43
53
  end
44
54
 
@@ -153,6 +163,13 @@ module BrainzLab
153
163
  end
154
164
  end
155
165
  end
166
+
167
+ def log_debug_operation(level, message, data)
168
+ return unless BrainzLab::Debug.enabled?
169
+
170
+ truncated_message = message.to_s.length > 50 ? "#{message.to_s[0..47]}..." : message.to_s
171
+ BrainzLab::Debug.log_operation(:recall, "#{level.to_s.upcase} \"#{truncated_message}\"", **data.slice(*data.keys.first(3)))
172
+ end
156
173
  end
157
174
  end
158
175
  end
@@ -31,20 +31,29 @@ module BrainzLab
31
31
 
32
32
  def post(path, body)
33
33
  uri = URI.join(@config.reflex_url, path)
34
+
35
+ # Call on_send callback if configured
36
+ invoke_on_send(:reflex, :post, path, body)
37
+
38
+ # Log debug output for request
39
+ log_debug_request(path, body)
40
+
34
41
  request = Net::HTTP::Post.new(uri)
35
42
  request['Content-Type'] = 'application/json'
36
43
  request['Authorization'] = "Bearer #{@config.reflex_auth_key}"
37
44
  request['User-Agent'] = "brainzlab-sdk-ruby/#{BrainzLab::VERSION}"
38
45
  request.body = JSON.generate(body)
39
46
 
40
- execute_with_retry(uri, request)
47
+ execute_with_retry(uri, request, path)
41
48
  rescue StandardError => e
42
- log_error("Failed to send to Reflex: #{e.message}")
49
+ handle_error(e, context: { path: path, body_size: body.to_s.length })
43
50
  nil
44
51
  end
45
52
 
46
- def execute_with_retry(uri, request)
53
+ def execute_with_retry(uri, request, path)
47
54
  retries = 0
55
+ start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
56
+
48
57
  begin
49
58
  http = Net::HTTP.new(uri.host, uri.port)
50
59
  http.use_ssl = uri.scheme == 'https'
@@ -52,6 +61,10 @@ module BrainzLab
52
61
  http.read_timeout = 10
53
62
 
54
63
  response = http.request(request)
64
+ duration_ms = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time) * 1000).round(2)
65
+
66
+ # Log debug output for response
67
+ log_debug_response(response.code.to_i, duration_ms)
55
68
 
56
69
  case response.code.to_i
57
70
  when 200..299
@@ -63,7 +76,10 @@ module BrainzLab
63
76
  when 429, 500..599
64
77
  raise RetryableError, "Server error: #{response.code}"
65
78
  else
66
- log_error("Reflex API error: #{response.code} - #{response.body}")
79
+ handle_error(
80
+ StandardError.new("Reflex API error: #{response.code}"),
81
+ context: { path: path, status: response.code, body: response.body }
82
+ )
67
83
  nil
68
84
  end
69
85
  rescue RetryableError, Net::OpenTimeout, Net::ReadTimeout => e
@@ -72,12 +88,57 @@ module BrainzLab
72
88
  sleep(RETRY_DELAY * retries)
73
89
  retry
74
90
  end
75
- log_error("Failed after #{MAX_RETRIES} retries: #{e.message}")
91
+ duration_ms = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time) * 1000).round(2)
92
+ log_debug_response(0, duration_ms, error: e.message)
93
+ handle_error(e, context: { path: path, retries: retries })
76
94
  nil
77
95
  end
78
96
  end
79
97
 
98
+ def log_debug_request(path, body)
99
+ return unless BrainzLab::Debug.enabled?
100
+
101
+ data = if body.is_a?(Hash) && body[:errors]
102
+ { count: body[:errors].size }
103
+ elsif body.is_a?(Hash) && body[:exception]
104
+ { exception: body[:exception][:type] }
105
+ else
106
+ {}
107
+ end
108
+
109
+ BrainzLab::Debug.log_request(:reflex, 'POST', path, data: data)
110
+ end
111
+
112
+ def log_debug_response(status, duration_ms, error: nil)
113
+ return unless BrainzLab::Debug.enabled?
114
+
115
+ BrainzLab::Debug.log_response(:reflex, status, duration_ms, error: error)
116
+ end
117
+
118
+ def invoke_on_send(service, method, path, payload)
119
+ return unless @config.on_send
120
+
121
+ @config.on_send.call(service, method, path, payload)
122
+ rescue StandardError => e
123
+ # Don't let callback errors break the SDK
124
+ log_error("on_send callback error: #{e.message}")
125
+ end
126
+
127
+ def handle_error(error, context: {})
128
+ log_error("#{error.message}")
129
+
130
+ # Call on_error callback if configured
131
+ return unless @config.on_error
132
+
133
+ @config.on_error.call(error, context.merge(service: :reflex))
134
+ rescue StandardError => e
135
+ # Don't let callback errors break the SDK
136
+ log_error("on_error callback error: #{e.message}")
137
+ end
138
+
80
139
  def log_error(message)
140
+ BrainzLab::Debug.log(message, level: :error) if BrainzLab::Debug.enabled?
141
+
81
142
  return unless @config.logger
82
143
 
83
144
  @config.logger.error("[BrainzLab::Reflex] #{message}")
@@ -15,15 +15,24 @@ module BrainzLab
15
15
  return if excluded?(exception)
16
16
  return if sampled_out?
17
17
 
18
- # Auto-provision project on first capture if app_name is configured
19
- ensure_provisioned!
20
-
21
- return unless BrainzLab.configuration.reflex_valid?
18
+ # Log debug output for the operation
19
+ log_debug_capture(exception)
22
20
 
23
21
  payload = build_payload(exception, context)
24
22
  payload = run_before_send(payload, exception)
25
23
  return if payload.nil?
26
24
 
25
+ # In development mode, log locally instead of sending to server
26
+ if BrainzLab.configuration.development_mode?
27
+ Development.record(service: :reflex, event_type: 'error', payload: payload)
28
+ return
29
+ end
30
+
31
+ # Auto-provision project on first capture if app_name is configured
32
+ ensure_provisioned!
33
+
34
+ return unless BrainzLab.configuration.reflex_valid?
35
+
27
36
  client.send_error(payload)
28
37
  end
29
38
 
@@ -32,15 +41,24 @@ module BrainzLab
32
41
  return if capture_disabled?
33
42
  return if sampled_out?
34
43
 
35
- # Auto-provision project on first capture if app_name is configured
36
- ensure_provisioned!
37
-
38
- return unless BrainzLab.configuration.reflex_valid?
44
+ # Log debug output for the operation
45
+ log_debug_message(message, level)
39
46
 
40
47
  payload = build_message_payload(message, level, context)
41
48
  payload = run_before_send(payload, nil)
42
49
  return if payload.nil?
43
50
 
51
+ # In development mode, log locally instead of sending to server
52
+ if BrainzLab.configuration.development_mode?
53
+ Development.record(service: :reflex, event_type: 'message', payload: payload)
54
+ return
55
+ end
56
+
57
+ # Auto-provision project on first capture if app_name is configured
58
+ ensure_provisioned!
59
+
60
+ return unless BrainzLab.configuration.reflex_valid?
61
+
44
62
  client.send_error(payload)
45
63
  end
46
64
 
@@ -384,6 +402,20 @@ module BrainzLab
384
402
  frame
385
403
  end
386
404
  end
405
+
406
+ def log_debug_capture(exception)
407
+ return unless BrainzLab::Debug.enabled?
408
+
409
+ truncated_message = exception.message.to_s.length > 40 ? "#{exception.message.to_s[0..37]}..." : exception.message.to_s
410
+ BrainzLab::Debug.log_operation(:reflex, "capture #{exception.class.name}: \"#{truncated_message}\"")
411
+ end
412
+
413
+ def log_debug_message(message, level)
414
+ return unless BrainzLab::Debug.enabled?
415
+
416
+ truncated_message = message.to_s.length > 40 ? "#{message.to_s[0..37]}..." : message.to_s
417
+ BrainzLab::Debug.log_operation(:reflex, "message [#{level.to_s.upcase}] \"#{truncated_message}\"")
418
+ end
387
419
  end
388
420
  end
389
421
  end
@@ -209,7 +209,27 @@ module BrainzLab
209
209
  end
210
210
 
211
211
  def log_error(operation, error)
212
- BrainzLab.debug_log("[Sentinel::Client] #{operation} failed: #{error.message}")
212
+ structured_error = ErrorHandler.wrap(error, service: 'Sentinel', operation: operation)
213
+ BrainzLab.debug_log("[Sentinel::Client] #{operation} failed: #{structured_error.message}")
214
+
215
+ # Call on_error callback if configured
216
+ if @config.on_error
217
+ @config.on_error.call(structured_error, { service: 'Sentinel', operation: operation })
218
+ end
219
+ end
220
+
221
+ def handle_response_error(response, operation)
222
+ return if response.is_a?(Net::HTTPSuccess) || response.is_a?(Net::HTTPCreated) || response.is_a?(Net::HTTPNoContent) || response.is_a?(Net::HTTPAccepted)
223
+
224
+ structured_error = ErrorHandler.from_response(response, service: 'Sentinel', operation: operation)
225
+ BrainzLab.debug_log("[Sentinel::Client] #{operation} failed: #{structured_error.message}")
226
+
227
+ # Call on_error callback if configured
228
+ if @config.on_error
229
+ @config.on_error.call(structured_error, { service: 'Sentinel', operation: operation })
230
+ end
231
+
232
+ structured_error
213
233
  end
214
234
  end
215
235
  end
@@ -281,7 +281,27 @@ module BrainzLab
281
281
  end
282
282
 
283
283
  def log_error(operation, error)
284
- BrainzLab.debug_log("[Synapse::Client] #{operation} failed: #{error.message}")
284
+ structured_error = ErrorHandler.wrap(error, service: 'Synapse', operation: operation)
285
+ BrainzLab.debug_log("[Synapse::Client] #{operation} failed: #{structured_error.message}")
286
+
287
+ # Call on_error callback if configured
288
+ if @config.on_error
289
+ @config.on_error.call(structured_error, { service: 'Synapse', operation: operation })
290
+ end
291
+ end
292
+
293
+ def handle_response_error(response, operation)
294
+ return if response.is_a?(Net::HTTPSuccess) || response.is_a?(Net::HTTPCreated) || response.is_a?(Net::HTTPNoContent) || response.is_a?(Net::HTTPAccepted)
295
+
296
+ structured_error = ErrorHandler.from_response(response, service: 'Synapse', operation: operation)
297
+ BrainzLab.debug_log("[Synapse::Client] #{operation} failed: #{structured_error.message}")
298
+
299
+ # Call on_error callback if configured
300
+ if @config.on_error
301
+ @config.on_error.call(structured_error, { service: 'Synapse', operation: operation })
302
+ end
303
+
304
+ structured_error
285
305
  end
286
306
  end
287
307
  end