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,401 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BrainzLab
|
|
4
|
+
module Pulse
|
|
5
|
+
class Instrumentation
|
|
6
|
+
class << self
|
|
7
|
+
def install!
|
|
8
|
+
return unless BrainzLab.configuration.pulse_enabled
|
|
9
|
+
|
|
10
|
+
install_active_record!
|
|
11
|
+
install_action_view!
|
|
12
|
+
install_active_support_cache!
|
|
13
|
+
install_action_controller!
|
|
14
|
+
install_http_clients!
|
|
15
|
+
install_active_job!
|
|
16
|
+
install_action_cable!
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
# Track SQL queries
|
|
22
|
+
def install_active_record!
|
|
23
|
+
return unless defined?(ActiveRecord)
|
|
24
|
+
|
|
25
|
+
ActiveSupport::Notifications.subscribe('sql.active_record') do |*args|
|
|
26
|
+
event = ActiveSupport::Notifications::Event.new(*args)
|
|
27
|
+
next if skip_query?(event.payload)
|
|
28
|
+
|
|
29
|
+
sql = event.payload[:sql]
|
|
30
|
+
record_span(
|
|
31
|
+
name: event.payload[:name] || 'SQL',
|
|
32
|
+
kind: 'db',
|
|
33
|
+
started_at: event.time,
|
|
34
|
+
ended_at: event.end,
|
|
35
|
+
duration_ms: event.duration,
|
|
36
|
+
data: {
|
|
37
|
+
sql: truncate_sql(sql),
|
|
38
|
+
name: event.payload[:name],
|
|
39
|
+
cached: event.payload[:cached] || false,
|
|
40
|
+
table: extract_table(sql),
|
|
41
|
+
operation: extract_operation(sql)
|
|
42
|
+
}
|
|
43
|
+
)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Track view rendering
|
|
48
|
+
def install_action_view!
|
|
49
|
+
return unless defined?(ActionView)
|
|
50
|
+
|
|
51
|
+
ActiveSupport::Notifications.subscribe('render_template.action_view') do |*args|
|
|
52
|
+
event = ActiveSupport::Notifications::Event.new(*args)
|
|
53
|
+
|
|
54
|
+
record_span(
|
|
55
|
+
name: short_path(event.payload[:identifier]),
|
|
56
|
+
kind: 'render',
|
|
57
|
+
started_at: event.time,
|
|
58
|
+
ended_at: event.end,
|
|
59
|
+
duration_ms: event.duration,
|
|
60
|
+
data: {
|
|
61
|
+
identifier: event.payload[:identifier],
|
|
62
|
+
layout: event.payload[:layout]
|
|
63
|
+
}
|
|
64
|
+
)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
ActiveSupport::Notifications.subscribe('render_partial.action_view') do |*args|
|
|
68
|
+
event = ActiveSupport::Notifications::Event.new(*args)
|
|
69
|
+
|
|
70
|
+
record_span(
|
|
71
|
+
name: short_path(event.payload[:identifier]),
|
|
72
|
+
kind: 'render',
|
|
73
|
+
started_at: event.time,
|
|
74
|
+
ended_at: event.end,
|
|
75
|
+
duration_ms: event.duration,
|
|
76
|
+
data: {
|
|
77
|
+
identifier: event.payload[:identifier],
|
|
78
|
+
partial: true
|
|
79
|
+
}
|
|
80
|
+
)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
ActiveSupport::Notifications.subscribe('render_collection.action_view') do |*args|
|
|
84
|
+
event = ActiveSupport::Notifications::Event.new(*args)
|
|
85
|
+
|
|
86
|
+
record_span(
|
|
87
|
+
name: short_path(event.payload[:identifier]),
|
|
88
|
+
kind: 'render',
|
|
89
|
+
started_at: event.time,
|
|
90
|
+
ended_at: event.end,
|
|
91
|
+
duration_ms: event.duration,
|
|
92
|
+
data: {
|
|
93
|
+
identifier: event.payload[:identifier],
|
|
94
|
+
count: event.payload[:count],
|
|
95
|
+
collection: true
|
|
96
|
+
}
|
|
97
|
+
)
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Track cache operations
|
|
102
|
+
def install_active_support_cache!
|
|
103
|
+
%w[cache_read.active_support cache_write.active_support cache_delete.active_support].each do |event_name|
|
|
104
|
+
ActiveSupport::Notifications.subscribe(event_name) do |*args|
|
|
105
|
+
event = ActiveSupport::Notifications::Event.new(*args)
|
|
106
|
+
operation = event_name.split('.').first.sub('cache_', '')
|
|
107
|
+
|
|
108
|
+
record_span(
|
|
109
|
+
name: "Cache #{operation}",
|
|
110
|
+
kind: 'cache',
|
|
111
|
+
started_at: event.time,
|
|
112
|
+
ended_at: event.end,
|
|
113
|
+
duration_ms: event.duration,
|
|
114
|
+
data: {
|
|
115
|
+
key: truncate_key(event.payload[:key]),
|
|
116
|
+
hit: event.payload[:hit],
|
|
117
|
+
operation: operation
|
|
118
|
+
}
|
|
119
|
+
)
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Track controller processing for timing breakdown
|
|
125
|
+
def install_action_controller!
|
|
126
|
+
return unless defined?(ActionController)
|
|
127
|
+
|
|
128
|
+
ActiveSupport::Notifications.subscribe('process_action.action_controller') do |*args|
|
|
129
|
+
event = ActiveSupport::Notifications::Event.new(*args)
|
|
130
|
+
payload = event.payload
|
|
131
|
+
|
|
132
|
+
# Store timing breakdown in thread local for the middleware
|
|
133
|
+
Thread.current[:brainzlab_pulse_breakdown] = {
|
|
134
|
+
view_ms: payload[:view_runtime]&.round(2),
|
|
135
|
+
db_ms: payload[:db_runtime]&.round(2)
|
|
136
|
+
}
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Track external HTTP requests
|
|
141
|
+
def install_http_clients!
|
|
142
|
+
# Net::HTTP instrumentation
|
|
143
|
+
if defined?(Net::HTTP)
|
|
144
|
+
ActiveSupport::Notifications.subscribe('request.net_http') do |*args|
|
|
145
|
+
event = ActiveSupport::Notifications::Event.new(*args)
|
|
146
|
+
|
|
147
|
+
record_span(
|
|
148
|
+
name: "HTTP #{event.payload[:method]} #{event.payload[:host]}",
|
|
149
|
+
kind: 'http',
|
|
150
|
+
started_at: event.time,
|
|
151
|
+
ended_at: event.end,
|
|
152
|
+
duration_ms: event.duration,
|
|
153
|
+
data: {
|
|
154
|
+
method: event.payload[:method],
|
|
155
|
+
host: event.payload[:host],
|
|
156
|
+
path: event.payload[:path],
|
|
157
|
+
status: event.payload[:code]
|
|
158
|
+
}
|
|
159
|
+
)
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Faraday instrumentation
|
|
164
|
+
return unless defined?(Faraday)
|
|
165
|
+
|
|
166
|
+
ActiveSupport::Notifications.subscribe('request.faraday') do |*args|
|
|
167
|
+
event = ActiveSupport::Notifications::Event.new(*args)
|
|
168
|
+
env = event.payload[:env]
|
|
169
|
+
next unless env
|
|
170
|
+
|
|
171
|
+
record_span(
|
|
172
|
+
name: "HTTP #{env.method.to_s.upcase} #{env.url.host}",
|
|
173
|
+
kind: 'http',
|
|
174
|
+
started_at: event.time,
|
|
175
|
+
ended_at: event.end,
|
|
176
|
+
duration_ms: event.duration,
|
|
177
|
+
data: {
|
|
178
|
+
method: env.method.to_s.upcase,
|
|
179
|
+
host: env.url.host,
|
|
180
|
+
path: env.url.path,
|
|
181
|
+
status: env.status
|
|
182
|
+
}
|
|
183
|
+
)
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# Track ActiveJob/SolidQueue
|
|
188
|
+
def install_active_job!
|
|
189
|
+
return unless defined?(ActiveJob)
|
|
190
|
+
|
|
191
|
+
# Track job enqueuing
|
|
192
|
+
ActiveSupport::Notifications.subscribe('enqueue.active_job') do |*args|
|
|
193
|
+
event = ActiveSupport::Notifications::Event.new(*args)
|
|
194
|
+
job = event.payload[:job]
|
|
195
|
+
|
|
196
|
+
record_span(
|
|
197
|
+
name: "Enqueue #{job.class.name}",
|
|
198
|
+
kind: 'job',
|
|
199
|
+
started_at: event.time,
|
|
200
|
+
ended_at: event.end,
|
|
201
|
+
duration_ms: event.duration,
|
|
202
|
+
data: {
|
|
203
|
+
job_class: job.class.name,
|
|
204
|
+
job_id: job.job_id,
|
|
205
|
+
queue: job.queue_name
|
|
206
|
+
}
|
|
207
|
+
)
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
# Track job retry
|
|
211
|
+
ActiveSupport::Notifications.subscribe('retry_stopped.active_job') do |*args|
|
|
212
|
+
event = ActiveSupport::Notifications::Event.new(*args)
|
|
213
|
+
job = event.payload[:job]
|
|
214
|
+
error = event.payload[:error]
|
|
215
|
+
|
|
216
|
+
record_span(
|
|
217
|
+
name: "Retry stopped #{job.class.name}",
|
|
218
|
+
kind: 'job',
|
|
219
|
+
started_at: event.time,
|
|
220
|
+
ended_at: event.end,
|
|
221
|
+
duration_ms: event.duration,
|
|
222
|
+
error: true,
|
|
223
|
+
error_class: error&.class&.name,
|
|
224
|
+
error_message: error&.message,
|
|
225
|
+
data: {
|
|
226
|
+
job_class: job.class.name,
|
|
227
|
+
job_id: job.job_id,
|
|
228
|
+
queue: job.queue_name,
|
|
229
|
+
executions: job.executions
|
|
230
|
+
}
|
|
231
|
+
)
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
# Track job discard
|
|
235
|
+
ActiveSupport::Notifications.subscribe('discard.active_job') do |*args|
|
|
236
|
+
event = ActiveSupport::Notifications::Event.new(*args)
|
|
237
|
+
job = event.payload[:job]
|
|
238
|
+
error = event.payload[:error]
|
|
239
|
+
|
|
240
|
+
record_span(
|
|
241
|
+
name: "Discarded #{job.class.name}",
|
|
242
|
+
kind: 'job',
|
|
243
|
+
started_at: event.time,
|
|
244
|
+
ended_at: event.end,
|
|
245
|
+
duration_ms: event.duration,
|
|
246
|
+
error: true,
|
|
247
|
+
error_class: error&.class&.name,
|
|
248
|
+
error_message: error&.message,
|
|
249
|
+
data: {
|
|
250
|
+
job_class: job.class.name,
|
|
251
|
+
job_id: job.job_id,
|
|
252
|
+
queue: job.queue_name,
|
|
253
|
+
executions: job.executions
|
|
254
|
+
}
|
|
255
|
+
)
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
# Track ActionCable/SolidCable
|
|
260
|
+
def install_action_cable!
|
|
261
|
+
return unless defined?(ActionCable)
|
|
262
|
+
|
|
263
|
+
ActiveSupport::Notifications.subscribe('perform_action.action_cable') do |*args|
|
|
264
|
+
event = ActiveSupport::Notifications::Event.new(*args)
|
|
265
|
+
|
|
266
|
+
record_span(
|
|
267
|
+
name: "Cable #{event.payload[:channel_class]}##{event.payload[:action]}",
|
|
268
|
+
kind: 'cable',
|
|
269
|
+
started_at: event.time,
|
|
270
|
+
ended_at: event.end,
|
|
271
|
+
duration_ms: event.duration,
|
|
272
|
+
data: {
|
|
273
|
+
channel: event.payload[:channel_class],
|
|
274
|
+
action: event.payload[:action]
|
|
275
|
+
}
|
|
276
|
+
)
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
ActiveSupport::Notifications.subscribe('transmit.action_cable') do |*args|
|
|
280
|
+
event = ActiveSupport::Notifications::Event.new(*args)
|
|
281
|
+
|
|
282
|
+
record_span(
|
|
283
|
+
name: "Cable transmit #{event.payload[:channel_class]}",
|
|
284
|
+
kind: 'cable',
|
|
285
|
+
started_at: event.time,
|
|
286
|
+
ended_at: event.end,
|
|
287
|
+
duration_ms: event.duration,
|
|
288
|
+
data: {
|
|
289
|
+
channel: event.payload[:channel_class],
|
|
290
|
+
via: event.payload[:via]
|
|
291
|
+
}
|
|
292
|
+
)
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
ActiveSupport::Notifications.subscribe('broadcast.action_cable') do |*args|
|
|
296
|
+
event = ActiveSupport::Notifications::Event.new(*args)
|
|
297
|
+
|
|
298
|
+
record_span(
|
|
299
|
+
name: "Cable broadcast #{event.payload[:broadcasting]}",
|
|
300
|
+
kind: 'cable',
|
|
301
|
+
started_at: event.time,
|
|
302
|
+
ended_at: event.end,
|
|
303
|
+
duration_ms: event.duration,
|
|
304
|
+
data: {
|
|
305
|
+
broadcasting: event.payload[:broadcasting],
|
|
306
|
+
coder: event.payload[:coder]
|
|
307
|
+
}
|
|
308
|
+
)
|
|
309
|
+
end
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
def record_span(name:, kind:, started_at:, ended_at:, duration_ms:, error: false, error_class: nil,
|
|
313
|
+
error_message: nil, data: {})
|
|
314
|
+
spans = Thread.current[:brainzlab_pulse_spans]
|
|
315
|
+
return unless spans
|
|
316
|
+
|
|
317
|
+
span = {
|
|
318
|
+
span_id: SecureRandom.uuid,
|
|
319
|
+
name: name,
|
|
320
|
+
kind: kind,
|
|
321
|
+
started_at: started_at,
|
|
322
|
+
ended_at: ended_at,
|
|
323
|
+
duration_ms: duration_ms.round(2),
|
|
324
|
+
data: data.compact
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if error
|
|
328
|
+
span[:error] = true
|
|
329
|
+
span[:error_class] = error_class
|
|
330
|
+
span[:error_message] = error_message
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
spans << span
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
def skip_query?(payload)
|
|
337
|
+
# Skip SCHEMA queries and internal Rails queries
|
|
338
|
+
return true if payload[:name] == 'SCHEMA'
|
|
339
|
+
return true if payload[:name]&.start_with?('EXPLAIN')
|
|
340
|
+
return true if payload[:sql]&.include?('pg_')
|
|
341
|
+
return true if payload[:sql]&.include?('information_schema')
|
|
342
|
+
return true if payload[:cached] && !include_cached_queries?
|
|
343
|
+
|
|
344
|
+
false
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
def include_cached_queries?
|
|
348
|
+
false
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
def truncate_sql(sql)
|
|
352
|
+
return nil unless sql
|
|
353
|
+
|
|
354
|
+
sql.to_s[0, 1000]
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
def truncate_key(key)
|
|
358
|
+
return nil unless key
|
|
359
|
+
|
|
360
|
+
key.to_s[0, 200]
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
def short_path(path)
|
|
364
|
+
return nil unless path
|
|
365
|
+
|
|
366
|
+
path.to_s.split('/').last(2).join('/')
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
def extract_table(sql)
|
|
370
|
+
return nil unless sql
|
|
371
|
+
|
|
372
|
+
# Match FROM "table" or FROM table patterns
|
|
373
|
+
# Also handles INSERT INTO, UPDATE, DELETE FROM
|
|
374
|
+
case sql.to_s
|
|
375
|
+
when /\bFROM\s+["'`]?(\w+)["'`]?/i
|
|
376
|
+
Regexp.last_match(1)
|
|
377
|
+
when /\bINTO\s+["'`]?(\w+)["'`]?/i
|
|
378
|
+
Regexp.last_match(1)
|
|
379
|
+
when /\bUPDATE\s+["'`]?(\w+)["'`]?/i
|
|
380
|
+
Regexp.last_match(1)
|
|
381
|
+
when /\bJOIN\s+["'`]?(\w+)["'`]?/i
|
|
382
|
+
Regexp.last_match(1)
|
|
383
|
+
end
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
def extract_operation(sql)
|
|
387
|
+
return nil unless sql
|
|
388
|
+
|
|
389
|
+
case sql.to_s.strip.upcase
|
|
390
|
+
when /\ASELECT/i then 'SELECT'
|
|
391
|
+
when /\AINSERT/i then 'INSERT'
|
|
392
|
+
when /\AUPDATE/i then 'UPDATE'
|
|
393
|
+
when /\ADELETE/i then 'DELETE'
|
|
394
|
+
when /\ABEGIN/i, /\ACOMMIT/i, /\AROLLBACK/i then 'TRANSACTION'
|
|
395
|
+
else 'QUERY'
|
|
396
|
+
end
|
|
397
|
+
end
|
|
398
|
+
end
|
|
399
|
+
end
|
|
400
|
+
end
|
|
401
|
+
end
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BrainzLab
|
|
4
|
+
module Pulse
|
|
5
|
+
# Distributed tracing context propagation using W3C Trace Context format
|
|
6
|
+
# https://www.w3.org/TR/trace-context/
|
|
7
|
+
module Propagation
|
|
8
|
+
# W3C Trace Context header names
|
|
9
|
+
TRACEPARENT_HEADER = 'traceparent'
|
|
10
|
+
TRACESTATE_HEADER = 'tracestate'
|
|
11
|
+
|
|
12
|
+
# HTTP header versions (with HTTP_ prefix for Rack env)
|
|
13
|
+
HTTP_TRACEPARENT = 'HTTP_TRACEPARENT'
|
|
14
|
+
HTTP_TRACESTATE = 'HTTP_TRACESTATE'
|
|
15
|
+
|
|
16
|
+
# Also support B3 format for compatibility
|
|
17
|
+
B3_TRACE_ID = 'X-B3-TraceId'
|
|
18
|
+
B3_SPAN_ID = 'X-B3-SpanId'
|
|
19
|
+
B3_SAMPLED = 'X-B3-Sampled'
|
|
20
|
+
B3_PARENT_SPAN_ID = 'X-B3-ParentSpanId'
|
|
21
|
+
|
|
22
|
+
class Context
|
|
23
|
+
attr_accessor :trace_id, :span_id, :parent_span_id, :sampled, :tracestate
|
|
24
|
+
|
|
25
|
+
def initialize(trace_id: nil, span_id: nil, parent_span_id: nil, sampled: true, tracestate: nil)
|
|
26
|
+
@trace_id = trace_id || generate_trace_id
|
|
27
|
+
@span_id = span_id || generate_span_id
|
|
28
|
+
@parent_span_id = parent_span_id
|
|
29
|
+
@sampled = sampled
|
|
30
|
+
@tracestate = tracestate
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def valid?
|
|
34
|
+
!trace_id.nil? && !span_id.nil?
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def to_h
|
|
38
|
+
{
|
|
39
|
+
trace_id: @trace_id,
|
|
40
|
+
span_id: @span_id,
|
|
41
|
+
parent_span_id: @parent_span_id,
|
|
42
|
+
sampled: @sampled,
|
|
43
|
+
tracestate: @tracestate
|
|
44
|
+
}.compact
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def generate_trace_id
|
|
50
|
+
SecureRandom.hex(16) # 32 hex chars = 128 bits
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def generate_span_id
|
|
54
|
+
SecureRandom.hex(8) # 16 hex chars = 64 bits
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
class << self
|
|
59
|
+
# Get current propagation context from thread local
|
|
60
|
+
def current
|
|
61
|
+
Thread.current[:brainzlab_propagation_context]
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Set current propagation context
|
|
65
|
+
def current=(context)
|
|
66
|
+
Thread.current[:brainzlab_propagation_context] = context
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Create new context and set as current
|
|
70
|
+
def start(trace_id: nil, parent_span_id: nil)
|
|
71
|
+
self.current = Context.new(
|
|
72
|
+
trace_id: trace_id,
|
|
73
|
+
parent_span_id: parent_span_id
|
|
74
|
+
)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Clear current context
|
|
78
|
+
def clear!
|
|
79
|
+
Thread.current[:brainzlab_propagation_context] = nil
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Inject trace context into outgoing HTTP headers
|
|
83
|
+
# @param headers [Hash] the headers hash to inject into
|
|
84
|
+
# @param context [Context] optional context (defaults to current)
|
|
85
|
+
# @param format [Symbol] :w3c (default), :b3, or :all
|
|
86
|
+
def inject(headers, context: nil, format: :w3c)
|
|
87
|
+
ctx = context || current
|
|
88
|
+
return headers unless ctx&.valid?
|
|
89
|
+
|
|
90
|
+
case format
|
|
91
|
+
when :w3c
|
|
92
|
+
inject_w3c(headers, ctx)
|
|
93
|
+
when :b3
|
|
94
|
+
inject_b3(headers, ctx)
|
|
95
|
+
when :all
|
|
96
|
+
inject_w3c(headers, ctx)
|
|
97
|
+
inject_b3(headers, ctx)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
headers
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Extract trace context from incoming HTTP headers (Rack env or plain headers)
|
|
104
|
+
# @param headers [Hash] the headers to extract from
|
|
105
|
+
# @return [Context, nil] the extracted context or nil
|
|
106
|
+
def extract(headers)
|
|
107
|
+
# Try W3C format first
|
|
108
|
+
ctx = extract_w3c(headers)
|
|
109
|
+
return ctx if ctx
|
|
110
|
+
|
|
111
|
+
# Fall back to B3 format
|
|
112
|
+
extract_b3(headers)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Extract and set as current context
|
|
116
|
+
# Returns the context for chaining
|
|
117
|
+
def extract!(headers)
|
|
118
|
+
self.current = extract(headers)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Create a child context for a new span
|
|
122
|
+
def child_context(parent: nil)
|
|
123
|
+
parent ||= current
|
|
124
|
+
return Context.new unless parent&.valid?
|
|
125
|
+
|
|
126
|
+
Context.new(
|
|
127
|
+
trace_id: parent.trace_id,
|
|
128
|
+
parent_span_id: parent.span_id,
|
|
129
|
+
sampled: parent.sampled,
|
|
130
|
+
tracestate: parent.tracestate
|
|
131
|
+
)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
private
|
|
135
|
+
|
|
136
|
+
# W3C Trace Context format injection
|
|
137
|
+
# traceparent: version-traceid-spanid-flags
|
|
138
|
+
# Example: 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01
|
|
139
|
+
def inject_w3c(headers, ctx)
|
|
140
|
+
version = '00'
|
|
141
|
+
flags = ctx.sampled ? '01' : '00'
|
|
142
|
+
trace_id = normalize_trace_id(ctx.trace_id, 32)
|
|
143
|
+
span_id = normalize_trace_id(ctx.span_id, 16)
|
|
144
|
+
|
|
145
|
+
headers[TRACEPARENT_HEADER] = "#{version}-#{trace_id}-#{span_id}-#{flags}"
|
|
146
|
+
headers[TRACESTATE_HEADER] = ctx.tracestate if ctx.tracestate
|
|
147
|
+
|
|
148
|
+
headers
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# W3C Trace Context format extraction
|
|
152
|
+
def extract_w3c(headers)
|
|
153
|
+
traceparent = headers[TRACEPARENT_HEADER] ||
|
|
154
|
+
headers[HTTP_TRACEPARENT] ||
|
|
155
|
+
headers['Traceparent']
|
|
156
|
+
return nil unless traceparent
|
|
157
|
+
|
|
158
|
+
# Parse: version-traceid-spanid-flags
|
|
159
|
+
parts = traceparent.to_s.split('-')
|
|
160
|
+
return nil if parts.length < 4
|
|
161
|
+
|
|
162
|
+
version, trace_id, span_id, flags = parts
|
|
163
|
+
|
|
164
|
+
# Validate version
|
|
165
|
+
return nil unless version == '00'
|
|
166
|
+
|
|
167
|
+
# Validate trace_id (32 hex chars, not all zeros)
|
|
168
|
+
return nil unless trace_id&.match?(/\A[a-f0-9]{32}\z/i)
|
|
169
|
+
return nil if trace_id == '0' * 32
|
|
170
|
+
|
|
171
|
+
# Validate span_id (16 hex chars, not all zeros)
|
|
172
|
+
return nil unless span_id&.match?(/\A[a-f0-9]{16}\z/i)
|
|
173
|
+
return nil if span_id == '0' * 16
|
|
174
|
+
|
|
175
|
+
sampled = flags.to_i(16) & 0x01 == 1
|
|
176
|
+
|
|
177
|
+
tracestate = headers[TRACESTATE_HEADER] ||
|
|
178
|
+
headers[HTTP_TRACESTATE] ||
|
|
179
|
+
headers['Tracestate']
|
|
180
|
+
|
|
181
|
+
Context.new(
|
|
182
|
+
trace_id: trace_id,
|
|
183
|
+
span_id: span_id,
|
|
184
|
+
sampled: sampled,
|
|
185
|
+
tracestate: tracestate
|
|
186
|
+
)
|
|
187
|
+
rescue StandardError
|
|
188
|
+
nil
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# B3 format injection (Zipkin compatibility)
|
|
192
|
+
def inject_b3(headers, ctx)
|
|
193
|
+
headers[B3_TRACE_ID] = normalize_trace_id(ctx.trace_id, 32)
|
|
194
|
+
headers[B3_SPAN_ID] = normalize_trace_id(ctx.span_id, 16)
|
|
195
|
+
headers[B3_SAMPLED] = ctx.sampled ? '1' : '0'
|
|
196
|
+
headers[B3_PARENT_SPAN_ID] = ctx.parent_span_id if ctx.parent_span_id
|
|
197
|
+
|
|
198
|
+
headers
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# B3 format extraction
|
|
202
|
+
def extract_b3(headers)
|
|
203
|
+
trace_id = headers[B3_TRACE_ID] ||
|
|
204
|
+
headers['HTTP_X_B3_TRACEID'] ||
|
|
205
|
+
headers['x-b3-traceid']
|
|
206
|
+
return nil unless trace_id
|
|
207
|
+
|
|
208
|
+
span_id = headers[B3_SPAN_ID] ||
|
|
209
|
+
headers['HTTP_X_B3_SPANID'] ||
|
|
210
|
+
headers['x-b3-spanid']
|
|
211
|
+
return nil unless span_id
|
|
212
|
+
|
|
213
|
+
sampled_header = headers[B3_SAMPLED] ||
|
|
214
|
+
headers['HTTP_X_B3_SAMPLED'] ||
|
|
215
|
+
headers['x-b3-sampled']
|
|
216
|
+
sampled = sampled_header != '0'
|
|
217
|
+
|
|
218
|
+
parent_span_id = headers[B3_PARENT_SPAN_ID] ||
|
|
219
|
+
headers['HTTP_X_B3_PARENTSPANID'] ||
|
|
220
|
+
headers['x-b3-parentspanid']
|
|
221
|
+
|
|
222
|
+
Context.new(
|
|
223
|
+
trace_id: trace_id,
|
|
224
|
+
span_id: span_id,
|
|
225
|
+
parent_span_id: parent_span_id,
|
|
226
|
+
sampled: sampled
|
|
227
|
+
)
|
|
228
|
+
rescue StandardError
|
|
229
|
+
nil
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def normalize_trace_id(id, length)
|
|
233
|
+
return nil unless id
|
|
234
|
+
|
|
235
|
+
hex = id.to_s.gsub('-', '').downcase
|
|
236
|
+
hex.rjust(length, '0').slice(0, length)
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
end
|