request_trail 0.4.0 → 0.5.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: ad0751f86ff717630ea62f478707c758fc9d45540721c54016a55e19c86030be
4
- data.tar.gz: 6192f331657a761ee7d537c29ded481629cfedfb1da74eb819e9181677010036
3
+ metadata.gz: b098702d0968a3cfbdc8b4147ab87bae1d24854a32d82b604993d17c75d367ba
4
+ data.tar.gz: 3a8b148a3a2eb1f7d23f938fbf2b6a70d4776c97a003bacd7ac3fd55989b3c16
5
5
  SHA512:
6
- metadata.gz: 45d23c714ab2cebe3d5d33db4e8832301e69e496d3cd27419c370eef734bf8581c7d9f815485ab595acf7ce95d9c82c07d580eca805386526edbcd21bfffc74b
7
- data.tar.gz: da3154bce7516976fdbff6437b877d639cf9b235a3308cb7101462d63e368b5b64530b932f101975f785cbb18365448e0ef18fbad84c2a5e2d8a3c9db126249c
6
+ metadata.gz: 4c3bd3e29cae157cce0bdf0f3a3aa243abc0e01bc830f7891f8c0604665246d77cbdc055e693a6f9919c4f788ee2bc9ba5e64739f26c4decd8c3a917e73a3ebe
7
+ data.tar.gz: f68396f353c7e5ff0debf97ead1ea725e3163a014525f7837bc62774d253872955bd6f6e3c9d82b97e20d88bea3d2f58104a46b74c314b31902d68fb4ac16cc1
data/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.5.0] - 2026-06-13
4
+
5
+ ### Added
6
+
7
+ - `config.ignore_paths` — skip tracing for specific paths; accepts strings (exact match) or regexes (e.g. `["/health", /^\/assets/]`)
8
+ - `config.sample_rate` — trace only N% of requests; accepts a float between `0.0` and `1.0` (default `1.0` = 100%)
9
+ - `RequestTrail::Formatters::Base` — mixin that documents the formatter duck-type contract; include it in custom formatters and implement `#format(request, collector) -> String`
10
+ - `FlameGraph` colour overrides — pass `colors: { controller: "\e[36m" }` to `FlameGraph.new` to replace per-layer ANSI codes; unspecified layers keep their defaults
11
+ - Rails install generator — run `rails generate request_trail:install` to scaffold `config/initializers/request_trail.rb` pre-populated with all options and their defaults
12
+
3
13
  ## [0.4.0] - 2026-06-12
4
14
 
5
15
  ### Added
@@ -40,7 +50,8 @@
40
50
  - `RequestTrail::Subscriber` — attach/detach API for notification subscriptions
41
51
  - `RequestTrail::Collector` — thread-safe per-request event accumulator
42
52
 
43
- [Unreleased]: https://github.com/eclectic-coding/request-trail/compare/v0.4.0...HEAD
53
+ [Unreleased]: https://github.com/eclectic-coding/request-trail/compare/v0.5.0...HEAD
54
+ [0.5.0]: https://github.com/eclectic-coding/request-trail/releases/tag/v0.5.0
44
55
  [0.4.0]: https://github.com/eclectic-coding/request-trail/releases/tag/v0.4.0
45
56
  [0.3.0]: https://github.com/eclectic-coding/request-trail/releases/tag/v0.3.0
46
57
  [0.2.0]: https://github.com/eclectic-coding/request-trail/releases/tag/v0.2.0
data/README.md CHANGED
@@ -12,6 +12,7 @@ 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
16
  - [Development](#development)
16
17
  - [Contributing](#contributing)
17
18
  - [License](#license)
@@ -76,6 +77,49 @@ Output (with ANSI colour when stdout is a TTY):
76
77
 
77
78
  Colour scheme: controller = blue, sql = yellow, cache = green, view = magenta. Plain bars are emitted when stdout is not a TTY (e.g. log files, CI).
78
79
 
80
+ Override any layer's ANSI code with the `colors:` option:
81
+
82
+ ```ruby
83
+ RequestTrail::Formatters::FlameGraph.new(
84
+ colorize: true,
85
+ colors: { controller: "\e[36m", sql: "\e[31m" }
86
+ )
87
+ ```
88
+
89
+ Unspecified layers keep their defaults.
90
+
91
+ ### Custom formatters
92
+
93
+ 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:
94
+
95
+ ```ruby
96
+ class MyFormatter
97
+ include RequestTrail::Formatters::Base
98
+
99
+ def format(request, collector)
100
+ "#{request.request_method} #{request.path} took #{collector.elapsed_ms.round}ms"
101
+ end
102
+ end
103
+
104
+ RequestTrail.configure do |config|
105
+ config.formatter = MyFormatter.new
106
+ end
107
+ ```
108
+
109
+ `format` receives:
110
+ - `request` — a `Rack::Request` with the current HTTP request
111
+ - `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`
112
+
113
+ ### Installation generator
114
+
115
+ Run the generator to scaffold the initializer:
116
+
117
+ ```bash
118
+ rails generate request_trail:install
119
+ ```
120
+
121
+ This creates `config/initializers/request_trail.rb` pre-populated with all available options and their defaults.
122
+
79
123
  ### Configuration
80
124
 
81
125
  Add an initializer to customize behavior:
@@ -88,6 +132,12 @@ RequestTrail.configure do |config|
88
132
  config.threshold_ms = 200 # only log requests slower than this (0 = log all)
89
133
  config.logger = nil # defaults to Rails.logger
90
134
  config.formatter = RequestTrail::Formatters::FlameGraph.new # optional
135
+
136
+ # skip tracing for specific paths (strings = exact match, regexes = pattern match)
137
+ config.ignore_paths = ["/health", "/up", /^\/assets/]
138
+
139
+ # trace only N% of requests — useful in high-traffic production environments
140
+ config.sample_rate = 0.1 # 0.0 = never, 1.0 = always (default)
91
141
  end
92
142
  ```
93
143
 
data/ROADMAP.md CHANGED
@@ -2,15 +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.5.0 — Filtering & Sampling
6
-
7
- - Path filters: skip tracing for `/assets`, `/health`, or custom regex patterns
8
- - Slow-request mode: only emit summaries above `threshold_ms`
9
- - Sampling: trace only N% of requests (useful in production)
10
- - Custom formatter API: `config.formatter = MyFormatter`
11
- - FlameGraph colour overrides: `FlameGraph.new(colorize: true, colors: { controller: "\e[36m" })`
12
- - Rails generator to scaffold the config initializer (`rails generate request_trail:install`)
13
-
14
5
  ## 0.6.0 — Structured Output & Integrations
15
6
 
16
7
  - JSON formatter for log aggregators (Datadog, Splunk, etc.)
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ return unless defined?(Rails::Generators)
4
+
5
+ require "rails/generators"
6
+
7
+ module RequestTrail
8
+ module Generators
9
+ class InstallGenerator < Rails::Generators::Base
10
+ source_root File.expand_path("templates", __dir__)
11
+ desc "Creates a RequestTrail initializer in config/initializers"
12
+
13
+ def copy_initializer
14
+ template "request_trail.rb.tt", "config/initializers/request_trail.rb"
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ RequestTrail.configure do |config|
4
+ config.enabled = true # set to false to disable entirely
5
+ config.log_level = :info # :debug, :info, :warn
6
+ config.threshold_ms = 0 # only log requests slower than this (0 = log all)
7
+ config.ignore_paths = [] # e.g. ["/health", /^\/assets/]
8
+ config.sample_rate = 1.0 # 0.0–1.0; 1.0 = trace every request
9
+
10
+ # config.logger = Rails.logger # defaults to Rails.logger
11
+ # config.formatter = RequestTrail::Formatters::FlameGraph.new(colorize: true)
12
+ end
@@ -5,12 +5,22 @@ require "logger"
5
5
  module RequestTrail
6
6
  class Configuration
7
7
  attr_writer :logger, :formatter
8
- attr_accessor :enabled, :log_level, :threshold_ms
8
+ attr_accessor :enabled, :log_level, :threshold_ms, :ignore_paths, :sample_rate
9
9
 
10
10
  def initialize
11
11
  @enabled = true
12
12
  @log_level = :info
13
13
  @threshold_ms = 0
14
+ @ignore_paths = []
15
+ @sample_rate = 1.0
16
+ end
17
+
18
+ def sampled?
19
+ rand < sample_rate
20
+ end
21
+
22
+ def ignored_path?(path)
23
+ ignore_paths.any? { |pattern| path_matches?(pattern, path) }
14
24
  end
15
25
 
16
26
  def logger
@@ -23,6 +33,13 @@ module RequestTrail
23
33
 
24
34
  private
25
35
 
36
+ def path_matches?(pattern, path)
37
+ case pattern
38
+ when Regexp then pattern.match?(path)
39
+ else pattern == path
40
+ end
41
+ end
42
+
26
43
  def rails_logger
27
44
  Rails.logger if defined?(Rails) && Rails.respond_to?(:logger) && Rails.logger
28
45
  end
@@ -2,6 +2,8 @@
2
2
 
3
3
  module RequestTrail
4
4
  class Formatter
5
+ include Formatters::Base
6
+
5
7
  def format(request, collector)
6
8
  header = "[RequestTrail] #{request.request_method} #{request.path} #{collector.elapsed_ms.round}ms"
7
9
  return tiered_format(header, collector) if collector.action_duration_ms.positive?
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RequestTrail
4
+ module Formatters
5
+ # Mixin that documents the formatter duck-type contract.
6
+ # Include it in custom formatters to make the interface explicit;
7
+ # the only required method is #format(request, collector) -> String.
8
+ module Base
9
+ # @param request [Rack::Request]
10
+ # @param collector [RequestTrail::Collector]
11
+ # @return [String] the log line(s) to emit
12
+ def format(_request, _collector)
13
+ raise NotImplementedError, "#{self.class}#format must return a String"
14
+ end
15
+ end
16
+ end
17
+ end
@@ -3,6 +3,8 @@
3
3
  module RequestTrail
4
4
  module Formatters
5
5
  class FlameGraph
6
+ include Base
7
+
6
8
  BAR_WIDTH = 36
7
9
  BAR_CHAR = "█"
8
10
 
@@ -15,8 +17,9 @@ module RequestTrail
15
17
  }.freeze
16
18
  RESET = "\e[0m"
17
19
 
18
- def initialize(colorize: false)
20
+ def initialize(colorize: false, colors: {})
19
21
  @colorize = colorize
22
+ @colors = COLORS.merge(colors)
20
23
  end
21
24
 
22
25
  def format(request, collector)
@@ -55,7 +58,7 @@ module RequestTrail
55
58
  line = "[RequestTrail] #{request.request_method} #{request.path} #{elapsed}ms #{bar}"
56
59
  return line unless colorize?
57
60
 
58
- "#{COLORS[:header]}#{line}#{RESET}"
61
+ "#{@colors[:header]}#{line}#{RESET}"
59
62
  end
60
63
 
61
64
  def row(indent, label, duration_ms, total_ms, color_key)
@@ -69,7 +72,7 @@ module RequestTrail
69
72
  bar = BAR_CHAR * width
70
73
  return bar unless colorize? && width.positive?
71
74
 
72
- "#{COLORS[color_key]}#{bar}#{RESET}"
75
+ "#{@colors[color_key]}#{bar}#{RESET}"
73
76
  end
74
77
 
75
78
  def colorize?
@@ -8,6 +8,8 @@ module RequestTrail
8
8
 
9
9
  def call(env)
10
10
  return @app.call(env) unless RequestTrail.configuration.enabled
11
+ return @app.call(env) if RequestTrail.configuration.ignored_path?(env["PATH_INFO"])
12
+ return @app.call(env) unless RequestTrail.configuration.sampled?
11
13
 
12
14
  Collector.start
13
15
  response = @app.call(env)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RequestTrail
4
- VERSION = "0.4.0"
4
+ VERSION = "0.5.0"
5
5
  end
data/lib/request_trail.rb CHANGED
@@ -4,6 +4,7 @@ require_relative "request_trail/version"
4
4
  require_relative "request_trail/configuration"
5
5
  require_relative "request_trail/collector"
6
6
  require_relative "request_trail/subscriber"
7
+ require_relative "request_trail/formatters/base"
7
8
  require_relative "request_trail/formatter"
8
9
  require_relative "request_trail/formatters/flame_graph"
9
10
  require_relative "request_trail/middleware"
@@ -1,13 +1,17 @@
1
1
  module RequestTrail
2
2
  class Configuration
3
3
  attr_writer logger: ::Logger
4
- attr_writer formatter: untyped
4
+ attr_writer formatter: Formatters::Base
5
5
  attr_accessor enabled: bool
6
6
  attr_accessor log_level: Symbol
7
7
  attr_accessor threshold_ms: Numeric
8
+ attr_accessor ignore_paths: Array[String | Regexp]
9
+ attr_accessor sample_rate: Float
8
10
 
9
11
  def initialize: () -> void
10
12
  def logger: () -> ::Logger
11
- def formatter: () -> untyped
13
+ def formatter: () -> Formatters::Base
14
+ def ignored_path?: (String path) -> bool
15
+ def sampled?: () -> bool
12
16
  end
13
17
  end
@@ -0,0 +1,7 @@
1
+ module RequestTrail
2
+ module Formatters
3
+ module Base
4
+ def format: (::Rack::Request request, Collector collector) -> String
5
+ end
6
+ end
7
+ end
@@ -6,7 +6,7 @@ module RequestTrail
6
6
  COLORS: Hash[Symbol, String]
7
7
  RESET: String
8
8
 
9
- def initialize: (?colorize: bool) -> void
9
+ def initialize: (?colorize: bool, ?colors: Hash[Symbol, String]) -> void
10
10
  def format: (::Rack::Request request, Collector collector) -> String
11
11
 
12
12
  private
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.4.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chuck Smith
@@ -54,10 +54,13 @@ files:
54
54
  - ROADMAP.md
55
55
  - Rakefile
56
56
  - codecov.yml
57
+ - lib/generators/request_trail/install/install_generator.rb
58
+ - lib/generators/request_trail/install/templates/request_trail.rb.tt
57
59
  - lib/request_trail.rb
58
60
  - lib/request_trail/collector.rb
59
61
  - lib/request_trail/configuration.rb
60
62
  - lib/request_trail/formatter.rb
63
+ - lib/request_trail/formatters/base.rb
61
64
  - lib/request_trail/formatters/flame_graph.rb
62
65
  - lib/request_trail/middleware.rb
63
66
  - lib/request_trail/railtie.rb
@@ -67,6 +70,7 @@ files:
67
70
  - sig/request_trail/collector.rbs
68
71
  - sig/request_trail/configuration.rbs
69
72
  - sig/request_trail/formatter.rbs
73
+ - sig/request_trail/formatters/base.rbs
70
74
  - sig/request_trail/formatters/flame_graph.rbs
71
75
  - sig/request_trail/middleware.rbs
72
76
  - sig/request_trail/subscriber.rbs