flare 0.4.0 → 1.0.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.

Potentially problematic release.


This version of flare might be problematic. Click here for more details.

Files changed (70) hide show
  1. data/.document +5 -0
  2. data/.gitignore +22 -0
  3. data/LICENSE +20 -0
  4. data/README.rdoc +18 -0
  5. data/Rakefile +55 -0
  6. data/VERSION +1 -0
  7. data/flare.gemspec +66 -0
  8. data/lib/flare/active_record.rb +102 -0
  9. data/lib/flare/collection.rb +47 -0
  10. data/lib/flare/configuration.rb +64 -123
  11. data/lib/flare/index_builder.rb +24 -0
  12. data/lib/flare/session.rb +142 -0
  13. data/lib/flare/tasks.rb +18 -0
  14. data/lib/flare.rb +19 -537
  15. data/test/helper.rb +10 -0
  16. data/test/test_flare.rb +7 -0
  17. metadata +89 -242
  18. checksums.yaml +0 -7
  19. data/CHANGELOG.md +0 -3
  20. data/LICENSE.txt +0 -21
  21. data/README.md +0 -148
  22. data/app/controllers/flare/application_controller.rb +0 -30
  23. data/app/controllers/flare/jobs_controller.rb +0 -55
  24. data/app/controllers/flare/requests_controller.rb +0 -73
  25. data/app/controllers/flare/spans_controller.rb +0 -101
  26. data/app/helpers/flare/application_helper.rb +0 -168
  27. data/app/views/flare/jobs/index.html.erb +0 -69
  28. data/app/views/flare/jobs/show.html.erb +0 -323
  29. data/app/views/flare/requests/index.html.erb +0 -120
  30. data/app/views/flare/requests/show.html.erb +0 -498
  31. data/app/views/flare/spans/index.html.erb +0 -112
  32. data/app/views/flare/spans/show.html.erb +0 -184
  33. data/app/views/layouts/flare/application.html.erb +0 -126
  34. data/config/routes.rb +0 -20
  35. data/exe/flare +0 -9
  36. data/lib/flare/backoff_policy.rb +0 -73
  37. data/lib/flare/cli/doctor_command.rb +0 -129
  38. data/lib/flare/cli/output.rb +0 -45
  39. data/lib/flare/cli/setup_command.rb +0 -404
  40. data/lib/flare/cli/status_command.rb +0 -47
  41. data/lib/flare/cli.rb +0 -50
  42. data/lib/flare/client_headers.rb +0 -39
  43. data/lib/flare/deadline.rb +0 -27
  44. data/lib/flare/engine.rb +0 -45
  45. data/lib/flare/filtering_span_processor.rb +0 -429
  46. data/lib/flare/http_metrics_config.rb +0 -101
  47. data/lib/flare/http_transport.rb +0 -76
  48. data/lib/flare/lifecycle.rb +0 -39
  49. data/lib/flare/marker.rb +0 -106
  50. data/lib/flare/metric_counter.rb +0 -51
  51. data/lib/flare/metric_flusher.rb +0 -279
  52. data/lib/flare/metric_key.rb +0 -42
  53. data/lib/flare/metric_span_processor.rb +0 -470
  54. data/lib/flare/metric_storage.rb +0 -67
  55. data/lib/flare/metric_submitter.rb +0 -239
  56. data/lib/flare/recording_batch_span_processor.rb +0 -238
  57. data/lib/flare/rule_manager.rb +0 -141
  58. data/lib/flare/sampler.rb +0 -130
  59. data/lib/flare/source_location.rb +0 -113
  60. data/lib/flare/sqlite_exporter.rb +0 -334
  61. data/lib/flare/storage/sqlite.rb +0 -794
  62. data/lib/flare/storage.rb +0 -54
  63. data/lib/flare/trace_blob.rb +0 -116
  64. data/lib/flare/trace_exporter.rb +0 -182
  65. data/lib/flare/trace_health_reporter.rb +0 -74
  66. data/lib/flare/upload_url_pool.rb +0 -108
  67. data/lib/flare/version.rb +0 -5
  68. data/lib/flare/web_marker_subscriber.rb +0 -76
  69. data/public/flare-assets/flare.css +0 -1245
  70. data/public/flare-assets/images/flipper.png +0 -0
@@ -1,429 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "concurrent/atomic/atomic_fixnum"
4
- require "logger"
5
- require "opentelemetry/sdk"
6
-
7
- require_relative "deadline"
8
-
9
- module Flare
10
- # BSP-shaped span processor whose filter is `sampled OR marked` instead
11
- # of BSP's `sampled` (BSP early-returns on RECORD_ONLY spans -- our
12
- # Path 2 spans have sampled=false so they'd be dropped). Forwards
13
- # matching spans to a trace exporter on a background worker thread so
14
- # the exporter never runs on the request/job thread (CAF-3).
15
- #
16
- # On every on_finish we also check marker.owner?(trace_id, span_id) and
17
- # unmark when the owning rack span finishes (CAF-2). Cleanup runs even
18
- # for spans we don't export.
19
- class FilteringSpanProcessor
20
- SUCCESS = OpenTelemetry::SDK::Trace::Export::SUCCESS
21
- FAILURE = OpenTelemetry::SDK::Trace::Export::FAILURE
22
- TIMEOUT = OpenTelemetry::SDK::Trace::Export::TIMEOUT
23
-
24
- DEFAULT_MAX_QUEUE = 5_000
25
- DEFAULT_FLUSH_INTERVAL = 5 # seconds
26
- DEFAULT_EXPORT_TIMEOUT = 30 # seconds
27
- DEFAULT_MARKED_TRACE_GRACE_PERIOD = 1.0 # seconds
28
-
29
- attr_reader :dropped_count, :failed_export_count, :exception_count, :buffer_high_watermark, :max_queue
30
-
31
- def initialize(exporter:, marker:,
32
- max_queue: DEFAULT_MAX_QUEUE,
33
- flush_interval: DEFAULT_FLUSH_INTERVAL,
34
- export_timeout: DEFAULT_EXPORT_TIMEOUT,
35
- marked_trace_grace_period: DEFAULT_MARKED_TRACE_GRACE_PERIOD,
36
- logger: nil)
37
- @exporter = exporter
38
- @marker = marker
39
- @max_queue = max_queue
40
- @flush_interval = flush_interval
41
- @export_timeout = export_timeout
42
- @marked_trace_grace_period = marked_trace_grace_period.to_f
43
- @logger = logger || Logger.new($stderr, level: Logger::WARN)
44
-
45
- @pending_by_trace = {}
46
- @trace_order = []
47
- @pending_count = 0
48
- @ready_queue = []
49
- @delayed_ready_by_trace = {}
50
- @mutex = Mutex.new
51
- @cond = ConditionVariable.new
52
- @stopped = false
53
- @active_exports = 0
54
- @export_completion_sequence = 0
55
- @last_export_result = SUCCESS
56
- @flush_owner = nil
57
- @pid = $$
58
-
59
- @dropped_count = Concurrent::AtomicFixnum.new(0)
60
- @failed_export_count = Concurrent::AtomicFixnum.new(0)
61
- @exception_count = Concurrent::AtomicFixnum.new(0)
62
- @buffer_high_watermark = Concurrent::AtomicFixnum.new(0)
63
-
64
- start_worker
65
- end
66
-
67
- def on_start(_span, _parent_context); end
68
-
69
- def on_finish(span)
70
- detect_forking
71
-
72
- ctx = span.context
73
- sampled = ctx&.trace_flags&.sampled?
74
- marked = ctx && @marker.marked?(ctx.trace_id)
75
- owner_finished = marked && @marker.owner?(ctx.trace_id, ctx.span_id)
76
-
77
- return unless sampled || marked
78
-
79
- span_data = span.respond_to?(:to_span_data) ? span.to_span_data : span
80
- enqueue(
81
- span_data,
82
- complete: owner_finished || sampled_completion_span?(span_data),
83
- delay: owner_finished ? @marked_trace_grace_period : 0
84
- )
85
- end
86
-
87
- def force_flush(timeout: nil)
88
- detect_forking
89
- deadline = Deadline.new(timeout)
90
- return TIMEOUT unless begin_flush(deadline)
91
- prior_result = @flush_prior_result
92
-
93
- batch = snapshot_for_flush(deadline)
94
- return TIMEOUT unless batch
95
-
96
- operation = start_flush_export(batch, deadline)
97
- result = wait_for_flush_export(operation, deadline)
98
- [prior_result, result].max
99
- ensure
100
- finish_flush if @flush_owner == Thread.current
101
- end
102
-
103
- def shutdown(timeout: nil)
104
- detect_forking
105
- deadline = Deadline.new(timeout)
106
- return TIMEOUT unless lock_before_deadline(deadline)
107
-
108
- begin
109
- @stopped = true
110
- @cond.broadcast
111
- ensure
112
- @mutex.unlock
113
- end
114
- @worker.join(deadline.remaining || 5)
115
- return TIMEOUT if @worker.alive? || deadline.expired?
116
-
117
- result = force_flush(timeout: deadline.remaining)
118
- return result unless result == SUCCESS
119
- return TIMEOUT if deadline.expired?
120
-
121
- exporter_result = @exporter.shutdown(timeout: deadline.remaining) if @exporter.respond_to?(:shutdown)
122
- return TIMEOUT if deadline.expired?
123
-
124
- exporter_result || SUCCESS
125
- end
126
-
127
- def buffer_size
128
- @mutex.synchronize { queued_span_count }
129
- end
130
-
131
- def reset_buffer_high_watermark
132
- @buffer_high_watermark.value = buffer_size
133
- end
134
-
135
- private
136
-
137
- def enqueue(span_data, complete:, delay: 0)
138
- @mutex.synchronize do
139
- trace_id = span_data.trace_id
140
- @trace_order << trace_id unless @pending_by_trace.key?(trace_id)
141
- @pending_by_trace[trace_id] ||= []
142
- @pending_by_trace[trace_id] << span_data
143
- @pending_count += 1
144
- evict_oldest_spans
145
-
146
- if complete
147
- delay.positive? ? delay_trace_ready(trace_id, delay) : mark_trace_ready(trace_id)
148
- end
149
- evict_oldest_spans
150
- update_buffer_high_watermark
151
- end
152
- end
153
-
154
- def worker_loop
155
- until stopped?
156
- @mutex.synchronize do
157
- timeout = next_wait_timeout
158
- waiting_for_export = @flush_owner || @active_exports.positive?
159
- @cond.wait(@mutex, timeout) if (@ready_queue.empty? || waiting_for_export) && !@stopped
160
- end
161
- drain_and_export
162
- end
163
- end
164
-
165
- def stopped?
166
- @mutex.synchronize { @stopped }
167
- end
168
-
169
- def drain_and_export
170
- batch = nil
171
- @mutex.synchronize do
172
- promote_due_delayed_traces
173
- return if @ready_queue.empty? || @flush_owner || @active_exports.positive?
174
-
175
- batch = @ready_queue
176
- @ready_queue = []
177
- @active_exports += 1
178
- end
179
-
180
- result = export_batch(batch, timeout: @export_timeout)
181
- ensure
182
- export_finished(result || FAILURE) if batch
183
- end
184
-
185
- def begin_flush(deadline)
186
- return false unless lock_before_deadline(deadline)
187
-
188
- begin
189
- initial_sequence = @export_completion_sequence
190
- while @flush_owner && @flush_owner != Thread.current
191
- return false if deadline.expired?
192
-
193
- @cond.wait(@mutex, deadline.remaining)
194
- end
195
- @flush_owner = Thread.current
196
-
197
- while @active_exports.positive?
198
- return false if deadline.expired?
199
-
200
- @cond.wait(@mutex, deadline.remaining)
201
- end
202
- @flush_prior_result = if @export_completion_sequence > initial_sequence
203
- @last_export_result
204
- else
205
- SUCCESS
206
- end
207
- ensure
208
- @mutex.unlock
209
- end
210
- true
211
- end
212
-
213
- def finish_flush
214
- @mutex.synchronize do
215
- @flush_owner = nil
216
- @cond.broadcast
217
- end
218
- end
219
-
220
- def snapshot_for_flush(deadline)
221
- return unless lock_before_deadline(deadline)
222
-
223
- begin
224
- @ready_queue.concat(@pending_by_trace.values.flatten)
225
- @pending_by_trace.clear
226
- @trace_order.clear
227
- @pending_count = 0
228
- unmark_delayed_traces
229
- @delayed_ready_by_trace.clear
230
- batch = @ready_queue
231
- @ready_queue = []
232
- batch
233
- ensure
234
- @mutex.unlock
235
- end
236
- end
237
-
238
- def start_flush_export(batch, deadline)
239
- operation = { done: false, result: nil }
240
- @mutex.synchronize { @active_exports += 1 }
241
- Thread.new do
242
- result = batch.empty? ? SUCCESS : export_batch(batch, timeout: deadline.remaining)
243
- if result == SUCCESS && !deadline.expired? && @exporter.respond_to?(:force_flush)
244
- result = @exporter.force_flush(timeout: deadline.remaining)
245
- end
246
- result = TIMEOUT if deadline.expired?
247
- operation[:result] = result
248
- rescue StandardError => e
249
- @exception_count.increment
250
- @logger.warn("[Flare::FilteringSpanProcessor] force flush failed: #{e.class}: #{e.message}")
251
- operation[:result] = FAILURE
252
- ensure
253
- @mutex.synchronize do
254
- operation[:done] = true
255
- complete_export(operation[:result])
256
- end
257
- end
258
- operation
259
- end
260
-
261
- def wait_for_flush_export(operation, deadline)
262
- @mutex.synchronize do
263
- until operation[:done]
264
- return TIMEOUT if deadline.expired?
265
-
266
- @cond.wait(@mutex, deadline.remaining)
267
- end
268
- end
269
- operation[:result]
270
- end
271
-
272
- def export_batch(batch, timeout:)
273
- result = @exporter.export(batch, timeout: timeout)
274
- @failed_export_count.increment if result != SUCCESS
275
- result
276
- rescue StandardError => e
277
- @exception_count.increment
278
- @logger.warn("[Flare::FilteringSpanProcessor] export failed: #{e.class}: #{e.message}")
279
- FAILURE
280
- end
281
-
282
- def export_finished(result)
283
- @mutex.synchronize do
284
- complete_export(result)
285
- end
286
- end
287
-
288
- def complete_export(result)
289
- @active_exports -= 1
290
- @export_completion_sequence += 1
291
- @last_export_result = result || FAILURE
292
- @cond.broadcast
293
- end
294
-
295
- def lock_before_deadline(deadline)
296
- return @mutex.lock unless deadline.remaining
297
-
298
- until @mutex.try_lock
299
- return false if deadline.expired?
300
-
301
- sleep([deadline.remaining, 0.001].min)
302
- end
303
- true
304
- end
305
-
306
- def mark_trace_ready(trace_id)
307
- batch = @pending_by_trace.delete(trace_id)
308
- return unless batch
309
-
310
- @trace_order.delete(trace_id)
311
- @delayed_ready_by_trace.delete(trace_id)
312
- @pending_count -= batch.length
313
- @ready_queue.concat(batch)
314
- @cond.signal
315
- end
316
-
317
- def delay_trace_ready(trace_id, delay)
318
- @delayed_ready_by_trace[trace_id] = monotonic_now + delay
319
- @cond.signal
320
- end
321
-
322
- def promote_due_delayed_traces
323
- now = monotonic_now
324
- ready_trace_ids = @delayed_ready_by_trace.select { |_, ready_at| ready_at <= now }.keys
325
- ready_trace_ids.each do |trace_id|
326
- mark_trace_ready(trace_id)
327
- @marker.unmark(trace_id)
328
- end
329
- end
330
-
331
- def unmark_delayed_traces
332
- @delayed_ready_by_trace.each_key { |trace_id| @marker.unmark(trace_id) }
333
- end
334
-
335
- def next_wait_timeout
336
- next_ready_at = @delayed_ready_by_trace.values.min
337
- return @flush_interval unless next_ready_at
338
-
339
- [next_ready_at - monotonic_now, 0].max
340
- end
341
-
342
- def evict_oldest_spans
343
- while queued_span_count > @max_queue
344
- trace_id = @trace_order.first
345
- unless trace_id
346
- @ready_queue.shift
347
- @dropped_count.increment
348
- next
349
- end
350
-
351
- spans = @pending_by_trace[trace_id]
352
- if spans.nil? || spans.empty?
353
- @trace_order.shift
354
- next
355
- end
356
-
357
- spans.shift
358
- @pending_count -= 1
359
- @dropped_count.increment
360
-
361
- if spans.empty?
362
- @pending_by_trace.delete(trace_id)
363
- @trace_order.shift
364
- end
365
- end
366
- end
367
-
368
- def queued_span_count
369
- @pending_count + @ready_queue.length
370
- end
371
-
372
- def update_buffer_high_watermark
373
- current = queued_span_count
374
- @buffer_high_watermark.update { |previous| current > previous ? current : previous }
375
- end
376
-
377
- def monotonic_now
378
- Process.clock_gettime(Process::CLOCK_MONOTONIC)
379
- end
380
-
381
- def sampled_completion_span?(span_data)
382
- root_span?(span_data) || entry_span?(span_data)
383
- end
384
-
385
- def root_span?(span_data)
386
- parent_id = span_data.parent_span_id if span_data.respond_to?(:parent_span_id)
387
- parent_id.nil? ||
388
- (parent_id.respond_to?(:empty?) && parent_id.empty?) ||
389
- parent_id == OpenTelemetry::Trace::INVALID_SPAN_ID
390
- end
391
-
392
- def entry_span?(span_data)
393
- return false unless span_data.respond_to?(:kind)
394
-
395
- span_data.kind == :server || span_data.kind == :consumer
396
- end
397
-
398
- def detect_forking
399
- return if @pid == $$
400
-
401
- # The child only retains the forking thread. Replace synchronization
402
- # objects so it cannot inherit locks or active-export bookkeeping owned
403
- # by vanished threads.
404
- @pid = $$
405
- @mutex = Mutex.new
406
- @cond = ConditionVariable.new
407
- @pending_by_trace = {}
408
- @trace_order = []
409
- @ready_queue = []
410
- @delayed_ready_by_trace = {}
411
- @pending_count = 0
412
- @active_exports = 0
413
- @export_completion_sequence = 0
414
- @last_export_result = SUCCESS
415
- @flush_prior_result = SUCCESS
416
- @flush_owner = nil
417
- @stopped = false
418
- @worker = nil
419
- start_worker
420
- end
421
-
422
- def start_worker
423
- return if @worker&.alive?
424
-
425
- @worker = Thread.new { worker_loop }
426
- @worker.name = "flare-filtering-span-processor"
427
- end
428
- end
429
- end
@@ -1,101 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Flare
4
- class HttpMetricsConfig
5
- class HostConfig
6
- attr_reader :rules
7
-
8
- def initialize
9
- @rules = []
10
- @all = false
11
- end
12
-
13
- def initialize_copy(source)
14
- @rules = source.rules.dup
15
- end
16
-
17
- def all?
18
- @all
19
- end
20
-
21
- # Track all paths for this host (normalized via normalize_path)
22
- def all
23
- @all = true
24
- end
25
-
26
- # Track paths matching this regex (normalized via normalize_path)
27
- def allow(pattern)
28
- @rules << {pattern: pattern, replacement: nil}
29
- end
30
-
31
- # Track paths matching this regex with a custom replacement string
32
- def map(pattern, replacement)
33
- @rules << {pattern: pattern, replacement: replacement}
34
- end
35
-
36
- # Returns the resolved path for a given raw path, or "*" if no match.
37
- # If :all, returns nil to signal "use normalize_path".
38
- # If a rule matches with a replacement, returns the replacement.
39
- # If a rule matches without a replacement, returns nil to signal "use normalize_path".
40
- # If no rules match, returns "*".
41
- def resolve(path)
42
- return nil if @all
43
-
44
- @rules.each do |rule|
45
- if rule[:pattern].match?(path)
46
- return rule[:replacement] # nil means use normalize_path
47
- end
48
- end
49
-
50
- "*"
51
- end
52
- end
53
-
54
- def initialize
55
- @hosts = {}
56
- end
57
-
58
- def initialize_copy(source)
59
- @hosts = source.instance_variable_get(:@hosts).transform_values(&:dup)
60
- end
61
-
62
- def host(hostname, mode = nil, &block)
63
- config = @hosts[hostname] ||= HostConfig.new
64
-
65
- if mode == :all
66
- config.all
67
- elsif block
68
- yield config
69
- end
70
- end
71
-
72
- # Resolve a host+path to the target path for metrics.
73
- # Returns "*" for unknown hosts or unmatched paths.
74
- # Returns nil to signal "use normalize_path".
75
- # Returns a string for custom replacements.
76
- def resolve(hostname, path)
77
- host_config = @hosts[hostname]
78
- return "*" unless host_config
79
-
80
- host_config.resolve(path)
81
- end
82
-
83
- DEFAULT = new.tap do |config|
84
- config.host "flare.am" do |h|
85
- h.allow %r{/api/metrics}
86
- end
87
-
88
- config.host "www.flippercloud.io" do |h|
89
- h.map %r{/adapter/features/[^/]+/(boolean|actors|groups|percentage_of_actors|percentage_of_time|expression|clear)}, "/adapter/features/:name/:gate"
90
- h.map %r{/adapter/features/[^/]+}, "/adapter/features/:name"
91
- h.map %r{/adapter/actors/[^/]+}, "/adapter/actors/:id"
92
- h.allow %r{/adapter/features}
93
- h.allow %r{/adapter/import}
94
- h.allow %r{/adapter/telemetry/summary}
95
- h.allow %r{/adapter/telemetry}
96
- h.allow %r{/adapter/events}
97
- h.allow %r{/adapter/audits}
98
- end
99
- end.freeze
100
- end
101
- end
@@ -1,76 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "net/http"
4
- require "uri"
5
-
6
- require_relative "deadline"
7
-
8
- module Flare
9
- # Tiny HTTP wrapper used by TraceExporter (and anything else that wants
10
- # to PUT/POST without pulling in a heavy client). Designed for injection
11
- # at the boundary so tests can swap in a recording fake; no other moving
12
- # parts.
13
- class HttpTransport
14
- DeadlineExceeded = Class.new(StandardError)
15
- DEFAULT_OPEN_TIMEOUT = 2
16
- DEFAULT_READ_TIMEOUT = 5
17
- DEFAULT_WRITE_TIMEOUT = 5
18
-
19
- Response = Struct.new(:code, :body, :headers, keyword_init: true) do
20
- def header(name)
21
- return nil unless headers
22
- headers[name] || headers[name.downcase] || headers[name.upcase]
23
- end
24
- end
25
-
26
- def initialize(open_timeout: DEFAULT_OPEN_TIMEOUT,
27
- read_timeout: DEFAULT_READ_TIMEOUT,
28
- write_timeout: DEFAULT_WRITE_TIMEOUT)
29
- @open_timeout = open_timeout
30
- @read_timeout = read_timeout
31
- @write_timeout = write_timeout
32
- end
33
-
34
- def get(url, headers = {}, timeout: nil)
35
- request(url, nil, headers, Net::HTTP::Get, timeout: timeout)
36
- end
37
-
38
- def put(url, body, headers = {}, timeout: nil)
39
- request(url, body, headers, Net::HTTP::Put, timeout: timeout)
40
- end
41
-
42
- def post(url, body, headers = {}, timeout: nil)
43
- request(url, body, headers, Net::HTTP::Post, timeout: timeout)
44
- end
45
-
46
- private
47
-
48
- def request(url, body, headers, klass, timeout: nil)
49
- deadline = Deadline.new(timeout)
50
- raise DeadlineExceeded if deadline.expired?
51
-
52
- uri = URI(url)
53
- http = Net::HTTP.new(uri.host, uri.port)
54
- http.use_ssl = uri.scheme == "https"
55
- http.open_timeout = effective_timeout(@open_timeout, deadline.remaining)
56
- http.read_timeout = effective_timeout(@read_timeout, deadline.remaining)
57
- http.write_timeout = effective_timeout(@write_timeout, deadline.remaining) if http.respond_to?(:write_timeout=)
58
-
59
- req = klass.new(uri.request_uri == "" ? "/" : uri.request_uri)
60
- headers.each { |k, v| req[k] = v }
61
- req.body = body if body
62
-
63
- response = http.request(req)
64
- raise DeadlineExceeded if deadline.expired?
65
-
66
- hash = response.each_header.to_h
67
- Response.new(code: response.code.to_s, body: response.body, headers: hash)
68
- end
69
-
70
- def effective_timeout(configured_timeout, remaining)
71
- return configured_timeout unless remaining
72
-
73
- [configured_timeout, remaining].min
74
- end
75
- end
76
- end
@@ -1,39 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "opentelemetry/sdk"
4
-
5
- require_relative "deadline"
6
-
7
- module Flare
8
- module Lifecycle
9
- # Flush all trace processors and the separate aggregated metric pipeline
10
- # using one monotonic timeout budget. This is safe to call from lifecycle
11
- # hooks for short-lived and fork-per-job workers.
12
- def force_flush(timeout: nil)
13
- deadline = Deadline.new(timeout)
14
- results = []
15
-
16
- results << tracer_provider_for_flush.force_flush(timeout: deadline.remaining)
17
- return OpenTelemetry::SDK::Trace::Export::TIMEOUT if deadline.expired?
18
-
19
- flusher = metric_flusher_for_flush
20
- results << flusher.force_flush(timeout: deadline.remaining) if flusher
21
- return OpenTelemetry::SDK::Trace::Export::TIMEOUT if deadline.expired?
22
-
23
- results.max || OpenTelemetry::SDK::Trace::Export::SUCCESS
24
- rescue => e
25
- warn "[Flare] Telemetry flush error: #{e.message}"
26
- OpenTelemetry::SDK::Trace::Export::FAILURE
27
- end
28
-
29
- def tracer_provider_for_flush
30
- OpenTelemetry.tracer_provider
31
- end
32
-
33
- def metric_flusher_for_flush
34
- @metric_flusher
35
- end
36
- end
37
-
38
- extend Lifecycle
39
- end