request_trail 0.6.0 → 0.7.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: 2c636ff49dd9c5f372269c4e9a9611a7e610e5e5f6f172a951e1f7d76579f804
4
- data.tar.gz: 2e7a528107b38f1c3e92d0bc11bbdfd2b51295daaf616f4e6cbb82dbe8cc71e1
3
+ metadata.gz: 46f0f0b5ac13bf9ea151c441b9de780076a0350c34a4268258701bd4bc9e2bd6
4
+ data.tar.gz: 0d1823f675d5074c2b1ee8664970737b81ac9a3b3535a58a1f56bdf029f5089e
5
5
  SHA512:
6
- metadata.gz: c4d2694d6a22c9a24884968967cb4210771e12efd1d43934df844dbdf699382ec994e620b0d6f75773b6584c5ae2f4a4c6cd6f346f808c400e70c36603b59352
7
- data.tar.gz: f8597a5b458895afa2d09081ba96fd422c971733918f64a2a7109133103e12b0725fdb20d4ec2efc2b7d8a8dca383d241037796b69c10d902b5bd819085a6700
6
+ metadata.gz: 2d1bf8caea4104913b8a4ef9c1b1c4aa517d4aaeb96f83f5fa49242ceee598cbdb6c118191e6bd0eecacd4ee3bdbaf77293f6725750768dff5dd4a182bfae6ee
7
+ data.tar.gz: c412309dd55a91d2eeec4c9cbed03999a1dc4e317638873ccb7662a384e146ee3af8d3a0f783fa9288047eca475d8cfc578048c54d556354e8fcf66d51a70575
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.7.0] - 2026-06-14
4
+
5
+ ### Added
6
+
7
+ - `config.n_plus_one_threshold` — flag requests where SQL query count meets or exceeds a threshold; appends `[N+1?]` to the header line in plain-text and FlameGraph output, and sets `"n_plus_one": true` in JSON output (default: `nil` = disabled)
8
+ - `RequestTrail::ActiveJobSubscriber` — module to include in `ApplicationJob`; adds an `around_perform` hook that traces background job execution across all ActiveJob backends (GoodJob, Solid Queue, DelayedJob, Sidekiq-via-ActiveJob)
9
+ - Outbound HTTP tracking — `Subscriber` subscribes to `request.faraday` notifications; `http_count` and `http_duration_ms` are added to `Collector` and surfaced in all formatters (plain text, FlameGraph, JSON)
10
+
3
11
  ## [0.6.0] - 2026-06-14
4
12
 
5
13
  ### Added
@@ -58,7 +66,9 @@
58
66
  - `RequestTrail::Subscriber` — attach/detach API for notification subscriptions
59
67
  - `RequestTrail::Collector` — thread-safe per-request event accumulator
60
68
 
61
- [Unreleased]: https://github.com/eclectic-coding/request-trail/compare/v0.5.0...HEAD
69
+ [Unreleased]: https://github.com/eclectic-coding/request-trail/compare/v0.7.0...HEAD
70
+ [0.7.0]: https://github.com/eclectic-coding/request-trail/compare/v0.6.0...v0.7.0
71
+ [0.6.0]: https://github.com/eclectic-coding/request-trail/compare/v0.5.0...v0.6.0
62
72
  [0.5.0]: https://github.com/eclectic-coding/request-trail/releases/tag/v0.5.0
63
73
  [0.4.0]: https://github.com/eclectic-coding/request-trail/releases/tag/v0.4.0
64
74
  [0.3.0]: https://github.com/eclectic-coding/request-trail/releases/tag/v0.3.0
data/README.md CHANGED
@@ -12,7 +12,18 @@ Middleware that traces a request through all the layers (middleware, controller,
12
12
 
13
13
  - [Installation](#installation)
14
14
  - [Usage](#usage)
15
- - [Custom formatters](#custom-formatters)
15
+ - [Rails](#rails)
16
+ - [JSON formatter](#json-formatter)
17
+ - [Flame graph formatter](#flame-graph-formatter)
18
+ - [N+1 detection](#n1-detection)
19
+ - [Rails log tags](#rails-log-tags)
20
+ - [Sidekiq](#sidekiq)
21
+ - [ActiveJob](#activejob)
22
+ - [Outbound HTTP tracking](#outbound-http-tracking)
23
+ - [Custom formatters](#custom-formatters)
24
+ - [Installation generator](#installation-generator)
25
+ - [Configuration](#configuration)
26
+ - [Non-Rails (plain Rack)](#non-rails-plain-rack)
16
27
  - [Development](#development)
17
28
  - [Contributing](#contributing)
18
29
  - [License](#license)
@@ -110,6 +121,115 @@ RequestTrail::Formatters::FlameGraph.new(
110
121
 
111
122
  Unspecified layers keep their defaults.
112
123
 
124
+ ### N+1 detection
125
+
126
+ Set `config.n_plus_one_threshold` to flag requests that fire too many SQL queries. When `sql_count >= threshold`, all formatters add a prominent marker:
127
+
128
+ ```ruby
129
+ RequestTrail.configure do |config|
130
+ config.n_plus_one_threshold = 10 # nil = disabled (default)
131
+ end
132
+ ```
133
+
134
+ Plain text and FlameGraph append `[N+1?]` to the header line:
135
+
136
+ ```
137
+ [RequestTrail] GET /orders 142ms [N+1?] | SQL: 15/85.0ms | Cache: 0 hits, 0 misses, 0.0ms
138
+ ```
139
+
140
+ JSON includes `"n_plus_one": true` in the payload:
141
+
142
+ ```json
143
+ {"method":"GET","path":"/orders","duration_ms":142.5,"n_plus_one":true,"sql":{"count":15,"duration_ms":85.0},...}
144
+ ```
145
+
146
+ ### Rails log tags
147
+
148
+ Add `RequestTrail.log_tag` to `config.log_tags` to include live request state on every tagged log line. The callable returns a lazy value that evaluates to `sql=N Nms` at log-line format time — so it reflects the accumulated query state at each point during the request:
149
+
150
+ ```ruby
151
+ # config/application.rb
152
+ config.log_tags = [:request_id, RequestTrail.log_tag]
153
+ ```
154
+
155
+ Example output:
156
+
157
+ ```
158
+ [abc-123] [sql=3 25ms] Completed 200 OK in 25ms
159
+ [abc-123] [sql=3 25ms] [RequestTrail] GET /orders 25ms | SQL: 3/10.0ms | Cache: 0 hits, 0 misses, 0.0ms
160
+ ```
161
+
162
+ The tag is omitted (blank-suppressed by Rails) on lines that fall outside an active request.
163
+
164
+ ### Sidekiq
165
+
166
+ Add `RequestTrail::SidekiqMiddleware` to the Sidekiq server middleware chain to trace background job execution with the same flame-graph output:
167
+
168
+ ```ruby
169
+ # config/initializers/sidekiq.rb
170
+ Sidekiq.configure_server do |config|
171
+ config.server_middleware do |chain|
172
+ chain.add RequestTrail::SidekiqMiddleware
173
+ end
174
+ end
175
+ ```
176
+
177
+ Output uses `JOB` as the method and includes the worker class and job ID:
178
+
179
+ ```
180
+ [RequestTrail] JOB HardWorker jid:abc123 42ms | SQL: 5/35.0ms | Cache: 0 hits, 0 misses, 0.0ms
181
+ ```
182
+
183
+ All configuration options (`threshold_ms`, `sample_rate`, `n_plus_one_threshold`, `formatter`, etc.) apply to Sidekiq jobs the same way they apply to HTTP requests.
184
+
185
+ ### ActiveJob
186
+
187
+ Include `RequestTrail::ActiveJobSubscriber` in `ApplicationJob` to trace background jobs across all ActiveJob backends (GoodJob, Solid Queue, DelayedJob, Sidekiq-via-ActiveJob, etc.):
188
+
189
+ ```ruby
190
+ # app/jobs/application_job.rb
191
+ class ApplicationJob < ActiveJob::Base
192
+ include RequestTrail::ActiveJobSubscriber
193
+ end
194
+ ```
195
+
196
+ All jobs that inherit from `ApplicationJob` are then automatically traced. Output uses `JOB` as the method and includes the job class and `job_id`:
197
+
198
+ ```
199
+ [RequestTrail] JOB OrderSyncJob jid:476b61a6-ae85-4ae8-ae64-bf92e8cfeefc 42ms | SQL: 5/35.0ms | Cache: 0 hits, 0 misses, 0.0ms
200
+ ```
201
+
202
+ All configuration options (`threshold_ms`, `sample_rate`, `n_plus_one_threshold`, `formatter`, etc.) apply to ActiveJob jobs the same way they apply to HTTP requests.
203
+
204
+ **`ActiveJobSubscriber` vs `SidekiqMiddleware`:** Use `ActiveJobSubscriber` when you want adapter-agnostic tracing via `ApplicationJob` inheritance. Use `SidekiqMiddleware` when running Sidekiq workers that do *not* go through ActiveJob (i.e., plain `Sidekiq::Worker` classes).
205
+
206
+ ### Outbound HTTP tracking
207
+
208
+ Outbound HTTP calls made via Faraday are tracked automatically once `Subscriber.attach` is called (which happens via the Railtie in Rails apps). No additional configuration is needed — the `request.faraday` notification is subscribed alongside SQL and cache events.
209
+
210
+ Plain text output includes an `HTTP` segment:
211
+
212
+ ```
213
+ [RequestTrail] GET /orders 142ms | SQL: 7/38.3ms | Cache: 4 hits, 1 miss, 2.0ms | HTTP: 2/45.0ms
214
+ ```
215
+
216
+ Tiered output adds an `http` row under the controller:
217
+
218
+ ```
219
+ [RequestTrail] GET /orders 142ms
220
+ controller 104ms
221
+ sql 38ms (7 queries)
222
+ cache 2ms (4 hits, 1 miss)
223
+ view 22ms
224
+ http 45ms (2 calls)
225
+ ```
226
+
227
+ JSON output includes an `"http"` key:
228
+
229
+ ```json
230
+ {"method":"GET","path":"/orders","duration_ms":142.5,...,"http":{"count":2,"duration_ms":45.0}}
231
+ ```
232
+
113
233
  ### Custom formatters
114
234
 
115
235
  Any object that responds to `format(request, collector)` and returns a `String` can be used as a formatter. Include `RequestTrail::Formatters::Base` to make the contract explicit:
@@ -130,7 +250,7 @@ end
130
250
 
131
251
  `format` receives:
132
252
  - `request` — a `Rack::Request` with the current HTTP request
133
- - `collector` — a `RequestTrail::Collector` exposing `elapsed_ms`, `sql_count`, `sql_duration_ms`, `cache_hits`, `cache_misses`, `cache_duration_ms`, `action_duration_ms`, and `view_duration_ms`
253
+ - `collector` — a `RequestTrail::Collector` exposing `elapsed_ms`, `sql_count`, `sql_duration_ms`, `cache_hits`, `cache_misses`, `cache_duration_ms`, `action_duration_ms`, `view_duration_ms`, `http_count`, and `http_duration_ms`
134
254
 
135
255
  ### Installation generator
136
256
 
@@ -149,17 +269,20 @@ Add an initializer to customize behavior:
149
269
  ```ruby
150
270
  # config/initializers/request_trail.rb
151
271
  RequestTrail.configure do |config|
152
- config.enabled = true # set to false to disable entirely
153
- config.log_level = :info # Rails logger level (:debug, :info, :warn)
154
- config.threshold_ms = 200 # only log requests slower than this (0 = log all)
155
- config.logger = nil # defaults to Rails.logger
156
- config.formatter = RequestTrail::Formatters::FlameGraph.new # optional
272
+ config.enabled = true # set to false to disable entirely
273
+ config.log_level = :info # Rails logger level (:debug, :info, :warn)
274
+ config.threshold_ms = 200 # only log requests slower than this (0 = log all)
275
+ config.logger = nil # defaults to Rails.logger
276
+ config.formatter = RequestTrail::Formatters::FlameGraph.new # optional
157
277
 
158
278
  # skip tracing for specific paths (strings = exact match, regexes = pattern match)
159
- config.ignore_paths = ["/health", "/up", /^\/assets/]
279
+ config.ignore_paths = ["/health", "/up", /^\/assets/]
160
280
 
161
281
  # trace only N% of requests — useful in high-traffic production environments
162
- config.sample_rate = 0.1 # 0.0 = never, 1.0 = always (default)
282
+ config.sample_rate = 0.1 # 0.0 = never, 1.0 = always (default)
283
+
284
+ # flag requests with too many SQL queries (nil = disabled)
285
+ config.n_plus_one_threshold = 10
163
286
  end
164
287
  ```
165
288
 
@@ -6,6 +6,7 @@ RequestTrail.configure do |config|
6
6
  config.threshold_ms = 0 # only log requests slower than this (0 = log all)
7
7
  config.ignore_paths = [] # e.g. ["/health", /^\/assets/]
8
8
  config.sample_rate = 1.0 # 0.0–1.0; 1.0 = trace every request
9
+ # config.n_plus_one_threshold = nil # warn when sql_count >= N (nil = disabled)
9
10
 
10
11
  # config.logger = Rails.logger # defaults to Rails.logger
11
12
  # config.formatter = RequestTrail::Formatters::FlameGraph.new(colorize: true)
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RequestTrail
4
+ module ActiveJobSubscriber
5
+ JobContext = Struct.new(:request_method, :path, keyword_init: true)
6
+
7
+ def self.included(base)
8
+ base.around_perform { |job, block| RequestTrail::ActiveJobSubscriber.trace(job, block) }
9
+ end
10
+
11
+ def self.trace(job, block)
12
+ if RequestTrail.configuration.enabled && RequestTrail.configuration.sampled?
13
+ Collector.start
14
+ block.call
15
+ log(job)
16
+ else
17
+ block.call
18
+ end
19
+ ensure
20
+ Collector.stop
21
+ end
22
+
23
+ def self.log(job)
24
+ collector = Collector.current
25
+ return if collector.elapsed_ms < RequestTrail.configuration.threshold_ms
26
+
27
+ message = RequestTrail.formatter.format(build_context(job), collector)
28
+ RequestTrail.configuration.logger.send(RequestTrail.configuration.log_level, message)
29
+ end
30
+
31
+ def self.build_context(job)
32
+ JobContext.new(
33
+ request_method: "JOB",
34
+ path: "#{job.class.name || job.class.to_s} jid:#{job.job_id}"
35
+ )
36
+ end
37
+ end
38
+ end
@@ -6,7 +6,8 @@ module RequestTrail
6
6
 
7
7
  attr_reader :sql_count, :sql_duration_ms,
8
8
  :cache_hits, :cache_misses, :cache_writes, :cache_duration_ms,
9
- :action_duration_ms, :view_duration_ms
9
+ :action_duration_ms, :view_duration_ms,
10
+ :http_count, :http_duration_ms
10
11
 
11
12
  def self.current
12
13
  Thread.current[THREAD_KEY]
@@ -30,6 +31,8 @@ module RequestTrail
30
31
  @cache_duration_ms = 0.0
31
32
  @action_duration_ms = 0.0
32
33
  @view_duration_ms = 0.0
34
+ @http_count = 0
35
+ @http_duration_ms = 0.0
33
36
  end
34
37
 
35
38
  def record_sql(duration_ms)
@@ -52,6 +55,11 @@ module RequestTrail
52
55
  @view_duration_ms = view_duration_ms
53
56
  end
54
57
 
58
+ def record_http(duration_ms:)
59
+ @http_count += 1
60
+ @http_duration_ms += duration_ms
61
+ end
62
+
55
63
  def elapsed_ms
56
64
  elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - @started_at
57
65
  (elapsed * 1000).round(2)
@@ -5,7 +5,7 @@ require "logger"
5
5
  module RequestTrail
6
6
  class Configuration
7
7
  attr_writer :logger, :formatter
8
- attr_accessor :enabled, :log_level, :threshold_ms, :ignore_paths, :sample_rate
8
+ attr_accessor :enabled, :log_level, :threshold_ms, :ignore_paths, :sample_rate, :n_plus_one_threshold
9
9
 
10
10
  def initialize
11
11
  @enabled = true
@@ -13,6 +13,7 @@ module RequestTrail
13
13
  @threshold_ms = 0
14
14
  @ignore_paths = []
15
15
  @sample_rate = 1.0
16
+ @n_plus_one_threshold = nil
16
17
  end
17
18
 
18
19
  def sampled?
@@ -5,10 +5,11 @@ module RequestTrail
5
5
  include Formatters::Base
6
6
 
7
7
  def format(request, collector)
8
- header = "[RequestTrail] #{request.request_method} #{request.path} #{collector.elapsed_ms.round}ms"
8
+ n1 = n_plus_one?(collector) ? " [N+1?]" : ""
9
+ header = "[RequestTrail] #{request.request_method} #{request.path} #{collector.elapsed_ms.round}ms#{n1}"
9
10
  return tiered_format(header, collector) if collector.action_duration_ms.positive?
10
11
 
11
- "#{header} | #{sql_summary(collector)} | #{cache_summary(collector)}"
12
+ "#{header} | #{sql_summary(collector)} | #{cache_summary(collector)} | #{http_summary(collector)}"
12
13
  end
13
14
 
14
15
  private
@@ -19,7 +20,8 @@ module RequestTrail
19
20
  " controller #{collector.action_duration_ms.round}ms",
20
21
  " sql #{collector.sql_duration_ms.round(1)}ms (#{sql_count_label(collector)})",
21
22
  " cache #{collector.cache_duration_ms.round(1)}ms (#{cache_detail(collector)})",
22
- " view #{collector.view_duration_ms.round(1)}ms"
23
+ " view #{collector.view_duration_ms.round(1)}ms",
24
+ " http #{collector.http_duration_ms.round(1)}ms (#{http_count_label(collector)})"
23
25
  ].join("\n")
24
26
  end
25
27
 
@@ -34,6 +36,14 @@ module RequestTrail
34
36
  "Cache: #{collector.cache_hits} #{hit_label}, #{collector.cache_misses} #{miss_label}, #{duration}ms"
35
37
  end
36
38
 
39
+ def http_summary(collector)
40
+ "HTTP: #{collector.http_count}/#{collector.http_duration_ms.round(1)}ms"
41
+ end
42
+
43
+ def http_count_label(collector)
44
+ collector.http_count == 1 ? "1 call" : "#{collector.http_count} calls"
45
+ end
46
+
37
47
  def sql_count_label(collector)
38
48
  collector.sql_count == 1 ? "1 query" : "#{collector.sql_count} queries"
39
49
  end
@@ -12,6 +12,13 @@ module RequestTrail
12
12
  def format(_request, _collector)
13
13
  raise NotImplementedError, "#{self.class}#format must return a String"
14
14
  end
15
+
16
+ private
17
+
18
+ def n_plus_one?(collector)
19
+ threshold = RequestTrail.configuration.n_plus_one_threshold
20
+ threshold ? collector.sql_count >= threshold : false
21
+ end
15
22
  end
16
23
  end
17
24
  end
@@ -13,7 +13,8 @@ module RequestTrail
13
13
  controller: "\e[34m",
14
14
  sql: "\e[33m",
15
15
  cache: "\e[32m",
16
- view: "\e[35m"
16
+ view: "\e[35m",
17
+ http: "\e[36m"
17
18
  }.freeze
18
19
  RESET = "\e[0m"
19
20
 
@@ -41,21 +42,24 @@ module RequestTrail
41
42
  row(" ", "controller", collector.action_duration_ms, total, :controller),
42
43
  row(" ", "sql", collector.sql_duration_ms, total, :sql),
43
44
  row(" ", "cache", collector.cache_duration_ms, total, :cache),
44
- row(" ", "view", collector.view_duration_ms, total, :view)
45
+ row(" ", "view", collector.view_duration_ms, total, :view),
46
+ row(" ", "http", collector.http_duration_ms, total, :http)
45
47
  ]
46
48
  end
47
49
 
48
50
  def flat_rows(collector, total)
49
51
  [
50
52
  row(" ", "sql", collector.sql_duration_ms, total, :sql),
51
- row(" ", "cache", collector.cache_duration_ms, total, :cache)
53
+ row(" ", "cache", collector.cache_duration_ms, total, :cache),
54
+ row(" ", "http", collector.http_duration_ms, total, :http)
52
55
  ]
53
56
  end
54
57
 
55
58
  def header_line(request, collector)
56
59
  elapsed = collector.elapsed_ms.round
57
60
  bar = BAR_CHAR * BAR_WIDTH
58
- line = "[RequestTrail] #{request.request_method} #{request.path} #{elapsed}ms #{bar}"
61
+ n1 = n_plus_one?(collector) ? " [N+1?]" : ""
62
+ line = "[RequestTrail] #{request.request_method} #{request.path} #{elapsed}ms#{n1} #{bar}"
59
63
  return line unless colorize?
60
64
 
61
65
  "#{@colors[:header]}#{line}#{RESET}"
@@ -20,8 +20,10 @@ module RequestTrail
20
20
  method: request.request_method,
21
21
  path: request.path,
22
22
  duration_ms: collector.elapsed_ms,
23
+ n_plus_one: n_plus_one?(collector),
23
24
  sql: { count: collector.sql_count, duration_ms: collector.sql_duration_ms },
24
- cache: cache_payload(collector)
25
+ cache: cache_payload(collector),
26
+ http: { count: collector.http_count, duration_ms: collector.http_duration_ms }
25
27
  }
26
28
  end
27
29
 
@@ -6,9 +6,11 @@ module RequestTrail
6
6
  CACHE_READ_EVENT = "cache_read.active_support"
7
7
  CACHE_WRITE_EVENT = "cache_write.active_support"
8
8
  ACTION_EVENT = "process_action.action_controller"
9
+ HTTP_EVENT = "request.faraday"
9
10
 
10
11
  def self.attach
11
- @attach ||= [sql_subscription, cache_read_subscription, cache_write_subscription, action_subscription]
12
+ @attach ||= [sql_subscription, cache_read_subscription, cache_write_subscription,
13
+ action_subscription, http_subscription]
12
14
  end
13
15
 
14
16
  def self.detach
@@ -51,5 +53,12 @@ module RequestTrail
51
53
  )
52
54
  end
53
55
  end
56
+
57
+ private_class_method def self.http_subscription
58
+ ActiveSupport::Notifications.subscribe(HTTP_EVENT) do |*args|
59
+ event = ActiveSupport::Notifications::Event.new(*args)
60
+ Collector.current&.record_http(duration_ms: event.duration)
61
+ end
62
+ end
54
63
  end
55
64
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RequestTrail
4
- VERSION = "0.6.0"
4
+ VERSION = "0.7.0"
5
5
  end
data/lib/request_trail.rb CHANGED
@@ -10,6 +10,7 @@ require_relative "request_trail/formatters/flame_graph"
10
10
  require_relative "request_trail/formatters/json"
11
11
  require_relative "request_trail/log_tag"
12
12
  require_relative "request_trail/middleware"
13
+ require_relative "request_trail/active_job_subscriber" if defined?(ActiveJob)
13
14
  require_relative "request_trail/sidekiq_middleware" if defined?(Sidekiq)
14
15
  require_relative "request_trail/railtie" if defined?(Rails::Railtie)
15
16
 
@@ -0,0 +1,15 @@
1
+ module RequestTrail
2
+ module ActiveJobSubscriber
3
+ class JobContext
4
+ attr_accessor request_method: String
5
+ attr_accessor path: String
6
+
7
+ def initialize: (request_method: String, path: String) -> void
8
+ end
9
+
10
+ def self.included: (Module base) -> void
11
+ def self.trace: (untyped job, ^() -> void block) -> void
12
+ def self.log: (untyped job) -> void
13
+ def self.build_context: (untyped job) -> JobContext
14
+ end
15
+ end
@@ -20,6 +20,8 @@ module RequestTrail
20
20
  attr_reader cache_duration_ms: Float
21
21
  attr_reader action_duration_ms: Float
22
22
  attr_reader view_duration_ms: Float
23
+ attr_reader http_count: Integer
24
+ attr_reader http_duration_ms: Float
23
25
 
24
26
  def self.current: () -> Collector?
25
27
  def self.start: () -> Collector
@@ -30,6 +32,7 @@ module RequestTrail
30
32
  def record_cache_read: (hit: bool, duration_ms: Float) -> Float
31
33
  def record_cache_write: (duration_ms: Float) -> Float
32
34
  def record_action: (duration_ms: Float, view_duration_ms: Float) -> Float
35
+ def record_http: (duration_ms: Float) -> Float
33
36
  def elapsed_ms: () -> (Float | Integer)
34
37
  end
35
38
  end
@@ -7,6 +7,7 @@ module RequestTrail
7
7
  attr_accessor threshold_ms: Numeric
8
8
  attr_accessor ignore_paths: Array[String | Regexp]
9
9
  attr_accessor sample_rate: Float
10
+ attr_accessor n_plus_one_threshold: Integer?
10
11
 
11
12
  def initialize: () -> void
12
13
  def logger: () -> ::Logger
@@ -2,6 +2,10 @@ module RequestTrail
2
2
  module Formatters
3
3
  module Base
4
4
  def format: (_RequestContext request, Collector collector) -> String
5
+
6
+ private
7
+
8
+ def n_plus_one?: (Collector collector) -> bool
5
9
  end
6
10
  end
7
- end
11
+ end
@@ -4,6 +4,7 @@ module RequestTrail
4
4
  CACHE_READ_EVENT: String
5
5
  CACHE_WRITE_EVENT: String
6
6
  ACTION_EVENT: String
7
+ HTTP_EVENT: String
7
8
 
8
9
  self.@attach: Array[untyped]?
9
10
 
@@ -16,5 +17,6 @@ module RequestTrail
16
17
  def self.cache_read_subscription: () -> untyped
17
18
  def self.cache_write_subscription: () -> untyped
18
19
  def self.action_subscription: () -> untyped
20
+ def self.http_subscription: () -> untyped
19
21
  end
20
22
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: request_trail
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chuck Smith
@@ -57,6 +57,7 @@ files:
57
57
  - lib/generators/request_trail/install/install_generator.rb
58
58
  - lib/generators/request_trail/install/templates/request_trail.rb.tt
59
59
  - lib/request_trail.rb
60
+ - lib/request_trail/active_job_subscriber.rb
60
61
  - lib/request_trail/collector.rb
61
62
  - lib/request_trail/configuration.rb
62
63
  - lib/request_trail/formatter.rb
@@ -70,6 +71,7 @@ files:
70
71
  - lib/request_trail/subscriber.rb
71
72
  - lib/request_trail/version.rb
72
73
  - sig/request_trail.rbs
74
+ - sig/request_trail/active_job_subscriber.rbs
73
75
  - sig/request_trail/collector.rbs
74
76
  - sig/request_trail/configuration.rbs
75
77
  - sig/request_trail/formatter.rbs