tracelit 0.1.8 → 0.1.9

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 66ee178565bf80581941df2481fb09e981e013c335ac8a2e4c6d8845a62fb4f1
4
- data.tar.gz: a40528eb98fe34dd2dd3d7399cf0e5d40ea5fd0fb83900daec8ee6728d98b846
3
+ metadata.gz: e7f493aa08c7a1d3595be84019013d49bfa48e2daae3a653cb38b17ad702a8f4
4
+ data.tar.gz: 07e1c738fb2a5eee274b48f30ff6fea1b97c613b14778de081471fd1fa9ed1d0
5
5
  SHA512:
6
- metadata.gz: 19b1d323be4bc9f8f1cf531f9728d371537a5430a1e0f6eb82aba66af17ee20b5bddb819e83bd1b8d5f624444ac0a6afaeb68319bbdb380469f7c8ab1ea0945e
7
- data.tar.gz: d8c722105fb3b85f0cab312ec82ea81a2de12fae214f9f555bfb7ce6c3929184f231c13f228b182c9c268ab62e3ee94751295b00f939347012cfa43ed797e81a
6
+ metadata.gz: 4a9c56ef732bc424fbc7a04484c5967bba3d9714d1f520f2b017b07c9fd939b8126b6fc7ff575fe538911be951a0a0da8025dde7e50724c42bdab005703219a8
7
+ data.tar.gz: 728eefa18600408fcb6475ec87fe83669a0f373500bcbe2a010e2f0f0776c8bd8a5120c4f16ffcca886125be307a58ab369e38161345ed1df567464ee7777ec8
@@ -13,12 +13,27 @@ module Tracelit
13
13
  # so there is no double-export for sampled error spans
14
14
  #
15
15
  # NOTE: opentelemetry-sdk 1.x uses on_finish (not on_end) as the hook name.
16
+ #
17
+ # Important: this processor must never block application threads. Exporting an
18
+ # unsampled error span synchronously in on_finish can block request / console
19
+ # threads when the ingest endpoint is slow or retrying. We enqueue span data
20
+ # into a bounded in-memory queue and export on a background worker thread.
16
21
  class ErrorSpanProcessor
22
+ QUEUE_CAPACITY = 512
23
+ SHUTDOWN_SENTINEL = Object.new
24
+
17
25
  def initialize(exporter)
18
26
  @exporter = exporter
27
+ @queue = SizedQueue.new(QUEUE_CAPACITY)
28
+ @shutdown = false
29
+ @worker = Thread.new do
30
+ Thread.current[:tracelit_error_export_worker] = true
31
+ worker_loop
32
+ end
33
+ @worker.abort_on_exception = false
19
34
  end
20
35
 
21
- def on_start(span, parent_context)
36
+ def on_start(_span, _parent_context)
22
37
  # nothing to do at start
23
38
  end
24
39
 
@@ -30,19 +45,71 @@ module Tracelit
30
45
  # This prevents double-export of error spans on traces that were sampled.
31
46
  return if span.context.trace_flags.sampled?
32
47
 
33
- # Force-export this error span regardless of sampling decision
34
- @exporter.export([span.to_span_data])
48
+ # Queue for background export; never block the caller.
49
+ enqueue(span.to_span_data)
35
50
  rescue StandardError
36
51
  # Never let processor errors propagate to the application
37
52
  end
38
53
 
39
54
  def force_flush(timeout: nil)
55
+ wait_for_queue_drain(timeout)
40
56
  @exporter.force_flush(timeout: timeout)
41
57
  end
42
58
 
43
59
  def shutdown(timeout: nil)
60
+ return if @shutdown
61
+ @shutdown = true
62
+ enqueue_shutdown_signal
63
+ @worker&.join(timeout || 1)
44
64
  # Do not shut down the shared exporter here —
45
65
  # the BatchSpanProcessor owns its lifecycle
46
66
  end
67
+
68
+ private
69
+
70
+ def worker_loop
71
+ loop do
72
+ item = @queue.pop
73
+ break if item.equal?(SHUTDOWN_SENTINEL)
74
+
75
+ begin
76
+ @exporter.export([item])
77
+ rescue StandardError
78
+ # Never let exporter failures crash the app worker thread
79
+ end
80
+ end
81
+ rescue StandardError
82
+ # Last-ditch guard: processor background failures must stay isolated.
83
+ end
84
+
85
+ def enqueue(span_data)
86
+ @queue.push(span_data, true)
87
+ rescue ThreadError
88
+ # Queue full — drop to protect application latency.
89
+ end
90
+
91
+ def enqueue_shutdown_signal
92
+ @queue.push(SHUTDOWN_SENTINEL, true)
93
+ rescue ThreadError
94
+ # Queue is full: drop one oldest item and retry so shutdown can proceed.
95
+ begin
96
+ @queue.pop(true)
97
+ rescue ThreadError
98
+ # no-op
99
+ end
100
+ begin
101
+ @queue.push(SHUTDOWN_SENTINEL, true)
102
+ rescue ThreadError
103
+ # no-op — join timeout is a final guard
104
+ end
105
+ end
106
+
107
+ def wait_for_queue_drain(timeout)
108
+ deadline = timeout ? Time.now + timeout : nil
109
+ until @queue.empty?
110
+ break if deadline && Time.now >= deadline
111
+ sleep(0.01)
112
+ end
113
+ end
47
114
  end
48
115
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Tracelit
4
- VERSION = "0.1.8"
4
+ VERSION = "0.1.9"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tracelit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.8
4
+ version: 0.1.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tracelit
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-05-05 00:00:00.000000000 Z
11
+ date: 2026-05-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: opentelemetry-sdk