opentrace 0.7.0 → 0.8.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 +4 -4
- data/lib/opentrace/client.rb +47 -23
- data/lib/opentrace/config.rb +11 -4
- data/lib/opentrace/middleware.rb +13 -7
- data/lib/opentrace/rails.rb +94 -135
- data/lib/opentrace/request_collector.rb +22 -9
- data/lib/opentrace/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fd043296872a0f5241acc4f8331bc2d6c6f7e39628e0501fc6e0480af7c423f9
|
|
4
|
+
data.tar.gz: b37915c9ed52898d6607a668c264c573f8d2012d937ec4053712ff2c65fb741e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a9567eefab80204781b112f43f915a755ad55327eef2255b7b9c655da9eb36e4eed55c6617e1711d00bbd74ec9dd1f88332b0f0c0c4e2e0562e75a1fdcb8a9fe
|
|
7
|
+
data.tar.gz: 11bf43482a27798a4bcf20c7cb700a565692d2b863d3011feb403df97e039b2b75cd35e0e53f12d7b17a107b7d07ef1959c0539d9507397376bb8901afcb9346
|
data/lib/opentrace/client.rb
CHANGED
|
@@ -11,7 +11,6 @@ module OpenTrace
|
|
|
11
11
|
class Client
|
|
12
12
|
MAX_QUEUE_SIZE = 1000
|
|
13
13
|
PAYLOAD_MAX_BYTES = 262_144 # 256 KB (default; use config.max_payload_bytes to override)
|
|
14
|
-
POLL_INTERVAL = 0.05 # 50ms
|
|
15
14
|
MAX_RATE_LIMIT_BACKOFF = 60 # Cap Retry-After at 60 seconds
|
|
16
15
|
API_VERSION = 1
|
|
17
16
|
|
|
@@ -95,12 +94,13 @@ module OpenTrace
|
|
|
95
94
|
end
|
|
96
95
|
|
|
97
96
|
def reset_after_fork!
|
|
98
|
-
# After fork, the old thread/queue/mutex from the parent are dead.
|
|
97
|
+
# After fork, the old thread/queue/mutex/connection from the parent are dead.
|
|
99
98
|
# Re-create everything cleanly in the child process.
|
|
100
99
|
@pid = Process.pid
|
|
101
100
|
@queue = Thread::Queue.new
|
|
102
101
|
@mutex = Mutex.new
|
|
103
102
|
@thread = nil
|
|
103
|
+
@http = nil # Parent's connection is unusable after fork
|
|
104
104
|
@circuit_breaker.reset!
|
|
105
105
|
@rate_limit_until = nil
|
|
106
106
|
@auth_suspended = false
|
|
@@ -143,6 +143,8 @@ module OpenTrace
|
|
|
143
143
|
end
|
|
144
144
|
rescue Exception # rubocop:disable Lint/RescueException
|
|
145
145
|
# Swallow all errors including thread kill
|
|
146
|
+
ensure
|
|
147
|
+
close_http
|
|
146
148
|
end
|
|
147
149
|
|
|
148
150
|
def rate_limited?
|
|
@@ -188,16 +190,14 @@ module OpenTrace
|
|
|
188
190
|
end
|
|
189
191
|
|
|
190
192
|
def pop_with_timeout(timeout)
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
sleep(POLL_INTERVAL)
|
|
198
|
-
end
|
|
193
|
+
if timeout <= 0
|
|
194
|
+
# Deadline already passed — still try a non-blocking pop in case
|
|
195
|
+
# items arrived while we were busy (e.g. during version check).
|
|
196
|
+
@queue.pop(true)
|
|
197
|
+
else
|
|
198
|
+
@queue.pop(timeout: timeout)
|
|
199
199
|
end
|
|
200
|
-
rescue ClosedQueueError
|
|
200
|
+
rescue ThreadError, ClosedQueueError
|
|
201
201
|
nil
|
|
202
202
|
end
|
|
203
203
|
|
|
@@ -330,7 +330,6 @@ module OpenTrace
|
|
|
330
330
|
end
|
|
331
331
|
|
|
332
332
|
def http_post(json, batch_id: nil)
|
|
333
|
-
http = build_http(@uri)
|
|
334
333
|
request = Net::HTTP::Post.new(@uri.request_uri)
|
|
335
334
|
request["Authorization"] = "Bearer #{@config.api_key}"
|
|
336
335
|
request["Content-Type"] = "application/json"
|
|
@@ -345,7 +344,11 @@ module OpenTrace
|
|
|
345
344
|
request.body = json
|
|
346
345
|
end
|
|
347
346
|
|
|
348
|
-
|
|
347
|
+
persistent_http.request(request)
|
|
348
|
+
rescue IOError, Errno::EPIPE, Errno::ECONNRESET, Net::OpenTimeout => _e
|
|
349
|
+
# Connection went stale — reset and retry once
|
|
350
|
+
close_http
|
|
351
|
+
persistent_http.request(request)
|
|
349
352
|
end
|
|
350
353
|
|
|
351
354
|
def retryable_response?(response)
|
|
@@ -359,13 +362,27 @@ module OpenTrace
|
|
|
359
362
|
delay + jitter
|
|
360
363
|
end
|
|
361
364
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
http
|
|
366
|
-
|
|
367
|
-
http
|
|
368
|
-
http
|
|
365
|
+
# Returns a persistent Net::HTTP connection, creating one if needed.
|
|
366
|
+
# Only called from the dispatch thread — no synchronization needed.
|
|
367
|
+
def persistent_http
|
|
368
|
+
return @http if @http&.started?
|
|
369
|
+
|
|
370
|
+
@http = Net::HTTP.new(@uri.host, @uri.port)
|
|
371
|
+
@http.use_ssl = (@uri.scheme == "https")
|
|
372
|
+
@http.open_timeout = @config.timeout
|
|
373
|
+
@http.read_timeout = @config.timeout
|
|
374
|
+
@http.write_timeout = @config.timeout
|
|
375
|
+
@http.keep_alive_timeout = 30
|
|
376
|
+
@http.start
|
|
377
|
+
@http
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
def close_http
|
|
381
|
+
@http&.finish
|
|
382
|
+
rescue IOError
|
|
383
|
+
# Already closed
|
|
384
|
+
ensure
|
|
385
|
+
@http = nil
|
|
369
386
|
end
|
|
370
387
|
|
|
371
388
|
def gzip_compress(string)
|
|
@@ -421,13 +438,20 @@ module OpenTrace
|
|
|
421
438
|
end
|
|
422
439
|
|
|
423
440
|
@compatibility_checked = true
|
|
424
|
-
rescue
|
|
425
|
-
# Server might not support /api/version yet — that's fine
|
|
441
|
+
rescue Exception # rubocop:disable Lint/RescueException
|
|
442
|
+
# Server might not support /api/version yet — that's fine.
|
|
443
|
+
# Broad rescue: this is best-effort and must never kill the dispatch loop.
|
|
426
444
|
@compatibility_checked = true
|
|
427
445
|
end
|
|
428
446
|
|
|
447
|
+
# One-shot GET for version check. Uses a throwaway connection
|
|
448
|
+
# so a failure doesn't poison the persistent connection.
|
|
429
449
|
def http_get(uri)
|
|
430
|
-
http =
|
|
450
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
451
|
+
http.use_ssl = (uri.scheme == "https")
|
|
452
|
+
http.open_timeout = @config.timeout
|
|
453
|
+
http.read_timeout = @config.timeout
|
|
454
|
+
http.write_timeout = @config.timeout
|
|
431
455
|
request = Net::HTTP::Get.new(uri.request_uri)
|
|
432
456
|
request["User-Agent"] = "opentrace-ruby/#{OpenTrace::VERSION}"
|
|
433
457
|
request["Authorization"] = "Bearer #{@config.api_key}"
|
data/lib/opentrace/config.rb
CHANGED
|
@@ -20,7 +20,9 @@ module OpenTrace
|
|
|
20
20
|
:request_summary, :timeline, :timeline_max_events,
|
|
21
21
|
:memory_tracking, :http_tracking,
|
|
22
22
|
:max_payload_bytes,
|
|
23
|
-
:trace_propagation
|
|
23
|
+
:trace_propagation,
|
|
24
|
+
:log_forwarding, :view_tracking, :cache_tracking,
|
|
25
|
+
:deprecation_tracking, :detailed_request_log
|
|
24
26
|
|
|
25
27
|
def initialize
|
|
26
28
|
@endpoint = nil
|
|
@@ -30,7 +32,7 @@ module OpenTrace
|
|
|
30
32
|
@timeout = 1.0
|
|
31
33
|
@enabled = true
|
|
32
34
|
@context = nil # nil | Hash | Proc
|
|
33
|
-
@min_level = :
|
|
35
|
+
@min_level = :info
|
|
34
36
|
@allowed_levels = nil # nil = use min_level threshold (backward compatible)
|
|
35
37
|
@hostname = nil
|
|
36
38
|
@pid = nil
|
|
@@ -46,7 +48,7 @@ module OpenTrace
|
|
|
46
48
|
@on_drop = nil # ->(count, reason) { ... }
|
|
47
49
|
@compression = true
|
|
48
50
|
@compression_threshold = 1024 # only compress payloads > 1KB
|
|
49
|
-
@sql_logging =
|
|
51
|
+
@sql_logging = false
|
|
50
52
|
@sql_duration_threshold_ms = 0.0
|
|
51
53
|
@ignore_paths = []
|
|
52
54
|
@pool_monitoring = false
|
|
@@ -54,12 +56,17 @@ module OpenTrace
|
|
|
54
56
|
@queue_monitoring = false
|
|
55
57
|
@queue_monitoring_interval = 60
|
|
56
58
|
@request_summary = true
|
|
57
|
-
@timeline =
|
|
59
|
+
@timeline = false
|
|
58
60
|
@timeline_max_events = 200
|
|
59
61
|
@memory_tracking = false
|
|
60
62
|
@http_tracking = false
|
|
61
63
|
@max_payload_bytes = 262_144 # 256 KB
|
|
62
64
|
@trace_propagation = true
|
|
65
|
+
@log_forwarding = false
|
|
66
|
+
@view_tracking = false
|
|
67
|
+
@cache_tracking = false
|
|
68
|
+
@deprecation_tracking = false
|
|
69
|
+
@detailed_request_log = false
|
|
63
70
|
end
|
|
64
71
|
|
|
65
72
|
def valid?
|
data/lib/opentrace/middleware.rb
CHANGED
|
@@ -10,6 +10,9 @@ module OpenTrace
|
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
def call(env)
|
|
13
|
+
# When OpenTrace is disabled, pass through with zero overhead
|
|
14
|
+
return @app.call(env) unless OpenTrace.enabled?
|
|
15
|
+
|
|
13
16
|
request_id = env["action_dispatch.request_id"] || env["HTTP_X_REQUEST_ID"]
|
|
14
17
|
OpenTrace.current_request_id = request_id
|
|
15
18
|
Fiber[:opentrace_sql_count] = 0
|
|
@@ -25,15 +28,18 @@ module OpenTrace
|
|
|
25
28
|
Fiber[:opentrace_parent_span_id] = parent_span_id
|
|
26
29
|
end
|
|
27
30
|
|
|
28
|
-
# Create RequestCollector
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
# Create RequestCollector only when features that need it are enabled
|
|
32
|
+
cfg = OpenTrace.config
|
|
33
|
+
needs_collector = cfg.request_summary &&
|
|
34
|
+
(cfg.view_tracking || cfg.cache_tracking || cfg.http_tracking ||
|
|
35
|
+
cfg.timeline || cfg.memory_tracking)
|
|
36
|
+
|
|
37
|
+
if needs_collector
|
|
38
|
+
max_timeline = cfg.timeline ? cfg.timeline_max_events : 0
|
|
39
|
+
collector = OpenTrace::RequestCollector.new(max_timeline: max_timeline)
|
|
33
40
|
Fiber[:opentrace_collector] = collector
|
|
34
41
|
|
|
35
|
-
|
|
36
|
-
if OpenTrace.config.memory_tracking
|
|
42
|
+
if cfg.memory_tracking
|
|
37
43
|
collector.memory_before = current_rss_mb
|
|
38
44
|
end
|
|
39
45
|
end
|
data/lib/opentrace/rails.rb
CHANGED
|
@@ -13,140 +13,111 @@ if defined?(::Rails::Railtie)
|
|
|
13
13
|
config.after_initialize do |app|
|
|
14
14
|
next unless OpenTrace.enabled?
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
Rails.logger.broadcast_to
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
16
|
+
# Log forwarding (opt-in) — registers LogForwarder/Logger wrapper
|
|
17
|
+
if OpenTrace.config.log_forwarding
|
|
18
|
+
if Rails.logger.respond_to?(:broadcast_to)
|
|
19
|
+
Rails.logger.broadcast_to(OpenTrace::LogForwarder.new)
|
|
20
|
+
else
|
|
21
|
+
if app.config.logger
|
|
22
|
+
app.config.logger = OpenTrace::Logger.new(app.config.logger)
|
|
23
|
+
Rails.logger = app.config.logger
|
|
24
|
+
elsif Rails.logger
|
|
25
|
+
Rails.logger = OpenTrace::Logger.new(Rails.logger)
|
|
26
|
+
end
|
|
26
27
|
end
|
|
27
28
|
end
|
|
28
29
|
|
|
29
|
-
# Subscribe to controller request notifications
|
|
30
|
-
ActiveSupport::Notifications.subscribe("process_action.action_controller") do
|
|
31
|
-
|
|
32
|
-
forward_request_log(event)
|
|
30
|
+
# Subscribe to controller request notifications — always on
|
|
31
|
+
ActiveSupport::Notifications.subscribe("process_action.action_controller") do |name, started, finished, id, payload|
|
|
32
|
+
forward_request_log(name, started, finished, id, payload)
|
|
33
33
|
rescue StandardError
|
|
34
34
|
# Swallow - never affect the host app
|
|
35
35
|
end
|
|
36
36
|
|
|
37
|
-
#
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
collector = Fiber[:opentrace_collector]
|
|
43
|
-
needs_forward = OpenTrace.config.sql_duration_threshold_ms <= 0
|
|
44
|
-
|
|
45
|
-
next unless sql_count || collector || needs_forward
|
|
37
|
+
# SQL subscriber — lightweight counter + optional forwarding
|
|
38
|
+
sql_logging = OpenTrace.config.sql_logging
|
|
39
|
+
ActiveSupport::Notifications.subscribe("sql.active_record") do |name, started, finished, id, payload|
|
|
40
|
+
sql_count = Fiber[:opentrace_sql_count]
|
|
41
|
+
collector = Fiber[:opentrace_collector]
|
|
46
42
|
|
|
47
|
-
|
|
43
|
+
if sql_count || collector || sql_logging
|
|
44
|
+
duration_ms = (finished && started) ? (finished - started) * 1000.0 : 0.0
|
|
48
45
|
|
|
49
|
-
# Increment per-request SQL counter (
|
|
46
|
+
# Increment per-request SQL counter (~1-5μs)
|
|
50
47
|
if sql_count
|
|
51
48
|
Fiber[:opentrace_sql_count] = sql_count + 1
|
|
52
|
-
Fiber[:opentrace_sql_total_ms] = (Fiber[:opentrace_sql_total_ms] || 0.0) +
|
|
49
|
+
Fiber[:opentrace_sql_total_ms] = (Fiber[:opentrace_sql_total_ms] || 0.0) + duration_ms
|
|
53
50
|
end
|
|
54
51
|
|
|
55
|
-
# Feed RequestCollector for
|
|
52
|
+
# Feed RequestCollector for summary (no table extraction on hot path)
|
|
56
53
|
if collector
|
|
57
|
-
payload = event.payload
|
|
58
54
|
unless payload[:name] == "SCHEMA"
|
|
59
|
-
|
|
60
|
-
if payload[:sql] =~ /\b(?:FROM|INTO|UPDATE|JOIN)\s+[`"]?(\w+)[`"]?/i
|
|
61
|
-
table = $1
|
|
62
|
-
end
|
|
63
|
-
collector.record_sql(name: payload[:name], duration_ms: event.duration || 0.0, table: table)
|
|
55
|
+
collector.record_sql(name: payload[:name], duration_ms: duration_ms)
|
|
64
56
|
end
|
|
65
57
|
end
|
|
66
58
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
end
|
|
71
|
-
else
|
|
72
|
-
# Even when sql_logging is off, still count queries for N+1 detection and feed collector
|
|
73
|
-
ActiveSupport::Notifications.subscribe("sql.active_record") do |*args|
|
|
74
|
-
sql_count = Fiber[:opentrace_sql_count]
|
|
75
|
-
collector = Fiber[:opentrace_collector]
|
|
76
|
-
next unless sql_count || collector
|
|
77
|
-
|
|
78
|
-
event = ActiveSupport::Notifications::Event.new(*args)
|
|
79
|
-
|
|
80
|
-
if sql_count
|
|
81
|
-
Fiber[:opentrace_sql_count] = sql_count + 1
|
|
82
|
-
Fiber[:opentrace_sql_total_ms] = (Fiber[:opentrace_sql_total_ms] || 0.0) + (event.duration || 0.0)
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
# Feed RequestCollector for timeline & summary
|
|
86
|
-
if collector
|
|
87
|
-
payload = event.payload
|
|
88
|
-
unless payload[:name] == "SCHEMA"
|
|
89
|
-
table = nil
|
|
90
|
-
if payload[:sql] =~ /\b(?:FROM|INTO|UPDATE|JOIN)\s+[`"]?(\w+)[`"]?/i
|
|
91
|
-
table = $1
|
|
92
|
-
end
|
|
93
|
-
collector.record_sql(name: payload[:name], duration_ms: event.duration || 0.0, table: table)
|
|
94
|
-
end
|
|
59
|
+
# Forward individual SQL log (opt-in)
|
|
60
|
+
if sql_logging
|
|
61
|
+
forward_sql_log(payload, duration_ms)
|
|
95
62
|
end
|
|
96
|
-
rescue StandardError
|
|
97
|
-
# Swallow
|
|
98
63
|
end
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
# Subscribe to ActiveJob notifications
|
|
102
|
-
ActiveSupport::Notifications.subscribe("perform.active_job") do |*args|
|
|
103
|
-
event = ActiveSupport::Notifications::Event.new(*args)
|
|
104
|
-
forward_job_log(event)
|
|
105
64
|
rescue StandardError
|
|
106
65
|
# Swallow
|
|
107
66
|
end
|
|
108
67
|
|
|
109
|
-
# Subscribe to
|
|
110
|
-
ActiveSupport::Notifications.subscribe("
|
|
68
|
+
# Subscribe to ActiveJob notifications — always on
|
|
69
|
+
ActiveSupport::Notifications.subscribe("perform.active_job") do |*args|
|
|
111
70
|
event = ActiveSupport::Notifications::Event.new(*args)
|
|
112
|
-
|
|
71
|
+
forward_job_log(event)
|
|
113
72
|
rescue StandardError
|
|
114
73
|
# Swallow
|
|
115
74
|
end
|
|
116
75
|
|
|
117
|
-
#
|
|
118
|
-
|
|
119
|
-
ActiveSupport::Notifications.subscribe(
|
|
120
|
-
|
|
121
|
-
next unless collector
|
|
122
|
-
|
|
123
|
-
event = ActiveSupport::Notifications::Event.new(*args)
|
|
124
|
-
template = event.payload[:identifier]
|
|
125
|
-
# Shorten: /Users/deploy/app/views/orders/show.html.erb → orders/show.html.erb
|
|
126
|
-
template = template.split("views/").last if template&.include?("views/")
|
|
127
|
-
|
|
128
|
-
collector.record_view(template: template, duration_ms: event.duration || 0.0)
|
|
76
|
+
# Deprecation warnings (opt-in)
|
|
77
|
+
if OpenTrace.config.deprecation_tracking
|
|
78
|
+
ActiveSupport::Notifications.subscribe("deprecation.rails") do |_name, _started, _finished, _id, payload|
|
|
79
|
+
forward_deprecation_log(payload)
|
|
129
80
|
rescue StandardError
|
|
130
81
|
# Swallow
|
|
131
82
|
end
|
|
132
83
|
end
|
|
133
84
|
|
|
134
|
-
#
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
85
|
+
# View render tracking (opt-in)
|
|
86
|
+
if OpenTrace.config.view_tracking
|
|
87
|
+
%w[render_template.action_view render_partial.action_view].each do |event_name|
|
|
88
|
+
ActiveSupport::Notifications.subscribe(event_name) do |_name, started, finished, _id, payload|
|
|
89
|
+
collector = Fiber[:opentrace_collector]
|
|
90
|
+
next unless collector
|
|
139
91
|
|
|
140
|
-
|
|
141
|
-
|
|
92
|
+
duration_ms = (finished && started) ? (finished - started) * 1000.0 : 0.0
|
|
93
|
+
template = payload[:identifier]
|
|
94
|
+
template = template.split("views/").last if template&.include?("views/")
|
|
142
95
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
96
|
+
collector.record_view(template: template, duration_ms: duration_ms)
|
|
97
|
+
rescue StandardError
|
|
98
|
+
# Swallow
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Cache operation tracking (opt-in)
|
|
104
|
+
if OpenTrace.config.cache_tracking
|
|
105
|
+
%w[cache_read.active_support cache_write.active_support cache_delete.active_support].each do |event_name|
|
|
106
|
+
ActiveSupport::Notifications.subscribe(event_name) do |_name, started, finished, _id, payload|
|
|
107
|
+
collector = Fiber[:opentrace_collector]
|
|
108
|
+
next unless collector
|
|
109
|
+
|
|
110
|
+
duration_ms = (finished && started) ? (finished - started) * 1000.0 : 0.0
|
|
111
|
+
action = event_name.split(".").first.sub("cache_", "").to_sym
|
|
112
|
+
|
|
113
|
+
collector.record_cache(
|
|
114
|
+
action: action,
|
|
115
|
+
hit: payload[:hit],
|
|
116
|
+
duration_ms: duration_ms
|
|
117
|
+
)
|
|
118
|
+
rescue StandardError
|
|
119
|
+
# Swallow
|
|
120
|
+
end
|
|
150
121
|
end
|
|
151
122
|
end
|
|
152
123
|
|
|
@@ -177,20 +148,21 @@ if defined?(::Rails::Railtie)
|
|
|
177
148
|
class << self
|
|
178
149
|
private
|
|
179
150
|
|
|
180
|
-
def forward_request_log(
|
|
151
|
+
def forward_request_log(_name, started, finished, _id, payload)
|
|
181
152
|
return unless OpenTrace.enabled?
|
|
182
|
-
|
|
183
|
-
payload = event.payload
|
|
184
153
|
return if ignored_path?(payload[:path])
|
|
154
|
+
|
|
155
|
+
duration_ms = (finished && started) ? (finished - started) * 1000.0 : 0.0
|
|
156
|
+
|
|
185
157
|
metadata = {
|
|
186
158
|
request_id: payload[:headers]&.env&.dig("action_dispatch.request_id")
|
|
187
159
|
}.compact
|
|
188
160
|
|
|
189
|
-
#
|
|
190
|
-
user_id = extract_user_id
|
|
161
|
+
# User ID from cached context only (never calls controller.current_user)
|
|
162
|
+
user_id = extract_user_id
|
|
191
163
|
metadata[:user_id] = user_id if user_id
|
|
192
164
|
|
|
193
|
-
# Exception auto-capture with fingerprinting
|
|
165
|
+
# Exception auto-capture with fingerprinting (always on)
|
|
194
166
|
if payload[:exception]
|
|
195
167
|
metadata[:exception_class] = payload[:exception][0]
|
|
196
168
|
metadata[:exception_message] = truncate(payload[:exception][1], 500)
|
|
@@ -203,11 +175,11 @@ if defined?(::Rails::Railtie)
|
|
|
203
175
|
payload[:exception][0], cleaned)
|
|
204
176
|
end
|
|
205
177
|
|
|
206
|
-
#
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
178
|
+
# Detailed request info (opt-in)
|
|
179
|
+
if OpenTrace.config.detailed_request_log
|
|
180
|
+
extract_params(payload, metadata)
|
|
181
|
+
extract_request_headers(payload, metadata)
|
|
182
|
+
end
|
|
211
183
|
|
|
212
184
|
# Build structured request_summary from collector (or fall back to Fiber-locals)
|
|
213
185
|
request_summary = nil
|
|
@@ -220,7 +192,7 @@ if defined?(::Rails::Railtie)
|
|
|
220
192
|
method: payload[:method],
|
|
221
193
|
path: payload[:path],
|
|
222
194
|
status: payload[:status],
|
|
223
|
-
duration_ms:
|
|
195
|
+
duration_ms: duration_ms.round(1),
|
|
224
196
|
sql_count: summary[:sql_query_count],
|
|
225
197
|
sql_total_ms: summary[:sql_total_ms],
|
|
226
198
|
sql_slowest_ms: summary[:sql_slowest_ms],
|
|
@@ -245,11 +217,10 @@ if defined?(::Rails::Railtie)
|
|
|
245
217
|
}.compact
|
|
246
218
|
|
|
247
219
|
# Compute time breakdown
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
http_pct = collector.http_count > 0 ? [((collector.http_total_ms / total) * 100).round(1), 100.0].min : 0.0
|
|
220
|
+
if duration_ms > 0
|
|
221
|
+
sql_pct = [((collector.sql_total_ms / duration_ms) * 100).round(1), 100.0].min
|
|
222
|
+
view_pct = [((collector.view_total_ms / duration_ms) * 100).round(1), 100.0].min
|
|
223
|
+
http_pct = collector.http_count > 0 ? [((collector.http_total_ms / duration_ms) * 100).round(1), 100.0].min : 0.0
|
|
253
224
|
other_pct = [100 - sql_pct - view_pct - http_pct, 0].max.round(1)
|
|
254
225
|
request_summary[:time_breakdown] = {
|
|
255
226
|
sql_pct: sql_pct,
|
|
@@ -265,7 +236,7 @@ if defined?(::Rails::Railtie)
|
|
|
265
236
|
metadata[:method] = payload[:method]
|
|
266
237
|
metadata[:path] = payload[:path]
|
|
267
238
|
metadata[:status] = payload[:status]
|
|
268
|
-
metadata[:duration_ms] =
|
|
239
|
+
metadata[:duration_ms] = duration_ms.round(1)
|
|
269
240
|
metadata[:sql_query_count] = Fiber[:opentrace_sql_count]
|
|
270
241
|
metadata[:sql_total_ms] = Fiber[:opentrace_sql_total_ms]&.round(1)
|
|
271
242
|
metadata[:n_plus_one_warning] = true if Fiber[:opentrace_sql_count] > 20
|
|
@@ -276,7 +247,7 @@ if defined?(::Rails::Railtie)
|
|
|
276
247
|
metadata[:method] = payload[:method]
|
|
277
248
|
metadata[:path] = payload[:path]
|
|
278
249
|
metadata[:status] = payload[:status]
|
|
279
|
-
metadata[:duration_ms] =
|
|
250
|
+
metadata[:duration_ms] = duration_ms.round(1)
|
|
280
251
|
end
|
|
281
252
|
|
|
282
253
|
level = if payload[:exception]
|
|
@@ -288,7 +259,7 @@ if defined?(::Rails::Railtie)
|
|
|
288
259
|
else
|
|
289
260
|
"INFO"
|
|
290
261
|
end
|
|
291
|
-
message = "#{payload[:method]} #{payload[:path]} #{payload[:status]} #{
|
|
262
|
+
message = "#{payload[:method]} #{payload[:path]} #{payload[:status]} #{duration_ms.round(1)}ms"
|
|
292
263
|
|
|
293
264
|
OpenTrace.log(level, message, metadata, request_summary: request_summary)
|
|
294
265
|
rescue StandardError
|
|
@@ -356,11 +327,10 @@ if defined?(::Rails::Railtie)
|
|
|
356
327
|
# Swallow
|
|
357
328
|
end
|
|
358
329
|
|
|
359
|
-
def forward_sql_log(
|
|
330
|
+
def forward_sql_log(payload, duration_ms)
|
|
360
331
|
return unless OpenTrace.enabled?
|
|
361
332
|
|
|
362
|
-
|
|
363
|
-
duration = event.duration&.round(2)
|
|
333
|
+
duration = duration_ms&.round(2)
|
|
364
334
|
threshold = OpenTrace.config.sql_duration_threshold_ms
|
|
365
335
|
|
|
366
336
|
# Skip if below threshold
|
|
@@ -389,10 +359,9 @@ if defined?(::Rails::Railtie)
|
|
|
389
359
|
# Swallow
|
|
390
360
|
end
|
|
391
361
|
|
|
392
|
-
def forward_deprecation_log(
|
|
362
|
+
def forward_deprecation_log(payload)
|
|
393
363
|
return unless OpenTrace.enabled?
|
|
394
364
|
|
|
395
|
-
payload = event.payload
|
|
396
365
|
message = payload[:message].to_s
|
|
397
366
|
callsite = payload[:callstack]&.first&.to_s
|
|
398
367
|
|
|
@@ -423,19 +392,9 @@ if defined?(::Rails::Railtie)
|
|
|
423
392
|
# Swallow
|
|
424
393
|
end
|
|
425
394
|
|
|
426
|
-
def extract_user_id
|
|
427
|
-
# Prefer user_id from cached request context (avoids calling
|
|
428
|
-
# current_user which may trigger DB queries or auth logic)
|
|
395
|
+
def extract_user_id
|
|
429
396
|
cached = Fiber[:opentrace_cached_context]
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
controller = payload[:controller_instance]
|
|
433
|
-
return unless controller
|
|
434
|
-
|
|
435
|
-
if controller.respond_to?(:current_user, true)
|
|
436
|
-
user = controller.send(:current_user)
|
|
437
|
-
user.respond_to?(:id) ? user.id : nil
|
|
438
|
-
end
|
|
397
|
+
cached[:user_id] if cached&.key?(:user_id)
|
|
439
398
|
rescue StandardError
|
|
440
399
|
nil
|
|
441
400
|
end
|
|
@@ -12,6 +12,7 @@ module OpenTrace
|
|
|
12
12
|
|
|
13
13
|
def initialize(max_timeline: MAX_TIMELINE_EVENTS)
|
|
14
14
|
@max_timeline = max_timeline
|
|
15
|
+
@timeline_enabled = max_timeline > 0
|
|
15
16
|
|
|
16
17
|
@sql_count = 0
|
|
17
18
|
@sql_total_ms = 0.0
|
|
@@ -36,8 +37,12 @@ module OpenTrace
|
|
|
36
37
|
@memory_before = nil
|
|
37
38
|
@memory_after = nil
|
|
38
39
|
|
|
39
|
-
@
|
|
40
|
-
|
|
40
|
+
if @timeline_enabled
|
|
41
|
+
@timeline = []
|
|
42
|
+
@request_start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
43
|
+
else
|
|
44
|
+
@timeline = nil
|
|
45
|
+
end
|
|
41
46
|
end
|
|
42
47
|
|
|
43
48
|
def record_sql(name:, duration_ms:, table: nil)
|
|
@@ -49,7 +54,9 @@ module OpenTrace
|
|
|
49
54
|
@sql_slowest_name = name
|
|
50
55
|
end
|
|
51
56
|
|
|
52
|
-
|
|
57
|
+
if @timeline_enabled
|
|
58
|
+
append_timeline({ t: :sql, n: name, ms: duration_ms.round(1), at: offset_ms })
|
|
59
|
+
end
|
|
53
60
|
end
|
|
54
61
|
|
|
55
62
|
def record_view(template:, duration_ms:)
|
|
@@ -61,7 +68,9 @@ module OpenTrace
|
|
|
61
68
|
@view_slowest_template = template
|
|
62
69
|
end
|
|
63
70
|
|
|
64
|
-
|
|
71
|
+
if @timeline_enabled
|
|
72
|
+
append_timeline({ t: :view, n: template, ms: duration_ms.round(1), at: offset_ms })
|
|
73
|
+
end
|
|
65
74
|
end
|
|
66
75
|
|
|
67
76
|
def record_cache(action:, hit: nil, duration_ms: 0.0)
|
|
@@ -75,7 +84,9 @@ module OpenTrace
|
|
|
75
84
|
@cache_deletes += 1
|
|
76
85
|
end
|
|
77
86
|
|
|
78
|
-
|
|
87
|
+
if @timeline_enabled
|
|
88
|
+
append_timeline({ t: :cache, a: action, hit: hit, ms: duration_ms.round(2), at: offset_ms })
|
|
89
|
+
end
|
|
79
90
|
end
|
|
80
91
|
|
|
81
92
|
def record_http(method:, url:, host:, status:, duration_ms:, error: nil)
|
|
@@ -87,9 +98,11 @@ module OpenTrace
|
|
|
87
98
|
@http_slowest_host = host
|
|
88
99
|
end
|
|
89
100
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
101
|
+
if @timeline_enabled
|
|
102
|
+
entry = { t: :http, n: "#{method} #{host}", ms: duration_ms.round(1), s: status, at: offset_ms }
|
|
103
|
+
entry[:err] = error if error
|
|
104
|
+
append_timeline(entry)
|
|
105
|
+
end
|
|
93
106
|
end
|
|
94
107
|
|
|
95
108
|
def summary
|
|
@@ -107,7 +120,7 @@ module OpenTrace
|
|
|
107
120
|
cache_writes: @cache_writes,
|
|
108
121
|
cache_hit_ratio: @cache_reads > 0 ? (@cache_hits.to_f / @cache_reads).round(2) : nil,
|
|
109
122
|
n_plus_one_warning: @sql_count > 20 ? true : nil,
|
|
110
|
-
timeline: @timeline.empty? ? nil : @timeline
|
|
123
|
+
timeline: (@timeline.nil? || @timeline.empty?) ? nil : @timeline
|
|
111
124
|
}
|
|
112
125
|
|
|
113
126
|
# HTTP stats (only present if calls were made)
|
data/lib/opentrace/version.rb
CHANGED