request_trail 0.1.0 → 0.2.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: ef1bf3bfcc191f502a9380fbe03bcdba11db1003b6b061db77b93769ac8697ba
4
- data.tar.gz: 6df870ef886ee153cda6babf4cb75c2d80484c9d42c61fccd926fe5278a42705
3
+ metadata.gz: 170ab890527afbaf7d18005ceca802f6a561795724cce97e1f9740f100dcc1ea
4
+ data.tar.gz: 91e793b4cf8bf1b086ac2908177bd618e8aabd71a37f239e45b115c9f986290e
5
5
  SHA512:
6
- metadata.gz: 484a485f9ffd159c6daf55abd72f9dc44204d483b2ed31242e502433224c7adf59b33a211e1fd501d703437caa1c4158775b463f0c3c4cdd7156283078ed3b89
7
- data.tar.gz: 99d772953da1f97dea448fb3e50bf8889c7a1e29fae0a67805e210d949cddbacad5e039bd7d59821480109c6a62566e73d3575d21a0f61badf3c19e5eec08731
6
+ metadata.gz: 59cceaa66b3ff236a3a9bab1637f78db87455c5beb1e8687bc89f459053803fd12b52131862923b1d26561cb6c349dee2c970e0cdd421216445758cd1ea6fc21
7
+ data.tar.gz: e8969dd6df420ff10c3e3a2879ace0a187797633e98764ddf152b28e1d435a4a0099825afb8af64859d8dd1eff6d9403251e9b88c4f249251efe12995f1dc0af
data/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.2.0] - 2026-06-11
4
+
5
+ ### Added
6
+
7
+ - Cache tracing via `cache_read.active_support` and `cache_write.active_support` notifications — records hit/miss/write counts and cumulative duration per request
8
+ - Summary line now includes a cache segment: `[RequestTrail] GET /orders 142ms | SQL: 7/38.3ms | Cache: 4 hits, 1 miss, 2.0ms`
9
+
3
10
  ## [0.1.0] - 2026-06-11
4
11
 
5
12
  ### Added
@@ -12,5 +19,6 @@
12
19
  - `RequestTrail::Subscriber` — attach/detach API for notification subscriptions
13
20
  - `RequestTrail::Collector` — thread-safe per-request event accumulator
14
21
 
15
- [Unreleased]: https://github.com/eclectic-coding/request-trail/compare/v0.1.0...HEAD
22
+ [Unreleased]: https://github.com/eclectic-coding/request-trail/compare/v0.2.0...HEAD
23
+ [0.2.0]: https://github.com/eclectic-coding/request-trail/releases/tag/v0.2.0
16
24
  [0.1.0]: https://github.com/eclectic-coding/request-trail/releases/tag/v0.1.0
data/README.md CHANGED
@@ -39,9 +39,14 @@ gem install request_trail
39
39
  RequestTrail auto-inserts itself via a Railtie. No manual middleware configuration is needed — just add the gem to your `Gemfile` and it will log a summary after every request:
40
40
 
41
41
  ```
42
- [RequestTrail] GET /orders 142ms | SQL: 7 queries / 38ms
42
+ [RequestTrail] GET /orders 142ms | SQL: 7/38.3ms | Cache: 4 hits, 1 miss, 2.0ms
43
43
  ```
44
44
 
45
+ The summary line shows:
46
+ - **Total request time** in milliseconds
47
+ - **SQL** — query count and cumulative Active Record time
48
+ - **Cache** — hit/miss/write counts and cumulative cache time
49
+
45
50
  ### Configuration
46
51
 
47
52
  Add an initializer to customize behavior:
data/ROADMAP.md CHANGED
@@ -2,14 +2,6 @@
2
2
 
3
3
  `request_trail` traces a Rails request through every processing layer — middleware, controller, ActiveRecord, cache — and emits a flame-graph-style summary to the log. This roadmap describes the incremental path to a stable 1.0.0.
4
4
 
5
- ## 0.2.0 — Cache Tracing
6
-
7
- - Subscribe to `cache_read`, `cache_write`, `cache_fetch_hit`, and `cache_delete` notifications
8
- - Add cache hit/miss/write counts and cumulative time to the summary line:
9
- ```
10
- [RequestTrail] GET /orders 142ms | SQL: 7/38ms | Cache: 4 hits, 1 miss, 2ms
11
- ```
12
-
13
5
  ## 0.3.0 — Controller & View Tracing
14
6
 
15
7
  - Subscribe to `process_action.action_controller` and `render_template.action_view`
@@ -4,7 +4,8 @@ module RequestTrail
4
4
  class Collector
5
5
  THREAD_KEY = :request_trail_collector
6
6
 
7
- attr_reader :sql_count, :sql_duration_ms
7
+ attr_reader :sql_count, :sql_duration_ms,
8
+ :cache_hits, :cache_misses, :cache_writes, :cache_duration_ms
8
9
 
9
10
  def self.current
10
11
  Thread.current[THREAD_KEY]
@@ -22,6 +23,10 @@ module RequestTrail
22
23
  @started_at = Process.clock_gettime(Process::CLOCK_MONOTONIC)
23
24
  @sql_count = 0
24
25
  @sql_duration_ms = 0.0
26
+ @cache_hits = 0
27
+ @cache_misses = 0
28
+ @cache_writes = 0
29
+ @cache_duration_ms = 0.0
25
30
  end
26
31
 
27
32
  def record_sql(duration_ms)
@@ -29,6 +34,16 @@ module RequestTrail
29
34
  @sql_duration_ms += duration_ms
30
35
  end
31
36
 
37
+ def record_cache_read(hit:, duration_ms:)
38
+ hit ? @cache_hits += 1 : @cache_misses += 1
39
+ @cache_duration_ms += duration_ms
40
+ end
41
+
42
+ def record_cache_write(duration_ms:)
43
+ @cache_writes += 1
44
+ @cache_duration_ms += duration_ms
45
+ end
46
+
32
47
  def elapsed_ms
33
48
  elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - @started_at
34
49
  (elapsed * 1000).round(2)
@@ -4,14 +4,20 @@ module RequestTrail
4
4
  class Formatter
5
5
  def format(request, collector)
6
6
  header = "[RequestTrail] #{request.request_method} #{request.path}"
7
- "#{header} #{collector.elapsed_ms.round}ms | #{sql_summary(collector)}"
7
+ "#{header} #{collector.elapsed_ms.round}ms | #{sql_summary(collector)} | #{cache_summary(collector)}"
8
8
  end
9
9
 
10
10
  private
11
11
 
12
12
  def sql_summary(collector)
13
- label = collector.sql_count == 1 ? "query" : "queries"
14
- "SQL: #{collector.sql_count} #{label} / #{collector.sql_duration_ms.round(1)}ms"
13
+ "SQL: #{collector.sql_count}/#{collector.sql_duration_ms.round(1)}ms"
14
+ end
15
+
16
+ def cache_summary(collector)
17
+ hit_label = collector.cache_hits == 1 ? "hit" : "hits"
18
+ miss_label = collector.cache_misses == 1 ? "miss" : "misses"
19
+ duration = collector.cache_duration_ms.round(1)
20
+ "Cache: #{collector.cache_hits} #{hit_label}, #{collector.cache_misses} #{miss_label}, #{duration}ms"
15
21
  end
16
22
  end
17
23
  end
@@ -2,20 +2,43 @@
2
2
 
3
3
  module RequestTrail
4
4
  class Subscriber
5
- SQL_EVENT = "sql.active_record"
5
+ SQL_EVENT = "sql.active_record"
6
+ CACHE_READ_EVENT = "cache_read.active_support"
7
+ CACHE_WRITE_EVENT = "cache_write.active_support"
6
8
 
7
9
  def self.attach
8
- @attach ||= ActiveSupport::Notifications.subscribe(SQL_EVENT) do |*args|
9
- event = ActiveSupport::Notifications::Event.new(*args)
10
- Collector.current&.record_sql(event.duration)
11
- end
10
+ @attach ||= [sql_subscription, cache_read_subscription, cache_write_subscription]
12
11
  end
13
12
 
14
13
  def self.detach
15
14
  return unless @attach
16
15
 
17
- ActiveSupport::Notifications.unsubscribe(@attach)
16
+ @attach&.each { |sub| ActiveSupport::Notifications.unsubscribe(sub) }
18
17
  @attach = nil
19
18
  end
19
+
20
+ private_class_method def self.sql_subscription
21
+ ActiveSupport::Notifications.subscribe(SQL_EVENT) do |*args|
22
+ event = ActiveSupport::Notifications::Event.new(*args)
23
+ Collector.current&.record_sql(event.duration)
24
+ end
25
+ end
26
+
27
+ private_class_method def self.cache_read_subscription
28
+ ActiveSupport::Notifications.subscribe(CACHE_READ_EVENT) do |*args|
29
+ event = ActiveSupport::Notifications::Event.new(*args)
30
+ Collector.current&.record_cache_read(
31
+ hit: event.payload.fetch(:hit, false),
32
+ duration_ms: event.duration
33
+ )
34
+ end
35
+ end
36
+
37
+ private_class_method def self.cache_write_subscription
38
+ ActiveSupport::Notifications.subscribe(CACHE_WRITE_EVENT) do |*args|
39
+ event = ActiveSupport::Notifications::Event.new(*args)
40
+ Collector.current&.record_cache_write(duration_ms: event.duration)
41
+ end
42
+ end
20
43
  end
21
44
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RequestTrail
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.0"
5
5
  end
@@ -0,0 +1,30 @@
1
+ module RequestTrail
2
+ class Collector
3
+ THREAD_KEY: Symbol
4
+
5
+ @started_at: Float
6
+ @sql_count: Integer
7
+ @sql_duration_ms: Float
8
+ @cache_hits: Integer
9
+ @cache_misses: Integer
10
+ @cache_writes: Integer
11
+ @cache_duration_ms: Float
12
+
13
+ attr_reader sql_count: Integer
14
+ attr_reader sql_duration_ms: Float
15
+ attr_reader cache_hits: Integer
16
+ attr_reader cache_misses: Integer
17
+ attr_reader cache_writes: Integer
18
+ attr_reader cache_duration_ms: Float
19
+
20
+ def self.current: () -> Collector?
21
+ def self.start: () -> Collector
22
+ def self.stop: () -> nil
23
+
24
+ def initialize: () -> void
25
+ def record_sql: (Float duration_ms) -> Float
26
+ def record_cache_read: (hit: bool, duration_ms: Float) -> Float
27
+ def record_cache_write: (duration_ms: Float) -> Float
28
+ def elapsed_ms: () -> (Float | Integer)
29
+ end
30
+ end
@@ -0,0 +1,11 @@
1
+ module RequestTrail
2
+ class Configuration
3
+ attr_writer logger: ::Logger
4
+ attr_accessor enabled: bool
5
+ attr_accessor log_level: Symbol
6
+ attr_accessor threshold_ms: Numeric
7
+
8
+ def initialize: () -> void
9
+ def logger: () -> ::Logger
10
+ end
11
+ end
@@ -0,0 +1,5 @@
1
+ module RequestTrail
2
+ class Formatter
3
+ def format: (::Rack::Request request, Collector collector) -> String
4
+ end
5
+ end
@@ -0,0 +1,6 @@
1
+ module RequestTrail
2
+ class Middleware
3
+ def initialize: (untyped app) -> void
4
+ def call: (Hash[String, untyped] env) -> untyped
5
+ end
6
+ end
@@ -0,0 +1,18 @@
1
+ module RequestTrail
2
+ class Subscriber
3
+ SQL_EVENT: String
4
+ CACHE_READ_EVENT: String
5
+ CACHE_WRITE_EVENT: String
6
+
7
+ self.@attach: Array[untyped]?
8
+
9
+ def self.attach: () -> Array[untyped]
10
+ def self.detach: () -> nil
11
+
12
+ private
13
+
14
+ def self.sql_subscription: () -> untyped
15
+ def self.cache_read_subscription: () -> untyped
16
+ def self.cache_write_subscription: () -> untyped
17
+ end
18
+ 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.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chuck Smith
@@ -63,6 +63,11 @@ files:
63
63
  - lib/request_trail/subscriber.rb
64
64
  - lib/request_trail/version.rb
65
65
  - sig/request_trail.rbs
66
+ - sig/request_trail/collector.rbs
67
+ - sig/request_trail/configuration.rbs
68
+ - sig/request_trail/formatter.rbs
69
+ - sig/request_trail/middleware.rbs
70
+ - sig/request_trail/subscriber.rbs
66
71
  homepage: https://github.com/eclectic-coding/request-trail
67
72
  licenses:
68
73
  - MIT