request_trail 0.5.0 → 0.6.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: b098702d0968a3cfbdc8b4147ab87bae1d24854a32d82b604993d17c75d367ba
4
- data.tar.gz: 3a8b148a3a2eb1f7d23f938fbf2b6a70d4776c97a003bacd7ac3fd55989b3c16
3
+ metadata.gz: 2c636ff49dd9c5f372269c4e9a9611a7e610e5e5f6f172a951e1f7d76579f804
4
+ data.tar.gz: 2e7a528107b38f1c3e92d0bc11bbdfd2b51295daaf616f4e6cbb82dbe8cc71e1
5
5
  SHA512:
6
- metadata.gz: 4c3bd3e29cae157cce0bdf0f3a3aa243abc0e01bc830f7891f8c0604665246d77cbdc055e693a6f9919c4f788ee2bc9ba5e64739f26c4decd8c3a917e73a3ebe
7
- data.tar.gz: f68396f353c7e5ff0debf97ead1ea725e3163a014525f7837bc62774d253872955bd6f6e3c9d82b97e20d88bea3d2f58104a46b74c314b31902d68fb4ac16cc1
6
+ metadata.gz: c4d2694d6a22c9a24884968967cb4210771e12efd1d43934df844dbdf699382ec994e620b0d6f75773b6584c5ae2f4a4c6cd6f346f808c400e70c36603b59352
7
+ data.tar.gz: f8597a5b458895afa2d09081ba96fd422c971733918f64a2a7109133103e12b0725fdb20d4ec2efc2b7d8a8dca383d241037796b69c10d902b5bd819085a6700
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.6.0] - 2026-06-14
4
+
5
+ ### Added
6
+
7
+ - `RequestTrail::Formatters::JSON` — structured JSON formatter for log aggregators (Datadog, Splunk, etc.); opt in via `config.formatter = RequestTrail::Formatters::JSON.new`
8
+ - `RequestTrail.log_tag` — callable proc for `config.log_tags`; returns a lazy `LogTag::Value` that evaluates to `sql=N Nms` at log-line time, so every tagged line reflects the live request SQL state
9
+ - `RequestTrail::SidekiqMiddleware` — optional Sidekiq server middleware that traces background job execution with the same flame-graph output; add `chain.add RequestTrail::SidekiqMiddleware` inside `Sidekiq.configure_server` to enable
10
+
3
11
  ## [0.5.0] - 2026-06-13
4
12
 
5
13
  ### Added
data/README.md CHANGED
@@ -55,6 +55,28 @@ Without controller data (plain Rack apps), a single-line summary is emitted:
55
55
  [RequestTrail] GET /orders 142ms | SQL: 7/38.3ms | Cache: 4 hits, 1 miss, 2.0ms
56
56
  ```
57
57
 
58
+ ### JSON formatter
59
+
60
+ Emit structured JSON for log aggregators (Datadog, Splunk, etc.):
61
+
62
+ ```ruby
63
+ RequestTrail.configure do |config|
64
+ config.formatter = RequestTrail::Formatters::JSON.new
65
+ end
66
+ ```
67
+
68
+ Example output (flat request):
69
+
70
+ ```json
71
+ {"method":"GET","path":"/orders","duration_ms":142.5,"sql":{"count":7,"duration_ms":38.0},"cache":{"hits":4,"misses":1,"writes":0,"duration_ms":2.0}}
72
+ ```
73
+
74
+ When controller data is present a `"controller"` key is added:
75
+
76
+ ```json
77
+ {"method":"GET","path":"/orders","duration_ms":142.5,"sql":{"count":7,"duration_ms":38.0},"cache":{"hits":4,"misses":1,"writes":0,"duration_ms":2.0},"controller":{"duration_ms":104.0,"view_duration_ms":22.0}}
78
+ ```
79
+
58
80
  ### Flame graph formatter
59
81
 
60
82
  Opt into the ASCII flame-graph formatter for a visual proportional breakdown:
data/ROADMAP.md CHANGED
@@ -2,13 +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.6.0 — Structured Output & Integrations
6
-
7
- - JSON formatter for log aggregators (Datadog, Splunk, etc.)
8
- - `config.logger` override for writing to a separate file or custom appender
9
- - Rails log tags integration
10
- - Optional Sidekiq adapter for tracing background job execution layers
11
-
12
5
  ## 1.0.0 — Stable Release
13
6
 
14
7
  - Frozen public API (`Request::Trail.configure`, middleware interface, formatter interface)
@@ -9,4 +9,15 @@ RequestTrail.configure do |config|
9
9
 
10
10
  # config.logger = Rails.logger # defaults to Rails.logger
11
11
  # config.formatter = RequestTrail::Formatters::FlameGraph.new(colorize: true)
12
- end
12
+ end
13
+
14
+ # Rails log tags — add the trail summary to every tagged log line:
15
+ # config.log_tags = [:request_id, RequestTrail.log_tag]
16
+
17
+ # Sidekiq — trace background jobs with the same flame-graph output.
18
+ # Add to config/initializers/sidekiq.rb:
19
+ # Sidekiq.configure_server do |config|
20
+ # config.server_middleware do |chain|
21
+ # chain.add RequestTrail::SidekiqMiddleware
22
+ # end
23
+ # end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module RequestTrail
6
+ module Formatters
7
+ class JSON
8
+ include Base
9
+
10
+ def format(request, collector)
11
+ payload = base_payload(request, collector)
12
+ payload[:controller] = controller_payload(collector) if collector.action_duration_ms.positive?
13
+ ::JSON.generate(payload)
14
+ end
15
+
16
+ private
17
+
18
+ def base_payload(request, collector)
19
+ {
20
+ method: request.request_method,
21
+ path: request.path,
22
+ duration_ms: collector.elapsed_ms,
23
+ sql: { count: collector.sql_count, duration_ms: collector.sql_duration_ms },
24
+ cache: cache_payload(collector)
25
+ }
26
+ end
27
+
28
+ def cache_payload(collector)
29
+ {
30
+ hits: collector.cache_hits,
31
+ misses: collector.cache_misses,
32
+ writes: collector.cache_writes,
33
+ duration_ms: collector.cache_duration_ms
34
+ }
35
+ end
36
+
37
+ def controller_payload(collector)
38
+ {
39
+ duration_ms: collector.action_duration_ms,
40
+ view_duration_ms: collector.view_duration_ms
41
+ }
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RequestTrail
4
+ module LogTag
5
+ class Value
6
+ def to_s
7
+ collector = Collector.current
8
+ return "" unless collector
9
+
10
+ "sql=#{collector.sql_count} #{collector.elapsed_ms.round}ms"
11
+ end
12
+
13
+ def blank?
14
+ to_s.empty?
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RequestTrail
4
+ class SidekiqMiddleware
5
+ JobContext = Struct.new(:request_method, :path, keyword_init: true)
6
+
7
+ def call(_worker, job, _queue)
8
+ return yield unless RequestTrail.configuration.enabled
9
+ return yield unless RequestTrail.configuration.sampled?
10
+
11
+ Collector.start
12
+ yield
13
+ log(job)
14
+ ensure
15
+ Collector.stop
16
+ end
17
+
18
+ private
19
+
20
+ def log(job)
21
+ collector = Collector.current
22
+ elapsed = collector.elapsed_ms
23
+ return if elapsed < RequestTrail.configuration.threshold_ms
24
+
25
+ context = JobContext.new(
26
+ request_method: "JOB",
27
+ path: "#{job["class"]} jid:#{job["jid"]}"
28
+ )
29
+ message = RequestTrail.formatter.format(context, collector)
30
+ RequestTrail.configuration.logger.send(RequestTrail.configuration.log_level, message)
31
+ end
32
+ end
33
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RequestTrail
4
- VERSION = "0.5.0"
4
+ VERSION = "0.6.0"
5
5
  end
data/lib/request_trail.rb CHANGED
@@ -7,7 +7,10 @@ require_relative "request_trail/subscriber"
7
7
  require_relative "request_trail/formatters/base"
8
8
  require_relative "request_trail/formatter"
9
9
  require_relative "request_trail/formatters/flame_graph"
10
+ require_relative "request_trail/formatters/json"
11
+ require_relative "request_trail/log_tag"
10
12
  require_relative "request_trail/middleware"
13
+ require_relative "request_trail/sidekiq_middleware" if defined?(Sidekiq)
11
14
  require_relative "request_trail/railtie" if defined?(Rails::Railtie)
12
15
 
13
16
  module RequestTrail
@@ -26,6 +29,10 @@ module RequestTrail
26
29
  configuration.formatter
27
30
  end
28
31
 
32
+ def log_tag
33
+ ->(_request) { LogTag::Value.new }
34
+ end
35
+
29
36
  def reset!
30
37
  @configuration = nil
31
38
  end
@@ -1,6 +1,6 @@
1
1
  module RequestTrail
2
2
  class Formatter
3
- def format: (::Rack::Request request, Collector collector) -> String
3
+ def format: (_RequestContext request, Collector collector) -> String
4
4
 
5
5
  private
6
6
 
@@ -1,7 +1,7 @@
1
1
  module RequestTrail
2
2
  module Formatters
3
3
  module Base
4
- def format: (::Rack::Request request, Collector collector) -> String
4
+ def format: (_RequestContext request, Collector collector) -> String
5
5
  end
6
6
  end
7
7
  end
@@ -7,14 +7,14 @@ module RequestTrail
7
7
  RESET: String
8
8
 
9
9
  def initialize: (?colorize: bool, ?colors: Hash[Symbol, String]) -> void
10
- def format: (::Rack::Request request, Collector collector) -> String
10
+ def format: (_RequestContext request, Collector collector) -> String
11
11
 
12
12
  private
13
13
 
14
14
  def detail_rows: (Collector collector, Float total) -> Array[String]
15
15
  def tiered_rows: (Collector collector, Float total) -> Array[String]
16
16
  def flat_rows: (Collector collector, Float total) -> Array[String]
17
- def header_line: (::Rack::Request request, Collector collector) -> String
17
+ def header_line: (_RequestContext request, Collector collector) -> String
18
18
  def row: (String indent, String label, Float duration_ms, Float total_ms, Symbol color_key) -> String
19
19
  def colorized_bar: (Float duration_ms, Float total_ms, Symbol color_key) -> String
20
20
  def colorize?: () -> bool
@@ -0,0 +1,15 @@
1
+ module RequestTrail
2
+ module Formatters
3
+ class JSON
4
+ include Base
5
+
6
+ def format: (_RequestContext request, Collector collector) -> String
7
+
8
+ private
9
+
10
+ def base_payload: (_RequestContext request, Collector collector) -> Hash[Symbol, untyped]
11
+ def cache_payload: (Collector collector) -> Hash[Symbol, untyped]
12
+ def controller_payload: (Collector collector) -> Hash[Symbol, untyped]
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,8 @@
1
+ module RequestTrail
2
+ module LogTag
3
+ class Value
4
+ def to_s: () -> String
5
+ def blank?: () -> bool
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,12 @@
1
+ module RequestTrail
2
+ class SidekiqMiddleware
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 call: (untyped _worker, Hash[String, untyped] job, untyped _queue) { () -> void } -> void
11
+ end
12
+ end
@@ -1,3 +1,14 @@
1
1
  module RequestTrail
2
2
  VERSION: String
3
+
4
+ interface _RequestContext
5
+ def request_method: () -> String
6
+ def path: () -> String
7
+ end
8
+
9
+ def self.configure: () { (Configuration) -> void } -> void
10
+ def self.configuration: () -> Configuration
11
+ def self.formatter: () -> Formatters::Base
12
+ def self.log_tag: () -> ^(untyped) -> LogTag::Value
13
+ def self.reset!: () -> nil
3
14
  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.5.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chuck Smith
@@ -62,8 +62,11 @@ files:
62
62
  - lib/request_trail/formatter.rb
63
63
  - lib/request_trail/formatters/base.rb
64
64
  - lib/request_trail/formatters/flame_graph.rb
65
+ - lib/request_trail/formatters/json.rb
66
+ - lib/request_trail/log_tag.rb
65
67
  - lib/request_trail/middleware.rb
66
68
  - lib/request_trail/railtie.rb
69
+ - lib/request_trail/sidekiq_middleware.rb
67
70
  - lib/request_trail/subscriber.rb
68
71
  - lib/request_trail/version.rb
69
72
  - sig/request_trail.rbs
@@ -72,7 +75,10 @@ files:
72
75
  - sig/request_trail/formatter.rbs
73
76
  - sig/request_trail/formatters/base.rbs
74
77
  - sig/request_trail/formatters/flame_graph.rbs
78
+ - sig/request_trail/formatters/json.rbs
79
+ - sig/request_trail/log_tag.rbs
75
80
  - sig/request_trail/middleware.rbs
81
+ - sig/request_trail/sidekiq_middleware.rbs
76
82
  - sig/request_trail/subscriber.rbs
77
83
  homepage: https://github.com/eclectic-coding/request-trail
78
84
  licenses: