grape_rails_logger 1.2.1 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 94417c5b83b15c0da840d88d8bdfeb896912ff28a57a876cd61adb64ad6a939b
4
- data.tar.gz: 9d4b7e286d214e6cdd0237483a217a73b928dd3c19c6fc7ec9a568f9f734d559
3
+ metadata.gz: 1779ba966e376295939d488c527346f2ee0380d77b5f48471e6d5ba8e60683e3
4
+ data.tar.gz: bceef6b5f6f3be3a7db4c2a256e52ccbd0170ab6bee98314480778e5d1a5bb77
5
5
  SHA512:
6
- metadata.gz: d11dc4b4774436c0d63b06a13232356b23e6334fc35e2c5a1a86ba4812678f841b4334d3d3bf8610deddeec30cbfbcc8bbabc774c73cda08f441d17520747d9b
7
- data.tar.gz: 13e60517292321d0e3f70c9c1eff49db53934446c6495d55d6b556125eb5473cfe163ee338e6d636b03a9ab186637ef7105c0ca14aedd270d03d3bc1729f0a5c
6
+ metadata.gz: 01aed9f8207e56efd5ae7dcc35d61e29a037bc8792503cd6624f891d7a20c5a6139d620e24579d508d45d5b3961940646ca99b47149592ee5fc7af4a0feafdb2
7
+ data.tar.gz: 8b89335e9415219f6cfdecd6d07c579064fd890cc483beaa537fd0529e4c1cfe839036fe6bef6c2ffbd21e4059650c42188c80634e95c7316415e225bf63cbcb
data/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
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
+
13
+ ## 1.2.2 (2026-09-15)
14
+
15
+ - Allow grape 4.x by expanding the runtime dependency to `< 5.0`
16
+ - Keep request log action names when grape stores path and method on endpoint config instead of `options`
17
+
3
18
  ## 1.2.1 (2026-07-18)
4
19
 
5
20
  - Fix HTTP status in Grape request logs for exception subclasses of mapped error types
data/README.md CHANGED
@@ -1,6 +1,6 @@
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.3)](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
 
@@ -22,7 +22,7 @@ Run `bundle install` or `gem install grape_rails_logger`.
22
22
 
23
23
  ## Usage
24
24
 
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.
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` (automatically filtered using Rails `filter_parameters`)
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 that detects sensitive patterns (password, secret, token, key) in parameter keys.
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 provides detailed request tracing when the `debug` gem is installed and `TRACE` environment variable is set:
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`. The middleware gracefully degrades if the `debug` gem is not installed.
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 6.0, 6.1, 7.0, 7.1, 7.2, 8.0+
63
- - Grape >= 1.6
64
- - Ruby >= 2.7
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+, the gem uses `ActiveSupport::IsolatedExecutionState` for improved thread/Fiber safety. In Rails 6-7.0, it falls back to `Thread.current`.
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
- 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
@@ -17,7 +17,8 @@ module GrapeRailsLogger
17
17
  end
18
18
  end
19
19
 
20
- FILTERED_PARAMS = %w[password secret token key].freeze
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
@@ -481,21 +474,52 @@ module GrapeRailsLogger
481
474
  def extract_action(event)
482
475
  endpoint = event.payload.dig(:env, "api.endpoint")
483
476
  return "unknown" unless endpoint
484
- return "unknown" unless endpoint.respond_to?(:options)
485
- return "unknown" unless endpoint.options
486
477
 
487
- method = endpoint.options[:method]&.first
488
- path = endpoint.options[:path]&.first
478
+ method = first_list_item(endpoint_http_methods(endpoint))
479
+ path = first_list_item(endpoint_paths(endpoint))
489
480
  return "unknown" unless method && path
490
481
 
491
- return method.downcase.to_s if path == "/" || path.empty?
482
+ method_name = method.to_s.downcase
483
+ return method_name if path == "/" || path.empty?
492
484
 
493
485
  clean_path = path.to_s.delete_prefix("/").gsub(/[:\/]/, "_").squeeze("_").gsub(/^_+|_+$/, "")
494
- "#{method.downcase}_#{clean_path}"
486
+ "#{method_name}_#{clean_path}"
495
487
  rescue
496
488
  "unknown"
497
489
  end
498
490
 
491
+ def endpoint_http_methods(endpoint)
492
+ config = endpoint_config(endpoint)
493
+ methods = config.http_methods if config.respond_to?(:http_methods)
494
+ return methods if methods
495
+ return nil unless endpoint.respond_to?(:options) && endpoint.options
496
+
497
+ endpoint.options[:method]
498
+ end
499
+
500
+ def endpoint_paths(endpoint)
501
+ config = endpoint_config(endpoint)
502
+ paths = config.path if config.respond_to?(:path)
503
+ return paths if paths
504
+ return nil unless endpoint.respond_to?(:options) && endpoint.options
505
+
506
+ endpoint.options[:path]
507
+ end
508
+
509
+ # grape 4 stores definition-time verbs and path on protected Endpoint#config
510
+ def endpoint_config(endpoint)
511
+ return unless endpoint.respond_to?(:config, true)
512
+
513
+ endpoint.send(:config)
514
+ end
515
+
516
+ def first_list_item(value)
517
+ return value if value.nil? || value.is_a?(String) || value.is_a?(Symbol)
518
+ return value.first if value.respond_to?(:first)
519
+
520
+ value
521
+ end
522
+
499
523
  def extract_controller(event)
500
524
  endpoint = event.payload.dig(:env, "api.endpoint")
501
525
  return nil unless endpoint&.source&.source_location
@@ -678,7 +702,8 @@ module GrapeRailsLogger
678
702
  endpoint = env["api.endpoint"]
679
703
  return nil unless endpoint
680
704
 
681
- api_class = endpoint.respond_to?(:options) ? endpoint.options[:for] : nil
705
+ api_class = endpoint.respond_to?(:api) ? endpoint.api : nil
706
+ api_class ||= endpoint.respond_to?(:options) ? endpoint.options[:for] : nil
682
707
  api_class ||= endpoint.respond_to?(:namespace) ? endpoint.namespace&.options&.dig(:for) : nil
683
708
  api_class ||= endpoint.respond_to?(:route) ? endpoint.route&.options&.dig(:for) : nil
684
709
  return nil unless api_class&.respond_to?(:content_types, true)
@@ -761,9 +786,9 @@ module GrapeRailsLogger
761
786
  when Hash
762
787
  filter_parameters_manually(value, depth + 1)
763
788
  when Array
764
- value.first(100).map { |v| v.is_a?(Hash) ? filter_parameters_manually(v, depth + 1) : filter_value(v) }
789
+ value.first(100).map { |v| v.is_a?(Hash) ? filter_parameters_manually(v, depth + 1) : v }
765
790
  else
766
- filter_value(value)
791
+ value
767
792
  end
768
793
  end
769
794
  end
@@ -773,14 +798,6 @@ module GrapeRailsLogger
773
798
  {}
774
799
  end
775
800
 
776
- def filter_value(value)
777
- return value unless value.is_a?(String)
778
- value_lower = value.downcase
779
- return "[FILTERED]" if FILTERED_PARAMS.any? { |param| value_lower.include?(param.downcase) }
780
-
781
- value
782
- end
783
-
784
801
  def should_filter_key?(key)
785
802
  key_lower = key.to_s.downcase
786
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
@@ -1,3 +1,3 @@
1
1
  module GrapeRailsLogger
2
- VERSION = "1.2.1"
2
+ VERSION = "1.2.3"
3
3
  end
@@ -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.1
4
+ version: 1.2.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrei Makarov
@@ -58,7 +58,7 @@ dependencies:
58
58
  version: '1.6'
59
59
  - - "<"
60
60
  - !ruby/object:Gem::Version
61
- version: '4.0'
61
+ version: '5.0'
62
62
  type: :runtime
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
@@ -68,7 +68,7 @@ dependencies:
68
68
  version: '1.6'
69
69
  - - "<"
70
70
  - !ruby/object:Gem::Version
71
- version: '4.0'
71
+ version: '5.0'
72
72
  - !ruby/object:Gem::Dependency
73
73
  name: rspec
74
74
  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: []