request_trail 0.2.0 → 0.3.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: 170ab890527afbaf7d18005ceca802f6a561795724cce97e1f9740f100dcc1ea
4
- data.tar.gz: 91e793b4cf8bf1b086ac2908177bd618e8aabd71a37f239e45b115c9f986290e
3
+ metadata.gz: 954b1805423f92d59582e41e86d55ac489577ef5c76d20701c0fbcd76956f2a8
4
+ data.tar.gz: 8464e352949c6eb14636ead83e5f0a556b8eb7dcabfd35186f35b3749ae8ac0a
5
5
  SHA512:
6
- metadata.gz: 59cceaa66b3ff236a3a9bab1637f78db87455c5beb1e8687bc89f459053803fd12b52131862923b1d26561cb6c349dee2c970e0cdd421216445758cd1ea6fc21
7
- data.tar.gz: e8969dd6df420ff10c3e3a2879ace0a187797633e98764ddf152b28e1d435a4a0099825afb8af64859d8dd1eff6d9403251e9b88c4f249251efe12995f1dc0af
6
+ metadata.gz: 2c75f6813431fe8922454592a7dfb170fbddab4258a696901e79d0e1ec56699834cd7d9d0200d8eef1a7a3067dc95988e7bd0419b66b0ee6566bf0f342301dfd
7
+ data.tar.gz: 7054bee0d5f25f35513c9697bfe5000128d7932df335beae13acaeb43af5522fde160c045554526a505efacbe326d9892689756b4ea8b289d04869d7c73fc565
data/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.3.0] - 2026-06-12
4
+
5
+ ### Added
6
+
7
+ - Controller and view tracing via `process_action.action_controller` — records total controller duration and view runtime per request
8
+ - Formatter switches to tiered multi-line output when controller data is present:
9
+ ```
10
+ [RequestTrail] GET /orders 142ms
11
+ controller 104ms
12
+ sql 38ms (7 queries)
13
+ cache 2ms (4 hits, 1 miss)
14
+ view 22ms
15
+ ```
16
+
3
17
  ## [0.2.0] - 2026-06-11
4
18
 
5
19
  ### Added
@@ -19,6 +33,7 @@
19
33
  - `RequestTrail::Subscriber` — attach/detach API for notification subscriptions
20
34
  - `RequestTrail::Collector` — thread-safe per-request event accumulator
21
35
 
22
- [Unreleased]: https://github.com/eclectic-coding/request-trail/compare/v0.2.0...HEAD
36
+ [Unreleased]: https://github.com/eclectic-coding/request-trail/compare/v0.3.0...HEAD
37
+ [0.3.0]: https://github.com/eclectic-coding/request-trail/releases/tag/v0.3.0
23
38
  [0.2.0]: https://github.com/eclectic-coding/request-trail/releases/tag/v0.2.0
24
39
  [0.1.0]: https://github.com/eclectic-coding/request-trail/releases/tag/v0.1.0
data/README.md CHANGED
@@ -36,16 +36,23 @@ gem install request_trail
36
36
 
37
37
  ### Rails
38
38
 
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:
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
+
41
+ When controller tracing is active, output is tiered:
40
42
 
41
43
  ```
42
- [RequestTrail] GET /orders 142ms | SQL: 7/38.3ms | Cache: 4 hits, 1 miss, 2.0ms
44
+ [RequestTrail] GET /orders 142ms
45
+ controller 104ms
46
+ sql 38ms (7 queries)
47
+ cache 2ms (4 hits, 1 miss)
48
+ view 22ms
43
49
  ```
44
50
 
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
51
+ Without controller data (plain Rack apps), a single-line summary is emitted:
52
+
53
+ ```
54
+ [RequestTrail] GET /orders 142ms | SQL: 7/38.3ms | Cache: 4 hits, 1 miss, 2.0ms
55
+ ```
49
56
 
50
57
  ### Configuration
51
58
 
@@ -78,9 +85,42 @@ run MyApp
78
85
 
79
86
  ## Development
80
87
 
81
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
88
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `bundle exec rake` to run the full CI suite (audit + lint + tests). You can also run `bin/console` for an interactive prompt.
89
+
90
+ ### Running tests
91
+
92
+ ```bash
93
+ bundle exec rake spec # full test suite
94
+ bundle exec rspec spec/path/to/file_spec.rb # single file
95
+ bundle exec rspec spec/path/to/file_spec.rb:42 # single example
96
+ ```
97
+
98
+ ### Dummy app
99
+
100
+ A minimal Rails app lives in `spec/dummy` for manual end-to-end testing. It mounts a single `GET /ping` endpoint and logs RequestTrail output to `spec/dummy/log/request_trail.log`.
82
101
 
83
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
102
+ Start the server:
103
+
104
+ ```bash
105
+ bundle exec rackup spec/dummy/config.ru --port 3000
106
+ ```
107
+
108
+ Then make a request and tail the log:
109
+
110
+ ```bash
111
+ curl http://localhost:3000/ping
112
+ tail -f spec/dummy/log/request_trail.log
113
+ ```
114
+
115
+ You should see tiered output like:
116
+
117
+ ```
118
+ [RequestTrail] GET /ping 33ms
119
+ controller 3ms
120
+ sql 0.0ms (0 queries)
121
+ cache 0.0ms (0 hits, 0 misses)
122
+ view 2.8ms
123
+ ```
84
124
 
85
125
  [Back to top](#requesttrail)
86
126
 
data/ROADMAP.md CHANGED
@@ -2,18 +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.3.0 — Controller & View Tracing
6
-
7
- - Subscribe to `process_action.action_controller` and `render_template.action_view`
8
- - Tiered multi-line breakdown showing time spent in each layer:
9
- ```
10
- [RequestTrail] GET /orders 142ms
11
- controller 104ms
12
- sql 38ms (7 queries)
13
- cache 2ms (4 hits, 1 miss)
14
- view 22ms
15
- ```
16
-
17
5
  ## 0.4.0 — Flame Graph Output
18
6
 
19
7
  - Indented ASCII flame-graph renderer with proportional timing bars
@@ -34,6 +22,7 @@
34
22
  - Slow-request mode: only emit summaries above `threshold_ms`
35
23
  - Sampling: trace only N% of requests (useful in production)
36
24
  - Custom formatter API: `config.formatter = MyFormatter`
25
+ - Rails generator to scaffold the config initializer (`rails generate request_trail:install`)
37
26
 
38
27
  ## 0.6.0 — Structured Output & Integrations
39
28
 
@@ -5,7 +5,8 @@ module RequestTrail
5
5
  THREAD_KEY = :request_trail_collector
6
6
 
7
7
  attr_reader :sql_count, :sql_duration_ms,
8
- :cache_hits, :cache_misses, :cache_writes, :cache_duration_ms
8
+ :cache_hits, :cache_misses, :cache_writes, :cache_duration_ms,
9
+ :action_duration_ms, :view_duration_ms
9
10
 
10
11
  def self.current
11
12
  Thread.current[THREAD_KEY]
@@ -27,6 +28,8 @@ module RequestTrail
27
28
  @cache_misses = 0
28
29
  @cache_writes = 0
29
30
  @cache_duration_ms = 0.0
31
+ @action_duration_ms = 0.0
32
+ @view_duration_ms = 0.0
30
33
  end
31
34
 
32
35
  def record_sql(duration_ms)
@@ -44,6 +47,11 @@ module RequestTrail
44
47
  @cache_duration_ms += duration_ms
45
48
  end
46
49
 
50
+ def record_action(duration_ms:, view_duration_ms:)
51
+ @action_duration_ms = duration_ms
52
+ @view_duration_ms = view_duration_ms
53
+ end
54
+
47
55
  def elapsed_ms
48
56
  elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - @started_at
49
57
  (elapsed * 1000).round(2)
@@ -3,12 +3,24 @@
3
3
  module RequestTrail
4
4
  class Formatter
5
5
  def format(request, collector)
6
- header = "[RequestTrail] #{request.request_method} #{request.path}"
7
- "#{header} #{collector.elapsed_ms.round}ms | #{sql_summary(collector)} | #{cache_summary(collector)}"
6
+ header = "[RequestTrail] #{request.request_method} #{request.path} #{collector.elapsed_ms.round}ms"
7
+ return tiered_format(header, collector) if collector.action_duration_ms.positive?
8
+
9
+ "#{header} | #{sql_summary(collector)} | #{cache_summary(collector)}"
8
10
  end
9
11
 
10
12
  private
11
13
 
14
+ def tiered_format(header, collector)
15
+ [
16
+ header,
17
+ " controller #{collector.action_duration_ms.round}ms",
18
+ " sql #{collector.sql_duration_ms.round(1)}ms (#{sql_count_label(collector)})",
19
+ " cache #{collector.cache_duration_ms.round(1)}ms (#{cache_detail(collector)})",
20
+ " view #{collector.view_duration_ms.round(1)}ms"
21
+ ].join("\n")
22
+ end
23
+
12
24
  def sql_summary(collector)
13
25
  "SQL: #{collector.sql_count}/#{collector.sql_duration_ms.round(1)}ms"
14
26
  end
@@ -19,5 +31,15 @@ module RequestTrail
19
31
  duration = collector.cache_duration_ms.round(1)
20
32
  "Cache: #{collector.cache_hits} #{hit_label}, #{collector.cache_misses} #{miss_label}, #{duration}ms"
21
33
  end
34
+
35
+ def sql_count_label(collector)
36
+ collector.sql_count == 1 ? "1 query" : "#{collector.sql_count} queries"
37
+ end
38
+
39
+ def cache_detail(collector)
40
+ hit_label = collector.cache_hits == 1 ? "hit" : "hits"
41
+ miss_label = collector.cache_misses == 1 ? "miss" : "misses"
42
+ "#{collector.cache_hits} #{hit_label}, #{collector.cache_misses} #{miss_label}"
43
+ end
22
44
  end
23
45
  end
@@ -5,9 +5,10 @@ module RequestTrail
5
5
  SQL_EVENT = "sql.active_record"
6
6
  CACHE_READ_EVENT = "cache_read.active_support"
7
7
  CACHE_WRITE_EVENT = "cache_write.active_support"
8
+ ACTION_EVENT = "process_action.action_controller"
8
9
 
9
10
  def self.attach
10
- @attach ||= [sql_subscription, cache_read_subscription, cache_write_subscription]
11
+ @attach ||= [sql_subscription, cache_read_subscription, cache_write_subscription, action_subscription]
11
12
  end
12
13
 
13
14
  def self.detach
@@ -40,5 +41,15 @@ module RequestTrail
40
41
  Collector.current&.record_cache_write(duration_ms: event.duration)
41
42
  end
42
43
  end
44
+
45
+ private_class_method def self.action_subscription
46
+ ActiveSupport::Notifications.subscribe(ACTION_EVENT) do |*args|
47
+ event = ActiveSupport::Notifications::Event.new(*args)
48
+ Collector.current&.record_action(
49
+ duration_ms: event.duration,
50
+ view_duration_ms: event.payload.fetch(:view_runtime, 0.0)
51
+ )
52
+ end
53
+ end
43
54
  end
44
55
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RequestTrail
4
- VERSION = "0.2.0"
4
+ VERSION = "0.3.0"
5
5
  end
@@ -9,6 +9,8 @@ module RequestTrail
9
9
  @cache_misses: Integer
10
10
  @cache_writes: Integer
11
11
  @cache_duration_ms: Float
12
+ @action_duration_ms: Float
13
+ @view_duration_ms: Float
12
14
 
13
15
  attr_reader sql_count: Integer
14
16
  attr_reader sql_duration_ms: Float
@@ -16,6 +18,8 @@ module RequestTrail
16
18
  attr_reader cache_misses: Integer
17
19
  attr_reader cache_writes: Integer
18
20
  attr_reader cache_duration_ms: Float
21
+ attr_reader action_duration_ms: Float
22
+ attr_reader view_duration_ms: Float
19
23
 
20
24
  def self.current: () -> Collector?
21
25
  def self.start: () -> Collector
@@ -25,6 +29,7 @@ module RequestTrail
25
29
  def record_sql: (Float duration_ms) -> Float
26
30
  def record_cache_read: (hit: bool, duration_ms: Float) -> Float
27
31
  def record_cache_write: (duration_ms: Float) -> Float
32
+ def record_action: (duration_ms: Float, view_duration_ms: Float) -> Float
28
33
  def elapsed_ms: () -> (Float | Integer)
29
34
  end
30
35
  end
@@ -1,5 +1,13 @@
1
1
  module RequestTrail
2
2
  class Formatter
3
3
  def format: (::Rack::Request request, Collector collector) -> String
4
+
5
+ private
6
+
7
+ def tiered_format: (String header, Collector collector) -> String
8
+ def sql_summary: (Collector collector) -> String
9
+ def cache_summary: (Collector collector) -> String
10
+ def sql_count_label: (Collector collector) -> String
11
+ def cache_detail: (Collector collector) -> String
4
12
  end
5
13
  end
@@ -3,6 +3,7 @@ module RequestTrail
3
3
  SQL_EVENT: String
4
4
  CACHE_READ_EVENT: String
5
5
  CACHE_WRITE_EVENT: String
6
+ ACTION_EVENT: String
6
7
 
7
8
  self.@attach: Array[untyped]?
8
9
 
@@ -14,5 +15,6 @@ module RequestTrail
14
15
  def self.sql_subscription: () -> untyped
15
16
  def self.cache_read_subscription: () -> untyped
16
17
  def self.cache_write_subscription: () -> untyped
18
+ def self.action_subscription: () -> untyped
17
19
  end
18
20
  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.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chuck Smith