grape_rails_logger 1.2.2 → 1.2.4
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 +17 -0
- data/README.md +19 -17
- 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 +95 -49
- data/lib/grape_rails_logger/timings.rb +32 -3
- data/lib/grape_rails_logger/version.rb +1 -1
- data/sig/grape_rails_logger.rbs +4 -1
- metadata +6 -20
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1309874132392d9a8663be8cdbcb56a4ce095d1e99773ba1ec520e8efa1f85e3
|
|
4
|
+
data.tar.gz: c831b29fd58a6a7128c351ea6cc99031a3ef24129c57d1ec03620fcd92628ba9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 739fb2ea500a4dcd09361b109a6c40812c4b986ab4025892d9413460f63eb92f62c28b57537337a4eacbb0b783a79a224ea47f281623d5452c0f2fcbd30ed839
|
|
7
|
+
data.tar.gz: 20a22a11f028bf57eaeb8463906ecc5628baa59e8a059a58099b94a5ef5a4bc0a72b23187d115b06f572c86a17952ce8d9e45d979b4dd28d422c3af2b947031a
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
|
+
## 1.2.4 (2026-11-07)
|
|
4
|
+
|
|
5
|
+
- Cache `ActiveSupport::ParameterFilter` (and legacy `ParameterFilter`) per `filter_parameters` configuration
|
|
6
|
+
- Use shallow param copy for flat hashes before in-place filtering; keep `deep_dup` when nested structures are present
|
|
7
|
+
- Speed request log build: `nested_param_structure?` fast path, frozen downcased filter tokens, `PARAM_EXCEPTIONS` hash lookup, local `payload` in `build_log_data`
|
|
8
|
+
- In `Timings.record_sql_notification`, derive duration from notification args without allocating `Event` when the payload matches the usual five-argument AS::Notifications shape
|
|
9
|
+
|
|
10
|
+
## 1.2.3 (2026-11-03)
|
|
11
|
+
|
|
12
|
+
- Keep tracing and request instrumentation from dispatching a failed request more than once
|
|
13
|
+
- Return false from `safe_log` / `log_data` when the logger write fails or no logger exists
|
|
14
|
+
- Filter manual params by key only; expand key patterns toward Rails `filter_parameters` defaults
|
|
15
|
+
- Prefer `#status` over `#to_a` when reading a non-Array Rack response status
|
|
16
|
+
- Skip `ActiveSupport::Notifications::Event` allocation for SQL outside an active Grape request
|
|
17
|
+
- Fail closed when release confirmation input reaches EOF
|
|
18
|
+
- Require a CHANGELOG version heading before release; run tests on pull requests to `main`
|
|
19
|
+
|
|
3
20
|
## 1.2.2 (2026-09-15)
|
|
4
21
|
|
|
5
22
|
- Allow grape 4.x by expanding the runtime dependency to `< 5.0`
|
data/README.md
CHANGED
|
@@ -1,15 +1,9 @@
|
|
|
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
|
|
|
7
|
-
Sponsored by [Kisko Labs](https://www.kiskolabs.com).
|
|
8
|
-
|
|
9
|
-
<a href="https://www.kiskolabs.com">
|
|
10
|
-
<img src="kisko.svg" width="200" alt="Sponsored by Kisko Labs" />
|
|
11
|
-
</a>
|
|
12
|
-
|
|
13
7
|
## Installation
|
|
14
8
|
|
|
15
9
|
Add to your Gemfile:
|
|
@@ -22,7 +16,7 @@ Run `bundle install` or `gem install grape_rails_logger`.
|
|
|
22
16
|
|
|
23
17
|
## Usage
|
|
24
18
|
|
|
25
|
-
|
|
19
|
+
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
20
|
|
|
27
21
|
## What gets logged
|
|
28
22
|
|
|
@@ -30,7 +24,7 @@ Each request logs structured data with:
|
|
|
30
24
|
- Request metadata: `method`, `path`, `status`, `duration`, `host`, `remote_addr`, `request_id`
|
|
31
25
|
- Route information: `controller`, `action`, `source_location` (file:line)
|
|
32
26
|
- Performance metrics: `duration` (ms), `db` (ActiveRecord query time in ms), `db_calls` (SQL query count)
|
|
33
|
-
- Parameters: `params` (
|
|
27
|
+
- Parameters: `params` (filtered with Rails `filter_parameters`)
|
|
34
28
|
- Exceptions: `exception` object with `class`, `message`, and `backtrace` (non-production only)
|
|
35
29
|
|
|
36
30
|
## Configuration
|
|
@@ -43,11 +37,11 @@ Rails.application.config.filter_parameters += [
|
|
|
43
37
|
]
|
|
44
38
|
```
|
|
45
39
|
|
|
46
|
-
When Rails `ParameterFilter` is not available, the gem falls back to manual filtering
|
|
40
|
+
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
41
|
|
|
48
42
|
## Debug tracing
|
|
49
43
|
|
|
50
|
-
Optional `DebugTracer` middleware
|
|
44
|
+
Optional `DebugTracer` middleware traces requests when the `debug` gem is present and `TRACE` is set:
|
|
51
45
|
|
|
52
46
|
```ruby
|
|
53
47
|
class API < Grape::API
|
|
@@ -55,22 +49,22 @@ class API < Grape::API
|
|
|
55
49
|
end
|
|
56
50
|
```
|
|
57
51
|
|
|
58
|
-
Enable tracing: `TRACE=1 rails server`.
|
|
52
|
+
Enable tracing: `TRACE=1 rails server`. Without the `debug` gem, the middleware does not start a tracer.
|
|
59
53
|
|
|
60
54
|
## Compatibility
|
|
61
55
|
|
|
62
|
-
- Rails
|
|
63
|
-
- Grape >= 1.6
|
|
64
|
-
- Ruby >=
|
|
56
|
+
- Rails 7.0, 7.1, 7.2, 8.0+
|
|
57
|
+
- Grape >= 1.6, < 5.0
|
|
58
|
+
- Ruby >= 3.4
|
|
65
59
|
|
|
66
|
-
In Rails 7.1+,
|
|
60
|
+
In Rails 7.1+, request state uses `ActiveSupport::IsolatedExecutionState`. On Rails 7.0, it uses `Thread.current`.
|
|
67
61
|
|
|
68
62
|
## Development
|
|
69
63
|
|
|
70
64
|
```bash
|
|
71
65
|
bundle install
|
|
72
66
|
bundle exec appraisal generate
|
|
73
|
-
bundle exec rspec
|
|
67
|
+
bundle exec polyrun parallel-rspec --workers 5 --merge-failures
|
|
74
68
|
bin/appraisals
|
|
75
69
|
bundle exec standardrb --fix
|
|
76
70
|
```
|
|
@@ -120,3 +114,11 @@ usr/bin/release.rb
|
|
|
120
114
|
## License
|
|
121
115
|
|
|
122
116
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
117
|
+
|
|
118
|
+
## Sponsors
|
|
119
|
+
|
|
120
|
+
Sponsored by [Kisko Labs](https://www.kiskolabs.com).
|
|
121
|
+
|
|
122
|
+
<a href="https://www.kiskolabs.com">
|
|
123
|
+
<img src="kisko.svg" width="200" alt="Sponsored by Kisko Labs" />
|
|
124
|
+
</a>
|
|
@@ -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
|
|
@@ -7,6 +7,9 @@ module GrapeRailsLogger
|
|
|
7
7
|
# This class is designed to be exception-safe: any error in logging
|
|
8
8
|
# is caught and logged separately, never breaking the request flow.
|
|
9
9
|
class GrapeRequestLogSubscriber
|
|
10
|
+
API_ENDPOINT_ENV_KEY = Grape::Env::API_ENDPOINT
|
|
11
|
+
EMPTY_HASH = {}.freeze
|
|
12
|
+
|
|
10
13
|
# Custom error class to distinguish logging errors from other errors
|
|
11
14
|
class LoggingError < StandardError
|
|
12
15
|
attr_reader :original_error
|
|
@@ -17,26 +20,35 @@ module GrapeRailsLogger
|
|
|
17
20
|
end
|
|
18
21
|
end
|
|
19
22
|
|
|
20
|
-
|
|
23
|
+
# Key substrings aligned with Rails filter_parameters defaults (key-only; values pass through).
|
|
24
|
+
FILTERED_PARAMS = %w[passw secret token _key crypt salt certificate otp ssn authorization].freeze
|
|
25
|
+
FILTERED_PARAMS_LOWER = FILTERED_PARAMS.map(&:downcase).freeze
|
|
21
26
|
PARAM_EXCEPTIONS = %w[controller action format].freeze
|
|
27
|
+
PARAM_EXCEPTIONS_EXCLUDE = PARAM_EXCEPTIONS.each_with_object({}) { |k, h| h[k] = true }.freeze
|
|
28
|
+
|
|
29
|
+
def initialize
|
|
30
|
+
super
|
|
31
|
+
@parameter_filter_config = nil
|
|
32
|
+
@parameter_filter = nil
|
|
33
|
+
end
|
|
22
34
|
|
|
23
35
|
def grape_request(event)
|
|
24
36
|
return unless event.is_a?(ActiveSupport::Notifications::Event)
|
|
25
37
|
|
|
26
|
-
|
|
38
|
+
payload = event.payload
|
|
39
|
+
env = payload[:env]
|
|
27
40
|
return unless env.is_a?(Hash)
|
|
28
41
|
|
|
29
|
-
|
|
30
|
-
logger = event.payload[:logger]
|
|
42
|
+
logger = payload[:logger]
|
|
31
43
|
|
|
32
44
|
logged_successfully = false
|
|
33
45
|
begin
|
|
34
|
-
endpoint = env[
|
|
46
|
+
endpoint = env[API_ENDPOINT_ENV_KEY]
|
|
35
47
|
request = (endpoint&.respond_to?(:request) && endpoint.request) ? endpoint.request : nil
|
|
36
48
|
|
|
37
49
|
data = build_log_data(event, request, env)
|
|
38
|
-
logged_successfully = log_data(data,
|
|
39
|
-
|
|
50
|
+
logged_successfully = log_data(data, payload[:exception_object], logger)
|
|
51
|
+
payload[:logged_successfully] = true if logged_successfully
|
|
40
52
|
rescue LoggingError
|
|
41
53
|
# If logging itself failed, don't try to log again (would cause infinite loop or duplicates)
|
|
42
54
|
# The error was already logged in safe_log's rescue block
|
|
@@ -59,18 +71,22 @@ module GrapeRailsLogger
|
|
|
59
71
|
end
|
|
60
72
|
|
|
61
73
|
def build_log_data(event, request, env)
|
|
62
|
-
|
|
63
|
-
|
|
74
|
+
payload = event.payload
|
|
75
|
+
db_runtime = payload[:db_runtime] || 0
|
|
76
|
+
db_calls = payload[:db_calls] || 0
|
|
64
77
|
total_runtime = begin
|
|
65
78
|
event.duration || 0
|
|
66
79
|
rescue
|
|
67
80
|
0
|
|
68
81
|
end
|
|
69
82
|
|
|
70
|
-
|
|
83
|
+
exception_object = payload[:exception_object]
|
|
84
|
+
status = extract_status(event) || (exception_object ? 500 : 200)
|
|
71
85
|
rails_request = rails_request_for(env)
|
|
72
86
|
method = request ? safe_string(request.request_method) : safe_string(env["REQUEST_METHOD"])
|
|
73
87
|
path = request ? safe_string(request.path) : safe_string(env["PATH_INFO"] || env["REQUEST_URI"])
|
|
88
|
+
duration_q = total_runtime.round(2)
|
|
89
|
+
db_q = db_runtime.round(2)
|
|
74
90
|
|
|
75
91
|
{
|
|
76
92
|
method: method,
|
|
@@ -83,9 +99,9 @@ module GrapeRailsLogger
|
|
|
83
99
|
host: extract_host(rails_request, request),
|
|
84
100
|
remote_addr: extract_remote_addr(rails_request, request),
|
|
85
101
|
request_id: extract_request_id(rails_request, env),
|
|
86
|
-
duration:
|
|
87
|
-
db:
|
|
88
|
-
db_calls: db_calls,
|
|
102
|
+
duration: duration_q,
|
|
103
|
+
db: db_q,
|
|
104
|
+
db_calls: Integer(db_calls),
|
|
89
105
|
params: filter_params(extract_params(request, env))
|
|
90
106
|
}
|
|
91
107
|
end
|
|
@@ -134,12 +150,6 @@ module GrapeRailsLogger
|
|
|
134
150
|
else
|
|
135
151
|
safe_log(:info, data, logger)
|
|
136
152
|
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
153
|
end
|
|
144
154
|
|
|
145
155
|
def build_exception_data(exception)
|
|
@@ -157,11 +167,9 @@ module GrapeRailsLogger
|
|
|
157
167
|
end
|
|
158
168
|
|
|
159
169
|
def safe_log(level, data, logger = nil)
|
|
160
|
-
# Get logger from parameter, config, or default to Rails.logger
|
|
161
170
|
effective_logger = logger || resolve_effective_logger
|
|
162
|
-
return unless effective_logger
|
|
171
|
+
return false unless effective_logger
|
|
163
172
|
|
|
164
|
-
# Get tag from config
|
|
165
173
|
tag = resolve_tag
|
|
166
174
|
|
|
167
175
|
if effective_logger.respond_to?(:tagged) && tag
|
|
@@ -171,12 +179,12 @@ module GrapeRailsLogger
|
|
|
171
179
|
else
|
|
172
180
|
effective_logger.public_send(level, data)
|
|
173
181
|
end
|
|
182
|
+
true
|
|
174
183
|
rescue => e
|
|
175
|
-
# Fallback to stderr if logging fails - never raise
|
|
176
|
-
# Only warn in development to avoid noise in production
|
|
177
184
|
if defined?(Rails) && Rails.respond_to?(:env) && Rails.env.development?
|
|
178
185
|
Rails.logger&.warn("GrapeRailsLogger log error: #{e.message}")
|
|
179
186
|
end
|
|
187
|
+
false
|
|
180
188
|
end
|
|
181
189
|
|
|
182
190
|
def resolve_tag
|
|
@@ -585,13 +593,13 @@ module GrapeRailsLogger
|
|
|
585
593
|
end
|
|
586
594
|
|
|
587
595
|
def extract_params(request, env = nil)
|
|
588
|
-
return
|
|
596
|
+
return EMPTY_HASH unless env.is_a?(Hash)
|
|
589
597
|
|
|
590
|
-
endpoint = env[
|
|
598
|
+
endpoint = env[API_ENDPOINT_ENV_KEY]
|
|
591
599
|
return {} unless endpoint&.respond_to?(:request) && endpoint.request
|
|
592
600
|
|
|
593
601
|
endpoint_params = endpoint.request.params
|
|
594
|
-
return
|
|
602
|
+
return EMPTY_HASH if endpoint_params.blank?
|
|
595
603
|
|
|
596
604
|
params_hash = if endpoint_params.respond_to?(:to_unsafe_h)
|
|
597
605
|
endpoint_params.to_unsafe_h
|
|
@@ -735,22 +743,27 @@ module GrapeRailsLogger
|
|
|
735
743
|
end
|
|
736
744
|
|
|
737
745
|
def filter_params(params)
|
|
738
|
-
return
|
|
746
|
+
return EMPTY_HASH unless params
|
|
739
747
|
return {} unless params.is_a?(Hash)
|
|
740
748
|
|
|
741
749
|
cleaned = if (filter = rails_parameter_filter)
|
|
742
750
|
# Create a deep copy since filter modifies in place (Rails 6+)
|
|
743
|
-
params_copy =
|
|
751
|
+
params_copy =
|
|
752
|
+
if nested_param_structure?(params)
|
|
753
|
+
params.respond_to?(:deep_dup) ? params.deep_dup : params.dup
|
|
754
|
+
else
|
|
755
|
+
params.dup
|
|
756
|
+
end
|
|
744
757
|
filter.filter(params_copy)
|
|
745
758
|
else
|
|
746
759
|
filter_parameters_manually(params)
|
|
747
760
|
end
|
|
748
761
|
|
|
749
|
-
cleaned.is_a?(Hash) ? cleaned.reject { |key, _|
|
|
762
|
+
cleaned.is_a?(Hash) ? cleaned.reject { |key, _| PARAM_EXCEPTIONS_EXCLUDE[key.to_s] } : {}
|
|
750
763
|
rescue
|
|
751
764
|
# Don't log - just fallback to manual filtering
|
|
752
765
|
# Logging happens only in subscriber
|
|
753
|
-
filter_parameters_manually(params).reject { |key, _|
|
|
766
|
+
filter_parameters_manually(params).reject { |key, _| PARAM_EXCEPTIONS_EXCLUDE[key.to_s] }
|
|
754
767
|
end
|
|
755
768
|
|
|
756
769
|
def rails_parameter_filter
|
|
@@ -761,14 +774,23 @@ module GrapeRailsLogger
|
|
|
761
774
|
filter_parameters = Rails.application.config.filter_parameters
|
|
762
775
|
return nil if filter_parameters.blank?
|
|
763
776
|
|
|
777
|
+
if filter_parameters.equal?(@parameter_filter_config) && @parameter_filter
|
|
778
|
+
return @parameter_filter
|
|
779
|
+
end
|
|
780
|
+
|
|
764
781
|
# Prefer ActiveSupport::ParameterFilter (Rails 6.1+)
|
|
765
782
|
# This is the Rails-native parameter filtering system
|
|
766
|
-
|
|
767
|
-
ActiveSupport::ParameterFilter
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
ActionDispatch::Http::ParameterFilter
|
|
771
|
-
|
|
783
|
+
built =
|
|
784
|
+
if defined?(ActiveSupport::ParameterFilter)
|
|
785
|
+
ActiveSupport::ParameterFilter.new(filter_parameters)
|
|
786
|
+
# Fall back to ActionDispatch::Http::ParameterFilter (Rails 6.0 and earlier)
|
|
787
|
+
elsif defined?(ActionDispatch::Http::ParameterFilter)
|
|
788
|
+
ActionDispatch::Http::ParameterFilter.new(filter_parameters)
|
|
789
|
+
end
|
|
790
|
+
return nil if built.nil?
|
|
791
|
+
|
|
792
|
+
@parameter_filter_config = filter_parameters
|
|
793
|
+
@parameter_filter = built
|
|
772
794
|
rescue
|
|
773
795
|
# Log but don't fail - fall back to manual filtering
|
|
774
796
|
# Don't log - just return nil
|
|
@@ -776,6 +798,33 @@ module GrapeRailsLogger
|
|
|
776
798
|
nil
|
|
777
799
|
end
|
|
778
800
|
|
|
801
|
+
def nested_param_structure?(params)
|
|
802
|
+
return false unless params.is_a?(Hash)
|
|
803
|
+
return false if params.empty?
|
|
804
|
+
|
|
805
|
+
return true if params.any? { |_, v| v.is_a?(Hash) }
|
|
806
|
+
|
|
807
|
+
params.each_value do |value|
|
|
808
|
+
next unless value.is_a?(Array)
|
|
809
|
+
|
|
810
|
+
return true if value.any? { |el| el.is_a?(Hash) }
|
|
811
|
+
end
|
|
812
|
+
|
|
813
|
+
stack = [params]
|
|
814
|
+
until stack.empty?
|
|
815
|
+
h = stack.pop
|
|
816
|
+
h.each_value do |value|
|
|
817
|
+
case value
|
|
818
|
+
when Hash
|
|
819
|
+
return true
|
|
820
|
+
when Array
|
|
821
|
+
value.each { |el| stack.push(el) if el.is_a?(Hash) }
|
|
822
|
+
end
|
|
823
|
+
end
|
|
824
|
+
end
|
|
825
|
+
false
|
|
826
|
+
end
|
|
827
|
+
|
|
779
828
|
def filter_parameters_manually(params, depth = 0)
|
|
780
829
|
return {} unless params
|
|
781
830
|
return {"[FILTERED]" => "[max_depth_exceeded]"} if depth > 10
|
|
@@ -793,9 +842,9 @@ module GrapeRailsLogger
|
|
|
793
842
|
when Hash
|
|
794
843
|
filter_parameters_manually(value, depth + 1)
|
|
795
844
|
when Array
|
|
796
|
-
value.first(100).map { |v| v.is_a?(Hash) ? filter_parameters_manually(v, depth + 1) :
|
|
845
|
+
value.first(100).map { |v| v.is_a?(Hash) ? filter_parameters_manually(v, depth + 1) : v }
|
|
797
846
|
else
|
|
798
|
-
|
|
847
|
+
value
|
|
799
848
|
end
|
|
800
849
|
end
|
|
801
850
|
end
|
|
@@ -805,17 +854,14 @@ module GrapeRailsLogger
|
|
|
805
854
|
{}
|
|
806
855
|
end
|
|
807
856
|
|
|
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
857
|
def should_filter_key?(key)
|
|
817
|
-
key_lower =
|
|
818
|
-
|
|
858
|
+
key_lower =
|
|
859
|
+
if key.is_a?(String)
|
|
860
|
+
key.downcase
|
|
861
|
+
else
|
|
862
|
+
key.to_s.downcase
|
|
863
|
+
end
|
|
864
|
+
FILTERED_PARAMS_LOWER.any? { |param| key_lower.include?(param) }
|
|
819
865
|
end
|
|
820
866
|
end
|
|
821
867
|
end
|
|
@@ -15,7 +15,7 @@ module GrapeRailsLogger
|
|
|
15
15
|
|
|
16
16
|
def self.reset_db_runtime
|
|
17
17
|
state = execution_state
|
|
18
|
-
state[:grape_db_runtime] = 0
|
|
18
|
+
state[:grape_db_runtime] = 0.0
|
|
19
19
|
state[:grape_db_calls] = 0
|
|
20
20
|
end
|
|
21
21
|
|
|
@@ -41,12 +41,41 @@ module GrapeRailsLogger
|
|
|
41
41
|
execution_state[:grape_db_calls] ||= 0
|
|
42
42
|
end
|
|
43
43
|
|
|
44
|
-
def self.append_db_runtime(
|
|
44
|
+
def self.append_db_runtime(event_or_duration)
|
|
45
45
|
state = execution_state
|
|
46
46
|
return unless state[:grape_db_tracking]
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
duration =
|
|
49
|
+
if event_or_duration.is_a?(Numeric)
|
|
50
|
+
event_or_duration
|
|
51
|
+
else
|
|
52
|
+
event_or_duration.duration
|
|
53
|
+
end
|
|
54
|
+
duration = duration.to_f if duration.is_a?(Numeric)
|
|
55
|
+
state[:grape_db_runtime] = (state[:grape_db_runtime] || 0) + duration
|
|
49
56
|
state[:grape_db_calls] = (state[:grape_db_calls] || 0) + 1
|
|
50
57
|
end
|
|
58
|
+
|
|
59
|
+
# Railtie SQL subscribe entry: skip Event allocation when no Grape request is tracked.
|
|
60
|
+
def self.record_sql_notification(*args)
|
|
61
|
+
state = execution_state
|
|
62
|
+
return unless state[:grape_db_tracking]
|
|
63
|
+
|
|
64
|
+
duration = sql_notification_duration(args)
|
|
65
|
+
append_db_runtime(duration.nil? ? ActiveSupport::Notifications::Event.new(*args) : duration)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def self.sql_notification_duration(args)
|
|
69
|
+
case args.length
|
|
70
|
+
when 5
|
|
71
|
+
d = args[2] - args[1]
|
|
72
|
+
d.is_a?(Numeric) ? d.to_f : d
|
|
73
|
+
when 1
|
|
74
|
+
arg = args.first
|
|
75
|
+
d = arg.duration if arg.respond_to?(:duration)
|
|
76
|
+
d.is_a?(Numeric) ? d.to_f : d
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
private_class_method :sql_notification_duration
|
|
51
80
|
end
|
|
52
81
|
end
|
data/sig/grape_rails_logger.rbs
CHANGED
|
@@ -22,7 +22,8 @@ module GrapeRailsLogger
|
|
|
22
22
|
def self.grape_request_active?: () -> bool
|
|
23
23
|
def self.db_runtime: () -> Float
|
|
24
24
|
def self.db_calls: () -> Integer
|
|
25
|
-
def self.append_db_runtime: (ActiveSupport::Notifications::Event
|
|
25
|
+
def self.append_db_runtime: (ActiveSupport::Notifications::Event | Float | Integer event_or_duration) -> void
|
|
26
|
+
def self.record_sql_notification: (*untyped args) -> void
|
|
26
27
|
end
|
|
27
28
|
|
|
28
29
|
module StatusExtractor
|
|
@@ -34,6 +35,8 @@ module GrapeRailsLogger
|
|
|
34
35
|
end
|
|
35
36
|
|
|
36
37
|
class GrapeRequestLogSubscriber
|
|
38
|
+
EMPTY_HASH: Hash[untyped, untyped]
|
|
39
|
+
|
|
37
40
|
def initialize: () -> void
|
|
38
41
|
def grape_request: (ActiveSupport::Notifications::Event event) -> void
|
|
39
42
|
end
|
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.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Andrei Makarov
|
|
@@ -89,14 +89,14 @@ dependencies:
|
|
|
89
89
|
requirements:
|
|
90
90
|
- - ">="
|
|
91
91
|
- !ruby/object:Gem::Version
|
|
92
|
-
version: 2.2.
|
|
92
|
+
version: 2.2.9
|
|
93
93
|
type: :development
|
|
94
94
|
prerelease: false
|
|
95
95
|
version_requirements: !ruby/object:Gem::Requirement
|
|
96
96
|
requirements:
|
|
97
97
|
- - ">="
|
|
98
98
|
- !ruby/object:Gem::Version
|
|
99
|
-
version: 2.2.
|
|
99
|
+
version: 2.2.9
|
|
100
100
|
- !ruby/object:Gem::Dependency
|
|
101
101
|
name: webmock
|
|
102
102
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -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: []
|