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.
Files changed (38) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +43 -0
  3. data/LICENSE +21 -0
  4. data/README.md +174 -0
  5. data/lib/generators/miniapm/install_generator.rb +27 -0
  6. data/lib/generators/miniapm/templates/README +19 -0
  7. data/lib/generators/miniapm/templates/initializer.rb +60 -0
  8. data/lib/miniapm/configuration.rb +176 -0
  9. data/lib/miniapm/context.rb +138 -0
  10. data/lib/miniapm/error_event.rb +130 -0
  11. data/lib/miniapm/exporters/errors.rb +67 -0
  12. data/lib/miniapm/exporters/otlp.rb +90 -0
  13. data/lib/miniapm/instrumentations/activejob.rb +271 -0
  14. data/lib/miniapm/instrumentations/activerecord.rb +123 -0
  15. data/lib/miniapm/instrumentations/base.rb +61 -0
  16. data/lib/miniapm/instrumentations/cache.rb +85 -0
  17. data/lib/miniapm/instrumentations/http/faraday.rb +112 -0
  18. data/lib/miniapm/instrumentations/http/httparty.rb +84 -0
  19. data/lib/miniapm/instrumentations/http/net_http.rb +99 -0
  20. data/lib/miniapm/instrumentations/rails/controller.rb +129 -0
  21. data/lib/miniapm/instrumentations/rails/railtie.rb +42 -0
  22. data/lib/miniapm/instrumentations/redis/redis.rb +135 -0
  23. data/lib/miniapm/instrumentations/redis/redis_client.rb +116 -0
  24. data/lib/miniapm/instrumentations/registry.rb +90 -0
  25. data/lib/miniapm/instrumentations/search/elasticsearch.rb +121 -0
  26. data/lib/miniapm/instrumentations/search/opensearch.rb +120 -0
  27. data/lib/miniapm/instrumentations/search/searchkick.rb +119 -0
  28. data/lib/miniapm/instrumentations/sidekiq.rb +185 -0
  29. data/lib/miniapm/middleware/error_handler.rb +120 -0
  30. data/lib/miniapm/middleware/rack.rb +103 -0
  31. data/lib/miniapm/span.rb +289 -0
  32. data/lib/miniapm/testing.rb +209 -0
  33. data/lib/miniapm/trace.rb +26 -0
  34. data/lib/miniapm/transport/batch_sender.rb +345 -0
  35. data/lib/miniapm/transport/http.rb +45 -0
  36. data/lib/miniapm/version.rb +5 -0
  37. data/lib/miniapm.rb +184 -0
  38. metadata +183 -0
@@ -0,0 +1,135 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MiniAPM
4
+ module Instrumentations
5
+ module Redis
6
+ class Redis
7
+ class << self
8
+ def install!
9
+ return if @installed
10
+ return unless defined?(::Redis)
11
+ # Skip if redis-client is present (it's the modern replacement)
12
+ return if defined?(::RedisClient)
13
+
14
+ @installed = true
15
+ ::Redis::Client.prepend(Patch)
16
+
17
+ MiniAPM.logger.debug { "MiniAPM: redis gem instrumentation installed" }
18
+ end
19
+
20
+ def installed?
21
+ @installed || false
22
+ end
23
+ end
24
+
25
+ module Patch
26
+ def call(command)
27
+ return super unless MiniAPM.enabled?
28
+ return super unless MiniAPM::Context.current_trace
29
+
30
+ operation = command.first.to_s.upcase
31
+ conn_info = extract_connection_info
32
+
33
+ span = MiniAPM::Span.new(
34
+ name: "REDIS #{operation}",
35
+ category: :cache,
36
+ trace_id: MiniAPM::Context.current_trace_id,
37
+ parent_span_id: MiniAPM::Context.current_span&.span_id,
38
+ attributes: {
39
+ "db.system" => "redis",
40
+ "db.operation" => operation,
41
+ "db.redis.database_index" => conn_info[:db],
42
+ "net.peer.name" => conn_info[:host],
43
+ "net.peer.port" => conn_info[:port]
44
+ }.compact
45
+ )
46
+
47
+ # Add key info for common operations
48
+ if command.length > 1 && %w[GET SET DEL INCR DECR EXPIRE TTL EXISTS HGET HSET LPUSH RPUSH].include?(operation)
49
+ key = command[1].to_s
50
+ span.add_attribute("db.redis.key", truncate_key(key))
51
+ end
52
+
53
+ MiniAPM::Context.with_span(span) do
54
+ begin
55
+ result = super
56
+ span.set_ok
57
+ result
58
+ rescue StandardError => e
59
+ span.record_exception(e)
60
+ raise
61
+ ensure
62
+ span.finish
63
+ MiniAPM.record_span(span)
64
+ end
65
+ end
66
+ end
67
+
68
+ def call_pipeline(pipeline)
69
+ return super unless MiniAPM.enabled?
70
+ return super unless MiniAPM::Context.current_trace
71
+
72
+ commands = pipeline.commands
73
+ operations = commands.map { |c| c.first.to_s.upcase }.uniq.join(", ")
74
+ conn_info = extract_connection_info
75
+
76
+ span = MiniAPM::Span.new(
77
+ name: "REDIS PIPELINE (#{commands.size} commands)",
78
+ category: :cache,
79
+ trace_id: MiniAPM::Context.current_trace_id,
80
+ parent_span_id: MiniAPM::Context.current_span&.span_id,
81
+ attributes: {
82
+ "db.system" => "redis",
83
+ "db.operation" => "PIPELINE",
84
+ "db.redis.database_index" => conn_info[:db],
85
+ "db.redis.pipeline_length" => commands.size,
86
+ "db.redis.operations" => operations,
87
+ "net.peer.name" => conn_info[:host],
88
+ "net.peer.port" => conn_info[:port]
89
+ }.compact
90
+ )
91
+
92
+ MiniAPM::Context.with_span(span) do
93
+ begin
94
+ result = super
95
+ span.set_ok
96
+ result
97
+ rescue StandardError => e
98
+ span.record_exception(e)
99
+ raise
100
+ ensure
101
+ span.finish
102
+ MiniAPM.record_span(span)
103
+ end
104
+ end
105
+ end
106
+
107
+ private
108
+
109
+ def extract_connection_info
110
+ # Redis::Client in older redis gem versions has different APIs
111
+ # Try various methods to get connection info safely
112
+ {
113
+ host: safe_connection_value(:host),
114
+ port: safe_connection_value(:port),
115
+ db: safe_connection_value(:db)
116
+ }
117
+ end
118
+
119
+ def safe_connection_value(attr)
120
+ respond_to?(attr) ? send(attr) : options[attr]
121
+ rescue
122
+ nil
123
+ end
124
+
125
+ def truncate_key(key)
126
+ key.length > 100 ? key[0...100] + "..." : key
127
+ end
128
+ end
129
+ end
130
+ end
131
+ end
132
+ end
133
+
134
+ # Auto-install when loaded
135
+ MiniAPM::Instrumentations::Redis::Redis.install!
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MiniAPM
4
+ module Instrumentations
5
+ module Redis
6
+ class RedisClient
7
+ class << self
8
+ def install!
9
+ return if @installed
10
+ return unless defined?(::RedisClient)
11
+
12
+ @installed = true
13
+
14
+ # RedisClient supports middleware registration
15
+ ::RedisClient.register(Middleware)
16
+
17
+ MiniAPM.logger.debug { "MiniAPM: redis-client instrumentation installed" }
18
+ end
19
+
20
+ def installed?
21
+ @installed || false
22
+ end
23
+ end
24
+
25
+ module Middleware
26
+ def call(command, config)
27
+ return super unless MiniAPM.enabled?
28
+ return super unless MiniAPM::Context.current_trace
29
+
30
+ operation = command.first.to_s.upcase
31
+
32
+ span = MiniAPM::Span.new(
33
+ name: "REDIS #{operation}",
34
+ category: :cache,
35
+ trace_id: MiniAPM::Context.current_trace_id,
36
+ parent_span_id: MiniAPM::Context.current_span&.span_id,
37
+ attributes: {
38
+ "db.system" => "redis",
39
+ "db.operation" => operation,
40
+ "db.redis.database_index" => config.db,
41
+ "net.peer.name" => config.host,
42
+ "net.peer.port" => config.port
43
+ }
44
+ )
45
+
46
+ # Add key info for common operations (first arg after command)
47
+ if command.length > 1 && %w[GET SET DEL INCR DECR EXPIRE TTL EXISTS HGET HSET LPUSH RPUSH].include?(operation)
48
+ key = command[1].to_s
49
+ span.add_attribute("db.redis.key", truncate_key(key))
50
+ end
51
+
52
+ MiniAPM::Context.with_span(span) do
53
+ begin
54
+ result = super
55
+ span.set_ok
56
+ result
57
+ rescue StandardError => e
58
+ span.record_exception(e)
59
+ raise
60
+ ensure
61
+ span.finish
62
+ MiniAPM.record_span(span)
63
+ end
64
+ end
65
+ end
66
+
67
+ def call_pipelined(commands, config)
68
+ return super unless MiniAPM.enabled?
69
+ return super unless MiniAPM::Context.current_trace
70
+
71
+ operations = commands.map { |c| c.first.to_s.upcase }.uniq.join(", ")
72
+
73
+ span = MiniAPM::Span.new(
74
+ name: "REDIS PIPELINE (#{commands.size} commands)",
75
+ category: :cache,
76
+ trace_id: MiniAPM::Context.current_trace_id,
77
+ parent_span_id: MiniAPM::Context.current_span&.span_id,
78
+ attributes: {
79
+ "db.system" => "redis",
80
+ "db.operation" => "PIPELINE",
81
+ "db.redis.database_index" => config.db,
82
+ "db.redis.pipeline_length" => commands.size,
83
+ "db.redis.operations" => operations,
84
+ "net.peer.name" => config.host,
85
+ "net.peer.port" => config.port
86
+ }
87
+ )
88
+
89
+ MiniAPM::Context.with_span(span) do
90
+ begin
91
+ result = super
92
+ span.set_ok
93
+ result
94
+ rescue StandardError => e
95
+ span.record_exception(e)
96
+ raise
97
+ ensure
98
+ span.finish
99
+ MiniAPM.record_span(span)
100
+ end
101
+ end
102
+ end
103
+
104
+ private
105
+
106
+ def truncate_key(key)
107
+ key.length > 100 ? key[0...100] + "..." : key
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
114
+
115
+ # Auto-install when loaded
116
+ MiniAPM::Instrumentations::Redis::RedisClient.install!
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MiniAPM
4
+ module Instrumentations
5
+ class Registry
6
+ INSTRUMENTATIONS = {
7
+ # Rails core (via ActiveSupport::Notifications)
8
+ rails: "miniapm/instrumentations/rails/controller",
9
+ activerecord: "miniapm/instrumentations/activerecord",
10
+ activejob: "miniapm/instrumentations/activejob",
11
+ cache: "miniapm/instrumentations/cache",
12
+
13
+ # Background jobs
14
+ sidekiq: "miniapm/instrumentations/sidekiq",
15
+
16
+ # HTTP clients
17
+ net_http: "miniapm/instrumentations/http/net_http",
18
+ httparty: "miniapm/instrumentations/http/httparty",
19
+ faraday: "miniapm/instrumentations/http/faraday",
20
+
21
+ # Search
22
+ opensearch: "miniapm/instrumentations/search/opensearch",
23
+ elasticsearch: "miniapm/instrumentations/search/elasticsearch",
24
+ searchkick: "miniapm/instrumentations/search/searchkick",
25
+
26
+ # Redis
27
+ redis_client: "miniapm/instrumentations/redis/redis_client",
28
+ redis: "miniapm/instrumentations/redis/redis"
29
+ }.freeze
30
+
31
+ class << self
32
+ def install_all!
33
+ INSTRUMENTATIONS.each do |name, path|
34
+ next unless should_install?(name)
35
+
36
+ begin
37
+ require_relative path.sub("miniapm/instrumentations/", "")
38
+ MiniAPM.logger.debug { "MiniAPM: Installed #{name} instrumentation" }
39
+ rescue LoadError => e
40
+ MiniAPM.logger.debug { "MiniAPM: Skipped #{name} (dependency not available: #{e.message})" }
41
+ rescue StandardError => e
42
+ MiniAPM.logger.warn { "MiniAPM: Failed to install #{name}: #{e.message}" }
43
+ end
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ def should_install?(name)
50
+ MiniAPM.configuration.instrumentations.enabled?(name) && gem_present?(name)
51
+ end
52
+
53
+ def gem_present?(name)
54
+ result = case name
55
+ when :rails
56
+ defined?(Rails) && defined?(ActionController)
57
+ when :activerecord
58
+ defined?(ActiveRecord::Base) && defined?(ActiveSupport::Notifications)
59
+ when :activejob
60
+ defined?(ActiveJob::Base) && defined?(ActiveSupport::Notifications)
61
+ when :sidekiq
62
+ defined?(Sidekiq)
63
+ when :cache
64
+ defined?(ActiveSupport::Cache::Store)
65
+ when :net_http
66
+ defined?(Net::HTTP)
67
+ when :httparty
68
+ defined?(HTTParty)
69
+ when :faraday
70
+ defined?(Faraday)
71
+ when :opensearch
72
+ defined?(OpenSearch::Client)
73
+ when :elasticsearch
74
+ defined?(Elasticsearch::Client)
75
+ when :searchkick
76
+ defined?(Searchkick)
77
+ when :redis_client
78
+ defined?(RedisClient)
79
+ when :redis
80
+ defined?(Redis) && !defined?(RedisClient)
81
+ else
82
+ false
83
+ end
84
+ # Convert to boolean (defined? returns string or nil)
85
+ !!result
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MiniAPM
4
+ module Instrumentations
5
+ module Search
6
+ class Elasticsearch
7
+ class << self
8
+ def install!
9
+ return if @installed
10
+ return unless defined?(::Elasticsearch::Transport::Client)
11
+
12
+ @installed = true
13
+ ::Elasticsearch::Transport::Client.prepend(Patch)
14
+
15
+ MiniAPM.logger.debug { "MiniAPM: Elasticsearch instrumentation installed" }
16
+ end
17
+
18
+ def installed?
19
+ @installed || false
20
+ end
21
+ end
22
+
23
+ module Patch
24
+ def perform_request(method, path, params = {}, body = nil, headers = nil, opts = {})
25
+ return super unless MiniAPM.enabled?
26
+ return super unless MiniAPM::Context.current_trace
27
+
28
+ operation = extract_operation(method, path)
29
+ index = extract_index(path)
30
+
31
+ span = MiniAPM::Span.new(
32
+ name: "ES #{operation}#{index ? " #{index}" : ""}",
33
+ category: :search,
34
+ trace_id: MiniAPM::Context.current_trace_id,
35
+ parent_span_id: MiniAPM::Context.current_span&.span_id,
36
+ attributes: {
37
+ "db.system" => "elasticsearch",
38
+ "db.operation" => operation,
39
+ "http.method" => method.to_s.upcase,
40
+ "http.url" => path
41
+ }
42
+ )
43
+
44
+ span.add_attribute("elasticsearch.index", index) if index
45
+
46
+ # Add query body for search operations (truncated)
47
+ if body && %w[search msearch].include?(operation)
48
+ span.add_attribute("db.statement", truncate_body(body))
49
+ end
50
+
51
+ MiniAPM::Context.with_span(span) do
52
+ begin
53
+ response = super
54
+
55
+ if response
56
+ span.add_attribute("http.status_code", response.status)
57
+ span.set_error("ES #{response.status}") if response.status >= 400
58
+ end
59
+
60
+ response
61
+ rescue StandardError => e
62
+ span.record_exception(e)
63
+ raise
64
+ ensure
65
+ span.finish
66
+ MiniAPM.record_span(span)
67
+ end
68
+ end
69
+ end
70
+
71
+ private
72
+
73
+ def extract_operation(method, path)
74
+ case
75
+ when path.include?("_search")
76
+ "search"
77
+ when path.include?("_msearch")
78
+ "msearch"
79
+ when path.include?("_bulk")
80
+ "bulk"
81
+ when path.include?("_count")
82
+ "count"
83
+ when path.include?("_update")
84
+ "update"
85
+ when path.include?("_delete_by_query")
86
+ "delete_by_query"
87
+ when path.include?("_refresh")
88
+ "refresh"
89
+ when method.to_s.upcase == "GET"
90
+ "get"
91
+ when method.to_s.upcase == "PUT"
92
+ "index"
93
+ when method.to_s.upcase == "POST"
94
+ "index"
95
+ when method.to_s.upcase == "DELETE"
96
+ "delete"
97
+ else
98
+ "query"
99
+ end
100
+ end
101
+
102
+ def extract_index(path)
103
+ # Extract index name from path like /my_index/_search
104
+ parts = path.to_s.split("/").reject { |p| p.empty? || p.start_with?("_") }
105
+ parts.first
106
+ end
107
+
108
+ def truncate_body(body)
109
+ json = body.is_a?(String) ? body : body.to_json
110
+ json.length > 1000 ? json[0...1000] + "..." : json
111
+ rescue StandardError
112
+ body.to_s[0...1000]
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
119
+
120
+ # Auto-install when loaded
121
+ MiniAPM::Instrumentations::Search::Elasticsearch.install!
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MiniAPM
4
+ module Instrumentations
5
+ module Search
6
+ class OpenSearch
7
+ class << self
8
+ def install!
9
+ return if @installed
10
+ return unless defined?(::OpenSearch::Transport::Client)
11
+
12
+ @installed = true
13
+ ::OpenSearch::Transport::Client.prepend(Patch)
14
+
15
+ MiniAPM.logger.debug { "MiniAPM: OpenSearch instrumentation installed" }
16
+ end
17
+
18
+ def installed?
19
+ @installed || false
20
+ end
21
+ end
22
+
23
+ module Patch
24
+ def perform_request(method, path, params = {}, body = nil, headers = nil, opts = {})
25
+ return super unless MiniAPM.enabled?
26
+ return super unless MiniAPM::Context.current_trace
27
+
28
+ operation = extract_operation(method, path)
29
+ index = extract_index(path)
30
+
31
+ span = MiniAPM::Span.new(
32
+ name: "OS #{operation}#{index ? " #{index}" : ""}",
33
+ category: :search,
34
+ trace_id: MiniAPM::Context.current_trace_id,
35
+ parent_span_id: MiniAPM::Context.current_span&.span_id,
36
+ attributes: {
37
+ "db.system" => "opensearch",
38
+ "db.operation" => operation,
39
+ "http.method" => method.to_s.upcase,
40
+ "http.url" => path
41
+ }
42
+ )
43
+
44
+ span.add_attribute("opensearch.index", index) if index
45
+
46
+ # Add query body for search operations (truncated)
47
+ if body && %w[search msearch].include?(operation)
48
+ span.add_attribute("db.statement", truncate_body(body))
49
+ end
50
+
51
+ MiniAPM::Context.with_span(span) do
52
+ begin
53
+ response = super
54
+
55
+ if response
56
+ span.add_attribute("http.status_code", response.status)
57
+ span.set_error("OS #{response.status}") if response.status >= 400
58
+ end
59
+
60
+ response
61
+ rescue StandardError => e
62
+ span.record_exception(e)
63
+ raise
64
+ ensure
65
+ span.finish
66
+ MiniAPM.record_span(span)
67
+ end
68
+ end
69
+ end
70
+
71
+ private
72
+
73
+ def extract_operation(method, path)
74
+ case
75
+ when path.include?("_search")
76
+ "search"
77
+ when path.include?("_msearch")
78
+ "msearch"
79
+ when path.include?("_bulk")
80
+ "bulk"
81
+ when path.include?("_count")
82
+ "count"
83
+ when path.include?("_update")
84
+ "update"
85
+ when path.include?("_delete_by_query")
86
+ "delete_by_query"
87
+ when path.include?("_refresh")
88
+ "refresh"
89
+ when method.to_s.upcase == "GET"
90
+ "get"
91
+ when method.to_s.upcase == "PUT"
92
+ "index"
93
+ when method.to_s.upcase == "POST"
94
+ "index"
95
+ when method.to_s.upcase == "DELETE"
96
+ "delete"
97
+ else
98
+ "query"
99
+ end
100
+ end
101
+
102
+ def extract_index(path)
103
+ parts = path.to_s.split("/").reject { |p| p.empty? || p.start_with?("_") }
104
+ parts.first
105
+ end
106
+
107
+ def truncate_body(body)
108
+ json = body.is_a?(String) ? body : body.to_json
109
+ json.length > 1000 ? json[0...1000] + "..." : json
110
+ rescue StandardError
111
+ body.to_s[0...1000]
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end
118
+
119
+ # Auto-install when loaded
120
+ MiniAPM::Instrumentations::Search::OpenSearch.install!
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MiniAPM
4
+ module Instrumentations
5
+ module Search
6
+ class Searchkick < Base
7
+ class << self
8
+ def install!
9
+ return if installed?
10
+ return unless defined?(::Searchkick)
11
+
12
+ mark_installed!
13
+
14
+ # Searchkick uses ActiveSupport::Notifications
15
+ subscribe("search.searchkick") do |event|
16
+ handle_search(event)
17
+ end
18
+
19
+ subscribe("request.searchkick") do |event|
20
+ handle_request(event)
21
+ end
22
+
23
+ subscribe("reindex.searchkick") do |event|
24
+ handle_reindex(event)
25
+ end
26
+
27
+ MiniAPM.logger.debug { "MiniAPM: Searchkick instrumentation installed" }
28
+ end
29
+
30
+ private
31
+
32
+ def handle_search(event)
33
+ return unless MiniAPM.enabled?
34
+ return unless Context.current_trace
35
+
36
+ payload = event.payload
37
+ model_name = payload[:name] || "unknown"
38
+
39
+ attributes = {
40
+ "db.system" => "elasticsearch",
41
+ "db.operation" => "search",
42
+ "searchkick.model" => model_name
43
+ }
44
+
45
+ # Add query info (truncated for privacy)
46
+ if payload[:query]
47
+ query_str = payload[:query].to_s
48
+ attributes["searchkick.query"] = query_str.length > 500 ? query_str[0...500] + "..." : query_str
49
+ end
50
+
51
+ # Add body for debugging
52
+ if payload[:body]
53
+ body_str = payload[:body].is_a?(String) ? payload[:body] : payload[:body].to_json
54
+ attributes["db.statement"] = body_str.length > 1000 ? body_str[0...1000] + "..." : body_str
55
+ end
56
+
57
+ span = create_span_from_event(
58
+ event,
59
+ name: "searchkick #{model_name}",
60
+ category: :search,
61
+ attributes: attributes
62
+ )
63
+
64
+ record_span(span)
65
+ end
66
+
67
+ def handle_request(event)
68
+ # Lower-level ES/OS requests from Searchkick
69
+ return unless MiniAPM.enabled?
70
+ return unless Context.current_trace
71
+
72
+ payload = event.payload
73
+
74
+ attributes = {
75
+ "db.system" => "elasticsearch",
76
+ "http.method" => payload[:method]&.to_s&.upcase,
77
+ "http.url" => payload[:path]
78
+ }
79
+
80
+ span = create_span_from_event(
81
+ event,
82
+ name: "searchkick #{payload[:method]} #{payload[:path]}",
83
+ category: :search,
84
+ attributes: attributes
85
+ )
86
+
87
+ record_span(span)
88
+ end
89
+
90
+ def handle_reindex(event)
91
+ return unless MiniAPM.enabled?
92
+ return unless Context.current_trace
93
+
94
+ payload = event.payload
95
+ model_name = payload[:name] || "unknown"
96
+
97
+ attributes = {
98
+ "db.system" => "elasticsearch",
99
+ "db.operation" => "reindex",
100
+ "searchkick.model" => model_name
101
+ }
102
+
103
+ span = create_span_from_event(
104
+ event,
105
+ name: "searchkick reindex #{model_name}",
106
+ category: :search,
107
+ attributes: attributes
108
+ )
109
+
110
+ record_span(span)
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
117
+
118
+ # Auto-install when loaded
119
+ MiniAPM::Instrumentations::Search::Searchkick.install!