grape_rails_logger 1.2.2 → 1.2.3
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 +10 -0
- data/README.md +11 -11
- data/lib/grape_rails_logger/debug_tracer.rb +23 -16
- data/lib/grape_rails_logger/endpoint_wrapper.rb +8 -3
- data/lib/grape_rails_logger/railtie.rb +1 -1
- data/lib/grape_rails_logger/subscriber.rb +7 -22
- data/lib/grape_rails_logger/timings.rb +7 -0
- data/lib/grape_rails_logger/version.rb +1 -1
- data/sig/grape_rails_logger.rbs +1 -0
- metadata +4 -18
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1779ba966e376295939d488c527346f2ee0380d77b5f48471e6d5ba8e60683e3
|
|
4
|
+
data.tar.gz: bceef6b5f6f3be3a7db4c2a256e52ccbd0170ab6bee98314480778e5d1a5bb77
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 01aed9f8207e56efd5ae7dcc35d61e29a037bc8792503cd6624f891d7a20c5a6139d620e24579d508d45d5b3961940646ca99b47149592ee5fc7af4a0feafdb2
|
|
7
|
+
data.tar.gz: 8b89335e9415219f6cfdecd6d07c579064fd890cc483beaa537fd0529e4c1cfe839036fe6bef6c2ffbd21e4059650c42188c80634e95c7316415e225bf63cbcb
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
|
+
## 1.2.3 (2026-11-03)
|
|
4
|
+
|
|
5
|
+
- Keep tracing and request instrumentation from dispatching a failed request more than once
|
|
6
|
+
- Return false from `safe_log` / `log_data` when the logger write fails or no logger exists
|
|
7
|
+
- Filter manual params by key only; expand key patterns toward Rails `filter_parameters` defaults
|
|
8
|
+
- Prefer `#status` over `#to_a` when reading a non-Array Rack response status
|
|
9
|
+
- Skip `ActiveSupport::Notifications::Event` allocation for SQL outside an active Grape request
|
|
10
|
+
- Fail closed when release confirmation input reaches EOF
|
|
11
|
+
- Require a CHANGELOG version heading before release; run tests on pull requests to `main`
|
|
12
|
+
|
|
3
13
|
## 1.2.2 (2026-09-15)
|
|
4
14
|
|
|
5
15
|
- Allow grape 4.x by expanding the runtime dependency to `< 5.0`
|
data/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# grape_rails_logger
|
|
2
2
|
|
|
3
|
-
[](https://badge.fury.io/rb/grape_rails_logger) [](https://github.com/amkisko/grape_rails_logger.rb/actions/workflows/test.yml) [](https://app.codecov.io/github/amkisko/grape_rails_logger.rb)
|
|
4
4
|
|
|
5
5
|
Rails-compatible structured logging for Grape APIs with ActiveRecord timing, parameter filtering, and exception tracking.
|
|
6
6
|
|
|
@@ -22,7 +22,7 @@ Run `bundle install` or `gem install grape_rails_logger`.
|
|
|
22
22
|
|
|
23
23
|
## Usage
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
In a Rails app, adding the gem is enough. The Railtie patches `Grape::Endpoint#build_stack`, subscribes to `grape.request`, and writes structured data through `Rails.logger`. Any Rails logger works; pair with `activesupport-json_logging` for JSON lines.
|
|
26
26
|
|
|
27
27
|
## What gets logged
|
|
28
28
|
|
|
@@ -30,7 +30,7 @@ Each request logs structured data with:
|
|
|
30
30
|
- Request metadata: `method`, `path`, `status`, `duration`, `host`, `remote_addr`, `request_id`
|
|
31
31
|
- Route information: `controller`, `action`, `source_location` (file:line)
|
|
32
32
|
- Performance metrics: `duration` (ms), `db` (ActiveRecord query time in ms), `db_calls` (SQL query count)
|
|
33
|
-
- Parameters: `params` (
|
|
33
|
+
- Parameters: `params` (filtered with Rails `filter_parameters`)
|
|
34
34
|
- Exceptions: `exception` object with `class`, `message`, and `backtrace` (non-production only)
|
|
35
35
|
|
|
36
36
|
## Configuration
|
|
@@ -43,11 +43,11 @@ Rails.application.config.filter_parameters += [
|
|
|
43
43
|
]
|
|
44
44
|
```
|
|
45
45
|
|
|
46
|
-
When Rails `ParameterFilter` is not available, the gem falls back to manual filtering
|
|
46
|
+
When Rails `ParameterFilter` is not available, the gem falls back to manual key filtering with patterns aligned to those defaults (`passw`, `secret`, `token`, `_key`, `crypt`, `salt`, `certificate`, `otp`, `ssn`, `authorization`).
|
|
47
47
|
|
|
48
48
|
## Debug tracing
|
|
49
49
|
|
|
50
|
-
Optional `DebugTracer` middleware
|
|
50
|
+
Optional `DebugTracer` middleware traces requests when the `debug` gem is present and `TRACE` is set:
|
|
51
51
|
|
|
52
52
|
```ruby
|
|
53
53
|
class API < Grape::API
|
|
@@ -55,22 +55,22 @@ class API < Grape::API
|
|
|
55
55
|
end
|
|
56
56
|
```
|
|
57
57
|
|
|
58
|
-
Enable tracing: `TRACE=1 rails server`.
|
|
58
|
+
Enable tracing: `TRACE=1 rails server`. Without the `debug` gem, the middleware does not start a tracer.
|
|
59
59
|
|
|
60
60
|
## Compatibility
|
|
61
61
|
|
|
62
|
-
- Rails
|
|
63
|
-
- Grape >= 1.6
|
|
64
|
-
- Ruby >=
|
|
62
|
+
- Rails 7.0, 7.1, 7.2, 8.0+
|
|
63
|
+
- Grape >= 1.6, < 5.0
|
|
64
|
+
- Ruby >= 3.4
|
|
65
65
|
|
|
66
|
-
In Rails 7.1+,
|
|
66
|
+
In Rails 7.1+, request state uses `ActiveSupport::IsolatedExecutionState`. On Rails 7.0, it uses `Thread.current`.
|
|
67
67
|
|
|
68
68
|
## Development
|
|
69
69
|
|
|
70
70
|
```bash
|
|
71
71
|
bundle install
|
|
72
72
|
bundle exec appraisal generate
|
|
73
|
-
bundle exec rspec
|
|
73
|
+
bundle exec polyrun parallel-rspec --workers 5 --merge-failures
|
|
74
74
|
bin/appraisals
|
|
75
75
|
bundle exec standardrb --fix
|
|
76
76
|
```
|
|
@@ -20,11 +20,6 @@ module GrapeRailsLogger
|
|
|
20
20
|
trace_request(env) do
|
|
21
21
|
@app.call(env)
|
|
22
22
|
end
|
|
23
|
-
rescue => e
|
|
24
|
-
# If tracing fails for any reason, log and pass through
|
|
25
|
-
# Never break the request flow
|
|
26
|
-
log_trace_error(e)
|
|
27
|
-
@app.call(env)
|
|
28
23
|
end
|
|
29
24
|
|
|
30
25
|
private
|
|
@@ -41,19 +36,31 @@ module GrapeRailsLogger
|
|
|
41
36
|
file_prefix = sanitize_file_prefix(request_method, request_path)
|
|
42
37
|
context = [request_method, request_path].compact.join(" ")
|
|
43
38
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
39
|
+
downstream_started = false
|
|
40
|
+
downstream_completed = false
|
|
41
|
+
downstream_response = nil
|
|
42
|
+
|
|
43
|
+
begin
|
|
44
|
+
Debug.new(
|
|
45
|
+
with_sql: true,
|
|
46
|
+
with_stack: false,
|
|
47
|
+
store_file: true,
|
|
48
|
+
file_prefix: file_prefix,
|
|
49
|
+
context: context
|
|
50
|
+
).trace do
|
|
51
|
+
downstream_started = true
|
|
52
|
+
downstream_response = yield
|
|
53
|
+
downstream_completed = true
|
|
54
|
+
downstream_response
|
|
55
|
+
end
|
|
56
|
+
downstream_response
|
|
57
|
+
rescue => e
|
|
58
|
+
log_trace_error(e)
|
|
59
|
+
raise if downstream_started && !downstream_completed
|
|
60
|
+
return downstream_response if downstream_completed
|
|
61
|
+
|
|
51
62
|
yield
|
|
52
63
|
end
|
|
53
|
-
rescue => e
|
|
54
|
-
# If Debug.new or trace fails, log and continue without tracing
|
|
55
|
-
log_trace_error(e)
|
|
56
|
-
yield
|
|
57
64
|
end
|
|
58
65
|
|
|
59
66
|
def sanitize_file_prefix(method, path)
|
|
@@ -20,6 +20,8 @@ module GrapeRailsLogger
|
|
|
20
20
|
|
|
21
21
|
logger = resolve_logger
|
|
22
22
|
downstream_response = nil
|
|
23
|
+
downstream_started = false
|
|
24
|
+
downstream_completed = false
|
|
23
25
|
|
|
24
26
|
Timings.track_grape_request do
|
|
25
27
|
# Wrap the entire request in ActiveSupport::Notifications
|
|
@@ -28,7 +30,9 @@ module GrapeRailsLogger
|
|
|
28
30
|
# Call the wrapped app - Error middleware will process exceptions and return final response
|
|
29
31
|
# NOTE: We do NOT read the body here - Grape will process it and parse params
|
|
30
32
|
# We'll extract params later from already-parsed sources (endpoint.request.params)
|
|
33
|
+
downstream_started = true
|
|
31
34
|
downstream_response = @app.call(env)
|
|
35
|
+
downstream_completed = true
|
|
32
36
|
|
|
33
37
|
# NOW collect all data AFTER Error middleware has processed exceptions
|
|
34
38
|
# At this point, response contains the final Rack response with correct status
|
|
@@ -41,7 +45,8 @@ module GrapeRailsLogger
|
|
|
41
45
|
end
|
|
42
46
|
rescue => e
|
|
43
47
|
handle_instrumentation_error(e)
|
|
44
|
-
return downstream_response if
|
|
48
|
+
return downstream_response if downstream_completed
|
|
49
|
+
raise if downstream_started
|
|
45
50
|
|
|
46
51
|
@app.call(env)
|
|
47
52
|
end
|
|
@@ -86,11 +91,11 @@ module GrapeRailsLogger
|
|
|
86
91
|
|
|
87
92
|
if response.is_a?(Array) && response[0].is_a?(Integer)
|
|
88
93
|
response[0]
|
|
94
|
+
elsif response.respond_to?(:status) && response.status.is_a?(Integer)
|
|
95
|
+
response.status
|
|
89
96
|
elsif response.respond_to?(:to_a)
|
|
90
97
|
array = response.to_a
|
|
91
98
|
array[0] if array.is_a?(Array) && array[0].is_a?(Integer)
|
|
92
|
-
elsif response.respond_to?(:status) && response.status.is_a?(Integer)
|
|
93
|
-
response.status
|
|
94
99
|
end
|
|
95
100
|
end
|
|
96
101
|
|
|
@@ -20,7 +20,7 @@ if defined?(Rails) && defined?(Rails::Railtie)
|
|
|
20
20
|
# Only subscribe if ActiveRecord is loaded (optional dependency)
|
|
21
21
|
if defined?(ActiveRecord)
|
|
22
22
|
ActiveSupport::Notifications.subscribe("sql.active_record") do |*args|
|
|
23
|
-
GrapeRailsLogger::Timings.
|
|
23
|
+
GrapeRailsLogger::Timings.record_sql_notification(*args)
|
|
24
24
|
rescue => e
|
|
25
25
|
# Never let DB timing errors break anything
|
|
26
26
|
# Only warn in development to avoid noise in production
|
|
@@ -17,7 +17,8 @@ module GrapeRailsLogger
|
|
|
17
17
|
end
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
# Key substrings aligned with Rails filter_parameters defaults (key-only; values pass through).
|
|
21
|
+
FILTERED_PARAMS = %w[passw secret token _key crypt salt certificate otp ssn authorization].freeze
|
|
21
22
|
PARAM_EXCEPTIONS = %w[controller action format].freeze
|
|
22
23
|
|
|
23
24
|
def grape_request(event)
|
|
@@ -134,12 +135,6 @@ module GrapeRailsLogger
|
|
|
134
135
|
else
|
|
135
136
|
safe_log(:info, data, logger)
|
|
136
137
|
end
|
|
137
|
-
# Mark that we successfully logged to prevent fallback from duplicating
|
|
138
|
-
true
|
|
139
|
-
rescue => e
|
|
140
|
-
# If logging itself fails, don't log again in fallback
|
|
141
|
-
# Mark that we tried to log so fallback knows not to duplicate
|
|
142
|
-
raise LoggingError.new(e)
|
|
143
138
|
end
|
|
144
139
|
|
|
145
140
|
def build_exception_data(exception)
|
|
@@ -157,11 +152,9 @@ module GrapeRailsLogger
|
|
|
157
152
|
end
|
|
158
153
|
|
|
159
154
|
def safe_log(level, data, logger = nil)
|
|
160
|
-
# Get logger from parameter, config, or default to Rails.logger
|
|
161
155
|
effective_logger = logger || resolve_effective_logger
|
|
162
|
-
return unless effective_logger
|
|
156
|
+
return false unless effective_logger
|
|
163
157
|
|
|
164
|
-
# Get tag from config
|
|
165
158
|
tag = resolve_tag
|
|
166
159
|
|
|
167
160
|
if effective_logger.respond_to?(:tagged) && tag
|
|
@@ -171,12 +164,12 @@ module GrapeRailsLogger
|
|
|
171
164
|
else
|
|
172
165
|
effective_logger.public_send(level, data)
|
|
173
166
|
end
|
|
167
|
+
true
|
|
174
168
|
rescue => e
|
|
175
|
-
# Fallback to stderr if logging fails - never raise
|
|
176
|
-
# Only warn in development to avoid noise in production
|
|
177
169
|
if defined?(Rails) && Rails.respond_to?(:env) && Rails.env.development?
|
|
178
170
|
Rails.logger&.warn("GrapeRailsLogger log error: #{e.message}")
|
|
179
171
|
end
|
|
172
|
+
false
|
|
180
173
|
end
|
|
181
174
|
|
|
182
175
|
def resolve_tag
|
|
@@ -793,9 +786,9 @@ module GrapeRailsLogger
|
|
|
793
786
|
when Hash
|
|
794
787
|
filter_parameters_manually(value, depth + 1)
|
|
795
788
|
when Array
|
|
796
|
-
value.first(100).map { |v| v.is_a?(Hash) ? filter_parameters_manually(v, depth + 1) :
|
|
789
|
+
value.first(100).map { |v| v.is_a?(Hash) ? filter_parameters_manually(v, depth + 1) : v }
|
|
797
790
|
else
|
|
798
|
-
|
|
791
|
+
value
|
|
799
792
|
end
|
|
800
793
|
end
|
|
801
794
|
end
|
|
@@ -805,14 +798,6 @@ module GrapeRailsLogger
|
|
|
805
798
|
{}
|
|
806
799
|
end
|
|
807
800
|
|
|
808
|
-
def filter_value(value)
|
|
809
|
-
return value unless value.is_a?(String)
|
|
810
|
-
value_lower = value.downcase
|
|
811
|
-
return "[FILTERED]" if FILTERED_PARAMS.any? { |param| value_lower.include?(param.downcase) }
|
|
812
|
-
|
|
813
|
-
value
|
|
814
|
-
end
|
|
815
|
-
|
|
816
801
|
def should_filter_key?(key)
|
|
817
802
|
key_lower = key.to_s.downcase
|
|
818
803
|
FILTERED_PARAMS.any? { |param| key_lower.include?(param.downcase) }
|
|
@@ -48,5 +48,12 @@ module GrapeRailsLogger
|
|
|
48
48
|
state[:grape_db_runtime] = (state[:grape_db_runtime] || 0) + event.duration
|
|
49
49
|
state[:grape_db_calls] = (state[:grape_db_calls] || 0) + 1
|
|
50
50
|
end
|
|
51
|
+
|
|
52
|
+
# Railtie SQL subscribe entry: skip Event allocation when no Grape request is tracked.
|
|
53
|
+
def self.record_sql_notification(*args)
|
|
54
|
+
return unless grape_request_active?
|
|
55
|
+
|
|
56
|
+
append_db_runtime(ActiveSupport::Notifications::Event.new(*args))
|
|
57
|
+
end
|
|
51
58
|
end
|
|
52
59
|
end
|
data/sig/grape_rails_logger.rbs
CHANGED
|
@@ -23,6 +23,7 @@ module GrapeRailsLogger
|
|
|
23
23
|
def self.db_runtime: () -> Float
|
|
24
24
|
def self.db_calls: () -> Integer
|
|
25
25
|
def self.append_db_runtime: (ActiveSupport::Notifications::Event event) -> void
|
|
26
|
+
def self.record_sql_notification: (*untyped args) -> void
|
|
26
27
|
end
|
|
27
28
|
|
|
28
29
|
module StatusExtractor
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: grape_rails_logger
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.2.
|
|
4
|
+
version: 1.2.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Andrei Makarov
|
|
@@ -125,20 +125,6 @@ dependencies:
|
|
|
125
125
|
- - "~>"
|
|
126
126
|
- !ruby/object:Gem::Version
|
|
127
127
|
version: '13'
|
|
128
|
-
- !ruby/object:Gem::Dependency
|
|
129
|
-
name: rspec_junit_formatter
|
|
130
|
-
requirement: !ruby/object:Gem::Requirement
|
|
131
|
-
requirements:
|
|
132
|
-
- - "~>"
|
|
133
|
-
- !ruby/object:Gem::Version
|
|
134
|
-
version: '0.6'
|
|
135
|
-
type: :development
|
|
136
|
-
prerelease: false
|
|
137
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
138
|
-
requirements:
|
|
139
|
-
- - "~>"
|
|
140
|
-
- !ruby/object:Gem::Version
|
|
141
|
-
version: '0.6'
|
|
142
128
|
- !ruby/object:Gem::Dependency
|
|
143
129
|
name: standard
|
|
144
130
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -307,8 +293,8 @@ dependencies:
|
|
|
307
293
|
- - "~>"
|
|
308
294
|
- !ruby/object:Gem::Version
|
|
309
295
|
version: '3'
|
|
310
|
-
description: Rails
|
|
311
|
-
|
|
296
|
+
description: Rails and ActiveSupport request logging for Grape APIs, with request
|
|
297
|
+
context and ActiveRecord timings.
|
|
312
298
|
email:
|
|
313
299
|
- contact@kiskolabs.com
|
|
314
300
|
executables: []
|
|
@@ -351,5 +337,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
351
337
|
requirements: []
|
|
352
338
|
rubygems_version: 4.0.6
|
|
353
339
|
specification_version: 4
|
|
354
|
-
summary:
|
|
340
|
+
summary: Structured request logging for Grape on Rails with ActiveRecord timing.
|
|
355
341
|
test_files: []
|