miniapm 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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +43 -0
- data/LICENSE +21 -0
- data/README.md +174 -0
- data/lib/generators/miniapm/install_generator.rb +27 -0
- data/lib/generators/miniapm/templates/README +19 -0
- data/lib/generators/miniapm/templates/initializer.rb +60 -0
- data/lib/miniapm/configuration.rb +176 -0
- data/lib/miniapm/context.rb +138 -0
- data/lib/miniapm/error_event.rb +130 -0
- data/lib/miniapm/exporters/errors.rb +67 -0
- data/lib/miniapm/exporters/otlp.rb +90 -0
- data/lib/miniapm/instrumentations/activejob.rb +271 -0
- data/lib/miniapm/instrumentations/activerecord.rb +123 -0
- data/lib/miniapm/instrumentations/base.rb +61 -0
- data/lib/miniapm/instrumentations/cache.rb +85 -0
- data/lib/miniapm/instrumentations/http/faraday.rb +112 -0
- data/lib/miniapm/instrumentations/http/httparty.rb +84 -0
- data/lib/miniapm/instrumentations/http/net_http.rb +99 -0
- data/lib/miniapm/instrumentations/rails/controller.rb +129 -0
- data/lib/miniapm/instrumentations/rails/railtie.rb +42 -0
- data/lib/miniapm/instrumentations/redis/redis.rb +135 -0
- data/lib/miniapm/instrumentations/redis/redis_client.rb +116 -0
- data/lib/miniapm/instrumentations/registry.rb +90 -0
- data/lib/miniapm/instrumentations/search/elasticsearch.rb +121 -0
- data/lib/miniapm/instrumentations/search/opensearch.rb +120 -0
- data/lib/miniapm/instrumentations/search/searchkick.rb +119 -0
- data/lib/miniapm/instrumentations/sidekiq.rb +185 -0
- data/lib/miniapm/middleware/error_handler.rb +120 -0
- data/lib/miniapm/middleware/rack.rb +103 -0
- data/lib/miniapm/span.rb +289 -0
- data/lib/miniapm/testing.rb +209 -0
- data/lib/miniapm/trace.rb +26 -0
- data/lib/miniapm/transport/batch_sender.rb +345 -0
- data/lib/miniapm/transport/http.rb +45 -0
- data/lib/miniapm/version.rb +5 -0
- data/lib/miniapm.rb +184 -0
- metadata +183 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MiniAPM
|
|
4
|
+
module Instrumentations
|
|
5
|
+
class Base
|
|
6
|
+
class << self
|
|
7
|
+
def install!
|
|
8
|
+
raise NotImplementedError, "Subclass must implement .install!"
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def installed?
|
|
12
|
+
@installed || false
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
protected
|
|
16
|
+
|
|
17
|
+
def mark_installed!
|
|
18
|
+
@installed = true
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def subscribe(event_name, &block)
|
|
22
|
+
ActiveSupport::Notifications.subscribe(event_name) do |*args|
|
|
23
|
+
event = ActiveSupport::Notifications::Event.new(*args)
|
|
24
|
+
block.call(event)
|
|
25
|
+
rescue StandardError => e
|
|
26
|
+
MiniAPM.logger.debug { "MiniAPM instrumentation error in #{event_name}: #{e.message}" }
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def create_span_from_event(event, name:, category:, attributes: {})
|
|
31
|
+
return unless MiniAPM.enabled?
|
|
32
|
+
return unless Context.current_trace
|
|
33
|
+
|
|
34
|
+
span = Span.new(
|
|
35
|
+
name: name,
|
|
36
|
+
category: category,
|
|
37
|
+
trace_id: Context.current_trace_id,
|
|
38
|
+
parent_span_id: Context.current_span&.span_id,
|
|
39
|
+
attributes: attributes
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
# Backfill timing from event
|
|
43
|
+
if event.time && event.end
|
|
44
|
+
span.instance_variable_set(:@start_time, (event.time.to_f * 1_000_000_000).to_i)
|
|
45
|
+
span.instance_variable_set(:@end_time, (event.end.to_f * 1_000_000_000).to_i)
|
|
46
|
+
else
|
|
47
|
+
span.finish
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
span
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def record_span(span)
|
|
54
|
+
return unless span
|
|
55
|
+
|
|
56
|
+
MiniAPM.record_span(span)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MiniAPM
|
|
4
|
+
module Instrumentations
|
|
5
|
+
class Cache < Base
|
|
6
|
+
EVENTS = %w[
|
|
7
|
+
cache_read.active_support
|
|
8
|
+
cache_write.active_support
|
|
9
|
+
cache_delete.active_support
|
|
10
|
+
cache_exist?.active_support
|
|
11
|
+
cache_fetch_hit.active_support
|
|
12
|
+
cache_generate.active_support
|
|
13
|
+
cache_increment.active_support
|
|
14
|
+
cache_decrement.active_support
|
|
15
|
+
].freeze
|
|
16
|
+
|
|
17
|
+
# Minimum duration to record (skip very fast operations)
|
|
18
|
+
MIN_DURATION_MS = 0.5
|
|
19
|
+
|
|
20
|
+
class << self
|
|
21
|
+
def install!
|
|
22
|
+
return if installed?
|
|
23
|
+
mark_installed!
|
|
24
|
+
|
|
25
|
+
EVENTS.each do |event_name|
|
|
26
|
+
subscribe(event_name) do |event|
|
|
27
|
+
handle_cache_event(event)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def handle_cache_event(event)
|
|
35
|
+
return unless MiniAPM.enabled?
|
|
36
|
+
return unless Context.current_trace
|
|
37
|
+
|
|
38
|
+
# Skip very fast cache operations to reduce noise
|
|
39
|
+
return if event.duration && event.duration < MIN_DURATION_MS
|
|
40
|
+
|
|
41
|
+
payload = event.payload
|
|
42
|
+
operation = event.name.sub("cache_", "").sub(".active_support", "")
|
|
43
|
+
key = payload[:key]
|
|
44
|
+
|
|
45
|
+
attributes = {
|
|
46
|
+
"cache.operation" => operation,
|
|
47
|
+
"cache.key" => truncate_key(key)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
# Add hit/miss info
|
|
51
|
+
if payload.key?(:hit)
|
|
52
|
+
attributes["cache.hit"] = payload[:hit]
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Add store class if available
|
|
56
|
+
if payload[:store]
|
|
57
|
+
attributes["cache.store"] = payload[:store].to_s
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Add super_operation for fetch
|
|
61
|
+
if payload[:super_operation]
|
|
62
|
+
attributes["cache.super_operation"] = payload[:super_operation].to_s
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
span = create_span_from_event(
|
|
66
|
+
event,
|
|
67
|
+
name: "cache #{operation}",
|
|
68
|
+
category: :cache,
|
|
69
|
+
attributes: attributes
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
record_span(span)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def truncate_key(key)
|
|
76
|
+
key_str = key.to_s
|
|
77
|
+
key_str.length > 200 ? key_str[0, 200] + "..." : key_str
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Auto-install when loaded
|
|
85
|
+
MiniAPM::Instrumentations::Cache.install!
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MiniAPM
|
|
4
|
+
module Instrumentations
|
|
5
|
+
module HTTP
|
|
6
|
+
class Faraday
|
|
7
|
+
class << self
|
|
8
|
+
def install!
|
|
9
|
+
return if @installed
|
|
10
|
+
return unless defined?(::Faraday)
|
|
11
|
+
|
|
12
|
+
@installed = true
|
|
13
|
+
|
|
14
|
+
# Define the middleware class only when Faraday is available
|
|
15
|
+
define_middleware_class!
|
|
16
|
+
|
|
17
|
+
# Register our middleware
|
|
18
|
+
::Faraday::Middleware.register_middleware(miniapm: middleware_class)
|
|
19
|
+
|
|
20
|
+
# Auto-inject into all connections by patching Connection
|
|
21
|
+
::Faraday::Connection.prepend(ConnectionPatch)
|
|
22
|
+
|
|
23
|
+
MiniAPM.logger.debug { "MiniAPM: Faraday instrumentation installed" }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def installed?
|
|
27
|
+
@installed || false
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def middleware_class
|
|
31
|
+
@middleware_class
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def define_middleware_class!
|
|
37
|
+
@middleware_class = Class.new(::Faraday::Middleware) do
|
|
38
|
+
def call(env)
|
|
39
|
+
return @app.call(env) unless MiniAPM.enabled?
|
|
40
|
+
return @app.call(env) unless MiniAPM::Context.current_trace
|
|
41
|
+
|
|
42
|
+
uri = env.url
|
|
43
|
+
http_method = env.method.to_s.upcase
|
|
44
|
+
|
|
45
|
+
# Inject trace context
|
|
46
|
+
MiniAPM::Context.inject_into_headers(env.request_headers)
|
|
47
|
+
|
|
48
|
+
span = MiniAPM::Span.new(
|
|
49
|
+
name: "#{http_method} #{uri.host}#{uri.path}",
|
|
50
|
+
category: :http_client,
|
|
51
|
+
trace_id: MiniAPM::Context.current_trace_id,
|
|
52
|
+
parent_span_id: MiniAPM::Context.current_span&.span_id,
|
|
53
|
+
attributes: {
|
|
54
|
+
"http.method" => http_method,
|
|
55
|
+
"http.url" => sanitize_url(uri),
|
|
56
|
+
"http.host" => uri.host,
|
|
57
|
+
"net.peer.name" => uri.host,
|
|
58
|
+
"net.peer.port" => uri.port || (uri.scheme == "https" ? 443 : 80)
|
|
59
|
+
}
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
MiniAPM::Context.with_span(span) do
|
|
63
|
+
begin
|
|
64
|
+
response = @app.call(env)
|
|
65
|
+
|
|
66
|
+
span.add_attribute("http.status_code", response.status)
|
|
67
|
+
|
|
68
|
+
if response.status >= 400
|
|
69
|
+
span.set_error("HTTP #{response.status}")
|
|
70
|
+
else
|
|
71
|
+
span.set_ok
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
response
|
|
75
|
+
rescue StandardError => e
|
|
76
|
+
span.record_exception(e)
|
|
77
|
+
raise
|
|
78
|
+
ensure
|
|
79
|
+
span.finish
|
|
80
|
+
MiniAPM.record_span(span)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
private
|
|
86
|
+
|
|
87
|
+
def sanitize_url(uri)
|
|
88
|
+
port = uri.port || (uri.scheme == "https" ? 443 : 80)
|
|
89
|
+
"#{uri.scheme}://#{uri.host}:#{port}#{uri.path}"
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Patch to auto-inject middleware
|
|
96
|
+
module ConnectionPatch
|
|
97
|
+
def initialize(url = nil, options = nil, &block)
|
|
98
|
+
super
|
|
99
|
+
|
|
100
|
+
middleware_class = MiniAPM::Instrumentations::HTTP::Faraday.middleware_class
|
|
101
|
+
# Add our middleware if not already present
|
|
102
|
+
unless @builder.handlers.any? { |h| h.klass == middleware_class }
|
|
103
|
+
@builder.insert(0, middleware_class)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Installation is handled by the registry, not auto-install
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MiniAPM
|
|
4
|
+
module Instrumentations
|
|
5
|
+
module HTTP
|
|
6
|
+
class HTTParty
|
|
7
|
+
class << self
|
|
8
|
+
def install!
|
|
9
|
+
return if @installed
|
|
10
|
+
return unless defined?(::HTTParty)
|
|
11
|
+
|
|
12
|
+
@installed = true
|
|
13
|
+
::HTTParty::Request.prepend(Patch)
|
|
14
|
+
|
|
15
|
+
MiniAPM.logger.debug { "MiniAPM: HTTParty instrumentation installed" }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def installed?
|
|
19
|
+
@installed || false
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
module Patch
|
|
24
|
+
def perform(&block)
|
|
25
|
+
return super unless MiniAPM.enabled?
|
|
26
|
+
return super unless MiniAPM::Context.current_trace
|
|
27
|
+
|
|
28
|
+
uri = self.uri
|
|
29
|
+
http_method = self.http_method.name.split("::").last.upcase
|
|
30
|
+
|
|
31
|
+
# Inject trace context into outgoing request
|
|
32
|
+
MiniAPM::Context.inject_into_headers(options[:headers] ||= {})
|
|
33
|
+
|
|
34
|
+
span = MiniAPM::Span.new(
|
|
35
|
+
name: "#{http_method} #{uri.host}#{uri.path}",
|
|
36
|
+
category: :http_client,
|
|
37
|
+
trace_id: MiniAPM::Context.current_trace_id,
|
|
38
|
+
parent_span_id: MiniAPM::Context.current_span&.span_id,
|
|
39
|
+
attributes: {
|
|
40
|
+
"http.method" => http_method,
|
|
41
|
+
"http.url" => sanitize_url(uri),
|
|
42
|
+
"http.host" => uri.host,
|
|
43
|
+
"net.peer.name" => uri.host,
|
|
44
|
+
"net.peer.port" => uri.port
|
|
45
|
+
}
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
MiniAPM::Context.with_span(span) do
|
|
49
|
+
begin
|
|
50
|
+
response = super
|
|
51
|
+
|
|
52
|
+
if response
|
|
53
|
+
span.add_attribute("http.status_code", response.code)
|
|
54
|
+
|
|
55
|
+
if response.code >= 400
|
|
56
|
+
span.set_error("HTTP #{response.code}")
|
|
57
|
+
else
|
|
58
|
+
span.set_ok
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
response
|
|
63
|
+
rescue StandardError => e
|
|
64
|
+
span.record_exception(e)
|
|
65
|
+
raise
|
|
66
|
+
ensure
|
|
67
|
+
span.finish
|
|
68
|
+
MiniAPM.record_span(span)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
private
|
|
74
|
+
|
|
75
|
+
def sanitize_url(uri)
|
|
76
|
+
"#{uri.scheme}://#{uri.host}:#{uri.port}#{uri.path}"
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Installation is handled by the registry, not auto-install
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MiniAPM
|
|
4
|
+
module Instrumentations
|
|
5
|
+
module HTTP
|
|
6
|
+
class NetHTTP
|
|
7
|
+
class << self
|
|
8
|
+
def install!
|
|
9
|
+
return if @installed
|
|
10
|
+
return unless defined?(::Net::HTTP)
|
|
11
|
+
|
|
12
|
+
@installed = true
|
|
13
|
+
::Net::HTTP.prepend(Patch)
|
|
14
|
+
|
|
15
|
+
MiniAPM.logger.debug { "MiniAPM: Net::HTTP instrumentation installed" }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def installed?
|
|
19
|
+
@installed || false
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
module Patch
|
|
24
|
+
def request(req, body = nil, &block)
|
|
25
|
+
return super unless MiniAPM.enabled?
|
|
26
|
+
return super unless MiniAPM::Context.current_trace
|
|
27
|
+
|
|
28
|
+
# Skip if this is MiniAPM's own request
|
|
29
|
+
return super if req["User-Agent"]&.include?("miniapm-ruby")
|
|
30
|
+
|
|
31
|
+
uri = build_uri(req)
|
|
32
|
+
|
|
33
|
+
# Inject trace context into outgoing request
|
|
34
|
+
MiniAPM::Context.inject_into_headers(req)
|
|
35
|
+
|
|
36
|
+
span = MiniAPM::Span.new(
|
|
37
|
+
name: "#{req.method} #{uri.host}#{uri.path}",
|
|
38
|
+
category: :http_client,
|
|
39
|
+
trace_id: MiniAPM::Context.current_trace_id,
|
|
40
|
+
parent_span_id: MiniAPM::Context.current_span&.span_id,
|
|
41
|
+
attributes: {
|
|
42
|
+
"http.method" => req.method,
|
|
43
|
+
"http.url" => sanitize_url(uri),
|
|
44
|
+
"http.host" => uri.host,
|
|
45
|
+
"net.peer.name" => uri.host,
|
|
46
|
+
"net.peer.port" => uri.port
|
|
47
|
+
}
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
MiniAPM::Context.with_span(span) do
|
|
51
|
+
begin
|
|
52
|
+
response = super
|
|
53
|
+
|
|
54
|
+
span.add_attribute("http.status_code", response.code.to_i)
|
|
55
|
+
span.add_attribute("http.response_content_length", response["content-length"].to_i) if response["content-length"]
|
|
56
|
+
|
|
57
|
+
if response.code.to_i >= 400
|
|
58
|
+
span.set_error("HTTP #{response.code}")
|
|
59
|
+
else
|
|
60
|
+
span.set_ok
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
response
|
|
64
|
+
rescue StandardError => e
|
|
65
|
+
span.record_exception(e)
|
|
66
|
+
raise
|
|
67
|
+
ensure
|
|
68
|
+
span.finish
|
|
69
|
+
MiniAPM.record_span(span)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
private
|
|
75
|
+
|
|
76
|
+
def build_uri(req)
|
|
77
|
+
scheme = use_ssl? ? "https" : "http"
|
|
78
|
+
host = address
|
|
79
|
+
port_str = (use_ssl? && port == 443) || (!use_ssl? && port == 80) ? "" : ":#{port}"
|
|
80
|
+
|
|
81
|
+
path = req.path || "/"
|
|
82
|
+
path = "/" + path unless path.start_with?("/")
|
|
83
|
+
|
|
84
|
+
URI.parse("#{scheme}://#{host}#{port_str}#{path}")
|
|
85
|
+
rescue StandardError
|
|
86
|
+
URI.parse("http://#{address}:#{port}#{req.path}")
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def sanitize_url(uri)
|
|
90
|
+
# Remove query params for privacy
|
|
91
|
+
"#{uri.scheme}://#{uri.host}:#{uri.port}#{uri.path}"
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Installation is handled by the registry, not auto-install
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MiniAPM
|
|
4
|
+
module Instrumentations
|
|
5
|
+
module Rails
|
|
6
|
+
class Controller < Base
|
|
7
|
+
class << self
|
|
8
|
+
def install!
|
|
9
|
+
return if installed?
|
|
10
|
+
mark_installed!
|
|
11
|
+
|
|
12
|
+
# Subscribe to controller processing
|
|
13
|
+
subscribe("process_action.action_controller") do |event|
|
|
14
|
+
handle_process_action(event)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Subscribe to view rendering
|
|
18
|
+
subscribe("render_template.action_view") do |event|
|
|
19
|
+
handle_render_template(event)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
subscribe("render_partial.action_view") do |event|
|
|
23
|
+
handle_render_partial(event)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
subscribe("render_collection.action_view") do |event|
|
|
27
|
+
handle_render_collection(event)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
subscribe("render_layout.action_view") do |event|
|
|
31
|
+
handle_render_layout(event)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def handle_process_action(event)
|
|
38
|
+
span = Context.current_span
|
|
39
|
+
return unless span&.root?
|
|
40
|
+
|
|
41
|
+
payload = event.payload
|
|
42
|
+
|
|
43
|
+
# Update root span with controller info
|
|
44
|
+
span.add_attribute("http.method", payload[:method])
|
|
45
|
+
span.add_attribute("http.route", "#{payload[:controller]}##{payload[:action]}")
|
|
46
|
+
span.add_attribute("rails.controller", payload[:controller])
|
|
47
|
+
span.add_attribute("rails.action", payload[:action])
|
|
48
|
+
span.add_attribute("rails.format", payload[:format]) if payload[:format]
|
|
49
|
+
|
|
50
|
+
if payload[:status]
|
|
51
|
+
span.add_attribute("http.status_code", payload[:status])
|
|
52
|
+
span.set_error("HTTP #{payload[:status]}") if payload[:status] >= 500
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Add timing breakdown
|
|
56
|
+
if payload[:db_runtime]
|
|
57
|
+
span.add_attribute("rails.db_runtime_ms", payload[:db_runtime].round(2))
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
if payload[:view_runtime]
|
|
61
|
+
span.add_attribute("rails.view_runtime_ms", payload[:view_runtime].round(2))
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Record exception if present
|
|
65
|
+
if payload[:exception_object]
|
|
66
|
+
span.record_exception(payload[:exception_object])
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def handle_render_template(event)
|
|
71
|
+
record_view_span("render_template", event)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def handle_render_partial(event)
|
|
75
|
+
record_view_span("render_partial", event)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def handle_render_collection(event)
|
|
79
|
+
record_view_span("render_collection", event)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def handle_render_layout(event)
|
|
83
|
+
record_view_span("render_layout", event)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def record_view_span(type, event)
|
|
87
|
+
return unless MiniAPM.enabled?
|
|
88
|
+
return unless Context.current_trace
|
|
89
|
+
|
|
90
|
+
payload = event.payload
|
|
91
|
+
template = payload[:identifier] || payload[:virtual_path] || "unknown"
|
|
92
|
+
|
|
93
|
+
# Clean up template path
|
|
94
|
+
if defined?(::Rails.root) && ::Rails.root
|
|
95
|
+
template = template.sub(::Rails.root.to_s + "/", "")
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
template_name = File.basename(template)
|
|
99
|
+
|
|
100
|
+
attributes = {
|
|
101
|
+
"rails.template" => template,
|
|
102
|
+
"rails.template.type" => type
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if payload[:layout]
|
|
106
|
+
attributes["rails.layout"] = payload[:layout]
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
if payload[:count]
|
|
110
|
+
attributes["rails.collection.count"] = payload[:count]
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
span = create_span_from_event(
|
|
114
|
+
event,
|
|
115
|
+
name: "#{type} #{template_name}",
|
|
116
|
+
category: :view,
|
|
117
|
+
attributes: attributes
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
record_span(span)
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Auto-install when loaded
|
|
129
|
+
MiniAPM::Instrumentations::Rails::Controller.install!
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/railtie"
|
|
4
|
+
|
|
5
|
+
module MiniAPM
|
|
6
|
+
module Instrumentations
|
|
7
|
+
module Rails
|
|
8
|
+
class Railtie < ::Rails::Railtie
|
|
9
|
+
initializer "miniapm.configure_rails_initialization" do |app|
|
|
10
|
+
# Insert middleware at the beginning of the stack
|
|
11
|
+
app.middleware.insert(0, MiniAPM::Middleware::Rack)
|
|
12
|
+
app.middleware.insert(1, MiniAPM::Middleware::ErrorHandler)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
config.after_initialize do
|
|
16
|
+
# Auto-detect Rails version
|
|
17
|
+
MiniAPM.configuration.rails_version ||= ::Rails::VERSION::STRING
|
|
18
|
+
|
|
19
|
+
# Auto-detect environment
|
|
20
|
+
MiniAPM.configuration.environment = ::Rails.env.to_s
|
|
21
|
+
|
|
22
|
+
# Disable in test by default unless explicitly enabled
|
|
23
|
+
if ::Rails.env.test? && ENV["MINI_APM_ENABLED_IN_TEST"].nil?
|
|
24
|
+
MiniAPM.configuration.enabled = false
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Use Rails logger if available
|
|
28
|
+
if ::Rails.logger && MiniAPM.configuration.auto_start
|
|
29
|
+
MiniAPM.logger = ::Rails.logger
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Start MiniAPM if auto_start is enabled
|
|
33
|
+
MiniAPM.start! if MiniAPM.configuration.auto_start
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Require middleware
|
|
41
|
+
require_relative "../../middleware/rack"
|
|
42
|
+
require_relative "../../middleware/error_handler"
|