brainzlab 0.1.1 → 0.1.2

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 (76) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +8 -0
  3. data/lib/brainzlab/beacon/client.rb +209 -0
  4. data/lib/brainzlab/beacon/provisioner.rb +44 -0
  5. data/lib/brainzlab/beacon.rb +215 -0
  6. data/lib/brainzlab/configuration.rb +341 -3
  7. data/lib/brainzlab/cortex/cache.rb +59 -0
  8. data/lib/brainzlab/cortex/client.rb +141 -0
  9. data/lib/brainzlab/cortex/provisioner.rb +49 -0
  10. data/lib/brainzlab/cortex.rb +227 -0
  11. data/lib/brainzlab/dendrite/client.rb +232 -0
  12. data/lib/brainzlab/dendrite/provisioner.rb +44 -0
  13. data/lib/brainzlab/dendrite.rb +195 -0
  14. data/lib/brainzlab/devtools/assets/devtools.css +1106 -0
  15. data/lib/brainzlab/devtools/assets/devtools.js +322 -0
  16. data/lib/brainzlab/devtools/assets/logo.svg +6 -0
  17. data/lib/brainzlab/devtools/assets/templates/debug_panel.html.erb +500 -0
  18. data/lib/brainzlab/devtools/assets/templates/error_page.html.erb +1086 -0
  19. data/lib/brainzlab/devtools/data/collector.rb +248 -0
  20. data/lib/brainzlab/devtools/middleware/asset_server.rb +63 -0
  21. data/lib/brainzlab/devtools/middleware/database_handler.rb +180 -0
  22. data/lib/brainzlab/devtools/middleware/debug_panel.rb +126 -0
  23. data/lib/brainzlab/devtools/middleware/error_page.rb +376 -0
  24. data/lib/brainzlab/devtools/renderers/debug_panel_renderer.rb +155 -0
  25. data/lib/brainzlab/devtools/renderers/error_page_renderer.rb +94 -0
  26. data/lib/brainzlab/devtools.rb +75 -0
  27. data/lib/brainzlab/flux/buffer.rb +96 -0
  28. data/lib/brainzlab/flux/client.rb +70 -0
  29. data/lib/brainzlab/flux/provisioner.rb +57 -0
  30. data/lib/brainzlab/flux.rb +174 -0
  31. data/lib/brainzlab/instrumentation/active_record.rb +18 -1
  32. data/lib/brainzlab/instrumentation/aws.rb +179 -0
  33. data/lib/brainzlab/instrumentation/dalli.rb +108 -0
  34. data/lib/brainzlab/instrumentation/excon.rb +152 -0
  35. data/lib/brainzlab/instrumentation/good_job.rb +102 -0
  36. data/lib/brainzlab/instrumentation/resque.rb +115 -0
  37. data/lib/brainzlab/instrumentation/solid_queue.rb +198 -0
  38. data/lib/brainzlab/instrumentation/stripe.rb +164 -0
  39. data/lib/brainzlab/instrumentation/typhoeus.rb +104 -0
  40. data/lib/brainzlab/instrumentation.rb +72 -0
  41. data/lib/brainzlab/nerve/client.rb +217 -0
  42. data/lib/brainzlab/nerve/provisioner.rb +44 -0
  43. data/lib/brainzlab/nerve.rb +219 -0
  44. data/lib/brainzlab/pulse/instrumentation.rb +35 -2
  45. data/lib/brainzlab/pulse/propagation.rb +1 -1
  46. data/lib/brainzlab/pulse/tracer.rb +1 -1
  47. data/lib/brainzlab/pulse.rb +1 -1
  48. data/lib/brainzlab/rails/log_subscriber.rb +1 -2
  49. data/lib/brainzlab/rails/railtie.rb +36 -3
  50. data/lib/brainzlab/recall/provisioner.rb +17 -0
  51. data/lib/brainzlab/recall.rb +6 -1
  52. data/lib/brainzlab/reflex.rb +2 -2
  53. data/lib/brainzlab/sentinel/client.rb +218 -0
  54. data/lib/brainzlab/sentinel/provisioner.rb +44 -0
  55. data/lib/brainzlab/sentinel.rb +165 -0
  56. data/lib/brainzlab/signal/client.rb +62 -0
  57. data/lib/brainzlab/signal/provisioner.rb +55 -0
  58. data/lib/brainzlab/signal.rb +136 -0
  59. data/lib/brainzlab/synapse/client.rb +290 -0
  60. data/lib/brainzlab/synapse/provisioner.rb +44 -0
  61. data/lib/brainzlab/synapse.rb +270 -0
  62. data/lib/brainzlab/utilities/circuit_breaker.rb +265 -0
  63. data/lib/brainzlab/utilities/health_check.rb +296 -0
  64. data/lib/brainzlab/utilities/log_formatter.rb +256 -0
  65. data/lib/brainzlab/utilities/rate_limiter.rb +230 -0
  66. data/lib/brainzlab/utilities.rb +17 -0
  67. data/lib/brainzlab/vault/cache.rb +80 -0
  68. data/lib/brainzlab/vault/client.rb +198 -0
  69. data/lib/brainzlab/vault/provisioner.rb +49 -0
  70. data/lib/brainzlab/vault.rb +268 -0
  71. data/lib/brainzlab/version.rb +1 -1
  72. data/lib/brainzlab/vision/client.rb +128 -0
  73. data/lib/brainzlab/vision/provisioner.rb +136 -0
  74. data/lib/brainzlab/vision.rb +157 -0
  75. data/lib/brainzlab.rb +101 -0
  76. metadata +60 -1
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BrainzLab
4
+ module Flux
5
+ class Buffer
6
+ MAX_EVENTS = 100
7
+ MAX_METRICS = 100
8
+ FLUSH_INTERVAL = 5 # seconds
9
+
10
+ def initialize(client)
11
+ @client = client
12
+ @events = []
13
+ @metrics = []
14
+ @mutex = Mutex.new
15
+ @last_flush = Time.now
16
+
17
+ start_flush_thread
18
+ end
19
+
20
+ def add(type, data)
21
+ @mutex.synchronize do
22
+ case type
23
+ when :event
24
+ @events << data
25
+ when :metric
26
+ @metrics << data
27
+ end
28
+
29
+ flush_if_needed
30
+ end
31
+ end
32
+
33
+ def flush!
34
+ events_to_send = nil
35
+ metrics_to_send = nil
36
+
37
+ @mutex.synchronize do
38
+ events_to_send = @events.dup
39
+ metrics_to_send = @metrics.dup
40
+ @events.clear
41
+ @metrics.clear
42
+ @last_flush = Time.now
43
+ end
44
+
45
+ send_batch(events_to_send, metrics_to_send)
46
+ end
47
+
48
+ def size
49
+ @mutex.synchronize { @events.size + @metrics.size }
50
+ end
51
+
52
+ private
53
+
54
+ def flush_if_needed
55
+ should_flush = @events.size >= MAX_EVENTS ||
56
+ @metrics.size >= MAX_METRICS ||
57
+ Time.now - @last_flush >= FLUSH_INTERVAL
58
+
59
+ flush_async if should_flush
60
+ end
61
+
62
+ def flush_async
63
+ events_to_send = @events.dup
64
+ metrics_to_send = @metrics.dup
65
+ @events.clear
66
+ @metrics.clear
67
+ @last_flush = Time.now
68
+
69
+ Thread.new do
70
+ send_batch(events_to_send, metrics_to_send)
71
+ end
72
+ end
73
+
74
+ def send_batch(events, metrics)
75
+ return if events.empty? && metrics.empty?
76
+
77
+ @client.send_batch(events: events, metrics: metrics)
78
+ rescue => e
79
+ BrainzLab.debug("[Flux] Batch send failed: #{e.message}")
80
+ end
81
+
82
+ def start_flush_thread
83
+ Thread.new do
84
+ loop do
85
+ sleep FLUSH_INTERVAL
86
+ begin
87
+ flush! if size > 0
88
+ rescue => e
89
+ BrainzLab.debug("[Flux] Flush thread error: #{e.message}")
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/http"
4
+ require "json"
5
+ require "uri"
6
+
7
+ module BrainzLab
8
+ module Flux
9
+ class Client
10
+ def initialize(config)
11
+ @config = config
12
+ end
13
+
14
+ def send_event(event)
15
+ post("/api/v1/events", event)
16
+ end
17
+
18
+ def send_events(events)
19
+ post("/api/v1/events/batch", { events: events })
20
+ end
21
+
22
+ def send_metric(metric)
23
+ post("/api/v1/metrics", metric)
24
+ end
25
+
26
+ def send_metrics(metrics)
27
+ post("/api/v1/metrics/batch", { metrics: metrics })
28
+ end
29
+
30
+ def send_batch(events:, metrics:)
31
+ post("/api/v1/flux/batch", { events: events, metrics: metrics })
32
+ end
33
+
34
+ private
35
+
36
+ def post(path, body)
37
+ uri = URI.parse("#{base_url}#{path}")
38
+ http = Net::HTTP.new(uri.host, uri.port)
39
+ http.use_ssl = uri.scheme == "https"
40
+ http.open_timeout = 5
41
+ http.read_timeout = 10
42
+
43
+ request = Net::HTTP::Post.new(uri.path)
44
+ request["Content-Type"] = "application/json"
45
+ request["Authorization"] = "Bearer #{api_key}"
46
+ request["User-Agent"] = "brainzlab-sdk/#{BrainzLab::VERSION}"
47
+ request.body = body.to_json
48
+
49
+ response = http.request(request)
50
+
51
+ unless response.is_a?(Net::HTTPSuccess)
52
+ BrainzLab.debug("[Flux] Request failed: #{response.code} - #{response.body}")
53
+ end
54
+
55
+ response
56
+ rescue => e
57
+ BrainzLab.debug("[Flux] Request error: #{e.message}")
58
+ nil
59
+ end
60
+
61
+ def base_url
62
+ @config.flux_url
63
+ end
64
+
65
+ def api_key
66
+ @config.flux_ingest_key || @config.flux_api_key
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/http"
4
+ require "json"
5
+ require "uri"
6
+
7
+ module BrainzLab
8
+ module Flux
9
+ class Provisioner
10
+ def initialize(config)
11
+ @config = config
12
+ end
13
+
14
+ def ensure_project!
15
+ return if (@config.flux_ingest_key && !@config.flux_ingest_key.to_s.empty?) ||
16
+ (@config.flux_api_key && !@config.flux_api_key.to_s.empty?)
17
+ return unless @config.flux_url && !@config.flux_url.to_s.empty?
18
+ return unless @config.secret_key && !@config.secret_key.to_s.empty?
19
+
20
+ BrainzLab.debug_log("[Flux] Auto-provisioning project...")
21
+ provision_project
22
+ end
23
+
24
+ private
25
+
26
+ def provision_project
27
+ uri = URI.parse("#{@config.flux_url}/api/v1/projects/provision")
28
+ http = Net::HTTP.new(uri.host, uri.port)
29
+ http.use_ssl = uri.scheme == "https"
30
+ http.open_timeout = 10
31
+ http.read_timeout = 30
32
+
33
+ request = Net::HTTP::Post.new(uri.path)
34
+ request["Content-Type"] = "application/json"
35
+ request["Authorization"] = "Bearer #{@config.secret_key}"
36
+ request["User-Agent"] = "brainzlab-sdk/#{BrainzLab::VERSION}"
37
+ request.body = {
38
+ name: @config.service || "default",
39
+ environment: @config.environment
40
+ }.to_json
41
+
42
+ response = http.request(request)
43
+
44
+ if response.is_a?(Net::HTTPSuccess)
45
+ data = JSON.parse(response.body)
46
+ @config.flux_ingest_key = data["ingest_key"]
47
+ @config.flux_api_key = data["api_key"]
48
+ BrainzLab.debug_log("[Flux] Project provisioned successfully")
49
+ else
50
+ BrainzLab.debug_log("[Flux] Provisioning failed: #{response.code} - #{response.body}")
51
+ end
52
+ rescue => e
53
+ BrainzLab.debug_log("[Flux] Provisioning error: #{e.message}")
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,174 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "flux/client"
4
+ require_relative "flux/buffer"
5
+ require_relative "flux/provisioner"
6
+
7
+ module BrainzLab
8
+ module Flux
9
+ class << self
10
+ # === EVENTS ===
11
+
12
+ # Track a custom event
13
+ # @param name [String] Event name (e.g., 'user.signup', 'order.completed')
14
+ # @param properties [Hash] Event properties
15
+ def track(name, properties = {})
16
+ return unless enabled?
17
+
18
+ ensure_provisioned!
19
+ return unless BrainzLab.configuration.flux_valid?
20
+
21
+ event = {
22
+ name: name,
23
+ timestamp: Time.now.utc.iso8601(3),
24
+ properties: properties.except(:user_id, :value, :tags, :session_id),
25
+ user_id: properties[:user_id],
26
+ session_id: properties[:session_id],
27
+ value: properties[:value],
28
+ tags: properties[:tags] || {},
29
+ environment: BrainzLab.configuration.environment,
30
+ service: BrainzLab.configuration.service
31
+ }
32
+
33
+ buffer.add(:event, event)
34
+ end
35
+
36
+ # Track event for a specific user
37
+ def track_for_user(user, name, properties = {})
38
+ user_id = user.respond_to?(:id) ? user.id.to_s : user.to_s
39
+ track(name, properties.merge(user_id: user_id))
40
+ end
41
+
42
+ # === METRICS ===
43
+
44
+ # Gauge: Current value (overwrites)
45
+ def gauge(name, value, tags: {})
46
+ return unless enabled?
47
+
48
+ ensure_provisioned!
49
+ return unless BrainzLab.configuration.flux_valid?
50
+
51
+ metric = {
52
+ type: "gauge",
53
+ name: name,
54
+ value: value,
55
+ tags: tags,
56
+ timestamp: Time.now.utc.iso8601(3)
57
+ }
58
+
59
+ buffer.add(:metric, metric)
60
+ end
61
+
62
+ # Counter: Increment value
63
+ def increment(name, value = 1, tags: {})
64
+ return unless enabled?
65
+
66
+ ensure_provisioned!
67
+ return unless BrainzLab.configuration.flux_valid?
68
+
69
+ metric = {
70
+ type: "counter",
71
+ name: name,
72
+ value: value,
73
+ tags: tags,
74
+ timestamp: Time.now.utc.iso8601(3)
75
+ }
76
+
77
+ buffer.add(:metric, metric)
78
+ end
79
+
80
+ # Counter: Decrement value
81
+ def decrement(name, value = 1, tags: {})
82
+ increment(name, -value, tags: tags)
83
+ end
84
+
85
+ # Distribution: Statistical aggregation
86
+ def distribution(name, value, tags: {})
87
+ return unless enabled?
88
+
89
+ ensure_provisioned!
90
+ return unless BrainzLab.configuration.flux_valid?
91
+
92
+ metric = {
93
+ type: "distribution",
94
+ name: name,
95
+ value: value,
96
+ tags: tags,
97
+ timestamp: Time.now.utc.iso8601(3)
98
+ }
99
+
100
+ buffer.add(:metric, metric)
101
+ end
102
+
103
+ # Set: Unique count (cardinality)
104
+ def set(name, value, tags: {})
105
+ return unless enabled?
106
+
107
+ ensure_provisioned!
108
+ return unless BrainzLab.configuration.flux_valid?
109
+
110
+ metric = {
111
+ type: "set",
112
+ name: name,
113
+ value: value.to_s,
114
+ tags: tags,
115
+ timestamp: Time.now.utc.iso8601(3)
116
+ }
117
+
118
+ buffer.add(:metric, metric)
119
+ end
120
+
121
+ # === CONVENIENCE METHODS ===
122
+
123
+ # Time a block and record as distribution
124
+ def measure(name, tags: {})
125
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
126
+ begin
127
+ yield
128
+ ensure
129
+ duration_ms = (Process.clock_gettime(Process::CLOCK_MONOTONIC) - start) * 1000
130
+ distribution(name, duration_ms, tags: tags.merge(unit: "ms"))
131
+ end
132
+ end
133
+
134
+ # Flush any buffered data immediately
135
+ def flush!
136
+ buffer.flush!
137
+ end
138
+
139
+ # === INTERNAL ===
140
+
141
+ def ensure_provisioned!
142
+ return if @provisioned
143
+
144
+ @provisioned = true
145
+ provisioner.ensure_project!
146
+ end
147
+
148
+ def provisioner
149
+ @provisioner ||= Provisioner.new(BrainzLab.configuration)
150
+ end
151
+
152
+ def client
153
+ @client ||= Client.new(BrainzLab.configuration)
154
+ end
155
+
156
+ def buffer
157
+ @buffer ||= Buffer.new(client)
158
+ end
159
+
160
+ def reset!
161
+ @client = nil
162
+ @buffer = nil
163
+ @provisioner = nil
164
+ @provisioned = false
165
+ end
166
+
167
+ private
168
+
169
+ def enabled?
170
+ BrainzLab.configuration.flux_effectively_enabled?
171
+ end
172
+ end
173
+ end
174
+ end
@@ -61,7 +61,7 @@ module BrainzLab
61
61
  sql: truncate_sql(sql),
62
62
  duration_ms: duration,
63
63
  cached: payload[:cached] || false,
64
- connection_name: payload[:connection]&.pool&.connection_class&.name
64
+ connection_name: extract_connection_name(payload[:connection])
65
65
  }.compact
66
66
  )
67
67
  rescue StandardError => e
@@ -95,6 +95,23 @@ module BrainzLab
95
95
  false
96
96
  end
97
97
 
98
+ def extract_connection_name(connection)
99
+ return nil unless connection
100
+
101
+ # Rails 8.1+ uses db_config.name on the pool
102
+ # Older versions used connection_class but that's removed in Rails 8.1
103
+ if connection.respond_to?(:pool)
104
+ pool = connection.pool
105
+ if pool.respond_to?(:db_config) && pool.db_config.respond_to?(:name)
106
+ pool.db_config.name
107
+ end
108
+ elsif connection.respond_to?(:db_config) && connection.db_config.respond_to?(:name)
109
+ connection.db_config.name
110
+ end
111
+ rescue StandardError
112
+ nil
113
+ end
114
+
98
115
  def truncate_sql(sql)
99
116
  return nil unless sql
100
117
 
@@ -0,0 +1,179 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BrainzLab
4
+ module Instrumentation
5
+ module AWSInstrumentation
6
+ class << self
7
+ def install!
8
+ return unless defined?(::Aws)
9
+
10
+ install_plugin!
11
+
12
+ BrainzLab.debug_log("[Instrumentation] AWS SDK instrumentation installed")
13
+ end
14
+
15
+ private
16
+
17
+ def install_plugin!
18
+ # AWS SDK v3 uses plugins
19
+ if defined?(::Aws::Plugins)
20
+ ::Aws.config[:plugins] ||= []
21
+ ::Aws.config[:plugins] << BrainzLabPlugin unless ::Aws.config[:plugins].include?(BrainzLabPlugin)
22
+ end
23
+
24
+ # Also hook into Seahorse for lower-level tracking
25
+ if defined?(::Seahorse::Client::Base)
26
+ install_seahorse_handler!
27
+ end
28
+ end
29
+
30
+ def install_seahorse_handler!
31
+ handler_class = Class.new(::Seahorse::Client::Handler) do
32
+ def call(context)
33
+ started_at = Time.now
34
+ service = context.client.class.name.split("::")[1] || "AWS"
35
+ operation = context.operation_name.to_s
36
+
37
+ begin
38
+ response = @handler.call(context)
39
+ track_success(service, operation, started_at, context, response)
40
+ response
41
+ rescue StandardError => e
42
+ track_error(service, operation, started_at, context, e)
43
+ raise
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ def track_success(service, operation, started_at, context, response)
50
+ duration_ms = ((Time.now - started_at) * 1000).round(2)
51
+
52
+ BrainzLab::Reflex.add_breadcrumb(
53
+ "AWS #{service}.#{operation}",
54
+ category: "aws",
55
+ level: :info,
56
+ data: {
57
+ service: service,
58
+ operation: operation,
59
+ region: context.config.region,
60
+ duration_ms: duration_ms
61
+ }
62
+ )
63
+
64
+ if BrainzLab.configuration.flux_effectively_enabled?
65
+ tags = { service: service, operation: operation, region: context.config.region }
66
+ BrainzLab::Flux.distribution("aws.duration_ms", duration_ms, tags: tags)
67
+ BrainzLab::Flux.increment("aws.requests", tags: tags)
68
+ end
69
+ end
70
+
71
+ def track_error(service, operation, started_at, context, error)
72
+ duration_ms = ((Time.now - started_at) * 1000).round(2)
73
+
74
+ BrainzLab::Reflex.add_breadcrumb(
75
+ "AWS #{service}.#{operation} failed: #{error.message}",
76
+ category: "aws",
77
+ level: :error,
78
+ data: {
79
+ service: service,
80
+ operation: operation,
81
+ error: error.class.name
82
+ }
83
+ )
84
+
85
+ if BrainzLab.configuration.flux_effectively_enabled?
86
+ tags = { service: service, operation: operation, error_class: error.class.name }
87
+ BrainzLab::Flux.increment("aws.errors", tags: tags)
88
+ end
89
+ end
90
+ end
91
+
92
+ ::Seahorse::Client::Base.add_plugin(
93
+ Class.new(::Seahorse::Client::Plugin) do
94
+ define_method(:add_handlers) do |handlers, config|
95
+ handlers.add(handler_class, step: :validate, priority: 0)
96
+ end
97
+ end
98
+ )
99
+ end
100
+ end
101
+
102
+ # Aws SDK Plugin
103
+ class BrainzLabPlugin
104
+ def self.add_handlers(handlers, config)
105
+ handlers.add(Handler, step: :validate, priority: 0)
106
+ end
107
+
108
+ class Handler
109
+ def initialize(handler)
110
+ @handler = handler
111
+ end
112
+
113
+ def call(context)
114
+ started_at = Time.now
115
+ service = extract_service(context)
116
+ operation = context.operation_name.to_s
117
+
118
+ begin
119
+ response = @handler.call(context)
120
+ track_request(service, operation, started_at, context, response)
121
+ response
122
+ rescue StandardError => e
123
+ track_error(service, operation, started_at, context, e)
124
+ raise
125
+ end
126
+ end
127
+
128
+ private
129
+
130
+ def extract_service(context)
131
+ context.client.class.name.to_s.split("::")[1] || "AWS"
132
+ end
133
+
134
+ def track_request(service, operation, started_at, context, response)
135
+ duration_ms = ((Time.now - started_at) * 1000).round(2)
136
+ region = context.config.region rescue "unknown"
137
+
138
+ BrainzLab::Reflex.add_breadcrumb(
139
+ "AWS #{service}.#{operation}",
140
+ category: "aws",
141
+ level: :info,
142
+ data: {
143
+ service: service,
144
+ operation: operation,
145
+ region: region,
146
+ duration_ms: duration_ms,
147
+ retries: context.retries
148
+ }
149
+ )
150
+
151
+ if BrainzLab.configuration.flux_effectively_enabled?
152
+ tags = { service: service, operation: operation, region: region }
153
+ BrainzLab::Flux.distribution("aws.duration_ms", duration_ms, tags: tags)
154
+ BrainzLab::Flux.increment("aws.requests", tags: tags)
155
+
156
+ if context.retries > 0
157
+ BrainzLab::Flux.increment("aws.retries", value: context.retries, tags: tags)
158
+ end
159
+ end
160
+ end
161
+
162
+ def track_error(service, operation, started_at, context, error)
163
+ BrainzLab::Reflex.add_breadcrumb(
164
+ "AWS #{service}.#{operation} failed",
165
+ category: "aws",
166
+ level: :error,
167
+ data: { service: service, operation: operation, error: error.class.name }
168
+ )
169
+
170
+ if BrainzLab.configuration.flux_effectively_enabled?
171
+ tags = { service: service, operation: operation, error_class: error.class.name }
172
+ BrainzLab::Flux.increment("aws.errors", tags: tags)
173
+ end
174
+ end
175
+ end
176
+ end
177
+ end
178
+ end
179
+ end