flare 0.1.0
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 +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +148 -0
- data/app/controllers/flare/application_controller.rb +22 -0
- data/app/controllers/flare/jobs_controller.rb +55 -0
- data/app/controllers/flare/requests_controller.rb +73 -0
- data/app/controllers/flare/spans_controller.rb +101 -0
- data/app/helpers/flare/application_helper.rb +168 -0
- data/app/views/flare/jobs/index.html.erb +69 -0
- data/app/views/flare/jobs/show.html.erb +323 -0
- data/app/views/flare/requests/index.html.erb +120 -0
- data/app/views/flare/requests/show.html.erb +498 -0
- data/app/views/flare/spans/index.html.erb +112 -0
- data/app/views/flare/spans/show.html.erb +184 -0
- data/app/views/layouts/flare/application.html.erb +126 -0
- data/config/routes.rb +20 -0
- data/exe/flare +9 -0
- data/lib/flare/backoff_policy.rb +73 -0
- data/lib/flare/cli/doctor_command.rb +129 -0
- data/lib/flare/cli/output.rb +45 -0
- data/lib/flare/cli/setup_command.rb +404 -0
- data/lib/flare/cli/status_command.rb +47 -0
- data/lib/flare/cli.rb +50 -0
- data/lib/flare/configuration.rb +121 -0
- data/lib/flare/engine.rb +43 -0
- data/lib/flare/http_metrics_config.rb +101 -0
- data/lib/flare/metric_counter.rb +45 -0
- data/lib/flare/metric_flusher.rb +124 -0
- data/lib/flare/metric_key.rb +42 -0
- data/lib/flare/metric_span_processor.rb +470 -0
- data/lib/flare/metric_storage.rb +42 -0
- data/lib/flare/metric_submitter.rb +221 -0
- data/lib/flare/source_location.rb +113 -0
- data/lib/flare/sqlite_exporter.rb +279 -0
- data/lib/flare/storage/sqlite.rb +789 -0
- data/lib/flare/storage.rb +54 -0
- data/lib/flare/version.rb +5 -0
- data/lib/flare.rb +411 -0
- data/public/flare-assets/flare.css +1245 -0
- data/public/flare-assets/images/flipper.png +0 -0
- metadata +240 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Flare
|
|
4
|
+
module Storage
|
|
5
|
+
class Base
|
|
6
|
+
def save_case(attributes)
|
|
7
|
+
raise NotImplementedError
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def save_clues(clues)
|
|
11
|
+
raise NotImplementedError
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def find_case(uuid)
|
|
15
|
+
raise NotImplementedError
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def list_cases(type: nil, status: nil, method: nil, name: nil, origin: nil, limit: 50, offset: 0)
|
|
19
|
+
raise NotImplementedError
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def clues_for_case(case_uuid)
|
|
23
|
+
raise NotImplementedError
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def list_clues(type: nil, search: nil, limit: 50, offset: 0)
|
|
27
|
+
raise NotImplementedError
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def find_clue(id)
|
|
31
|
+
raise NotImplementedError
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def prune(retention_hours:, max_cases:)
|
|
35
|
+
raise NotImplementedError
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def clear_all
|
|
39
|
+
raise NotImplementedError
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def count_cases(type: nil, status: nil, method: nil, name: nil, origin: nil)
|
|
43
|
+
raise NotImplementedError
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def count_clues(type: nil, search: nil)
|
|
47
|
+
raise NotImplementedError
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# storage/sqlite is loaded on demand when spans are enabled
|
|
54
|
+
# to avoid requiring sqlite3 in production environments
|
data/lib/flare.rb
ADDED
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "flare/version"
|
|
4
|
+
require_relative "flare/configuration"
|
|
5
|
+
|
|
6
|
+
require "opentelemetry/sdk"
|
|
7
|
+
|
|
8
|
+
require_relative "flare/source_location"
|
|
9
|
+
require_relative "flare/metric_key"
|
|
10
|
+
require_relative "flare/metric_storage"
|
|
11
|
+
require_relative "flare/metric_span_processor"
|
|
12
|
+
require_relative "flare/metric_flusher"
|
|
13
|
+
require_relative "flare/backoff_policy"
|
|
14
|
+
require_relative "flare/metric_submitter"
|
|
15
|
+
|
|
16
|
+
module Flare
|
|
17
|
+
class Error < StandardError; end
|
|
18
|
+
|
|
19
|
+
MISSING_PARENT_ID = "0000000000000000"
|
|
20
|
+
TRANSACTION_NAME_ATTRIBUTE = "flare.transaction_name" unless const_defined?(:TRANSACTION_NAME_ATTRIBUTE)
|
|
21
|
+
|
|
22
|
+
module_function
|
|
23
|
+
|
|
24
|
+
def configuration
|
|
25
|
+
@configuration ||= Configuration.new
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def configure
|
|
29
|
+
yield(configuration) if block_given?
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def enabled?
|
|
33
|
+
configuration.enabled
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Set the transaction name for the current span. This overrides the
|
|
37
|
+
# default name derived from Rails controller/action or job class.
|
|
38
|
+
#
|
|
39
|
+
# Useful for Rack middleware, mounted apps, or any request that
|
|
40
|
+
# doesn't go through the Rails router.
|
|
41
|
+
#
|
|
42
|
+
# Flare.transaction_name("RestApi::Routes::Audits#get")
|
|
43
|
+
#
|
|
44
|
+
def transaction_name(name)
|
|
45
|
+
span = OpenTelemetry::Trace.current_span
|
|
46
|
+
return unless span.respond_to?(:set_attribute)
|
|
47
|
+
|
|
48
|
+
span.set_attribute(TRANSACTION_NAME_ATTRIBUTE, name)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def logger
|
|
52
|
+
@logger ||= Logger.new(STDOUT)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def logger=(logger)
|
|
56
|
+
@logger = logger
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def log(message)
|
|
60
|
+
return unless configuration.debug
|
|
61
|
+
|
|
62
|
+
logger.info("[Flare] #{message}")
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def exporter
|
|
66
|
+
@exporter ||= begin
|
|
67
|
+
require_relative "flare/sqlite_exporter"
|
|
68
|
+
SQLiteExporter.new(configuration.database_path)
|
|
69
|
+
rescue LoadError
|
|
70
|
+
raise LoadError, "Flare spans require the sqlite3 gem. Add `gem 'sqlite3'` to your Gemfile."
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def exporter=(exporter)
|
|
75
|
+
@exporter = exporter
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def span_processor
|
|
79
|
+
@span_processor ||= OpenTelemetry::SDK::Trace::Export::BatchSpanProcessor.new(
|
|
80
|
+
exporter,
|
|
81
|
+
max_queue_size: 1000,
|
|
82
|
+
max_export_batch_size: 100,
|
|
83
|
+
schedule_delay: 1000 # 1 second
|
|
84
|
+
)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def span_processor=(span_processor)
|
|
88
|
+
@span_processor = span_processor
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def tracer
|
|
92
|
+
@tracer ||= OpenTelemetry.tracer_provider.tracer("Flare", Flare::VERSION)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def untraced(&block)
|
|
96
|
+
OpenTelemetry::Common::Utilities.untraced(&block)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def metric_storage
|
|
100
|
+
@metric_storage
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def metric_storage=(storage)
|
|
104
|
+
@metric_storage = storage
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def metric_flusher
|
|
108
|
+
@metric_flusher
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def metric_flusher=(flusher)
|
|
112
|
+
@metric_flusher = flusher
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Manually flush metrics (useful for testing or forced flushes).
|
|
116
|
+
def flush_metrics
|
|
117
|
+
@metric_flusher&.flush_now || 0
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Re-initialize metric flusher after fork.
|
|
121
|
+
# Call this from Puma/Unicorn after_fork hooks.
|
|
122
|
+
def after_fork
|
|
123
|
+
@metric_flusher&.after_fork
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Configure OpenTelemetry SDK and instrumentations. Must run before the
|
|
127
|
+
# middleware stack is built so Rack/ActionPack can insert their middleware.
|
|
128
|
+
# Note: metrics flusher is started separately via start_metrics_flusher
|
|
129
|
+
# after user initializers have run.
|
|
130
|
+
def configure_opentelemetry
|
|
131
|
+
return if @otel_configured
|
|
132
|
+
|
|
133
|
+
# Suppress noisy OTel INFO logs
|
|
134
|
+
OpenTelemetry.logger = Logger.new(STDOUT, level: Logger::WARN)
|
|
135
|
+
|
|
136
|
+
service_name = if defined?(Rails) && Rails.application
|
|
137
|
+
Rails.application.class.module_parent_name.underscore rescue "rails_app"
|
|
138
|
+
else
|
|
139
|
+
"app"
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Require only the instrumentations we want
|
|
143
|
+
require "opentelemetry-instrumentation-rack"
|
|
144
|
+
require "opentelemetry-instrumentation-net_http"
|
|
145
|
+
require "opentelemetry-instrumentation-active_support"
|
|
146
|
+
require "opentelemetry/instrumentation/active_support/span_subscriber"
|
|
147
|
+
require "opentelemetry-instrumentation-action_pack" if defined?(ActionController)
|
|
148
|
+
require "opentelemetry-instrumentation-action_view" if defined?(ActionView)
|
|
149
|
+
require "opentelemetry-instrumentation-active_job" if defined?(ActiveJob)
|
|
150
|
+
|
|
151
|
+
# Tell the SDK not to try configuring OTLP from env vars.
|
|
152
|
+
# Flare manages its own exporters (SQLite for spans, HTTP for metrics).
|
|
153
|
+
ENV["OTEL_TRACES_EXPORTER"] ||= "none"
|
|
154
|
+
|
|
155
|
+
log "Configuring OpenTelemetry (service=#{service_name})"
|
|
156
|
+
|
|
157
|
+
OpenTelemetry::SDK.configure do |c|
|
|
158
|
+
c.service_name = service_name
|
|
159
|
+
|
|
160
|
+
# Spans: detailed trace data stored in SQLite
|
|
161
|
+
if configuration.spans_enabled
|
|
162
|
+
c.add_span_processor(span_processor)
|
|
163
|
+
log "Spans enabled (database=#{configuration.database_path})"
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Configure specific instrumentations
|
|
167
|
+
c.use "OpenTelemetry::Instrumentation::Rack",
|
|
168
|
+
untraced_requests: ->(env) {
|
|
169
|
+
request = Rack::Request.new(env)
|
|
170
|
+
return true if request.path.start_with?("/flare")
|
|
171
|
+
|
|
172
|
+
configuration.ignore_request.call(request)
|
|
173
|
+
}
|
|
174
|
+
c.use "OpenTelemetry::Instrumentation::Net::HTTP"
|
|
175
|
+
c.use "OpenTelemetry::Instrumentation::ActiveSupport"
|
|
176
|
+
c.use "OpenTelemetry::Instrumentation::ActionPack" if defined?(ActionController)
|
|
177
|
+
c.use "OpenTelemetry::Instrumentation::ActionView" if defined?(ActionView)
|
|
178
|
+
c.use "OpenTelemetry::Instrumentation::ActiveJob" if defined?(ActiveJob)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# Subscribe to common ActiveSupport notification patterns
|
|
182
|
+
# This captures SQL, cache, mailer, and custom notifications.
|
|
183
|
+
# Required for both spans (detailed traces) and metrics (aggregated counters)
|
|
184
|
+
# because DB, cache, and mailer data flows through ActiveSupport notifications.
|
|
185
|
+
if configuration.spans_enabled || configuration.metrics_enabled
|
|
186
|
+
subscribe_to_notifications
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
at_exit do
|
|
190
|
+
log "Shutting down..."
|
|
191
|
+
if configuration.spans_enabled
|
|
192
|
+
span_processor.force_flush
|
|
193
|
+
span_processor.shutdown
|
|
194
|
+
log "Span processor flushed and stopped"
|
|
195
|
+
end
|
|
196
|
+
log "Shutdown complete"
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
@otel_configured = true
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# Start the metrics flusher. Called from config.after_initialize so
|
|
203
|
+
# user configuration (metrics_enabled, flush_interval, etc.) is applied.
|
|
204
|
+
def start_metrics_flusher
|
|
205
|
+
return unless configuration.metrics_enabled
|
|
206
|
+
|
|
207
|
+
@metric_storage ||= MetricStorage.new
|
|
208
|
+
metric_processor = MetricSpanProcessor.new(
|
|
209
|
+
storage: @metric_storage,
|
|
210
|
+
http_metrics_config: configuration.http_metrics_config
|
|
211
|
+
)
|
|
212
|
+
OpenTelemetry.tracer_provider.add_span_processor(metric_processor)
|
|
213
|
+
|
|
214
|
+
log "Metrics enabled (endpoint=#{configuration.url} key=#{configuration.key ? 'present' : 'missing'})"
|
|
215
|
+
|
|
216
|
+
if configuration.metrics_submission_configured?
|
|
217
|
+
submitter = MetricSubmitter.new(
|
|
218
|
+
endpoint: configuration.url,
|
|
219
|
+
api_key: configuration.key
|
|
220
|
+
)
|
|
221
|
+
@metric_flusher = MetricFlusher.new(
|
|
222
|
+
storage: @metric_storage,
|
|
223
|
+
submitter: submitter,
|
|
224
|
+
interval: configuration.metrics_flush_interval
|
|
225
|
+
)
|
|
226
|
+
@metric_flusher.start
|
|
227
|
+
log "Metrics flusher started (interval=#{configuration.metrics_flush_interval}s)"
|
|
228
|
+
|
|
229
|
+
at_exit { @metric_flusher&.stop }
|
|
230
|
+
else
|
|
231
|
+
log "Metrics submission not configured (missing url or key)"
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
# Payload transformers for different notification types
|
|
236
|
+
NOTIFICATION_TRANSFORMERS = {
|
|
237
|
+
"sql.active_record" => ->(payload) {
|
|
238
|
+
attrs = {}
|
|
239
|
+
attrs["db.system"] = payload[:connection]&.adapter_name&.downcase rescue nil
|
|
240
|
+
attrs["db.statement"] = payload[:sql] if payload[:sql]
|
|
241
|
+
attrs["name"] = payload[:name] if payload[:name]
|
|
242
|
+
attrs["db.name"] = payload[:connection]&.pool&.db_config&.name rescue nil
|
|
243
|
+
# Capture source location (app code that triggered this query)
|
|
244
|
+
SourceLocation.add_to_attributes(attrs)
|
|
245
|
+
attrs
|
|
246
|
+
},
|
|
247
|
+
"instantiation.active_record" => ->(payload) {
|
|
248
|
+
attrs = {}
|
|
249
|
+
attrs["record_count"] = payload[:record_count] if payload[:record_count]
|
|
250
|
+
attrs["class_name"] = payload[:class_name] if payload[:class_name]
|
|
251
|
+
attrs
|
|
252
|
+
},
|
|
253
|
+
"cache_read.active_support" => ->(payload) {
|
|
254
|
+
store = payload[:store]
|
|
255
|
+
store_name = store.is_a?(String) ? store : store&.class&.name
|
|
256
|
+
{ "key" => payload[:key]&.to_s, "hit" => payload[:hit], "store" => store_name }
|
|
257
|
+
},
|
|
258
|
+
"cache_write.active_support" => ->(payload) {
|
|
259
|
+
store = payload[:store]
|
|
260
|
+
store_name = store.is_a?(String) ? store : store&.class&.name
|
|
261
|
+
{ "key" => payload[:key]&.to_s, "store" => store_name }
|
|
262
|
+
},
|
|
263
|
+
"cache_delete.active_support" => ->(payload) {
|
|
264
|
+
store = payload[:store]
|
|
265
|
+
store_name = store.is_a?(String) ? store : store&.class&.name
|
|
266
|
+
{ "key" => payload[:key]&.to_s, "store" => store_name }
|
|
267
|
+
},
|
|
268
|
+
"cache_exist?.active_support" => ->(payload) {
|
|
269
|
+
store = payload[:store]
|
|
270
|
+
store_name = store.is_a?(String) ? store : store&.class&.name
|
|
271
|
+
{ "key" => payload[:key]&.to_s, "exist" => payload[:exist], "store" => store_name }
|
|
272
|
+
},
|
|
273
|
+
"cache_fetch_hit.active_support" => ->(payload) {
|
|
274
|
+
store = payload[:store]
|
|
275
|
+
store_name = store.is_a?(String) ? store : store&.class&.name
|
|
276
|
+
{ "key" => payload[:key]&.to_s, "store" => store_name }
|
|
277
|
+
},
|
|
278
|
+
"deliver.action_mailer" => ->(payload) {
|
|
279
|
+
attrs = {}
|
|
280
|
+
attrs["mailer"] = payload[:mailer] if payload[:mailer]
|
|
281
|
+
attrs["message_id"] = payload[:message_id] if payload[:message_id]
|
|
282
|
+
attrs["to"] = Array(payload[:to]).join(", ") if payload[:to]
|
|
283
|
+
attrs["subject"] = payload[:subject] if payload[:subject]
|
|
284
|
+
attrs
|
|
285
|
+
},
|
|
286
|
+
"process.action_mailer" => ->(payload) {
|
|
287
|
+
attrs = {}
|
|
288
|
+
attrs["mailer"] = payload[:mailer] if payload[:mailer]
|
|
289
|
+
attrs["action"] = payload[:action] if payload[:action]
|
|
290
|
+
attrs
|
|
291
|
+
}
|
|
292
|
+
}.freeze
|
|
293
|
+
|
|
294
|
+
def subscribe_to_notifications
|
|
295
|
+
NOTIFICATION_TRANSFORMERS.each do |pattern, transformer|
|
|
296
|
+
OpenTelemetry::Instrumentation::ActiveSupport.subscribe(tracer, pattern, transformer)
|
|
297
|
+
rescue
|
|
298
|
+
# Ignore errors for patterns that don't exist
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
# Auto-subscribe to custom patterns (default: "app.*")
|
|
302
|
+
# This lets users just do: ActiveSupport::Notifications.instrument("app.whatever") { }
|
|
303
|
+
subscribe_to_custom_patterns
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
def subscribe_to_custom_patterns
|
|
307
|
+
configuration.subscribe_patterns.each do |prefix|
|
|
308
|
+
# Subscribe to all notifications starting with this prefix
|
|
309
|
+
pattern = /\A#{Regexp.escape(prefix)}/
|
|
310
|
+
default_transformer = ->(payload) {
|
|
311
|
+
attrs = payload.transform_keys(&:to_s).select { |_, v|
|
|
312
|
+
v.is_a?(String) || v.is_a?(Numeric) || v.is_a?(TrueClass) || v.is_a?(FalseClass)
|
|
313
|
+
}
|
|
314
|
+
SourceLocation.add_to_attributes(attrs)
|
|
315
|
+
attrs
|
|
316
|
+
}
|
|
317
|
+
OpenTelemetry::Instrumentation::ActiveSupport.subscribe(tracer, pattern, default_transformer)
|
|
318
|
+
end
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
# Subscribe to any ActiveSupport::Notification and create spans for it
|
|
322
|
+
#
|
|
323
|
+
# @param pattern [String, Regexp] The notification pattern to subscribe to
|
|
324
|
+
# @param transformer [Proc, nil] Optional proc to transform payload into span attributes
|
|
325
|
+
# If nil, all payload keys become span attributes
|
|
326
|
+
#
|
|
327
|
+
# @example Subscribe to a custom notification
|
|
328
|
+
# Flare.subscribe("my_service.call")
|
|
329
|
+
#
|
|
330
|
+
# @example Subscribe with custom attribute transformer
|
|
331
|
+
# Flare.subscribe("stripe.charge") do |payload|
|
|
332
|
+
# { "charge_id" => payload[:id], "amount" => payload[:amount] }
|
|
333
|
+
# end
|
|
334
|
+
#
|
|
335
|
+
def subscribe(pattern, &transformer)
|
|
336
|
+
transformer ||= ->(payload) {
|
|
337
|
+
# Default: convert all payload keys to string attributes
|
|
338
|
+
payload.transform_keys(&:to_s).transform_values(&:to_s)
|
|
339
|
+
}
|
|
340
|
+
OpenTelemetry::Instrumentation::ActiveSupport.subscribe(tracer, pattern, transformer)
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
# Instrument a block of code, creating a span that shows up in Flare
|
|
344
|
+
#
|
|
345
|
+
# NOTE: This method only works when Flare is loaded (typically development).
|
|
346
|
+
# For instrumentation that works in all environments, use ActiveSupport::Notifications
|
|
347
|
+
# directly and subscribe with Flare.subscribe in your initializer.
|
|
348
|
+
#
|
|
349
|
+
# @param name [String] The name of the span (e.g., "my_service.call", "external_api.fetch")
|
|
350
|
+
# @param attributes [Hash] Optional attributes to add to the span
|
|
351
|
+
# @yield The block to instrument
|
|
352
|
+
# @return The return value of the block
|
|
353
|
+
#
|
|
354
|
+
# @example Basic usage (dev only)
|
|
355
|
+
# Flare.instrument("geocoding.lookup") do
|
|
356
|
+
# geocoder.lookup(address)
|
|
357
|
+
# end
|
|
358
|
+
#
|
|
359
|
+
# @example For all environments, use ActiveSupport::Notifications instead:
|
|
360
|
+
# # In your app code (works everywhere):
|
|
361
|
+
# ActiveSupport::Notifications.instrument("myapp.geocoding", address: addr) do
|
|
362
|
+
# geocoder.lookup(addr)
|
|
363
|
+
# end
|
|
364
|
+
#
|
|
365
|
+
# # In config/initializers/flare.rb (only loaded in dev):
|
|
366
|
+
# Flare.subscribe("myapp.geocoding")
|
|
367
|
+
#
|
|
368
|
+
def instrument(name, attributes = {}, &block)
|
|
369
|
+
return yield unless enabled?
|
|
370
|
+
|
|
371
|
+
# Add source location
|
|
372
|
+
location = SourceLocation.find
|
|
373
|
+
if location
|
|
374
|
+
attributes["code.filepath"] = location[:filepath]
|
|
375
|
+
attributes["code.lineno"] = location[:lineno]
|
|
376
|
+
attributes["code.function"] = location[:function] if location[:function]
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
tracer.in_span(name, attributes: attributes, kind: :internal) do |span|
|
|
380
|
+
yield span
|
|
381
|
+
end
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
def storage
|
|
385
|
+
@storage ||= begin
|
|
386
|
+
require_relative "flare/storage/sqlite"
|
|
387
|
+
Storage::SQLite.new(configuration.database_path)
|
|
388
|
+
rescue LoadError
|
|
389
|
+
raise LoadError, "Flare spans require the sqlite3 gem. Add `gem 'sqlite3'` to your Gemfile."
|
|
390
|
+
end
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
def reset_storage!
|
|
394
|
+
@storage = nil
|
|
395
|
+
end
|
|
396
|
+
|
|
397
|
+
def reset!
|
|
398
|
+
@configuration = nil
|
|
399
|
+
@exporter = nil
|
|
400
|
+
@span_processor = nil
|
|
401
|
+
@tracer = nil
|
|
402
|
+
@storage = nil
|
|
403
|
+
@metric_flusher&.stop
|
|
404
|
+
@metric_flusher = nil
|
|
405
|
+
@metric_storage = nil
|
|
406
|
+
@otel_configured = false
|
|
407
|
+
end
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
require_relative "flare/storage"
|
|
411
|
+
require_relative "flare/engine" if defined?(Rails)
|