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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e4483b69705ac60afc5e74a55a4b1d865ba08c6569e449cb2e7fcc9b82e8e449
4
- data.tar.gz: d54444fd7424e615b480ca6bf85346eb3afef76d07f57991b5f342a8b7d79f39
3
+ metadata.gz: 1309874132392d9a8663be8cdbcb56a4ce095d1e99773ba1ec520e8efa1f85e3
4
+ data.tar.gz: c831b29fd58a6a7128c351ea6cc99031a3ef24129c57d1ec03620fcd92628ba9
5
5
  SHA512:
6
- metadata.gz: 9c92013348863f50c25ed04f6c1c0141dc1b0b2fe6f73d4713cc2ba3ae0476beda731d5a8e0f9daa7fe7937f98fd07570483a3d9bdf1f30828b80c4a13496f7b
7
- data.tar.gz: 4b95fcc595e37b91076965409651fc453cd112d16414c328ac20ab5daab090cade361f3836199ec351c9de1fddd091d4c05a7800dbde06f4ca26e1b79b2a378a
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
- [![Gem Version](https://badge.fury.io/rb/grape_rails_logger.svg?v=1.1.0)](https://badge.fury.io/rb/grape_rails_logger) [![Test Status](https://github.com/amkisko/grape_rails_logger.rb/actions/workflows/test.yml/badge.svg)](https://github.com/amkisko/grape_rails_logger.rb/actions/workflows/test.yml) [![codecov](https://codecov.io/gh/amkisko/grape_rails_logger.rb/graph/badge.svg?token=RC5T0Y2Z5A)](https://app.codecov.io/github/amkisko/grape_rails_logger.rb)
3
+ [![Gem Version](https://badge.fury.io/rb/grape_rails_logger.svg?v=1.2.4)](https://badge.fury.io/rb/grape_rails_logger) [![Test Status](https://github.com/amkisko/grape_rails_logger.rb/actions/workflows/test.yml/badge.svg)](https://github.com/amkisko/grape_rails_logger.rb/actions/workflows/test.yml) [![codecov](https://codecov.io/gh/amkisko/grape_rails_logger.rb/graph/badge.svg?token=RC5T0Y2Z5A)](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
- The gem works automatically in Rails applications. No configuration needed. It automatically patches `Grape::Endpoint#build_stack` to instrument requests and subscribes to `grape.request` notifications, logging structured data via `Rails.logger`. Works with any Rails logger or integrates with `activesupport-json_logging` for JSON output.
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` (automatically filtered using Rails `filter_parameters`)
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 that detects sensitive patterns (password, secret, token, key) in parameter keys.
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 provides detailed request tracing when the `debug` gem is installed and `TRACE` environment variable is set:
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`. The middleware gracefully degrades if the `debug` gem is not installed.
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 6.0, 6.1, 7.0, 7.1, 7.2, 8.0+
63
- - Grape >= 1.6
64
- - Ruby >= 2.7
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+, the gem uses `ActiveSupport::IsolatedExecutionState` for improved thread/Fiber safety. In Rails 6-7.0, it falls back to `Thread.current`.
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
- 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
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 downstream_response
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.append_db_runtime(ActiveSupport::Notifications::Event.new(*args))
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
- FILTERED_PARAMS = %w[password secret token key].freeze
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
- env = event.payload[:env]
38
+ payload = event.payload
39
+ env = payload[:env]
27
40
  return unless env.is_a?(Hash)
28
41
 
29
- # Get logger from event payload (passed from middleware)
30
- logger = event.payload[:logger]
42
+ logger = payload[:logger]
31
43
 
32
44
  logged_successfully = false
33
45
  begin
34
- endpoint = env[Grape::Env::API_ENDPOINT] if env.is_a?(Hash)
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, event.payload[:exception_object], logger)
39
- event.payload[:logged_successfully] = true if logged_successfully
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
- db_runtime = event.payload[:db_runtime] || 0
63
- db_calls = event.payload[:db_calls] || 0
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
- status = extract_status(event) || (event.payload[:exception_object] ? 500 : 200)
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: total_runtime.round(2),
87
- db: db_runtime.round(2),
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 {} unless env.is_a?(Hash)
596
+ return EMPTY_HASH unless env.is_a?(Hash)
589
597
 
590
- endpoint = env[Grape::Env::API_ENDPOINT]
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 {} if endpoint_params.blank?
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 {} unless params
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 = params.respond_to?(:deep_dup) ? params.deep_dup : params.dup
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, _| PARAM_EXCEPTIONS.include?(key.to_s) } : {}
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, _| PARAM_EXCEPTIONS.include?(key.to_s) }
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
- if defined?(ActiveSupport::ParameterFilter)
767
- ActiveSupport::ParameterFilter.new(filter_parameters)
768
- # Fall back to ActionDispatch::Http::ParameterFilter (Rails 6.0 and earlier)
769
- elsif defined?(ActionDispatch::Http::ParameterFilter)
770
- ActionDispatch::Http::ParameterFilter.new(filter_parameters)
771
- end
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) : filter_value(v) }
845
+ value.first(100).map { |v| v.is_a?(Hash) ? filter_parameters_manually(v, depth + 1) : v }
797
846
  else
798
- filter_value(value)
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 = key.to_s.downcase
818
- FILTERED_PARAMS.any? { |param| key_lower.include?(param.downcase) }
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(event)
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
- state[:grape_db_runtime] = (state[:grape_db_runtime] || 0) + event.duration
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
@@ -1,3 +1,3 @@
1
1
  module GrapeRailsLogger
2
- VERSION = "1.2.2"
2
+ VERSION = "1.2.4"
3
3
  end
@@ -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 event) -> void
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.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.0
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.0
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-compatible, ActiveSupport-integrated logging for Grape APIs, including
311
- request context and ActiveRecord timings.
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: Unified JSON request logging for Grape on Rails with DB timing.
340
+ summary: Structured request logging for Grape on Rails with ActiveRecord timing.
355
341
  test_files: []