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 +4 -4
- data/CHANGELOG.md +8 -0
- data/README.md +22 -0
- data/ROADMAP.md +0 -7
- data/lib/generators/request_trail/install/templates/request_trail.rb.tt +12 -1
- data/lib/request_trail/formatters/json.rb +45 -0
- data/lib/request_trail/log_tag.rb +18 -0
- data/lib/request_trail/sidekiq_middleware.rb +33 -0
- data/lib/request_trail/version.rb +1 -1
- data/lib/request_trail.rb +7 -0
- data/sig/request_trail/formatter.rbs +1 -1
- data/sig/request_trail/formatters/base.rbs +1 -1
- data/sig/request_trail/formatters/flame_graph.rbs +2 -2
- data/sig/request_trail/formatters/json.rbs +15 -0
- data/sig/request_trail/log_tag.rbs +8 -0
- data/sig/request_trail/sidekiq_middleware.rbs +12 -0
- data/sig/request_trail.rbs +11 -0
- metadata +7 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2c636ff49dd9c5f372269c4e9a9611a7e610e5e5f6f172a951e1f7d76579f804
|
|
4
|
+
data.tar.gz: 2e7a528107b38f1c3e92d0bc11bbdfd2b51295daaf616f4e6cbb82dbe8cc71e1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
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
|
|
@@ -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: (
|
|
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: (
|
|
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,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
|
data/sig/request_trail.rbs
CHANGED
|
@@ -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.
|
|
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:
|