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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +68 -0
- data/LICENSE +11 -0
- data/README.md +571 -0
- data/lib/brainzlab/beacon/client.rb +227 -0
- data/lib/brainzlab/beacon/provisioner.rb +44 -0
- data/lib/brainzlab/beacon.rb +215 -0
- data/lib/brainzlab/configuration.rb +676 -0
- data/lib/brainzlab/context.rb +90 -0
- data/lib/brainzlab/cortex/cache.rb +59 -0
- data/lib/brainzlab/cortex/client.rb +159 -0
- data/lib/brainzlab/cortex/provisioner.rb +49 -0
- data/lib/brainzlab/cortex.rb +223 -0
- data/lib/brainzlab/debug.rb +305 -0
- data/lib/brainzlab/dendrite/client.rb +250 -0
- data/lib/brainzlab/dendrite/provisioner.rb +44 -0
- data/lib/brainzlab/dendrite.rb +195 -0
- data/lib/brainzlab/development/logger.rb +150 -0
- data/lib/brainzlab/development/store.rb +121 -0
- data/lib/brainzlab/development.rb +72 -0
- data/lib/brainzlab/devtools/assets/devtools.css +1329 -0
- data/lib/brainzlab/devtools/assets/devtools.js +396 -0
- data/lib/brainzlab/devtools/assets/logo.svg +6 -0
- data/lib/brainzlab/devtools/assets/templates/debug_panel.html.erb +511 -0
- data/lib/brainzlab/devtools/assets/templates/error_page.html.erb +1086 -0
- data/lib/brainzlab/devtools/data/collector.rb +248 -0
- data/lib/brainzlab/devtools/middleware/asset_server.rb +63 -0
- data/lib/brainzlab/devtools/middleware/database_handler.rb +177 -0
- data/lib/brainzlab/devtools/middleware/debug_panel.rb +126 -0
- data/lib/brainzlab/devtools/middleware/error_page.rb +377 -0
- data/lib/brainzlab/devtools/renderers/debug_panel_renderer.rb +159 -0
- data/lib/brainzlab/devtools/renderers/error_page_renderer.rb +98 -0
- data/lib/brainzlab/devtools.rb +75 -0
- data/lib/brainzlab/errors.rb +490 -0
- data/lib/brainzlab/flux/buffer.rb +96 -0
- data/lib/brainzlab/flux/client.rb +68 -0
- data/lib/brainzlab/flux/provisioner.rb +124 -0
- data/lib/brainzlab/flux.rb +184 -0
- data/lib/brainzlab/instrumentation/action_cable.rb +351 -0
- data/lib/brainzlab/instrumentation/action_controller.rb +649 -0
- data/lib/brainzlab/instrumentation/action_dispatch.rb +259 -0
- data/lib/brainzlab/instrumentation/action_mailbox.rb +197 -0
- data/lib/brainzlab/instrumentation/action_mailer.rb +182 -0
- data/lib/brainzlab/instrumentation/action_view.rb +380 -0
- data/lib/brainzlab/instrumentation/active_job.rb +569 -0
- data/lib/brainzlab/instrumentation/active_record.rb +559 -0
- data/lib/brainzlab/instrumentation/active_storage.rb +541 -0
- data/lib/brainzlab/instrumentation/active_support_cache.rb +730 -0
- data/lib/brainzlab/instrumentation/aws.rb +183 -0
- data/lib/brainzlab/instrumentation/dalli.rb +108 -0
- data/lib/brainzlab/instrumentation/delayed_job.rb +234 -0
- data/lib/brainzlab/instrumentation/elasticsearch.rb +209 -0
- data/lib/brainzlab/instrumentation/excon.rb +152 -0
- data/lib/brainzlab/instrumentation/faraday.rb +181 -0
- data/lib/brainzlab/instrumentation/good_job.rb +102 -0
- data/lib/brainzlab/instrumentation/grape.rb +293 -0
- data/lib/brainzlab/instrumentation/graphql.rb +252 -0
- data/lib/brainzlab/instrumentation/httparty.rb +193 -0
- data/lib/brainzlab/instrumentation/mongodb.rb +187 -0
- data/lib/brainzlab/instrumentation/net_http.rb +114 -0
- data/lib/brainzlab/instrumentation/rails_deprecation.rb +139 -0
- data/lib/brainzlab/instrumentation/railties.rb +134 -0
- data/lib/brainzlab/instrumentation/redis.rb +324 -0
- data/lib/brainzlab/instrumentation/resque.rb +114 -0
- data/lib/brainzlab/instrumentation/sidekiq.rb +265 -0
- data/lib/brainzlab/instrumentation/solid_queue.rb +194 -0
- data/lib/brainzlab/instrumentation/stripe.rb +163 -0
- data/lib/brainzlab/instrumentation/typhoeus.rb +106 -0
- data/lib/brainzlab/instrumentation.rb +360 -0
- data/lib/brainzlab/nerve/client.rb +235 -0
- data/lib/brainzlab/nerve/provisioner.rb +44 -0
- data/lib/brainzlab/nerve.rb +219 -0
- data/lib/brainzlab/pulse/client.rb +203 -0
- data/lib/brainzlab/pulse/instrumentation.rb +401 -0
- data/lib/brainzlab/pulse/propagation.rb +241 -0
- data/lib/brainzlab/pulse/provisioner.rb +114 -0
- data/lib/brainzlab/pulse/tracer.rb +111 -0
- data/lib/brainzlab/pulse.rb +294 -0
- data/lib/brainzlab/rails/log_formatter.rb +807 -0
- data/lib/brainzlab/rails/log_subscriber.rb +334 -0
- data/lib/brainzlab/rails/railtie.rb +606 -0
- data/lib/brainzlab/recall/buffer.rb +66 -0
- data/lib/brainzlab/recall/client.rb +158 -0
- data/lib/brainzlab/recall/logger.rb +116 -0
- data/lib/brainzlab/recall/provisioner.rb +130 -0
- data/lib/brainzlab/recall.rb +175 -0
- data/lib/brainzlab/reflex/breadcrumbs.rb +55 -0
- data/lib/brainzlab/reflex/client.rb +150 -0
- data/lib/brainzlab/reflex/provisioner.rb +116 -0
- data/lib/brainzlab/reflex.rb +421 -0
- data/lib/brainzlab/sentinel/client.rb +236 -0
- data/lib/brainzlab/sentinel/provisioner.rb +44 -0
- data/lib/brainzlab/sentinel.rb +165 -0
- data/lib/brainzlab/signal/client.rb +60 -0
- data/lib/brainzlab/signal/provisioner.rb +115 -0
- data/lib/brainzlab/signal.rb +136 -0
- data/lib/brainzlab/synapse/client.rb +308 -0
- data/lib/brainzlab/synapse/provisioner.rb +44 -0
- data/lib/brainzlab/synapse.rb +270 -0
- data/lib/brainzlab/testing/event_store.rb +377 -0
- data/lib/brainzlab/testing/helpers.rb +650 -0
- data/lib/brainzlab/testing/matchers.rb +391 -0
- data/lib/brainzlab/testing.rb +327 -0
- data/lib/brainzlab/utilities/circuit_breaker.rb +290 -0
- data/lib/brainzlab/utilities/health_check.rb +294 -0
- data/lib/brainzlab/utilities/log_formatter.rb +254 -0
- data/lib/brainzlab/utilities/rate_limiter.rb +230 -0
- data/lib/brainzlab/utilities.rb +17 -0
- data/lib/brainzlab/vault/cache.rb +80 -0
- data/lib/brainzlab/vault/client.rb +216 -0
- data/lib/brainzlab/vault/provisioner.rb +49 -0
- data/lib/brainzlab/vault.rb +262 -0
- data/lib/brainzlab/version.rb +5 -0
- data/lib/brainzlab/vision/client.rb +175 -0
- data/lib/brainzlab/vision/provisioner.rb +136 -0
- data/lib/brainzlab/vision.rb +155 -0
- data/lib/brainzlab-sdk.rb +3 -0
- data/lib/brainzlab.rb +306 -0
- data/lib/generators/brainzlab/install/install_generator.rb +63 -0
- data/lib/generators/brainzlab/install/templates/brainzlab.rb.tt +77 -0
- metadata +251 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BrainzLab
|
|
4
|
+
module Instrumentation
|
|
5
|
+
module TyphoeusInstrumentation
|
|
6
|
+
class << self
|
|
7
|
+
def install!
|
|
8
|
+
return unless defined?(::Typhoeus)
|
|
9
|
+
|
|
10
|
+
install_callbacks!
|
|
11
|
+
|
|
12
|
+
BrainzLab.debug_log('[Instrumentation] Typhoeus instrumentation installed')
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
def install_callbacks!
|
|
18
|
+
::Typhoeus.on_complete do |response|
|
|
19
|
+
track_request(response)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def track_request(response)
|
|
24
|
+
request = response.request
|
|
25
|
+
return unless request
|
|
26
|
+
|
|
27
|
+
uri = begin
|
|
28
|
+
URI.parse(request.base_url)
|
|
29
|
+
rescue StandardError
|
|
30
|
+
nil
|
|
31
|
+
end
|
|
32
|
+
return unless uri
|
|
33
|
+
|
|
34
|
+
host = uri.host
|
|
35
|
+
return if skip_host?(host)
|
|
36
|
+
|
|
37
|
+
method = (request.options[:method] || :get).to_s.upcase
|
|
38
|
+
path = uri.path.empty? ? '/' : uri.path
|
|
39
|
+
status = response.response_code
|
|
40
|
+
duration_ms = (response.total_time * 1000).round(2)
|
|
41
|
+
|
|
42
|
+
# Add breadcrumb
|
|
43
|
+
BrainzLab::Reflex.add_breadcrumb(
|
|
44
|
+
"HTTP #{method} #{host}#{path} -> #{status}",
|
|
45
|
+
category: 'http',
|
|
46
|
+
level: response.success? ? :info : :error,
|
|
47
|
+
data: {
|
|
48
|
+
method: method,
|
|
49
|
+
host: host,
|
|
50
|
+
path: path,
|
|
51
|
+
status: status,
|
|
52
|
+
duration_ms: duration_ms
|
|
53
|
+
}
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
# Track with Flux
|
|
57
|
+
return unless BrainzLab.configuration.flux_effectively_enabled?
|
|
58
|
+
|
|
59
|
+
tags = { host: host, method: method, status: status.to_s }
|
|
60
|
+
BrainzLab::Flux.distribution('http.typhoeus.duration_ms', duration_ms, tags: tags)
|
|
61
|
+
BrainzLab::Flux.increment('http.typhoeus.requests', tags: tags)
|
|
62
|
+
|
|
63
|
+
BrainzLab::Flux.increment('http.typhoeus.errors', tags: tags) unless response.success?
|
|
64
|
+
|
|
65
|
+
return unless response.timed_out?
|
|
66
|
+
|
|
67
|
+
BrainzLab::Flux.increment('http.typhoeus.timeouts', tags: { host: host })
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def skip_host?(host)
|
|
71
|
+
return true unless host
|
|
72
|
+
|
|
73
|
+
ignore_hosts = BrainzLab.configuration.http_ignore_hosts || []
|
|
74
|
+
ignore_hosts.any? { |h| host.include?(h) }
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Hydra instrumentation for parallel requests
|
|
79
|
+
module HydraInstrumentation
|
|
80
|
+
def self.install!
|
|
81
|
+
return unless defined?(::Typhoeus::Hydra)
|
|
82
|
+
|
|
83
|
+
::Typhoeus::Hydra.class_eval do
|
|
84
|
+
alias_method :original_run, :run
|
|
85
|
+
|
|
86
|
+
def run
|
|
87
|
+
started_at = Time.now
|
|
88
|
+
request_count = queued_requests.size
|
|
89
|
+
|
|
90
|
+
result = original_run
|
|
91
|
+
|
|
92
|
+
duration_ms = ((Time.now - started_at) * 1000).round(2)
|
|
93
|
+
|
|
94
|
+
if BrainzLab.configuration.flux_effectively_enabled?
|
|
95
|
+
BrainzLab::Flux.distribution('http.typhoeus.hydra.duration_ms', duration_ms)
|
|
96
|
+
BrainzLab::Flux.distribution('http.typhoeus.hydra.request_count', request_count)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
result
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BrainzLab
|
|
4
|
+
module Instrumentation
|
|
5
|
+
class << self
|
|
6
|
+
def install!
|
|
7
|
+
config = BrainzLab.configuration
|
|
8
|
+
|
|
9
|
+
# Skip all instrumentation if SDK is disabled
|
|
10
|
+
unless config.enabled?
|
|
11
|
+
BrainzLab.debug_log('[Instrumentation] SDK disabled via BRAINZLAB_SDK_ENABLED=false, skipping all instrumentation')
|
|
12
|
+
return
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Skip Rails-specific instrumentation if brainzlab-rails gem is handling it
|
|
16
|
+
# This prevents double-tracking of events
|
|
17
|
+
if config.rails_instrumentation_handled_externally
|
|
18
|
+
BrainzLab.debug_log('[Instrumentation] Rails instrumentation handled by brainzlab-rails gem, skipping SDK instrumentation')
|
|
19
|
+
install_non_rails_instrumentation!(config)
|
|
20
|
+
return
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# HTTP client instrumentation
|
|
24
|
+
if config.instrument_http
|
|
25
|
+
install_net_http!
|
|
26
|
+
install_faraday!
|
|
27
|
+
install_httparty!
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Database instrumentation (breadcrumbs for Reflex)
|
|
31
|
+
install_active_record! if config.instrument_active_record
|
|
32
|
+
|
|
33
|
+
# Redis instrumentation
|
|
34
|
+
install_redis! if config.instrument_redis
|
|
35
|
+
|
|
36
|
+
# Background job instrumentation
|
|
37
|
+
install_sidekiq! if config.instrument_sidekiq
|
|
38
|
+
|
|
39
|
+
# GraphQL instrumentation
|
|
40
|
+
install_graphql! if config.instrument_graphql
|
|
41
|
+
|
|
42
|
+
# MongoDB instrumentation
|
|
43
|
+
install_mongodb! if config.instrument_mongodb
|
|
44
|
+
|
|
45
|
+
# Elasticsearch instrumentation
|
|
46
|
+
install_elasticsearch! if config.instrument_elasticsearch
|
|
47
|
+
|
|
48
|
+
# Rails MVC instrumentation
|
|
49
|
+
install_action_controller! if config.instrument_action_controller
|
|
50
|
+
install_action_view! if config.instrument_action_view
|
|
51
|
+
install_action_mailer! if config.instrument_action_mailer
|
|
52
|
+
|
|
53
|
+
# ActiveJob instrumentation (covers all job backends)
|
|
54
|
+
install_active_job! if config.instrument_active_job
|
|
55
|
+
|
|
56
|
+
# ActiveSupport::Cache instrumentation
|
|
57
|
+
install_active_support_cache! if config.instrument_active_support_cache
|
|
58
|
+
|
|
59
|
+
# Delayed::Job instrumentation
|
|
60
|
+
install_delayed_job! if config.instrument_delayed_job
|
|
61
|
+
|
|
62
|
+
# Grape API instrumentation
|
|
63
|
+
install_grape! if config.instrument_grape
|
|
64
|
+
|
|
65
|
+
# Modern job queue instrumentation
|
|
66
|
+
install_solid_queue! if config.instrument_solid_queue
|
|
67
|
+
install_good_job! if config.instrument_good_job
|
|
68
|
+
install_resque! if config.instrument_resque
|
|
69
|
+
|
|
70
|
+
# Additional HTTP clients
|
|
71
|
+
install_excon! if config.instrument_excon
|
|
72
|
+
install_typhoeus! if config.instrument_typhoeus
|
|
73
|
+
|
|
74
|
+
# Caching
|
|
75
|
+
install_dalli! if config.instrument_dalli
|
|
76
|
+
|
|
77
|
+
# Cloud & Payment
|
|
78
|
+
install_aws! if config.instrument_aws
|
|
79
|
+
install_stripe! if config.instrument_stripe
|
|
80
|
+
|
|
81
|
+
# File storage
|
|
82
|
+
install_active_storage! if config.instrument_active_storage
|
|
83
|
+
|
|
84
|
+
# WebSocket
|
|
85
|
+
install_action_cable! if config.instrument_action_cable
|
|
86
|
+
|
|
87
|
+
# Rails framework events
|
|
88
|
+
install_action_dispatch! if config.instrument_action_dispatch
|
|
89
|
+
install_rails_deprecation! if config.instrument_rails_deprecation
|
|
90
|
+
install_action_mailbox! if config.instrument_action_mailbox
|
|
91
|
+
install_railties! if config.instrument_railties
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def install_net_http!
|
|
95
|
+
require_relative 'instrumentation/net_http'
|
|
96
|
+
NetHttp.install!
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def install_faraday!
|
|
100
|
+
return unless defined?(::Faraday)
|
|
101
|
+
|
|
102
|
+
require_relative 'instrumentation/faraday'
|
|
103
|
+
FaradayMiddleware.install!
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def install_httparty!
|
|
107
|
+
return unless defined?(::HTTParty)
|
|
108
|
+
|
|
109
|
+
require_relative 'instrumentation/httparty'
|
|
110
|
+
HTTPartyInstrumentation.install!
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def install_active_record!
|
|
114
|
+
require_relative 'instrumentation/active_record'
|
|
115
|
+
ActiveRecord.install!
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def install_redis!
|
|
119
|
+
return unless defined?(::Redis)
|
|
120
|
+
|
|
121
|
+
require_relative 'instrumentation/redis'
|
|
122
|
+
RedisInstrumentation.install!
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def install_sidekiq!
|
|
126
|
+
return unless defined?(::Sidekiq)
|
|
127
|
+
|
|
128
|
+
require_relative 'instrumentation/sidekiq'
|
|
129
|
+
SidekiqInstrumentation.install!
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def install_graphql!
|
|
133
|
+
return unless defined?(::GraphQL)
|
|
134
|
+
|
|
135
|
+
require_relative 'instrumentation/graphql'
|
|
136
|
+
GraphQLInstrumentation.install!
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def install_mongodb!
|
|
140
|
+
return unless defined?(::Mongo) || defined?(::Mongoid)
|
|
141
|
+
|
|
142
|
+
require_relative 'instrumentation/mongodb'
|
|
143
|
+
MongoDBInstrumentation.install!
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def install_elasticsearch!
|
|
147
|
+
return unless defined?(::Elasticsearch) || defined?(::OpenSearch)
|
|
148
|
+
|
|
149
|
+
require_relative 'instrumentation/elasticsearch'
|
|
150
|
+
ElasticsearchInstrumentation.install!
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def install_action_controller!
|
|
154
|
+
return unless defined?(::ActionController)
|
|
155
|
+
|
|
156
|
+
require_relative 'instrumentation/action_controller'
|
|
157
|
+
ActionController.install!
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def install_action_view!
|
|
161
|
+
return unless defined?(::ActionView)
|
|
162
|
+
|
|
163
|
+
require_relative 'instrumentation/action_view'
|
|
164
|
+
ActionView.install!
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def install_action_mailer!
|
|
168
|
+
return unless defined?(::ActionMailer)
|
|
169
|
+
|
|
170
|
+
require_relative 'instrumentation/action_mailer'
|
|
171
|
+
ActionMailerInstrumentation.install!
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def install_active_job!
|
|
175
|
+
return unless defined?(::ActiveJob)
|
|
176
|
+
|
|
177
|
+
require_relative 'instrumentation/active_job'
|
|
178
|
+
ActiveJob.install!
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def install_active_support_cache!
|
|
182
|
+
return unless defined?(::ActiveSupport::Cache)
|
|
183
|
+
|
|
184
|
+
require_relative 'instrumentation/active_support_cache'
|
|
185
|
+
ActiveSupportCache.install!
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def install_delayed_job!
|
|
189
|
+
return unless defined?(::Delayed::Job) || defined?(::Delayed::Backend)
|
|
190
|
+
|
|
191
|
+
require_relative 'instrumentation/delayed_job'
|
|
192
|
+
DelayedJobInstrumentation.install!
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def install_grape!
|
|
196
|
+
return unless defined?(::Grape::API)
|
|
197
|
+
|
|
198
|
+
require_relative 'instrumentation/grape'
|
|
199
|
+
GrapeInstrumentation.install!
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def install_solid_queue!
|
|
203
|
+
return unless defined?(::SolidQueue)
|
|
204
|
+
|
|
205
|
+
require_relative 'instrumentation/solid_queue'
|
|
206
|
+
SolidQueueInstrumentation.install!
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def install_good_job!
|
|
210
|
+
return unless defined?(::GoodJob)
|
|
211
|
+
|
|
212
|
+
require_relative 'instrumentation/good_job'
|
|
213
|
+
GoodJobInstrumentation.install!
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def install_resque!
|
|
217
|
+
return unless defined?(::Resque)
|
|
218
|
+
|
|
219
|
+
require_relative 'instrumentation/resque'
|
|
220
|
+
ResqueInstrumentation.install!
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
def install_excon!
|
|
224
|
+
return unless defined?(::Excon)
|
|
225
|
+
|
|
226
|
+
require_relative 'instrumentation/excon'
|
|
227
|
+
ExconInstrumentation.install!
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def install_typhoeus!
|
|
231
|
+
return unless defined?(::Typhoeus)
|
|
232
|
+
|
|
233
|
+
require_relative 'instrumentation/typhoeus'
|
|
234
|
+
TyphoeusInstrumentation.install!
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def install_dalli!
|
|
238
|
+
return unless defined?(::Dalli::Client)
|
|
239
|
+
|
|
240
|
+
require_relative 'instrumentation/dalli'
|
|
241
|
+
DalliInstrumentation.install!
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def install_aws!
|
|
245
|
+
return unless defined?(::Aws)
|
|
246
|
+
|
|
247
|
+
require_relative 'instrumentation/aws'
|
|
248
|
+
AWSInstrumentation.install!
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def install_stripe!
|
|
252
|
+
return unless defined?(::Stripe)
|
|
253
|
+
|
|
254
|
+
require_relative 'instrumentation/stripe'
|
|
255
|
+
StripeInstrumentation.install!
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
def install_active_storage!
|
|
259
|
+
return unless defined?(::ActiveStorage)
|
|
260
|
+
|
|
261
|
+
require_relative 'instrumentation/active_storage'
|
|
262
|
+
ActiveStorage.install!
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
def install_action_cable!
|
|
266
|
+
return unless defined?(::ActionCable)
|
|
267
|
+
|
|
268
|
+
require_relative 'instrumentation/action_cable'
|
|
269
|
+
ActionCable.install!
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
def install_action_dispatch!
|
|
273
|
+
return unless defined?(::ActionDispatch)
|
|
274
|
+
|
|
275
|
+
require_relative 'instrumentation/action_dispatch'
|
|
276
|
+
ActionDispatch.install!
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
def install_rails_deprecation!
|
|
280
|
+
return unless defined?(::Rails)
|
|
281
|
+
|
|
282
|
+
require_relative 'instrumentation/rails_deprecation'
|
|
283
|
+
RailsDeprecation.install!
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
def install_action_mailbox!
|
|
287
|
+
return unless defined?(::ActionMailbox)
|
|
288
|
+
|
|
289
|
+
require_relative 'instrumentation/action_mailbox'
|
|
290
|
+
ActionMailbox.install!
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
def install_railties!
|
|
294
|
+
return unless defined?(::Rails)
|
|
295
|
+
|
|
296
|
+
require_relative 'instrumentation/railties'
|
|
297
|
+
Railties.install!
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
# Manual installation methods for lazy-loaded libraries
|
|
301
|
+
def install_http!
|
|
302
|
+
install_net_http!
|
|
303
|
+
install_faraday!
|
|
304
|
+
install_httparty!
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
private
|
|
308
|
+
|
|
309
|
+
# Install only non-Rails-specific instrumentation
|
|
310
|
+
# Used when brainzlab-rails gem handles Rails events via ActiveSupport::Notifications
|
|
311
|
+
def install_non_rails_instrumentation!(config)
|
|
312
|
+
# HTTP client instrumentation (not Rails-specific)
|
|
313
|
+
if config.instrument_http
|
|
314
|
+
install_net_http!
|
|
315
|
+
install_faraday!
|
|
316
|
+
install_httparty!
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
# Redis instrumentation (not Rails-specific)
|
|
320
|
+
install_redis! if config.instrument_redis
|
|
321
|
+
|
|
322
|
+
# Sidekiq instrumentation (not Rails-specific, has its own hooks)
|
|
323
|
+
install_sidekiq! if config.instrument_sidekiq
|
|
324
|
+
|
|
325
|
+
# GraphQL instrumentation (not Rails-specific)
|
|
326
|
+
install_graphql! if config.instrument_graphql
|
|
327
|
+
|
|
328
|
+
# MongoDB instrumentation (not Rails-specific)
|
|
329
|
+
install_mongodb! if config.instrument_mongodb
|
|
330
|
+
|
|
331
|
+
# Elasticsearch instrumentation (not Rails-specific)
|
|
332
|
+
install_elasticsearch! if config.instrument_elasticsearch
|
|
333
|
+
|
|
334
|
+
# Delayed::Job instrumentation (not Rails-specific, has its own hooks)
|
|
335
|
+
install_delayed_job! if config.instrument_delayed_job
|
|
336
|
+
|
|
337
|
+
# Grape API instrumentation (not Rails-specific)
|
|
338
|
+
install_grape! if config.instrument_grape
|
|
339
|
+
|
|
340
|
+
# Modern job queue instrumentation (have their own hooks)
|
|
341
|
+
install_solid_queue! if config.instrument_solid_queue
|
|
342
|
+
install_good_job! if config.instrument_good_job
|
|
343
|
+
install_resque! if config.instrument_resque
|
|
344
|
+
|
|
345
|
+
# Additional HTTP clients (not Rails-specific)
|
|
346
|
+
install_excon! if config.instrument_excon
|
|
347
|
+
install_typhoeus! if config.instrument_typhoeus
|
|
348
|
+
|
|
349
|
+
# Dalli/Memcached (not Rails-specific, has its own hooks)
|
|
350
|
+
install_dalli! if config.instrument_dalli
|
|
351
|
+
|
|
352
|
+
# Cloud & Payment (not Rails-specific)
|
|
353
|
+
install_aws! if config.instrument_aws
|
|
354
|
+
install_stripe! if config.instrument_stripe
|
|
355
|
+
|
|
356
|
+
BrainzLab.debug_log('[Instrumentation] Non-Rails instrumentation installed (HTTP, Redis, Sidekiq, GraphQL, etc.)')
|
|
357
|
+
end
|
|
358
|
+
end
|
|
359
|
+
end
|
|
360
|
+
end
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'net/http'
|
|
4
|
+
require 'json'
|
|
5
|
+
require 'uri'
|
|
6
|
+
|
|
7
|
+
module BrainzLab
|
|
8
|
+
module Nerve
|
|
9
|
+
class Client
|
|
10
|
+
def initialize(config)
|
|
11
|
+
@config = config
|
|
12
|
+
@base_url = config.nerve_url || 'https://nerve.brainzlab.ai'
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Report a job execution
|
|
16
|
+
def report_job(job_class:, job_id:, queue:, status:, started_at:, ended_at:, **attributes)
|
|
17
|
+
response = request(
|
|
18
|
+
:post,
|
|
19
|
+
'/api/v1/jobs',
|
|
20
|
+
body: {
|
|
21
|
+
job_class: job_class,
|
|
22
|
+
job_id: job_id,
|
|
23
|
+
queue: queue,
|
|
24
|
+
status: status,
|
|
25
|
+
started_at: started_at.iso8601(3),
|
|
26
|
+
ended_at: ended_at.iso8601(3),
|
|
27
|
+
duration_ms: ((ended_at - started_at) * 1000).round(2),
|
|
28
|
+
**attributes
|
|
29
|
+
}
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
response.is_a?(Net::HTTPSuccess) || response.is_a?(Net::HTTPCreated)
|
|
33
|
+
rescue StandardError => e
|
|
34
|
+
log_error('report_job', e)
|
|
35
|
+
false
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Report a job failure
|
|
39
|
+
def report_failure(job_class:, job_id:, queue:, error_class:, error_message:, backtrace: nil, **attributes)
|
|
40
|
+
response = request(
|
|
41
|
+
:post,
|
|
42
|
+
'/api/v1/jobs/failures',
|
|
43
|
+
body: {
|
|
44
|
+
job_class: job_class,
|
|
45
|
+
job_id: job_id,
|
|
46
|
+
queue: queue,
|
|
47
|
+
error_class: error_class,
|
|
48
|
+
error_message: error_message,
|
|
49
|
+
backtrace: backtrace&.first(20),
|
|
50
|
+
failed_at: Time.now.utc.iso8601(3),
|
|
51
|
+
**attributes
|
|
52
|
+
}
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
response.is_a?(Net::HTTPSuccess) || response.is_a?(Net::HTTPCreated)
|
|
56
|
+
rescue StandardError => e
|
|
57
|
+
log_error('report_failure', e)
|
|
58
|
+
false
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Get job statistics
|
|
62
|
+
def stats(queue: nil, job_class: nil, period: '1h')
|
|
63
|
+
params = { period: period }
|
|
64
|
+
params[:queue] = queue if queue
|
|
65
|
+
params[:job_class] = job_class if job_class
|
|
66
|
+
|
|
67
|
+
response = request(:get, '/api/v1/stats', params: params)
|
|
68
|
+
|
|
69
|
+
return nil unless response.is_a?(Net::HTTPSuccess)
|
|
70
|
+
|
|
71
|
+
JSON.parse(response.body, symbolize_names: true)
|
|
72
|
+
rescue StandardError => e
|
|
73
|
+
log_error('stats', e)
|
|
74
|
+
nil
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# List recent jobs
|
|
78
|
+
def list_jobs(queue: nil, status: nil, limit: 100)
|
|
79
|
+
params = { limit: limit }
|
|
80
|
+
params[:queue] = queue if queue
|
|
81
|
+
params[:status] = status if status
|
|
82
|
+
|
|
83
|
+
response = request(:get, '/api/v1/jobs', params: params)
|
|
84
|
+
|
|
85
|
+
return [] unless response.is_a?(Net::HTTPSuccess)
|
|
86
|
+
|
|
87
|
+
data = JSON.parse(response.body, symbolize_names: true)
|
|
88
|
+
data[:jobs] || []
|
|
89
|
+
rescue StandardError => e
|
|
90
|
+
log_error('list_jobs', e)
|
|
91
|
+
[]
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# List queues
|
|
95
|
+
def list_queues
|
|
96
|
+
response = request(:get, '/api/v1/queues')
|
|
97
|
+
|
|
98
|
+
return [] unless response.is_a?(Net::HTTPSuccess)
|
|
99
|
+
|
|
100
|
+
data = JSON.parse(response.body, symbolize_names: true)
|
|
101
|
+
data[:queues] || []
|
|
102
|
+
rescue StandardError => e
|
|
103
|
+
log_error('list_queues', e)
|
|
104
|
+
[]
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Get queue details
|
|
108
|
+
def get_queue(name)
|
|
109
|
+
response = request(:get, "/api/v1/queues/#{CGI.escape(name)}")
|
|
110
|
+
|
|
111
|
+
return nil unless response.is_a?(Net::HTTPSuccess)
|
|
112
|
+
|
|
113
|
+
JSON.parse(response.body, symbolize_names: true)
|
|
114
|
+
rescue StandardError => e
|
|
115
|
+
log_error('get_queue', e)
|
|
116
|
+
nil
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Retry a failed job
|
|
120
|
+
def retry_job(job_id)
|
|
121
|
+
response = request(:post, "/api/v1/jobs/#{job_id}/retry")
|
|
122
|
+
response.is_a?(Net::HTTPSuccess)
|
|
123
|
+
rescue StandardError => e
|
|
124
|
+
log_error('retry_job', e)
|
|
125
|
+
false
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Delete a job
|
|
129
|
+
def delete_job(job_id)
|
|
130
|
+
response = request(:delete, "/api/v1/jobs/#{job_id}")
|
|
131
|
+
response.is_a?(Net::HTTPSuccess) || response.is_a?(Net::HTTPNoContent)
|
|
132
|
+
rescue StandardError => e
|
|
133
|
+
log_error('delete_job', e)
|
|
134
|
+
false
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Report queue metrics
|
|
138
|
+
def report_metrics(queue:, size:, latency_ms: nil, workers: nil)
|
|
139
|
+
response = request(
|
|
140
|
+
:post,
|
|
141
|
+
'/api/v1/metrics',
|
|
142
|
+
body: {
|
|
143
|
+
queue: queue,
|
|
144
|
+
size: size,
|
|
145
|
+
latency_ms: latency_ms,
|
|
146
|
+
workers: workers,
|
|
147
|
+
timestamp: Time.now.utc.iso8601(3)
|
|
148
|
+
}
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
response.is_a?(Net::HTTPSuccess)
|
|
152
|
+
rescue StandardError => e
|
|
153
|
+
log_error('report_metrics', e)
|
|
154
|
+
false
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def provision(project_id:, app_name:)
|
|
158
|
+
response = request(
|
|
159
|
+
:post,
|
|
160
|
+
'/api/v1/projects/provision',
|
|
161
|
+
body: { project_id: project_id, app_name: app_name },
|
|
162
|
+
use_service_key: true
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
response.is_a?(Net::HTTPSuccess) || response.is_a?(Net::HTTPCreated)
|
|
166
|
+
rescue StandardError => e
|
|
167
|
+
log_error('provision', e)
|
|
168
|
+
false
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
private
|
|
172
|
+
|
|
173
|
+
def request(method, path, headers: {}, body: nil, params: nil, use_service_key: false)
|
|
174
|
+
uri = URI.parse("#{@base_url}#{path}")
|
|
175
|
+
|
|
176
|
+
uri.query = URI.encode_www_form(params) if params
|
|
177
|
+
|
|
178
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
179
|
+
http.use_ssl = uri.scheme == 'https'
|
|
180
|
+
http.open_timeout = 5
|
|
181
|
+
http.read_timeout = 10
|
|
182
|
+
|
|
183
|
+
request = case method
|
|
184
|
+
when :get
|
|
185
|
+
Net::HTTP::Get.new(uri)
|
|
186
|
+
when :post
|
|
187
|
+
Net::HTTP::Post.new(uri)
|
|
188
|
+
when :put
|
|
189
|
+
Net::HTTP::Put.new(uri)
|
|
190
|
+
when :delete
|
|
191
|
+
Net::HTTP::Delete.new(uri)
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
request['Content-Type'] = 'application/json'
|
|
195
|
+
request['Accept'] = 'application/json'
|
|
196
|
+
|
|
197
|
+
if use_service_key
|
|
198
|
+
request['X-Service-Key'] = @config.nerve_master_key || @config.secret_key
|
|
199
|
+
else
|
|
200
|
+
auth_key = @config.nerve_api_key || @config.secret_key
|
|
201
|
+
request['Authorization'] = "Bearer #{auth_key}" if auth_key
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
headers.each { |k, v| request[k] = v }
|
|
205
|
+
request.body = body.to_json if body
|
|
206
|
+
|
|
207
|
+
http.request(request)
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def log_error(operation, error)
|
|
211
|
+
structured_error = ErrorHandler.wrap(error, service: 'Nerve', operation: operation)
|
|
212
|
+
BrainzLab.debug_log("[Nerve::Client] #{operation} failed: #{structured_error.message}")
|
|
213
|
+
|
|
214
|
+
# Call on_error callback if configured
|
|
215
|
+
if @config.on_error
|
|
216
|
+
@config.on_error.call(structured_error, { service: 'Nerve', operation: operation })
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def handle_response_error(response, operation)
|
|
221
|
+
return if response.is_a?(Net::HTTPSuccess) || response.is_a?(Net::HTTPCreated) || response.is_a?(Net::HTTPNoContent)
|
|
222
|
+
|
|
223
|
+
structured_error = ErrorHandler.from_response(response, service: 'Nerve', operation: operation)
|
|
224
|
+
BrainzLab.debug_log("[Nerve::Client] #{operation} failed: #{structured_error.message}")
|
|
225
|
+
|
|
226
|
+
# Call on_error callback if configured
|
|
227
|
+
if @config.on_error
|
|
228
|
+
@config.on_error.call(structured_error, { service: 'Nerve', operation: operation })
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
structured_error
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
end
|