rails_api_logger 0.12.0 → 1.0.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 876eed123167d23e2eb7b6985b88633ac310e51fbc6322e0c0697bc391979173
4
- data.tar.gz: 0abcc58ce8bd138c7497b30671283dec800740377aa11c83a6de79865720c82c
3
+ metadata.gz: 94143c1b37ced487b63c0f994a1ce48077f2e2dbd65b525430f7015670b6faf0
4
+ data.tar.gz: d387bed67f8534081603c3ac48d26afcf2ae2f19be190088146389e97958cefd
5
5
  SHA512:
6
- metadata.gz: dd4a44520b68a7b670c667d1d0783b2726f32603d4d738850e3da5da9abd7ed9799b7190cbe38e21efada2c642becccf215e7c90ede41d3613d74578a0ddf802
7
- data.tar.gz: a130063a418cf202c6715bedac8d3c6298fa673272dd1153a6cbaf3e0d3e7a407cc667dab41109cada36ce6cb156e22d70729223ae5e5eb9c7fb8f18f61b85d2
6
+ metadata.gz: af01668c07dd98ff8a959a858241a3a910c0fbec4a181795b231cd00bf99f791629060f81e528e79ce664b81780c33b64f7f5216a60c2c47d3963a9e5b9c3453
7
+ data.tar.gz: 99a2c9c802084adca6687b91fb76c21c0708883443913619528e132630723c6db8353f45b5cd46f99a490b57ad0cca5a6db53acb1c722ea86a8c24301b3c29a8
@@ -20,6 +20,7 @@ blocks:
20
20
  - checkout --use-cache
21
21
  - cache restore
22
22
  - bundle config set path 'vendor/bundle'
23
+ - bundle config set clean 'true'
23
24
  - bundle install -j 4
24
25
 
25
26
  - cache store
@@ -58,7 +59,7 @@ blocks:
58
59
  - sem-service start postgres 14
59
60
  - bin/rails db:create db:migrate
60
61
  - bundle exec rspec
61
- - name: tests postgres same db and separate target | rails 8.0
62
+ - name: tests postgres same db | rails 8.0
62
63
  env_vars:
63
64
  - name: SAME_DB
64
65
  value: "true"
@@ -70,7 +71,7 @@ blocks:
70
71
  - sem-service start postgres 14
71
72
  - bin/rails db:create db:migrate
72
73
  - bundle exec rspec
73
- - name: tests postgres separate db and separate target | rails 6.1
74
+ - name: tests postgres separate db and separate target | rails 8.1
74
75
  env_vars:
75
76
  - name: SAME_DB
76
77
  value: "false"
@@ -81,57 +82,49 @@ blocks:
81
82
  - name: RAILS_ENV
82
83
  value: test
83
84
  - name: BUNDLE_GEMFILE
84
- value: /home/semaphore/rails_api_logger/gemfiles/rails_6.1.gemfile
85
+ value: /home/semaphore/rails_api_logger/gemfiles/rails_8.1.gemfile
85
86
  commands:
86
87
  - sem-service start postgres 14
87
88
  - bin/rails db:create db:migrate
88
89
  - bundle exec rspec
89
- - name: tests postgres separate db and separate target | rails 7.0
90
+ - name: tests mysql separate db and separate target | rails 8.0
90
91
  env_vars:
91
92
  - name: SAME_DB
92
93
  value: "false"
93
94
  - name: SAME_TARGET
94
95
  value: "false"
95
96
  - name: TARGET_DB
96
- value: postgres
97
+ value: mysql
97
98
  - name: RAILS_ENV
98
99
  value: test
99
- - name: BUNDLE_GEMFILE
100
- value: /home/semaphore/rails_api_logger/gemfiles/rails_7.0.gemfile
101
100
  commands:
102
- - sem-service start postgres 14
101
+ - sem-service start mysql 8.0
103
102
  - bin/rails db:create db:migrate
104
103
  - bundle exec rspec
105
- - name: tests postgres separate db and separate target | rails 7.1
104
+ - name: tests mysql separate db and same target | rails 8.0
106
105
  env_vars:
107
106
  - name: SAME_DB
108
107
  value: "false"
109
108
  - name: SAME_TARGET
110
- value: "false"
109
+ value: "true"
111
110
  - name: TARGET_DB
112
- value: postgres
111
+ value: mysql
113
112
  - name: RAILS_ENV
114
113
  value: test
115
- - name: BUNDLE_GEMFILE
116
- value: /home/semaphore/rails_api_logger/gemfiles/rails_7.1.gemfile
117
114
  commands:
118
- - sem-service start postgres 14
115
+ - sem-service start mysql 8.0
119
116
  - bin/rails db:create db:migrate
120
117
  - bundle exec rspec
121
- - name: tests postgres separate db and separate target | rails 7.2
118
+ - name: tests mysql same db | rails 8.0
122
119
  env_vars:
123
120
  - name: SAME_DB
124
- value: "false"
125
- - name: SAME_TARGET
126
- value: "false"
121
+ value: "true"
127
122
  - name: TARGET_DB
128
- value: postgres
123
+ value: mysql
129
124
  - name: RAILS_ENV
130
125
  value: test
131
- - name: BUNDLE_GEMFILE
132
- value: /home/semaphore/rails_api_logger/gemfiles/rails_7.2.gemfile
133
126
  commands:
134
- - sem-service start postgres 14
127
+ - sem-service start mysql 8.0
135
128
  - bin/rails db:create db:migrate
136
129
  - bundle exec rspec
137
130
  - name: tests sqlite separate db and separate target | rails 8.0
data/Appraisals CHANGED
@@ -1,40 +1,15 @@
1
- appraise "rails-6.1" do
2
- gem "concurrent-ruby", "1.3.4"
3
- gem "rails", "~> 6.1.0"
4
- gem "rspec-rails", "~> 6.1.0"
5
- gem "sqlite3", "~> 1.7.3"
6
- gem "ammeter", "~> 1.1"
7
- gem "webmock", "~> 3.25.0"
8
- end
9
-
10
- appraise "rails-7.0" do
11
- gem "concurrent-ruby", "1.3.4"
12
- gem "rails", "~> 7.0.0"
13
- gem "rspec-rails", "~> 7.1.0"
14
- gem "sqlite3", "~> 1.7.3"
15
- gem "ammeter", "~> 1.1"
16
- gem "webmock", "~> 3.25.0"
17
- end
18
-
19
- appraise "rails-7.1" do
20
- gem "rails", "~> 7.1.0"
21
- gem "rspec-rails", "~> 7.1.0"
22
- gem "sqlite3", "~> 1.7.3"
23
- gem "ammeter", "~> 1.1"
24
- gem "webmock", "~> 3.25.0"
25
- end
26
-
27
- appraise "rails-7.2" do
28
- gem "rails", "~> 7.2.0"
1
+ appraise "rails-8.0" do
2
+ gem "rails", "~> 8.0.0"
29
3
  gem "rspec-rails", "~> 7.1.0"
30
- gem "sqlite3", "~> 1.7.3"
4
+ gem "sqlite3", "~> 2.1.0"
31
5
  gem "ammeter", "~> 1.1"
6
+ gem "puma"
32
7
  gem "webmock", "~> 3.25.0"
33
8
  end
34
9
 
35
- appraise "rails-8.0" do
36
- gem "rails", "~> 8.0.0"
37
- gem "rspec-rails", "~> 7.1.0"
10
+ appraise "rails-8.1" do
11
+ gem "rails", "~> 8.1.0"
12
+ gem "rspec-rails", "~> 8.0"
38
13
  gem "sqlite3", "~> 2.1.0"
39
14
  gem "ammeter", "~> 1.1"
40
15
  gem "puma"
data/CHANGELOG.md CHANGED
@@ -1,3 +1,24 @@
1
+ # 1.0.0
2
+
3
+ * **Breaking:** Drop support for Rails < 8.0 and Ruby < 3.2.
4
+ * Logging never breaks the host app anymore. When the log can't be written (the logging database is down, a
5
+ validation fails, a column is too small...), the error is reported to `Rails.error` with source
6
+ `rails_api_logger` and the request goes on without a log. Before, the app answered 500, an outbound request was
7
+ not sent, or a successful outbound request raised. The errors of your own requests are still raised as before.
8
+ * Fix a crash (500 in the host app) when a request body is not valid UTF-8, for example a file upload
9
+ ([#6](https://github.com/renuo/rails_api_logger/issues/6)). Request and response bodies that are not valid UTF-8
10
+ are now stored as `"[Binary: <size> bytes]"`. Before, such a response body was stored as `nil`.
11
+ * The install migration creates `started_at` and `ended_at` as `datetime` instead of `timestamp`. On MySQL 8,
12
+ `timestamp` columns have no fractional seconds, so the duration of a request was rounded to whole seconds.
13
+ Existing MySQL apps can migrate their columns with:
14
+
15
+ ```ruby
16
+ %i[inbound_request_logs outbound_request_logs].each do |table|
17
+ change_column table, :started_at, :datetime, precision: 6
18
+ change_column table, :ended_at, :datetime, precision: 6
19
+ end
20
+ ```
21
+
1
22
  # 0.12.0
2
23
 
3
24
  * Add `client_reference` column to both `inbound_request_logs` and `outbound_request_logs`. This is a free-form
data/README.md CHANGED
@@ -23,7 +23,7 @@ This gem creates two database tables to log the following information:
23
23
 
24
24
  ## Installation
25
25
 
26
- Add this line to your application's Gemfile:
26
+ Requires Rails 8.0 or newer. Add this line to your application's Gemfile:
27
27
 
28
28
  ```ruby
29
29
  gem 'rails_api_logger'
@@ -105,6 +105,11 @@ end
105
105
 
106
106
  This will guarantee that the log is always persisted, even in case of errors.
107
107
 
108
+ ### Errors while logging
109
+
110
+ Logging never breaks your app. If a log can't be written, for example because the logging database is down,
111
+ the error is reported to `Rails.error` with source `rails_api_logger` and the request goes on without a log.
112
+
108
113
  ### Database Transactions Caveats
109
114
 
110
115
  If you log your outbound requests inside of parent app transactions, your logs will not be persisted if
@@ -146,6 +151,9 @@ config.middleware.insert_before Rails::Rack::Logger, RailsApiLogger::Middleware,
146
151
  skip_response_body_regexp: /api\/letters/
147
152
  ```
148
153
 
154
+ Bodies that are not valid UTF-8, such as file uploads, images or PDFs, are not stored.
155
+ The log keeps only their size, for example `"[Binary: 2048 bytes]"`.
156
+
149
157
  In the implementation of your API, you can call any time `attach_inbound_request_loggable(model)`
150
158
  to attach an already persisted model to the log record.
151
159
 
@@ -250,13 +258,13 @@ end
250
258
 
251
259
  ## Development
252
260
 
253
- We use Appraisals to un on different Rails versions. This is a run example:
261
+ We use Appraisals to run on different Rails versions. This is a run example:
254
262
 
255
263
  ```bash
256
264
  export SAME_TARGET=false
257
265
  export SAME_DB=false
258
266
  export TARGET_DB=postgres
259
- export BUNDLE_GEMFILE=/Users/alessandrorodi/RenuoWorkspace/rails_api_logger/gemfiles/rails_6.1.gemfile
267
+ export BUNDLE_GEMFILE=/Users/alessandrorodi/RenuoWorkspace/rails_api_logger/gemfiles/rails_8.0.gemfile
260
268
  bundle exec rails db:create db:migrate
261
269
  bundle exec rspec
262
270
  ```
@@ -5,11 +5,12 @@ module InboundRequestsLogger
5
5
 
6
6
  def attach_inbound_request_loggable(loggable)
7
7
  return unless request.env["INBOUND_REQUEST_LOG"].present?
8
- request.env["INBOUND_REQUEST_LOG"].update(loggable: loggable) if loggable&.persisted?
8
+ return unless loggable&.persisted?
9
+ RailsApiLogger.safely { request.env["INBOUND_REQUEST_LOG"].update(loggable: loggable) }
9
10
  end
10
11
 
11
12
  def attach_inbound_request_client_reference(client_reference)
12
13
  return unless request.env["INBOUND_REQUEST_LOG"].present?
13
- request.env["INBOUND_REQUEST_LOG"].update(client_reference: client_reference)
14
+ RailsApiLogger.safely { request.env["INBOUND_REQUEST_LOG"].update(client_reference: client_reference) }
14
15
  end
15
16
  end
@@ -19,22 +19,26 @@ module RailsApiLogger
19
19
  request = ActionDispatch::Request.new(env)
20
20
  logging = log?(env, request)
21
21
  if logging
22
- env["INBOUND_REQUEST_LOG"] = InboundRequestLog.from_request(request, skip_request_body: skip_request_body?(env))
22
+ env["INBOUND_REQUEST_LOG"] = RailsApiLogger.safely do
23
+ InboundRequestLog.from_request(request, skip_request_body: skip_request_body?(env))
24
+ end
23
25
  request.body.rewind if request.body.respond_to?(:rewind)
24
26
  end
25
27
  status, headers, body = @app.call(env)
26
- if logging
27
- updates = {response_code: status, ended_at: Time.current}
28
- updates[:response_body] = if skip_response_body?(env)
29
- "[Skipped]"
30
- else
31
- parsed_body(body)
32
- end
33
- # this usually works. let's be optimistic.
34
- begin
35
- env["INBOUND_REQUEST_LOG"].update_columns(updates)
36
- rescue JSON::GeneratorError => _e # this can be raised by activerecord if the string is not UTF-8.
37
- env["INBOUND_REQUEST_LOG"].update_columns(updates.except(:response_body))
28
+ if logging && env["INBOUND_REQUEST_LOG"]
29
+ RailsApiLogger.safely do
30
+ updates = {response_code: status, ended_at: Time.current}
31
+ updates[:response_body] = if skip_response_body?(env)
32
+ "[Skipped]"
33
+ else
34
+ parsed_body(body)
35
+ end
36
+ # this usually works. let's be optimistic.
37
+ begin
38
+ env["INBOUND_REQUEST_LOG"].update_columns(updates)
39
+ rescue JSON::GeneratorError => _e # this can be raised by activerecord if the string is not UTF-8.
40
+ env["INBOUND_REQUEST_LOG"].update_columns(updates.except(:response_body))
41
+ end
38
42
  end
39
43
  end
40
44
  [status, headers, body]
@@ -60,7 +64,6 @@ module RailsApiLogger
60
64
  end
61
65
 
62
66
  def parsed_body(body)
63
- return unless body.present?
64
67
  parsable_body = if body.respond_to?(:to_ary)
65
68
  body.to_ary[0]
66
69
  elsif body.respond_to?(:body)
@@ -69,9 +72,7 @@ module RailsApiLogger
69
72
  body
70
73
  end
71
74
 
72
- JSON.parse(parsable_body)
73
- rescue JSON::ParserError, ArgumentError
74
- parsable_body
75
+ RequestLog.parse_body(parsable_body).presence
75
76
  end
76
77
 
77
78
  def request_with_state_change?(request)
@@ -8,17 +8,23 @@ module RailsApiLogger
8
8
  end
9
9
 
10
10
  def call(url, request)
11
- log = OutboundRequestLog.from_request(request, loggable: @loggable, client_reference: @client_reference,
12
- skip_request_body: @skip_request_body)
11
+ log = RailsApiLogger.safely do
12
+ OutboundRequestLog.from_request(request, loggable: @loggable, client_reference: @client_reference,
13
+ skip_request_body: @skip_request_body)
14
+ end
13
15
  yield.tap do |response|
14
- log.from_response(response, skip_response_body: @skip_response_body)
16
+ RailsApiLogger.safely { log&.from_response(response, skip_response_body: @skip_response_body) }
15
17
  end
16
18
  rescue => e
17
19
  log.response_body = {error: e.message} if log
18
20
  raise
19
21
  ensure
20
- log.ended_at = Time.current
21
- log.save!
22
+ if log
23
+ RailsApiLogger.safely do
24
+ log.ended_at = Time.current
25
+ log.save!
26
+ end
27
+ end
22
28
  end
23
29
  end
24
30
  end
@@ -4,13 +4,8 @@ module RailsApiLogger
4
4
 
5
5
  connects_to(**RailsApiLogger.connects_to) if RailsApiLogger.connects_to
6
6
 
7
- if Gem::Version.new(Rails.version) >= Gem::Version.new("7.1")
8
- serialize :request_body, coder: JSON
9
- serialize :response_body, coder: JSON
10
- else
11
- serialize :request_body, JSON
12
- serialize :response_body, JSON
13
- end
7
+ serialize :request_body, coder: JSON
8
+ serialize :response_body, coder: JSON
14
9
 
15
10
  belongs_to :loggable, optional: true, polymorphic: true
16
11
 
@@ -24,17 +19,26 @@ module RailsApiLogger
24
19
  body = "[Skipped]"
25
20
  else
26
21
  request_body = (request.body.respond_to?(:read) ? request.body.read : request.body)
27
- body = request_body&.dup&.force_encoding("UTF-8")
28
- begin
29
- body = JSON.parse(body) if body.present?
30
- rescue JSON::ParserError
31
- body
32
- end
22
+ body = parse_body(request_body)
33
23
  end
34
24
  create(path: request.path, request_body: body, method: request.method, started_at: Time.current,
35
25
  loggable: loggable, client_reference: client_reference)
36
26
  end
37
27
 
28
+ # Bodies that are not valid UTF-8 (file uploads, images, PDFs...) can't be stored as text,
29
+ # so only their size is logged.
30
+ def self.parse_body(body)
31
+ return body unless body.is_a?(String)
32
+
33
+ body = body.dup.force_encoding("UTF-8")
34
+ return "[Binary: #{body.bytesize} bytes]" unless body.valid_encoding?
35
+ return body if body.blank?
36
+
37
+ JSON.parse(body)
38
+ rescue JSON::ParserError
39
+ body
40
+ end
41
+
38
42
  def from_response(response, skip_response_body: false)
39
43
  self.response_code = response.code
40
44
  self.response_body = skip_response_body ? "[Skipped]" : manipulate_body(response.body)
@@ -74,13 +78,7 @@ module RailsApiLogger
74
78
  private
75
79
 
76
80
  def manipulate_body(body)
77
- body_duplicate = body&.dup&.force_encoding("UTF-8")
78
- begin
79
- body_duplicate = JSON.parse(body_duplicate) if body_duplicate.present?
80
- rescue JSON::ParserError
81
- body_duplicate
82
- end
83
- body_duplicate
81
+ self.class.parse_body(body)
84
82
  end
85
83
  end
86
84
  end
@@ -1,11 +1,11 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- rails_api_logger (0.11.0)
5
- activejob (>= 6.0)
6
- activerecord (>= 6.0)
4
+ rails_api_logger (1.0.0)
5
+ activejob (>= 8.0)
6
+ activerecord (>= 8.0)
7
7
  nokogiri
8
- railties (>= 6.0)
8
+ railties (>= 8.0)
9
9
  zeitwerk (>= 2.0.0)
10
10
 
11
11
  GEM
@@ -263,7 +263,7 @@ GEM
263
263
  unicode-display_width (3.1.4)
264
264
  unicode-emoji (~> 4.0, >= 4.0.4)
265
265
  unicode-emoji (4.0.4)
266
- uri (1.0.3)
266
+ uri (1.1.2)
267
267
  useragent (0.16.11)
268
268
  webmock (3.25.1)
269
269
  addressable (>= 2.8.0)
@@ -295,4 +295,4 @@ DEPENDENCIES
295
295
  webmock (~> 3.25.0)
296
296
 
297
297
  BUNDLED WITH
298
- 2.5.4
298
+ 4.0.20
@@ -2,10 +2,11 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
- gem "rails", "~> 7.1.0"
6
- gem "rspec-rails", "~> 7.1.0"
7
- gem "sqlite3", "~> 1.7.3"
5
+ gem "rails", "~> 8.1.0"
6
+ gem "rspec-rails", "~> 8.0"
7
+ gem "sqlite3", "~> 2.1.0"
8
8
  gem "ammeter", "~> 1.1"
9
+ gem "puma"
9
10
  gem "webmock", "~> 3.25.0"
10
11
 
11
12
  gemspec path: "../"