brainzlab 0.1.33 → 0.1.35

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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +38 -0
  3. data/lib/brainzlab/beacon/client.rb +4 -6
  4. data/lib/brainzlab/configuration.rb +363 -258
  5. data/lib/brainzlab/cortex/client.rb +4 -6
  6. data/lib/brainzlab/debug.rb +6 -6
  7. data/lib/brainzlab/dendrite/client.rb +4 -6
  8. data/lib/brainzlab/development/logger.rb +2 -2
  9. data/lib/brainzlab/development/store.rb +2 -4
  10. data/lib/brainzlab/devtools/middleware/error_page.rb +1 -1
  11. data/lib/brainzlab/errors.rb +5 -12
  12. data/lib/brainzlab/instrumentation/action_cable.rb +4 -4
  13. data/lib/brainzlab/instrumentation/action_controller.rb +12 -12
  14. data/lib/brainzlab/instrumentation/action_dispatch.rb +1 -1
  15. data/lib/brainzlab/instrumentation/action_mailbox.rb +7 -13
  16. data/lib/brainzlab/instrumentation/action_view.rb +29 -33
  17. data/lib/brainzlab/instrumentation/active_job.rb +6 -10
  18. data/lib/brainzlab/instrumentation/active_record.rb +53 -25
  19. data/lib/brainzlab/instrumentation/active_storage.rb +4 -4
  20. data/lib/brainzlab/instrumentation/active_support_cache.rb +6 -8
  21. data/lib/brainzlab/instrumentation/aws.rb +10 -7
  22. data/lib/brainzlab/instrumentation/grape.rb +37 -2
  23. data/lib/brainzlab/instrumentation/rails_deprecation.rb +1 -1
  24. data/lib/brainzlab/instrumentation/railties.rb +2 -2
  25. data/lib/brainzlab/nerve/client.rb +4 -6
  26. data/lib/brainzlab/pulse/client.rb +1 -1
  27. data/lib/brainzlab/pulse/explain_capture.rb +123 -0
  28. data/lib/brainzlab/pulse.rb +6 -1
  29. data/lib/brainzlab/rails/railtie.rb +1 -3
  30. data/lib/brainzlab/reflex/client.rb +1 -1
  31. data/lib/brainzlab/sentinel/client.rb +4 -6
  32. data/lib/brainzlab/signal/client.rb +16 -2
  33. data/lib/brainzlab/signal.rb +8 -0
  34. data/lib/brainzlab/synapse/client.rb +4 -6
  35. data/lib/brainzlab/testing/event_store.rb +3 -3
  36. data/lib/brainzlab/testing/matchers.rb +4 -4
  37. data/lib/brainzlab/testing.rb +5 -3
  38. data/lib/brainzlab/utilities/route_path_normalizer.rb +91 -0
  39. data/lib/brainzlab/utilities.rb +1 -0
  40. data/lib/brainzlab/vault/client.rb +4 -6
  41. data/lib/brainzlab/version.rb +1 -1
  42. data/lib/brainzlab/vision/client.rb +3 -3
  43. data/lib/brainzlab.rb +1 -0
  44. data/lib/fluyenta-ruby.rb +1 -1
  45. metadata +3 -1
@@ -112,10 +112,10 @@ module BrainzLab
112
112
  "expected event '#{@event_name}' to have been tracked, but no such event was found.\n" \
113
113
  "Tracked events: #{BrainzLab::Testing.event_store.events.map { |e| e[:name] }.inspect}"
114
114
  else
115
- "expected event '#{@event_name}' to have been tracked with properties:\n" \
116
- " #{@expected_properties.inspect}\n" \
117
- "but was tracked with:\n" \
118
- " #{actual_events.map { |e| e[:properties] }.inspect}"
115
+ "expected event '#{@event_name}' to have been tracked with properties:\n " \
116
+ "#{@expected_properties.inspect}\n" \
117
+ "but was tracked with:\n " \
118
+ "#{actual_events.map { |e| e[:properties] }.inspect}"
119
119
  end
120
120
  end
121
121
 
@@ -198,16 +198,18 @@ module BrainzLab
198
198
  end
199
199
 
200
200
  # Stub distributed tracing methods
201
- BrainzLab::Pulse.define_singleton_method(:inject) do |headers, **opts|
201
+ BrainzLab::Pulse.define_singleton_method(:inject) do |headers, **_opts|
202
202
  headers['traceparent'] = '00-test-trace-id-12345-test-span-id-67890-01'
203
203
  headers
204
204
  end
205
205
 
206
206
  BrainzLab::Pulse.define_singleton_method(:extract) do |headers|
207
+ return unless headers['traceparent']
208
+
207
209
  BrainzLab::Pulse::Propagation::Context.new(
208
210
  trace_id: 'test-trace-id-12345',
209
211
  span_id: 'test-span-id-67890'
210
- ) if headers['traceparent']
212
+ )
211
213
  end
212
214
 
213
215
  BrainzLab::Pulse.define_singleton_method(:extract!) do |headers|
@@ -304,7 +306,7 @@ module BrainzLab
304
306
  BrainzLab::Cortex.define_singleton_method(:read) { |*| nil }
305
307
  BrainzLab::Cortex.define_singleton_method(:write) { |*| true }
306
308
  BrainzLab::Cortex.define_singleton_method(:delete) { |*| true }
307
- BrainzLab::Cortex.define_singleton_method(:fetch) { |key, **opts, &block| block&.call }
309
+ BrainzLab::Cortex.define_singleton_method(:fetch) { |_key, **_opts, &block| block&.call }
308
310
  end
309
311
 
310
312
  def stub_vault!
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BrainzLab
4
+ module Utilities
5
+ # Collapses concrete request paths into route patterns, so that
6
+ # /users/1, /users/2 ... /users/99999 aggregate as a single /users/:id
7
+ # endpoint instead of one endpoint per record.
8
+ #
9
+ # Rails instrumentation names traces by controller#action and does not need
10
+ # this. Grape and plain Rack do: when the router does not expose a route
11
+ # pattern the SDK falls back to the raw PATH_INFO, which is where the
12
+ # cardinality blowup comes from.
13
+ #
14
+ # Two modes:
15
+ # * with path params — substitute the exact values the router resolved
16
+ # * without them — fall back to recognising id-shaped segments
17
+ module RoutePathNormalizer
18
+ # Params that describe the dispatch rather than the path itself.
19
+ SKIP_PARAMS = %i[controller action format].freeze
20
+
21
+ UUID_PATTERN = /\A\h{8}-\h{4}-\h{4}-\h{4}-\h{12}\z/
22
+ NUMERIC_PATTERN = /\A\d+\z/
23
+ # 16+ hex chars: git SHAs, hex tokens. Shorter hex strings are too easily
24
+ # a real word segment ("beef", "cafe") to risk substituting.
25
+ LONG_HEX_PATTERN = /\A\h{16,}\z/
26
+
27
+ module_function
28
+
29
+ # @param path [String, nil] the concrete request path
30
+ # @param path_params [Hash, nil] params the router resolved, when available
31
+ # @return [String, nil] the path with variable segments replaced
32
+ def normalize(path, path_params = nil)
33
+ return path if path.nil? || path.empty?
34
+
35
+ params = normalize_params(path_params)
36
+
37
+ # -1 keeps trailing empty segments, so a trailing slash survives.
38
+ segments = path.split('/', -1)
39
+ normalized = segments.map do |segment|
40
+ next segment if segment.empty?
41
+
42
+ params.empty? ? substitute_by_shape(segment) : substitute_by_param(segment, params)
43
+ end
44
+
45
+ normalized.join('/')
46
+ end
47
+
48
+ def normalize_params(path_params)
49
+ return {} if path_params.nil? || path_params.empty?
50
+
51
+ # Map value => [param names]. A value claimed by more than one param is
52
+ # ambiguous, so it is left alone rather than guessed at.
53
+ path_params.each_with_object({}) do |(key, value), map|
54
+ next if SKIP_PARAMS.include?(key.to_sym)
55
+
56
+ (map[value.to_s] ||= []) << key
57
+ end
58
+ end
59
+
60
+ def substitute_by_param(segment, params)
61
+ keys = params[segment]
62
+ return ":#{keys.first}" if keys && keys.length == 1
63
+
64
+ # "42.json" when the id param is "42"
65
+ if segment.include?('.')
66
+ base, extension = segment.split('.', 2)
67
+ keys = params[base]
68
+ return ":#{keys.first}.#{extension}" if keys && keys.length == 1
69
+ end
70
+
71
+ segment
72
+ end
73
+
74
+ def substitute_by_shape(segment)
75
+ base, extension = segment.include?('.') ? segment.split('.', 2) : [segment, nil]
76
+ placeholder = placeholder_for(base)
77
+ return segment if placeholder.nil?
78
+
79
+ extension ? "#{placeholder}.#{extension}" : placeholder
80
+ end
81
+
82
+ def placeholder_for(segment)
83
+ return ':uuid' if segment.match?(UUID_PATTERN)
84
+ return ':id' if segment.match?(NUMERIC_PATTERN)
85
+ return ':id' if segment.match?(LONG_HEX_PATTERN)
86
+
87
+ nil
88
+ end
89
+ end
90
+ end
91
+ end
@@ -4,6 +4,7 @@ require_relative 'utilities/rate_limiter'
4
4
  require_relative 'utilities/circuit_breaker'
5
5
  require_relative 'utilities/health_check'
6
6
  require_relative 'utilities/log_formatter'
7
+ require_relative 'utilities/route_path_normalizer'
7
8
 
8
9
  module BrainzLab
9
10
  module Utilities
@@ -193,9 +193,9 @@ module BrainzLab
193
193
  BrainzLab.debug_log("[Vault::Client] #{operation} failed: #{structured_error.message}")
194
194
 
195
195
  # Call on_error callback if configured
196
- if @config.on_error
197
- @config.on_error.call(structured_error, { service: 'Vault', operation: operation })
198
- end
196
+ return unless @config.on_error
197
+
198
+ @config.on_error.call(structured_error, { service: 'Vault', operation: operation })
199
199
  end
200
200
 
201
201
  def handle_response_error(response, operation)
@@ -205,9 +205,7 @@ module BrainzLab
205
205
  BrainzLab.debug_log("[Vault::Client] #{operation} failed: #{structured_error.message}")
206
206
 
207
207
  # Call on_error callback if configured
208
- if @config.on_error
209
- @config.on_error.call(structured_error, { service: 'Vault', operation: operation })
210
- end
208
+ @config.on_error&.call(structured_error, { service: 'Vault', operation: operation })
211
209
 
212
210
  structured_error
213
211
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module BrainzLab
4
- VERSION = '0.1.33'
4
+ VERSION = '0.1.35'
5
5
  end
@@ -154,9 +154,9 @@ module BrainzLab
154
154
  BrainzLab.debug_log("[Vision::Client] #{operation} failed: #{error.message}")
155
155
 
156
156
  # Call on_error callback if configured
157
- if @config.on_error
158
- @config.on_error.call(error, { service: 'Vision', operation: operation })
159
- end
157
+ return unless @config.on_error
158
+
159
+ @config.on_error.call(error, { service: 'Vision', operation: operation })
160
160
  end
161
161
 
162
162
  def auth_key
data/lib/brainzlab.rb CHANGED
@@ -63,6 +63,7 @@ module BrainzLab
63
63
 
64
64
  def configure
65
65
  yield(configuration) if block_given?
66
+ configuration.validate!
66
67
  configuration
67
68
  end
68
69
 
data/lib/fluyenta-ruby.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'brainzlab'
3
+ require_relative 'brainzlab'
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.33
4
+ version: 0.1.35
5
5
  platform: ruby
6
6
  authors:
7
7
  - BrainzLab
@@ -177,6 +177,7 @@ files:
177
177
  - lib/brainzlab/nerve/provisioner.rb
178
178
  - lib/brainzlab/pulse.rb
179
179
  - lib/brainzlab/pulse/client.rb
180
+ - lib/brainzlab/pulse/explain_capture.rb
180
181
  - lib/brainzlab/pulse/instrumentation.rb
181
182
  - lib/brainzlab/pulse/propagation.rb
182
183
  - lib/brainzlab/pulse/provisioner.rb
@@ -211,6 +212,7 @@ files:
211
212
  - lib/brainzlab/utilities/health_check.rb
212
213
  - lib/brainzlab/utilities/log_formatter.rb
213
214
  - lib/brainzlab/utilities/rate_limiter.rb
215
+ - lib/brainzlab/utilities/route_path_normalizer.rb
214
216
  - lib/brainzlab/vault.rb
215
217
  - lib/brainzlab/vault/cache.rb
216
218
  - lib/brainzlab/vault/client.rb