opentrace 0.14.1 → 0.15.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1e351721dd14bd088cfae4e5ab01a8ef16686fff3f2d1cb9aed984fad47c48c6
4
- data.tar.gz: 8a098bfb548ea1a867aa1a57a50ebec3214a466e29d88f5a515a5409839650f5
3
+ metadata.gz: ad0233533a9c236a70cf379346c1f64f7ffecc58cf91ce413e55665e8b4f5675
4
+ data.tar.gz: 92d7052f7901fad33897b767441ea01f8ed299bc47d90d43153110a2fdd22c1e
5
5
  SHA512:
6
- metadata.gz: b383410de64041a2b247d8b44d511bf5bde57f4e7c62fd7ceabdd0dd875a487d0ec3c21eb46d524d3ed55cb4faaef3e8acc4046bfec724040990f918d5f02fd2
7
- data.tar.gz: e822d353c5b393c0a408fe63926df46631ef0b90b726fa48db9d6feaf033185778af3436988994eb3b555f32c4009d4603e4e5f3f96b9a092c7d4f3758275187
6
+ metadata.gz: 6f9b631bf0faab3e6671a41eccc04039339f2f95cfa90a6c380517572256c08c3115bb4c30ddae230933bac18f8c9858195a55beacda27ff2e44c5efb02ef4f7
7
+ data.tar.gz: 3671e28a4393225a5e891c09a38a734b974c05b70fd393ff892b06bb816fd5954880f7eab5da257248fe025df948f96b0fb94b2f526338d060a928514d5a4917
@@ -14,6 +14,7 @@ module OpenTrace
14
14
  @state = CLOSED
15
15
  @failure_count = 0
16
16
  @last_failure_at = nil
17
+ @half_open_probe_sent = false
17
18
  @mutex = Mutex.new
18
19
  end
19
20
 
@@ -25,12 +26,18 @@ module OpenTrace
25
26
  when OPEN
26
27
  if Time.now - @last_failure_at >= @recovery_timeout
27
28
  @state = HALF_OPEN
29
+ @half_open_probe_sent = true
28
30
  true
29
31
  else
30
32
  false
31
33
  end
32
34
  when HALF_OPEN
33
- false
35
+ if @half_open_probe_sent
36
+ false
37
+ else
38
+ @half_open_probe_sent = true
39
+ true
40
+ end
34
41
  end
35
42
  end
36
43
  end
@@ -38,6 +45,7 @@ module OpenTrace
38
45
  def record_success
39
46
  @mutex.synchronize do
40
47
  @failure_count = 0
48
+ @half_open_probe_sent = false
41
49
  @state = CLOSED
42
50
  end
43
51
  end
@@ -46,7 +54,12 @@ module OpenTrace
46
54
  @mutex.synchronize do
47
55
  @failure_count += 1
48
56
  @last_failure_at = Time.now
49
- @state = OPEN if @failure_count >= @failure_threshold
57
+ if @state == HALF_OPEN
58
+ @half_open_probe_sent = false
59
+ @state = OPEN
60
+ elsif @failure_count >= @failure_threshold
61
+ @state = OPEN
62
+ end
50
63
  end
51
64
  end
52
65
 
@@ -55,6 +68,7 @@ module OpenTrace
55
68
  @state = CLOSED
56
69
  @failure_count = 0
57
70
  @last_failure_at = nil
71
+ @half_open_probe_sent = false
58
72
  end
59
73
  end
60
74
  end
@@ -322,11 +322,20 @@ module OpenTrace
322
322
  @rate_limit_until = Time.now + retry_after
323
323
 
324
324
  # Re-enqueue batch items if space allows
325
+ re_enqueued = 0
325
326
  batch.each do |payload|
326
327
  break if @queue.size >= MAX_QUEUE_SIZE
327
- @queue.push(payload)
328
- rescue ClosedQueueError
329
- break
328
+ begin
329
+ @queue.push(payload)
330
+ re_enqueued += 1
331
+ rescue ClosedQueueError
332
+ break
333
+ end
334
+ end
335
+ dropped = batch.size - re_enqueued
336
+ if dropped > 0
337
+ @stats.increment(:dropped_error, dropped)
338
+ fire_on_drop(dropped, :shutdown)
330
339
  end
331
340
  end
332
341
 
@@ -404,6 +413,8 @@ module OpenTrace
404
413
  end
405
414
 
406
415
  def unix_socket_send(json)
416
+ require "socket" unless defined?(UNIXSocket)
417
+
407
418
  payload = if @config.compression && json.bytesize > @config.compression_threshold
408
419
  gzip_compress(json)
409
420
  else
@@ -416,9 +427,14 @@ module OpenTrace
416
427
  socket.write(payload)
417
428
  socket.flush
418
429
 
419
- # Read 4-byte status code response
420
- response_data = socket.read(4)
421
- status = response_data&.unpack1("N") || 500
430
+ # Read 4-byte status code response with timeout
431
+ if IO.select([socket], nil, nil, 5)
432
+ response_data = socket.read(4)
433
+ status = response_data&.unpack1("N") || 500
434
+ else
435
+ @stats.increment(:socket_timeouts)
436
+ status = 500
437
+ end
422
438
  socket.close
423
439
 
424
440
  UnixSocketResponse.new(status)
@@ -23,7 +23,7 @@ module OpenTrace
23
23
  :trace_propagation,
24
24
  :log_forwarding, :view_tracking, :cache_tracking,
25
25
  :deprecation_tracking, :detailed_request_log,
26
- :sample_rate, :sampler, :before_send,
26
+ :sampler, :before_send,
27
27
  :sql_normalization, :log_trace_injection,
28
28
  :source_context, :before_breadcrumb,
29
29
  :pii_scrubbing, :pii_patterns, :pii_disabled_patterns,
@@ -35,7 +35,7 @@ module OpenTrace
35
35
  :runtime_metrics, :runtime_metrics_interval
36
36
 
37
37
  # Custom writers that invalidate caches
38
- attr_reader :enabled, :min_level, :allowed_levels, :ignore_paths
38
+ attr_reader :enabled, :min_level, :allowed_levels, :ignore_paths, :sample_rate
39
39
 
40
40
  def enabled=(val)
41
41
  @enabled = val
@@ -56,6 +56,14 @@ module OpenTrace
56
56
  @ignore_paths = val
57
57
  end
58
58
 
59
+ def sample_rate=(val)
60
+ val = val.to_f
61
+ unless (0.0..1.0).cover?(val)
62
+ raise ArgumentError, "sample_rate must be between 0.0 and 1.0, got #{val}"
63
+ end
64
+ @sample_rate = val
65
+ end
66
+
59
67
  def initialize
60
68
  @endpoint = nil
61
69
  @api_key = nil
@@ -5,6 +5,22 @@ require_relative "trace_context"
5
5
 
6
6
  module OpenTrace
7
7
  class Middleware
8
+ # All Fiber-local keys managed by this middleware.
9
+ # Centralised so cleanup never misses one.
10
+ FIBER_KEYS = %i[
11
+ opentrace_collector
12
+ opentrace_cached_context
13
+ opentrace_sql_count
14
+ opentrace_sql_total_ms
15
+ opentrace_trace_id
16
+ opentrace_span_id
17
+ opentrace_parent_span_id
18
+ opentrace_transaction_name
19
+ opentrace_breadcrumbs
20
+ opentrace_session_id
21
+ opentrace_pending_explains
22
+ ].freeze
23
+
8
24
  def initialize(app)
9
25
  @app = app
10
26
  end
@@ -65,22 +81,16 @@ module OpenTrace
65
81
  collector.memory_after = current_rss_mb
66
82
  end
67
83
 
68
- Fiber[:opentrace_collector] = nil
69
- Fiber[:opentrace_cached_context] = nil
70
- Fiber[:opentrace_sql_count] = nil
71
- Fiber[:opentrace_sql_total_ms] = nil
72
- Fiber[:opentrace_trace_id] = nil
73
- Fiber[:opentrace_span_id] = nil
74
- Fiber[:opentrace_parent_span_id] = nil
75
- Fiber[:opentrace_transaction_name] = nil
76
- Fiber[:opentrace_breadcrumbs] = nil
77
- Fiber[:opentrace_session_id] = nil
78
- Fiber[:opentrace_pending_explains] = nil
84
+ cleanup_fiber_locals
79
85
  OpenTrace.current_request_id = nil
80
86
  end
81
87
 
82
88
  private
83
89
 
90
+ def cleanup_fiber_locals
91
+ FIBER_KEYS.each { |key| Fiber[key] = nil }
92
+ end
93
+
84
94
  # Extract trace context from incoming request headers.
85
95
  # Priority: W3C traceparent > X-Trace-ID > request_id > generate new
86
96
  def extract_trace_context(env)
@@ -13,7 +13,9 @@ module OpenTrace
13
13
  elsif entry.is_a?(Hash)
14
14
  entry # legacy direct payload
15
15
  end
16
- rescue StandardError
16
+ rescue StandardError => e
17
+ OpenTrace.stats.increment(:payload_build_errors) if OpenTrace.respond_to?(:stats)
18
+ $stderr.puts "[OpenTrace] PayloadBuilder error: #{e.class}: #{e.message}" if OpenTrace.respond_to?(:config) && OpenTrace.config.respond_to?(:debug) && OpenTrace.config.debug
17
19
  nil
18
20
  end
19
21
 
@@ -26,10 +26,8 @@ module OpenTrace
26
26
  @started_at = Time.now
27
27
  end
28
28
 
29
- # Hot path: no mutex. Under CRuby's GIL, Hash#[]= with integer
30
- # increment is effectively atomic for single operations.
31
29
  def increment(counter, amount = 1)
32
- @counters[counter] += amount
30
+ @mutex.synchronize { @counters[counter] += amount }
33
31
  end
34
32
 
35
33
  def get(counter)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module OpenTrace
4
- VERSION = "0.14.1"
4
+ VERSION = "0.15.0"
5
5
  end
data/lib/opentrace.rb CHANGED
@@ -25,13 +25,17 @@ module OpenTrace
25
25
  # Null object for when OpenTrace is not configured.
26
26
  # All methods are no-ops, avoiding nil checks on the hot path.
27
27
  class NilClient
28
+ def initialize
29
+ @nil_stats = NilStats.new
30
+ end
31
+
28
32
  def enqueue(_) = nil
29
33
  def shutdown(timeout: 5) = nil
30
34
  def queue_size = 0
31
35
  def circuit_state = :closed
32
36
  def auth_suspended? = false
33
37
  def stats_snapshot = { queue_size: 0, circuit_state: :closed, auth_suspended: false }
34
- def stats = NilStats.new
38
+ def stats = @nil_stats
35
39
  def supports?(_) = false
36
40
  end
37
41
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: opentrace
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.14.1
4
+ version: 0.15.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - OpenTrace