rails_api_logger 0.8.2 → 0.10.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 +4 -4
- data/.gitignore +4 -0
- data/.semaphore/semaphore.yml +53 -2
- data/CHANGELOG.md +59 -2
- data/README.md +58 -51
- data/Rakefile +7 -2
- data/app/middlewares/rails_api_logger/middleware.rb +80 -0
- data/app/models/rails_api_logger/inbound_request_log.rb +5 -0
- data/app/models/rails_api_logger/loggable.rb +27 -0
- data/app/models/rails_api_logger/logger.rb +22 -0
- data/app/models/rails_api_logger/outbound_request_log.rb +5 -0
- data/app/models/rails_api_logger/request_log.rb +80 -0
- data/bin/rails +16 -0
- data/lib/generators/rails_api_logger/install_generator.rb +1 -1
- data/lib/rails_api_logger/engine.rb +25 -0
- data/lib/rails_api_logger/version.rb +5 -0
- data/lib/rails_api_logger.rb +9 -26
- data/rails_api_logger.gemspec +10 -5
- metadata +51 -19
- data/CODE_OF_CONDUCT.md +0 -74
- data/lib/rails_api_logger/inbound_request_log.rb +0 -2
- data/lib/rails_api_logger/inbound_requests_logger_middleware.rb +0 -59
- data/lib/rails_api_logger/outbound_request_log.rb +0 -2
- data/lib/rails_api_logger/request_log.rb +0 -70
- /data/{lib/rails_api_logger → app/controllers}/inbound_requests_logger.rb +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2fda8a1c76ae8f88f8753f6a24b0e794031f2fe699cf517b2bdc0dc4a9492047
|
4
|
+
data.tar.gz: 6435707109046e2f3444290e8f1c18381a2bbd3b9558602198752c077de3f7ed
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d8660e1388c86679bc3be3004049b783e96eef9bf18c6c61fb4d885fdd9cf4f2408bb131adf35193652687a3f37b2a7411e3e7953eb919fc468ab99a165e147c
|
7
|
+
data.tar.gz: 53bbe981627e011159acaf23468f1b1bf00b6171ae9f50fc6d577d3461937a871e9955f29e391fdb38027902fd6db42a6a9b20614d6da21d50bacbf0bb2cc980
|
data/.gitignore
CHANGED
data/.semaphore/semaphore.yml
CHANGED
@@ -21,12 +21,63 @@ blocks:
|
|
21
21
|
- cache restore
|
22
22
|
- bundle config set path 'vendor/bundle'
|
23
23
|
- bundle install -j 4
|
24
|
-
|
24
|
+
|
25
25
|
- cache store
|
26
26
|
jobs:
|
27
|
-
- name:
|
27
|
+
- name: linter
|
28
28
|
commands:
|
29
29
|
- bundle exec standardrb
|
30
|
+
- name: tests sqlite separate db
|
31
|
+
env_vars:
|
32
|
+
- name: TARGET_DB
|
33
|
+
value: sqlite
|
34
|
+
- name: RAILS_ENV
|
35
|
+
value: test
|
36
|
+
commands:
|
37
|
+
- bundle exec rspec
|
38
|
+
- name: tests postgres separate db
|
39
|
+
env_vars:
|
40
|
+
- name: TARGET_DB
|
41
|
+
value: postgres
|
42
|
+
- name: RAILS_ENV
|
43
|
+
value: test
|
44
|
+
commands:
|
45
|
+
- sem-service start postgres 14
|
46
|
+
- bin/rails db:create db:schema:load
|
47
|
+
- bundle exec rspec
|
48
|
+
- name: tests sqlite same db
|
49
|
+
env_vars:
|
50
|
+
- name: SAME_DB
|
51
|
+
value: "true"
|
52
|
+
- name: TARGET_DB
|
53
|
+
value: sqlite
|
54
|
+
- name: RAILS_ENV
|
55
|
+
value: test
|
56
|
+
commands:
|
57
|
+
- bundle exec rspec
|
58
|
+
- name: tests postgres same db
|
59
|
+
env_vars:
|
60
|
+
- name: SAME_DB
|
61
|
+
value: "true"
|
62
|
+
- name: TARGET_DB
|
63
|
+
value: postgres
|
64
|
+
- name: RAILS_ENV
|
65
|
+
value: test
|
66
|
+
commands:
|
67
|
+
- sem-service start postgres 14
|
68
|
+
- bin/rails db:create db:schema:load
|
69
|
+
- bundle exec rspec
|
70
|
+
- name: tests postgres same target
|
71
|
+
env_vars:
|
72
|
+
- name: SAME_TARGET
|
73
|
+
value: "true"
|
74
|
+
- name: TARGET_DB
|
75
|
+
value: postgres
|
76
|
+
- name: RAILS_ENV
|
77
|
+
value: test
|
78
|
+
commands:
|
79
|
+
- sem-service start postgres 14
|
80
|
+
- bin/rails db:create db:schema:load
|
30
81
|
- bundle exec rspec
|
31
82
|
promotions:
|
32
83
|
- name: main
|
data/CHANGELOG.md
CHANGED
@@ -1,35 +1,90 @@
|
|
1
|
+
# 0.10.0
|
2
|
+
|
3
|
+
**BREAKING CHANGES**
|
4
|
+
|
5
|
+
This version contains many breaking changes. Consider this when upgrading:
|
6
|
+
|
7
|
+
* Replace calls to `RailsApiLogger.new` with `RailsApiLogger::Logger.new`. More in general the logger has been renamed.
|
8
|
+
* `InboundRequestLog` has been renamed to `RailsApiLogger::InboundRequestLog`. Table name did not change.
|
9
|
+
* `OutboundRequestLog` has been renamed to `RailsApiLogger::OutboundRequestLog`. Table name did not change.
|
10
|
+
* If you had `has_many :inbound_request_logs` or `has_many :outbound_request_logs` defined, this will break. There's
|
11
|
+
now [three methods](app/models/rails_api_logger/loggable.rb) you can use on your model.
|
12
|
+
* `InboundRequestsLoggerMiddleware` has been renamed to `RailsApiLogger::Middleware`
|
13
|
+
|
14
|
+
> Do the changes above and then continue with the following steps if you want to connect rails_api_logger to a different
|
15
|
+
database:
|
16
|
+
|
17
|
+
* Specify a database called `api_logger`. [Check here](spec/dummy/config/database.yml) for an example.
|
18
|
+
* Check that everything still works (also in production!) with the new configuration.
|
19
|
+
* Add the migrations in the right folder to create again the necessary tables. Run the migrations that will generate a
|
20
|
+
new schema file.
|
21
|
+
* Release and do the same in production. Adapt your build/release steps if you need.
|
22
|
+
|
23
|
+
* Add the following line into `production.rb`:
|
24
|
+
`config.rails_api_logger.connects_to = { database: { writing: :api_logger } }` if you want to point to a new database.
|
25
|
+
|
26
|
+
> If you are not on SQLite you can point also `api_logger` database to the current database you have, so you benefit from
|
27
|
+
isolated transactions but don't need to create a new database or migrate data.
|
28
|
+
|
29
|
+
### List of changes in this version:
|
30
|
+
|
31
|
+
* Namespace correctly. Renamed all classes
|
32
|
+
* Added tests with a dummy app
|
33
|
+
* Use a separate database connection configuration to isolate transactions
|
34
|
+
* I acknowledge that there might be issues on mysql. I don't use it so I won't fix them, but PR are welcome.
|
35
|
+
* Added `host_regexp` option to the middleware.
|
36
|
+
|
37
|
+
# 0.9.0
|
38
|
+
|
39
|
+
* Add option skip_request_body to skip the request body. Use this option when you don't want to persist the request
|
40
|
+
body. `[Skipped]` will be persisted instead.
|
41
|
+
* Add option skip_request_body_regexp to skip logging the body of requests matching a regexp.
|
42
|
+
* Renamed the option skip_body into skip_response_body. This is a breaking change!
|
43
|
+
* Renamed the option skip_body_regexp into skip_response_body_regexp. This is a breaking change!
|
44
|
+
|
1
45
|
# 0.8.1
|
46
|
+
|
2
47
|
* Fix Rails 7.1 warnings.
|
3
48
|
|
4
49
|
# 0.8.0
|
5
|
-
|
50
|
+
|
51
|
+
* Add option skip_body to skip the body for request responses. Use this option when you don't want to persist the
|
52
|
+
response body. `[Skipped]` will be persisted instead. This is not a breaking change.
|
6
53
|
|
7
54
|
# 0.7.0
|
55
|
+
|
8
56
|
* Fix an issue in the middleware where the request body was not read correctly if there were encoding issues.
|
9
57
|
* Improved documentation about outboud request logging.
|
10
58
|
* Add option skip_body_regexp to skip logging the body of requests matching a regexp.
|
11
59
|
|
12
60
|
# 0.6.3
|
61
|
+
|
13
62
|
* Fix the CHANGELOG path in gemspec.
|
14
63
|
|
15
64
|
# 0.6.2
|
65
|
+
|
16
66
|
* Fixes Zeitwerk warning.
|
17
67
|
|
18
68
|
# 0.6.1
|
69
|
+
|
19
70
|
* Fixes the loading of concern into controllers.
|
20
71
|
|
21
72
|
# 0.6.0
|
73
|
+
|
22
74
|
* Fixes an important concurrency issue by removing instance variables in the rack middleware.
|
23
75
|
|
24
76
|
# 0.5.0
|
77
|
+
|
25
78
|
* Started using Zeitwerk.
|
26
79
|
* Removed RailsAdmin specific code.
|
27
80
|
* Improved RailsApiLogger class.
|
28
81
|
|
29
82
|
# 0.4.1
|
83
|
+
|
30
84
|
* Fixed the `.failed` scope.
|
31
85
|
|
32
86
|
# 0.4.0
|
87
|
+
|
33
88
|
* Added `started_at`, `ended_at` and `duration` methods.
|
34
89
|
|
35
90
|
Migrate your tables with:
|
@@ -41,12 +96,14 @@ add_column :outbound_request_logs, :started_at, :timestamp
|
|
41
96
|
add_column :outbound_request_logs, :ended_at, :timestamp
|
42
97
|
```
|
43
98
|
|
44
|
-
|
45
99
|
# 0.3.0
|
100
|
+
|
46
101
|
* Added `formatted_request_body` and `formatted_response_body` methods.
|
47
102
|
|
48
103
|
# 0.2.0
|
104
|
+
|
49
105
|
* Switch to a middleware solution.
|
50
106
|
|
51
107
|
# 0.1.0
|
108
|
+
|
52
109
|
* Initial release.
|
data/README.md
CHANGED
@@ -1,23 +1,23 @@
|
|
1
1
|
# Rails API Logger
|
2
2
|
|
3
|
-
The simplest way to log API requests
|
3
|
+
The simplest way to log API requests in your database.
|
4
4
|
|
5
5
|
The Rails API logger gem introduces a set of tools to log and debug API requests.
|
6
|
+
|
6
7
|
It works on two sides:
|
7
8
|
|
8
9
|
* **Inbound requests**: API exposed by your application
|
9
|
-
* **Outbound requests**: API invoked by your application
|
10
|
+
* **Outbound requests**: API invoked by your application
|
10
11
|
|
11
|
-
This gem has been extracted from various Renuo projects
|
12
|
-
technique multiple times successfully.
|
12
|
+
This gem has been extracted from various [Renuo](https://www.renuo.ch) projects.
|
13
13
|
|
14
14
|
This gem creates two database tables to log the following information:
|
15
15
|
|
16
16
|
* **path** the path/url invoked
|
17
17
|
* **method** the method used to invoke the API (get, post, put, etc...)
|
18
18
|
* **request_body** what was included in the request body
|
19
|
-
* **response_body** what was included in the response body
|
20
|
-
* **response_code** the HTTP response code of the request
|
19
|
+
* **response_body** what was included in the response body
|
20
|
+
* **response_code** the HTTP response code of the request
|
21
21
|
* **started_at** when the request started
|
22
22
|
* **ended_at** when the request finished
|
23
23
|
|
@@ -33,14 +33,31 @@ And then execute:
|
|
33
33
|
|
34
34
|
```bash
|
35
35
|
bundle install
|
36
|
-
|
37
|
-
|
38
|
-
bundle exec rails db:migrate
|
36
|
+
bin/rails g rails_api_logger:install
|
37
|
+
bin/rails db:migrate
|
39
38
|
```
|
40
39
|
|
41
40
|
This will generate two tables `inbound_request_logs` and `outbound_request_logs`.
|
42
41
|
These tables will contain the logs.
|
43
42
|
|
43
|
+
## Ensure logging of data
|
44
|
+
|
45
|
+
RailsApiLogger can use a separate database, to ensure that the logs are written in the database even if a
|
46
|
+
surrounding database transaction is rolled back.
|
47
|
+
|
48
|
+
Make sure to add the following in your `config/environments/production.rb`:
|
49
|
+
|
50
|
+
```ruby
|
51
|
+
config.rails_api_logger.connects_to = { database: { writing: :api_logger } }
|
52
|
+
```
|
53
|
+
|
54
|
+
and [configure a new database](spec/dummy/config/database.yml) accordingly.
|
55
|
+
|
56
|
+
> ⚠️ If you skip this step, rails_api_logger will use your primary database but a rollback will also rollback the
|
57
|
+
> writing of logs
|
58
|
+
> If you are not on SQLite you can point also `api_logger` to the same database! By doing so you can use a single
|
59
|
+
> database but still guarantee the writing of logs in an isolated transaction.
|
60
|
+
|
44
61
|
## Log Outbound Requests
|
45
62
|
|
46
63
|
Given an outbound request in the following format:
|
@@ -59,7 +76,7 @@ uri = URI('http://example.com/some_path?query=string')
|
|
59
76
|
http = Net::HTTP.start(uri.host, uri.port)
|
60
77
|
request = Net::HTTP::Get.new(uri)
|
61
78
|
|
62
|
-
log = OutboundRequestLog.from_request(request)
|
79
|
+
log = RailsApiLogger::OutboundRequestLog.from_request(request)
|
63
80
|
|
64
81
|
response = http.request(request)
|
65
82
|
|
@@ -76,7 +93,7 @@ request = Net::HTTP::Post.new(uri)
|
|
76
93
|
request.body = { answer: 42 }.to_json
|
77
94
|
request.content_type = 'application/json'
|
78
95
|
|
79
|
-
response = RailsApiLogger.new.call(nil, request) do
|
96
|
+
response = RailsApiLogger::Logger.new.call(nil, request) do
|
80
97
|
Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(request) }
|
81
98
|
end
|
82
99
|
```
|
@@ -86,25 +103,7 @@ This will guarantee that the log is always persisted, even in case of errors.
|
|
86
103
|
### Database Transactions Caveats
|
87
104
|
|
88
105
|
If you log your outbound requests inside of parent app transactions, your logs will not be persisted if
|
89
|
-
the transaction is rolled-back.
|
90
|
-
to the same (or another database if you're into that stuff) when logging.
|
91
|
-
|
92
|
-
```
|
93
|
-
# config/initializers/outbound_request_log_patch.rb
|
94
|
-
|
95
|
-
module OutboundRequestLogTransactionPatch
|
96
|
-
extend ActiveSupport::Concern
|
97
|
-
|
98
|
-
included do
|
99
|
-
connects_to database: { writing: :primary, reading: :primary }
|
100
|
-
end
|
101
|
-
end
|
102
|
-
|
103
|
-
OutboundRequestLog.include(OutboundRequestLogTransactionPatch)
|
104
|
-
```
|
105
|
-
|
106
|
-
You can also log the request in a separate thread to provoke the checkout of a separate database connection.
|
107
|
-
Have a look at [this example here](https://github.com/renuo/rails_api_logger/blob/28d4ced88fea5a5f4fd72f5a1db42ad4734eb547/spec/outbound_request_log_spec.rb#L28-L30).
|
106
|
+
the transaction is rolled-back. Use a separate database to prevent this.
|
108
107
|
|
109
108
|
## Log Inbound Requests
|
110
109
|
|
@@ -112,34 +111,41 @@ If you are exposing some API you might be interested in logging the requests you
|
|
112
111
|
You can do so by adding this middleware in `config/application.rb`
|
113
112
|
|
114
113
|
```ruby
|
115
|
-
config.middleware.insert_before Rails::Rack::Logger,
|
114
|
+
config.middleware.insert_before Rails::Rack::Logger, RailsApiLogger::Middleware
|
116
115
|
```
|
117
116
|
|
118
117
|
this will by default only log requests that have an impact in your system (POST, PUT, and PATCH calls).
|
119
118
|
If you want to log all requests (also GET ones) use
|
120
119
|
|
121
120
|
```ruby
|
122
|
-
config.middleware.insert_before Rails::Rack::Logger,
|
121
|
+
config.middleware.insert_before Rails::Rack::Logger, RailsApiLogger::Middleware, only_state_change: false
|
123
122
|
```
|
124
123
|
|
125
124
|
If you want to log only requests on a certain path, you can pass a regular expression:
|
126
125
|
|
127
126
|
```ruby
|
128
|
-
config.middleware.insert_before Rails::Rack::Logger,
|
127
|
+
config.middleware.insert_before Rails::Rack::Logger, RailsApiLogger::Middleware, path_regexp: /api/
|
129
128
|
```
|
130
129
|
|
131
|
-
If you want to
|
130
|
+
If you want to log only requests on a certain host, you can also use a regular expression:
|
132
131
|
|
133
132
|
```ruby
|
134
|
-
config.middleware.insert_before Rails::Rack::Logger,
|
133
|
+
config.middleware.insert_before Rails::Rack::Logger, RailsApiLogger::Middleware, host_regexp: /api.example.com/
|
135
134
|
```
|
136
135
|
|
136
|
+
If you want to skip logging the response or request body of certain requests, you can pass a regular expression:
|
137
|
+
|
138
|
+
```ruby
|
139
|
+
config.middleware.insert_before Rails::Rack::Logger, RailsApiLogger::Middleware,
|
140
|
+
skip_request_body_regexp: /api\/books/,
|
141
|
+
skip_response_body_regexp: /api\/letters/
|
142
|
+
```
|
137
143
|
|
138
144
|
In the implementation of your API, you can call any time `attach_inbound_request_loggable(model)`
|
139
145
|
to attach an already persisted model to the log record.
|
140
146
|
|
141
|
-
|
142
147
|
For example:
|
148
|
+
|
143
149
|
```ruby
|
144
150
|
|
145
151
|
def create
|
@@ -156,18 +162,21 @@ end
|
|
156
162
|
in the User model you can define:
|
157
163
|
|
158
164
|
```ruby
|
159
|
-
|
165
|
+
has_many_inbound_request_logs
|
160
166
|
```
|
161
167
|
|
162
|
-
to be able to access the logs attached to the model.
|
168
|
+
to be able to access the inbound logs attached to the model.
|
169
|
+
|
170
|
+
You also have `has_many_outbound_request_logs` and `has_many_request_logs` that includes both.
|
163
171
|
|
164
172
|
## RailsAdmin integration
|
165
173
|
|
166
174
|
We provide here some code samples to integrate the models in [RailsAdmin](https://github.com/sferik/rails_admin).
|
167
175
|
|
168
|
-
This configuration will give you some nice views, and searches to work with the logs efficiently.
|
176
|
+
This configuration will give you some nice views, and searches to work with the logs efficiently.
|
177
|
+
|
169
178
|
```ruby
|
170
|
-
%w[InboundRequestLog OutboundRequestLog].each do |logging_model|
|
179
|
+
%w[RailsApiLogger::InboundRequestLog RailsApiLogger::OutboundRequestLog].each do |logging_model|
|
171
180
|
config.model logging_model do
|
172
181
|
list do
|
173
182
|
filters %i[method path response_code request_body response_body created_at]
|
@@ -202,24 +211,22 @@ This configuration will give you some nice views, and searches to work with the
|
|
202
211
|
end
|
203
212
|
```
|
204
213
|
|
205
|
-
|
206
214
|
## Development
|
207
215
|
|
208
|
-
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can
|
216
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can
|
217
|
+
also run `bin/console` for an interactive prompt that will allow you to experiment.
|
209
218
|
|
210
|
-
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the
|
219
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the
|
220
|
+
version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version,
|
221
|
+
push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
211
222
|
|
212
223
|
## Contributing
|
213
224
|
|
214
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/renuo/rails_api_logger.
|
215
|
-
This project is intended to be a safe, welcoming space for collaboration
|
216
|
-
|
225
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/renuo/rails_api_logger.
|
226
|
+
This project is intended to be a safe, welcoming space for collaboration.
|
227
|
+
|
228
|
+
Try to be a decent human being while interacting with other people.
|
217
229
|
|
218
230
|
## License
|
219
231
|
|
220
232
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
221
|
-
|
222
|
-
## Code of Conduct
|
223
|
-
|
224
|
-
Everyone interacting in the RailsApiLogger project's codebases, issue trackers, chat rooms and mailing lists is
|
225
|
-
expected to follow the [code of conduct](https://github.com/renuo/rails_api_logger/blob/main/CODE_OF_CONDUCT.md).
|
data/Rakefile
CHANGED
@@ -1,6 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "bundler/setup"
|
1
4
|
require "bundler/gem_tasks"
|
2
|
-
require "
|
5
|
+
require "rake/testtask"
|
3
6
|
|
4
|
-
|
7
|
+
APP_RAKEFILE = File.expand_path("spec/dummy/Rakefile", __dir__)
|
8
|
+
load "rails/tasks/engine.rake"
|
9
|
+
load "rails/tasks/statistics.rake"
|
5
10
|
|
6
11
|
task default: :spec
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module RailsApiLogger
|
2
|
+
class Middleware
|
3
|
+
attr_accessor :only_state_change, :host_regexp, :path_regexp, :skip_request_body_regexp, :skip_response_body_regexp
|
4
|
+
|
5
|
+
def initialize(app, only_state_change: true,
|
6
|
+
host_regexp: /.*/,
|
7
|
+
path_regexp: /.*/,
|
8
|
+
skip_request_body_regexp: nil,
|
9
|
+
skip_response_body_regexp: nil)
|
10
|
+
@app = app
|
11
|
+
self.only_state_change = only_state_change
|
12
|
+
self.host_regexp = host_regexp
|
13
|
+
self.path_regexp = path_regexp
|
14
|
+
self.skip_request_body_regexp = skip_request_body_regexp
|
15
|
+
self.skip_response_body_regexp = skip_response_body_regexp
|
16
|
+
end
|
17
|
+
|
18
|
+
def call(env)
|
19
|
+
request = ActionDispatch::Request.new(env)
|
20
|
+
logging = log?(env, request)
|
21
|
+
if logging
|
22
|
+
env["INBOUND_REQUEST_LOG"] = InboundRequestLog.from_request(request, skip_request_body: skip_request_body?(env))
|
23
|
+
request.body.rewind if request.body.respond_to?(:read)
|
24
|
+
end
|
25
|
+
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))
|
38
|
+
end
|
39
|
+
end
|
40
|
+
[status, headers, body]
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def skip_request_body?(env)
|
46
|
+
skip_request_body_regexp && env["PATH_INFO"] =~ skip_request_body_regexp
|
47
|
+
end
|
48
|
+
|
49
|
+
def skip_response_body?(env)
|
50
|
+
skip_response_body_regexp && env["PATH_INFO"] =~ skip_response_body_regexp
|
51
|
+
end
|
52
|
+
|
53
|
+
def log?(env, request)
|
54
|
+
# The HTTP_HOST header is preferred to the SERVER_NAME header per the Rack spec: https://github.com/rack/rack/blob/main/SPEC.rdoc#label-The+Environment
|
55
|
+
host = env["HTTP_HOST"] || env["SERVER_NAME"]
|
56
|
+
path = env["PATH_INFO"]
|
57
|
+
(host =~ host_regexp) &&
|
58
|
+
(path =~ path_regexp) &&
|
59
|
+
(!only_state_change || request_with_state_change?(request))
|
60
|
+
end
|
61
|
+
|
62
|
+
def parsed_body(body)
|
63
|
+
return unless body.present?
|
64
|
+
|
65
|
+
if body.respond_to?(:to_ary)
|
66
|
+
JSON.parse(body.to_ary[0])
|
67
|
+
elsif body.respond_to?(:body)
|
68
|
+
JSON.parse(body.body)
|
69
|
+
else
|
70
|
+
body
|
71
|
+
end
|
72
|
+
rescue JSON::ParserError, ArgumentError
|
73
|
+
body
|
74
|
+
end
|
75
|
+
|
76
|
+
def request_with_state_change?(request)
|
77
|
+
request.post? || request.put? || request.patch? || request.delete?
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module RailsApiLogger
|
2
|
+
module Loggable
|
3
|
+
def self.included(base)
|
4
|
+
base.extend ClassMethods
|
5
|
+
end
|
6
|
+
|
7
|
+
# :nodoc:
|
8
|
+
module ClassMethods
|
9
|
+
def has_many_outbound_request_logs
|
10
|
+
has_many :outbound_request_logs, -> { order(:created_at) },
|
11
|
+
class_name: "RailsApiLogger::OutboundRequestLog",
|
12
|
+
inverse_of: :loggable, dependent: :destroy, as: :loggable
|
13
|
+
end
|
14
|
+
|
15
|
+
def has_many_inbound_request_logs
|
16
|
+
has_many :inbound_request_logs, -> { order(:created_at) },
|
17
|
+
class_name: "RailsApiLogger::InboundRequestLog",
|
18
|
+
inverse_of: :loggable, dependent: :destroy, as: :loggable
|
19
|
+
end
|
20
|
+
|
21
|
+
def has_many_request_logs
|
22
|
+
has_many_inbound_request_logs
|
23
|
+
has_many_outbound_request_logs
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module RailsApiLogger
|
2
|
+
class Logger
|
3
|
+
def initialize(loggable = nil, skip_request_body: false, skip_response_body: false)
|
4
|
+
@loggable = loggable
|
5
|
+
@skip_request_body = skip_request_body
|
6
|
+
@skip_response_body = skip_response_body
|
7
|
+
end
|
8
|
+
|
9
|
+
def call(url, request)
|
10
|
+
log = OutboundRequestLog.from_request(request, loggable: @loggable, skip_request_body: @skip_request_body)
|
11
|
+
yield.tap do |response|
|
12
|
+
log.from_response(response, skip_response_body: @skip_response_body)
|
13
|
+
end
|
14
|
+
rescue => e
|
15
|
+
log.response_body = {error: e.message} if log
|
16
|
+
raise
|
17
|
+
ensure
|
18
|
+
log.ended_at = Time.current
|
19
|
+
log.save!
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module RailsApiLogger
|
2
|
+
class RequestLog < ActiveRecord::Base
|
3
|
+
self.abstract_class = true
|
4
|
+
|
5
|
+
connects_to(**RailsApiLogger.connects_to) if RailsApiLogger.connects_to
|
6
|
+
|
7
|
+
serialize :request_body, coder: JSON
|
8
|
+
serialize :response_body, coder: JSON
|
9
|
+
|
10
|
+
belongs_to :loggable, optional: true, polymorphic: true
|
11
|
+
|
12
|
+
scope :failed, -> { where(response_code: 400..599).or(where.not(ended_at: nil).where(response_code: nil)) }
|
13
|
+
|
14
|
+
validates :method, presence: true
|
15
|
+
validates :path, presence: true
|
16
|
+
|
17
|
+
def self.from_request(request, loggable: nil, skip_request_body: false)
|
18
|
+
if skip_request_body
|
19
|
+
body = "[Skipped]"
|
20
|
+
else
|
21
|
+
request_body = (request.body.respond_to?(:read) ? request.body.read : request.body)
|
22
|
+
body = request_body&.dup&.force_encoding("UTF-8")
|
23
|
+
begin
|
24
|
+
body = JSON.parse(body) if body.present?
|
25
|
+
rescue JSON::ParserError
|
26
|
+
body
|
27
|
+
end
|
28
|
+
end
|
29
|
+
create(path: request.path, request_body: body, method: request.method, started_at: Time.current, loggable: loggable)
|
30
|
+
end
|
31
|
+
|
32
|
+
def from_response(response, skip_response_body: false)
|
33
|
+
self.response_code = response.code
|
34
|
+
self.response_body = skip_response_body ? "[Skipped]" : manipulate_body(response.body)
|
35
|
+
self
|
36
|
+
end
|
37
|
+
|
38
|
+
def formatted_request_body
|
39
|
+
formatted_body(request_body)
|
40
|
+
end
|
41
|
+
|
42
|
+
def formatted_response_body
|
43
|
+
formatted_body(response_body)
|
44
|
+
end
|
45
|
+
|
46
|
+
def formatted_body(body)
|
47
|
+
if body.is_a?(String) && body.blank?
|
48
|
+
""
|
49
|
+
elsif body.is_a?(Hash)
|
50
|
+
JSON.pretty_generate(body)
|
51
|
+
else
|
52
|
+
xml = Nokogiri::XML(body)
|
53
|
+
if xml.errors.any?
|
54
|
+
body
|
55
|
+
else
|
56
|
+
xml.to_xml(indent: 2)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
rescue
|
60
|
+
body
|
61
|
+
end
|
62
|
+
|
63
|
+
def duration
|
64
|
+
return if started_at.nil? || ended_at.nil?
|
65
|
+
ended_at - started_at
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def manipulate_body(body)
|
71
|
+
body_duplicate = body&.dup&.force_encoding("UTF-8")
|
72
|
+
begin
|
73
|
+
body_duplicate = JSON.parse(body_duplicate) if body_duplicate.present?
|
74
|
+
rescue JSON::ParserError
|
75
|
+
body_duplicate
|
76
|
+
end
|
77
|
+
body_duplicate
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
data/bin/rails
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
# This command will automatically be run when you run "rails" with Rails gems
|
5
|
+
# installed from the root of your application.
|
6
|
+
|
7
|
+
ENGINE_ROOT = File.expand_path('..', __dir__)
|
8
|
+
ENGINE_PATH = File.expand_path('../lib/rails_api_logger/engine', __dir__)
|
9
|
+
APP_PATH = File.expand_path('../spec/dummy/config/application', __dir__)
|
10
|
+
|
11
|
+
# Set up gems listed in the Gemfile.
|
12
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
|
13
|
+
require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
|
14
|
+
|
15
|
+
require 'rails/all'
|
16
|
+
require 'rails/engine/commands'
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module RailsApiLogger
|
2
|
+
class Engine < ::Rails::Engine
|
3
|
+
isolate_namespace RailsApiLogger
|
4
|
+
|
5
|
+
config.generators do |g|
|
6
|
+
g.test_framework :rspec
|
7
|
+
end
|
8
|
+
|
9
|
+
config.rails_api_logger = ActiveSupport::OrderedOptions.new
|
10
|
+
|
11
|
+
initializer "rails_api_logger.config" do
|
12
|
+
config.rails_api_logger.each do |name, value|
|
13
|
+
RailsApiLogger.public_send(:"#{name}=", value)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
ActiveSupport.on_load(:action_controller) do
|
18
|
+
include InboundRequestsLogger
|
19
|
+
end
|
20
|
+
|
21
|
+
ActiveSupport.on_load(:active_record) do
|
22
|
+
include RailsApiLogger::Loggable
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/rails_api_logger.rb
CHANGED
@@ -1,34 +1,17 @@
|
|
1
|
-
require "
|
1
|
+
require "rails_api_logger/version"
|
2
|
+
require "rails_api_logger/engine"
|
3
|
+
require_relative "../app/models/rails_api_logger/loggable"
|
4
|
+
require_relative "../app/middlewares/rails_api_logger/middleware"
|
5
|
+
|
6
|
+
require "rails"
|
2
7
|
require "nokogiri"
|
8
|
+
|
3
9
|
require "zeitwerk"
|
4
10
|
|
5
11
|
loader = Zeitwerk::Loader.for_gem
|
6
|
-
loader.collapse("#{__dir__}/rails_api_logger")
|
7
12
|
loader.ignore("#{__dir__}/generators")
|
8
13
|
loader.setup
|
9
14
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
def initialize(loggable = nil, skip_body: false)
|
14
|
-
@loggable = loggable
|
15
|
-
@skip_body = skip_body
|
16
|
-
end
|
17
|
-
|
18
|
-
def call(url, request)
|
19
|
-
log = OutboundRequestLog.from_request(request, loggable: @loggable)
|
20
|
-
yield.tap do |response|
|
21
|
-
log.from_response(response, skip_body: @skip_body)
|
22
|
-
end
|
23
|
-
rescue => e
|
24
|
-
log.response_body = {error: e.message}
|
25
|
-
raise
|
26
|
-
ensure
|
27
|
-
log.ended_at = Time.current
|
28
|
-
log.save!
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
ActiveSupport.on_load(:action_controller) do
|
33
|
-
include InboundRequestsLogger
|
15
|
+
module RailsApiLogger
|
16
|
+
mattr_accessor :connects_to
|
34
17
|
end
|
data/rails_api_logger.gemspec
CHANGED
@@ -1,6 +1,8 @@
|
|
1
|
+
require_relative "lib/rails_api_logger/version"
|
2
|
+
|
1
3
|
Gem::Specification.new do |spec|
|
2
4
|
spec.name = "rails_api_logger"
|
3
|
-
spec.version =
|
5
|
+
spec.version = RailsApiLogger::VERSION
|
4
6
|
spec.authors = ["Alessandro Rodi"]
|
5
7
|
spec.email = ["alessandro.rodi@renuo.ch"]
|
6
8
|
|
@@ -23,16 +25,19 @@ Gem::Specification.new do |spec|
|
|
23
25
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
24
26
|
spec.require_paths = ["lib"]
|
25
27
|
|
26
|
-
|
27
|
-
spec.add_dependency "activerecord",
|
28
|
-
spec.add_dependency "
|
28
|
+
rails_version = ">= 7.1"
|
29
|
+
spec.add_dependency "activerecord", rails_version
|
30
|
+
spec.add_dependency "activejob", rails_version
|
31
|
+
spec.add_dependency "railties", rails_version
|
29
32
|
spec.add_dependency "nokogiri"
|
30
33
|
spec.add_dependency "zeitwerk", ">= 2.0.0"
|
31
34
|
|
32
|
-
spec.add_development_dependency "sqlite3", "~> 1.
|
35
|
+
spec.add_development_dependency "sqlite3", "~> 2.1.0"
|
33
36
|
spec.add_development_dependency "pg", "~> 1.5.4"
|
37
|
+
spec.add_development_dependency "mysql2", "~> 0.5.6"
|
34
38
|
spec.add_development_dependency "standard", "~> 1.31"
|
35
39
|
spec.add_development_dependency "rake", "~> 12.0"
|
36
40
|
spec.add_development_dependency "rspec", "~> 3.0"
|
41
|
+
spec.add_development_dependency "rspec-rails", "~> 7.1.0"
|
37
42
|
spec.add_development_dependency "rack"
|
38
43
|
end
|
metadata
CHANGED
@@ -1,57 +1,57 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rails_api_logger
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.10.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alessandro Rodi
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-11-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: activerecord
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
19
|
+
version: '7.1'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version:
|
26
|
+
version: '7.1'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: activejob
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
33
|
+
version: '7.1'
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version:
|
40
|
+
version: '7.1'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
42
|
+
name: railties
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
45
|
- - ">="
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version:
|
47
|
+
version: '7.1'
|
48
48
|
type: :runtime
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version:
|
54
|
+
version: '7.1'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: nokogiri
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -86,14 +86,14 @@ dependencies:
|
|
86
86
|
requirements:
|
87
87
|
- - "~>"
|
88
88
|
- !ruby/object:Gem::Version
|
89
|
-
version: 1.
|
89
|
+
version: 2.1.0
|
90
90
|
type: :development
|
91
91
|
prerelease: false
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
93
93
|
requirements:
|
94
94
|
- - "~>"
|
95
95
|
- !ruby/object:Gem::Version
|
96
|
-
version: 1.
|
96
|
+
version: 2.1.0
|
97
97
|
- !ruby/object:Gem::Dependency
|
98
98
|
name: pg
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|
@@ -108,6 +108,20 @@ dependencies:
|
|
108
108
|
- - "~>"
|
109
109
|
- !ruby/object:Gem::Version
|
110
110
|
version: 1.5.4
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: mysql2
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: 0.5.6
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: 0.5.6
|
111
125
|
- !ruby/object:Gem::Dependency
|
112
126
|
name: standard
|
113
127
|
requirement: !ruby/object:Gem::Requirement
|
@@ -150,6 +164,20 @@ dependencies:
|
|
150
164
|
- - "~>"
|
151
165
|
- !ruby/object:Gem::Version
|
152
166
|
version: '3.0'
|
167
|
+
- !ruby/object:Gem::Dependency
|
168
|
+
name: rspec-rails
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - "~>"
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: 7.1.0
|
174
|
+
type: :development
|
175
|
+
prerelease: false
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - "~>"
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: 7.1.0
|
153
181
|
- !ruby/object:Gem::Dependency
|
154
182
|
name: rack
|
155
183
|
requirement: !ruby/object:Gem::Requirement
|
@@ -177,21 +205,25 @@ files:
|
|
177
205
|
- ".semaphore/main-deploy.yml"
|
178
206
|
- ".semaphore/semaphore.yml"
|
179
207
|
- CHANGELOG.md
|
180
|
-
- CODE_OF_CONDUCT.md
|
181
208
|
- Gemfile
|
182
209
|
- LICENSE.txt
|
183
210
|
- README.md
|
184
211
|
- Rakefile
|
212
|
+
- app/controllers/inbound_requests_logger.rb
|
213
|
+
- app/middlewares/rails_api_logger/middleware.rb
|
214
|
+
- app/models/rails_api_logger/inbound_request_log.rb
|
215
|
+
- app/models/rails_api_logger/loggable.rb
|
216
|
+
- app/models/rails_api_logger/logger.rb
|
217
|
+
- app/models/rails_api_logger/outbound_request_log.rb
|
218
|
+
- app/models/rails_api_logger/request_log.rb
|
185
219
|
- bin/console
|
220
|
+
- bin/rails
|
186
221
|
- bin/setup
|
187
222
|
- lib/generators/rails_api_logger/install_generator.rb
|
188
223
|
- lib/generators/templates/create_rails_api_logger_table.rb.tt
|
189
224
|
- lib/rails_api_logger.rb
|
190
|
-
- lib/rails_api_logger/
|
191
|
-
- lib/rails_api_logger/
|
192
|
-
- lib/rails_api_logger/inbound_requests_logger_middleware.rb
|
193
|
-
- lib/rails_api_logger/outbound_request_log.rb
|
194
|
-
- lib/rails_api_logger/request_log.rb
|
225
|
+
- lib/rails_api_logger/engine.rb
|
226
|
+
- lib/rails_api_logger/version.rb
|
195
227
|
- rails_api_logger.gemspec
|
196
228
|
homepage: https://github.com/renuo/rails_api_logger
|
197
229
|
licenses:
|
data/CODE_OF_CONDUCT.md
DELETED
@@ -1,74 +0,0 @@
|
|
1
|
-
# Contributor Covenant Code of Conduct
|
2
|
-
|
3
|
-
## Our Pledge
|
4
|
-
|
5
|
-
In the interest of fostering an open and welcoming environment, we as
|
6
|
-
contributors and maintainers pledge to making participation in our project and
|
7
|
-
our community a harassment-free experience for everyone, regardless of age, body
|
8
|
-
size, disability, ethnicity, gender identity and expression, level of experience,
|
9
|
-
nationality, personal appearance, race, religion, or sexual identity and
|
10
|
-
orientation.
|
11
|
-
|
12
|
-
## Our Standards
|
13
|
-
|
14
|
-
Examples of behavior that contributes to creating a positive environment
|
15
|
-
include:
|
16
|
-
|
17
|
-
* Using welcoming and inclusive language
|
18
|
-
* Being respectful of differing viewpoints and experiences
|
19
|
-
* Gracefully accepting constructive criticism
|
20
|
-
* Focusing on what is best for the community
|
21
|
-
* Showing empathy towards other community members
|
22
|
-
|
23
|
-
Examples of unacceptable behavior by participants include:
|
24
|
-
|
25
|
-
* The use of sexualized language or imagery and unwelcome sexual attention or
|
26
|
-
advances
|
27
|
-
* Trolling, insulting/derogatory comments, and personal or political attacks
|
28
|
-
* Public or private harassment
|
29
|
-
* Publishing others' private information, such as a physical or electronic
|
30
|
-
address, without explicit permission
|
31
|
-
* Other conduct which could reasonably be considered inappropriate in a
|
32
|
-
professional setting
|
33
|
-
|
34
|
-
## Our Responsibilities
|
35
|
-
|
36
|
-
Project maintainers are responsible for clarifying the standards of acceptable
|
37
|
-
behavior and are expected to take appropriate and fair corrective action in
|
38
|
-
response to any instances of unacceptable behavior.
|
39
|
-
|
40
|
-
Project maintainers have the right and responsibility to remove, edit, or
|
41
|
-
reject comments, commits, code, wiki edits, issues, and other contributions
|
42
|
-
that are not aligned to this Code of Conduct, or to ban temporarily or
|
43
|
-
permanently any contributor for other behaviors that they deem inappropriate,
|
44
|
-
threatening, offensive, or harmful.
|
45
|
-
|
46
|
-
## Scope
|
47
|
-
|
48
|
-
This Code of Conduct applies both within project spaces and in public spaces
|
49
|
-
when an individual is representing the project or its community. Examples of
|
50
|
-
representing a project or community include using an official project e-mail
|
51
|
-
address, posting via an official social media account, or acting as an appointed
|
52
|
-
representative at an online or offline event. Representation of a project may be
|
53
|
-
further defined and clarified by project maintainers.
|
54
|
-
|
55
|
-
## Enforcement
|
56
|
-
|
57
|
-
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
58
|
-
reported by contacting the project team at alessandro.rodi@renuo.ch. All
|
59
|
-
complaints will be reviewed and investigated and will result in a response that
|
60
|
-
is deemed necessary and appropriate to the circumstances. The project team is
|
61
|
-
obligated to maintain confidentiality with regard to the reporter of an incident.
|
62
|
-
Further details of specific enforcement policies may be posted separately.
|
63
|
-
|
64
|
-
Project maintainers who do not follow or enforce the Code of Conduct in good
|
65
|
-
faith may face temporary or permanent repercussions as determined by other
|
66
|
-
members of the project's leadership.
|
67
|
-
|
68
|
-
## Attribution
|
69
|
-
|
70
|
-
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
71
|
-
available at [https://contributor-covenant.org/version/1/4][version]
|
72
|
-
|
73
|
-
[homepage]: https://contributor-covenant.org
|
74
|
-
[version]: https://contributor-covenant.org/version/1/4/
|
@@ -1,59 +0,0 @@
|
|
1
|
-
class InboundRequestsLoggerMiddleware
|
2
|
-
attr_accessor :only_state_change, :path_regexp, :skip_body_regexp
|
3
|
-
|
4
|
-
def initialize(app, only_state_change: true, path_regexp: /.*/, skip_body_regexp: nil)
|
5
|
-
@app = app
|
6
|
-
self.only_state_change = only_state_change
|
7
|
-
self.path_regexp = path_regexp
|
8
|
-
self.skip_body_regexp = skip_body_regexp
|
9
|
-
end
|
10
|
-
|
11
|
-
def call(env)
|
12
|
-
request = ActionDispatch::Request.new(env)
|
13
|
-
logging = log?(env, request)
|
14
|
-
if logging
|
15
|
-
env["INBOUND_REQUEST_LOG"] = InboundRequestLog.from_request(request)
|
16
|
-
request.body.rewind
|
17
|
-
end
|
18
|
-
status, headers, body = @app.call(env)
|
19
|
-
if logging
|
20
|
-
updates = {response_code: status, ended_at: Time.current}
|
21
|
-
updates[:response_body] = parsed_body(body) if log_response_body?(env)
|
22
|
-
# this usually works. let's be optimistic.
|
23
|
-
begin
|
24
|
-
env["INBOUND_REQUEST_LOG"].update_columns(updates)
|
25
|
-
rescue JSON::GeneratorError => _e # this can be raised by activerecord if the string is not UTF-8.
|
26
|
-
env["INBOUND_REQUEST_LOG"].update_columns(updates.except(:response_body))
|
27
|
-
end
|
28
|
-
end
|
29
|
-
[status, headers, body]
|
30
|
-
end
|
31
|
-
|
32
|
-
private
|
33
|
-
|
34
|
-
def log_response_body?(env)
|
35
|
-
skip_body_regexp.nil? || env["PATH_INFO"] !~ skip_body_regexp
|
36
|
-
end
|
37
|
-
|
38
|
-
def log?(env, request)
|
39
|
-
env["PATH_INFO"] =~ path_regexp && (!only_state_change || request_with_state_change?(request))
|
40
|
-
end
|
41
|
-
|
42
|
-
def parsed_body(body)
|
43
|
-
return unless body.present?
|
44
|
-
|
45
|
-
if body.respond_to?(:to_ary)
|
46
|
-
JSON.parse(body.to_ary[0])
|
47
|
-
elsif body.respond_to?(:body)
|
48
|
-
JSON.parse(body.body)
|
49
|
-
else
|
50
|
-
body
|
51
|
-
end
|
52
|
-
rescue JSON::ParserError, ArgumentError
|
53
|
-
body
|
54
|
-
end
|
55
|
-
|
56
|
-
def request_with_state_change?(request)
|
57
|
-
request.post? || request.put? || request.patch? || request.delete?
|
58
|
-
end
|
59
|
-
end
|
@@ -1,70 +0,0 @@
|
|
1
|
-
class RequestLog < ActiveRecord::Base
|
2
|
-
self.abstract_class = true
|
3
|
-
|
4
|
-
serialize :request_body, coder: JSON
|
5
|
-
serialize :response_body, coder: JSON
|
6
|
-
|
7
|
-
belongs_to :loggable, optional: true, polymorphic: true
|
8
|
-
|
9
|
-
scope :failed, -> { where(response_code: 400..599).or(where.not(ended_at: nil).where(response_code: nil)) }
|
10
|
-
|
11
|
-
validates :method, presence: true
|
12
|
-
validates :path, presence: true
|
13
|
-
|
14
|
-
def self.from_request(request, loggable: nil)
|
15
|
-
request_body = (request.body.respond_to?(:read) ? request.body.read : request.body)
|
16
|
-
body = request_body&.dup&.force_encoding("UTF-8")
|
17
|
-
begin
|
18
|
-
body = JSON.parse(body) if body.present?
|
19
|
-
rescue JSON::ParserError
|
20
|
-
body
|
21
|
-
end
|
22
|
-
create(path: request.path, request_body: body, method: request.method, started_at: Time.current, loggable: loggable)
|
23
|
-
end
|
24
|
-
|
25
|
-
def from_response(response, skip_body: false)
|
26
|
-
self.response_code = response.code
|
27
|
-
self.response_body = skip_body ? "[Skipped]" : manipulate_body(response.body)
|
28
|
-
self
|
29
|
-
end
|
30
|
-
|
31
|
-
def formatted_request_body
|
32
|
-
formatted_body(request_body)
|
33
|
-
end
|
34
|
-
|
35
|
-
def formatted_response_body
|
36
|
-
formatted_body(response_body)
|
37
|
-
end
|
38
|
-
|
39
|
-
def formatted_body(body)
|
40
|
-
if body.is_a?(Hash)
|
41
|
-
JSON.pretty_generate(body)
|
42
|
-
else
|
43
|
-
xml = Nokogiri::XML(body)
|
44
|
-
if xml.errors.any?
|
45
|
-
body
|
46
|
-
else
|
47
|
-
xml.to_xml(indent: 2)
|
48
|
-
end
|
49
|
-
end
|
50
|
-
rescue
|
51
|
-
body
|
52
|
-
end
|
53
|
-
|
54
|
-
def duration
|
55
|
-
return if started_at.nil? || ended_at.nil?
|
56
|
-
ended_at - started_at
|
57
|
-
end
|
58
|
-
|
59
|
-
private
|
60
|
-
|
61
|
-
def manipulate_body(body)
|
62
|
-
body_duplicate = body&.dup&.force_encoding("UTF-8")
|
63
|
-
begin
|
64
|
-
body_duplicate = JSON.parse(body_duplicate) if body_duplicate.present?
|
65
|
-
rescue JSON::ParserError
|
66
|
-
body_duplicate
|
67
|
-
end
|
68
|
-
body_duplicate
|
69
|
-
end
|
70
|
-
end
|
File without changes
|