Skip to content

Commit

Permalink
Add more options to skip request body
Browse files Browse the repository at this point in the history
  • Loading branch information
coorasse committed Sep 12, 2024
1 parent d171251 commit 0bcf1f2
Show file tree
Hide file tree
Showing 9 changed files with 124 additions and 47 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
# 0.9.0
* Add option skip_request_body to skip the request body. Use this option when you don't want to persist the request body. `[Skipped]` will be persisted instead.
* Add option skip_request_body_regexp to skip logging the body of requests matching a regexp.
* Renamed the option skip_body into skip_response_body. This is a breaking change!
* Renamed the option skip_body_regexp into skip_response_body_regexp. This is a breaking change!

# 0.8.1
* Fix Rails 7.1 warnings.

Expand Down
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,10 +128,12 @@ If you want to log only requests on a certain path, you can pass a regular expre
config.middleware.insert_before Rails::Rack::Logger, InboundRequestsLoggerMiddleware, path_regexp: /api/
```

If you want to skip logging the body of certain requests, you can pass a regular expression:
If you want to skip logging the response or request body of certain requests, you can pass a regular expression:

```ruby
config.middleware.insert_before Rails::Rack::Logger, InboundRequestsLoggerMiddleware, skip_body_regexp: /api/letters/
config.middleware.insert_before Rails::Rack::Logger, InboundRequestsLoggerMiddleware,
skip_request_body_regexp: /api/books/,
skip_response_body_regexp: /api/letters/
```


Expand Down
9 changes: 5 additions & 4 deletions lib/rails_api_logger.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,16 @@
class RailsApiLogger
class Error < StandardError; end

def initialize(loggable = nil, skip_body: false)
def initialize(loggable = nil, skip_request_body: false, skip_response_body: false)
@loggable = loggable
@skip_body = skip_body
@skip_request_body = skip_request_body
@skip_response_body = skip_response_body
end

def call(url, request)
log = OutboundRequestLog.from_request(request, loggable: @loggable)
log = OutboundRequestLog.from_request(request, loggable: @loggable, skip_request_body: @skip_request_body)
yield.tap do |response|
log.from_response(response, skip_body: @skip_body)
log.from_response(response, skip_response_body: @skip_response_body)
end
rescue => e
log.response_body = {error: e.message}
Expand Down
26 changes: 19 additions & 7 deletions lib/rails_api_logger/inbound_requests_logger_middleware.rb
Original file line number Diff line number Diff line change
@@ -1,24 +1,32 @@
class InboundRequestsLoggerMiddleware
attr_accessor :only_state_change, :path_regexp, :skip_body_regexp
attr_accessor :only_state_change, :path_regexp, :skip_request_body_regexp, :skip_response_body_regexp

def initialize(app, only_state_change: true, path_regexp: /.*/, skip_body_regexp: nil)
def initialize(app, only_state_change: true,
path_regexp: /.*/,
skip_request_body_regexp: nil,
skip_response_body_regexp: nil)
@app = app
self.only_state_change = only_state_change
self.path_regexp = path_regexp
self.skip_body_regexp = skip_body_regexp
self.skip_request_body_regexp = skip_request_body_regexp
self.skip_response_body_regexp = skip_response_body_regexp
end

def call(env)
request = ActionDispatch::Request.new(env)
logging = log?(env, request)
if logging
env["INBOUND_REQUEST_LOG"] = InboundRequestLog.from_request(request)
env["INBOUND_REQUEST_LOG"] = InboundRequestLog.from_request(request, skip_request_body: skip_request_body?(env))
request.body.rewind
end
status, headers, body = @app.call(env)
if logging
updates = {response_code: status, ended_at: Time.current}
updates[:response_body] = parsed_body(body) if log_response_body?(env)
updates[:response_body] = if skip_response_body?(env)
"[Skipped]"
else
parsed_body(body)
end
# this usually works. let's be optimistic.
begin
env["INBOUND_REQUEST_LOG"].update_columns(updates)
Expand All @@ -31,8 +39,12 @@ def call(env)

private

def log_response_body?(env)
skip_body_regexp.nil? || env["PATH_INFO"] !~ skip_body_regexp
def skip_request_body?(env)
skip_request_body_regexp && env["PATH_INFO"] =~ skip_request_body_regexp
end

def skip_response_body?(env)
skip_response_body_regexp && env["PATH_INFO"] =~ skip_response_body_regexp
end

def log?(env, request)
Expand Down
26 changes: 16 additions & 10 deletions lib/rails_api_logger/request_log.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,24 @@ class RequestLog < ActiveRecord::Base
validates :method, presence: true
validates :path, presence: true

def self.from_request(request, loggable: nil)
request_body = (request.body.respond_to?(:read) ? request.body.read : request.body)
body = request_body&.dup&.force_encoding("UTF-8")
begin
body = JSON.parse(body) if body.present?
rescue JSON::ParserError
body
def self.from_request(request, loggable: nil, skip_request_body: false)
if skip_request_body
body = "[Skipped]"
else
request_body = (request.body.respond_to?(:read) ? request.body.read : request.body)
body = request_body&.dup&.force_encoding("UTF-8")
begin
body = JSON.parse(body) if body.present?
rescue JSON::ParserError
body
end
end
create(path: request.path, request_body: body, method: request.method, started_at: Time.current, loggable: loggable)
end

def from_response(response, skip_body: false)
def from_response(response, skip_response_body: false)
self.response_code = response.code
self.response_body = skip_body ? "[Skipped]" : manipulate_body(response.body)
self.response_body = skip_response_body ? "[Skipped]" : manipulate_body(response.body)
self
end

Expand All @@ -37,7 +41,9 @@ def formatted_response_body
end

def formatted_body(body)
if body.is_a?(Hash)
if body.is_a?(String) && body.blank?
""
elsif body.is_a?(Hash)
JSON.pretty_generate(body)
else
xml = Nokogiri::XML(body)
Expand Down
2 changes: 1 addition & 1 deletion rails_api_logger.gemspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Gem::Specification.new do |spec|
spec.name = "rails_api_logger"
spec.version = "0.8.2"
spec.version = "0.9.0"
spec.authors = ["Alessandro Rodi"]
spec.email = ["[email protected]"]

Expand Down
32 changes: 26 additions & 6 deletions spec/inbound_requests_logger_middleware_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,13 @@ def request
end

RSpec.describe InboundRequestsLoggerMiddleware do
let(:skip_body_regexp) { nil }
let(:skip_request_body_regexp) { nil }
let(:skip_response_body_regexp) { nil }
let(:app) do
InboundRequestsLoggerMiddleware.new(MyApp.new, path_regexp: path_regexp, skip_body_regexp: skip_body_regexp)
InboundRequestsLoggerMiddleware.new(MyApp.new,
path_regexp: path_regexp,
skip_request_body_regexp: skip_request_body_regexp,
skip_response_body_regexp: skip_response_body_regexp)
end
let(:request) { Rack::MockRequest.new(app) }
let(:response) { request.post("/api/v1/books") }
Expand Down Expand Up @@ -54,10 +58,26 @@ def request
expect(inbound_request_log.loggable_id).to be_present
end

context "when the PATH_INFO matches the skip_body_regexp" do
let(:skip_body_regexp) { /books/ }
context "when the PATH_INFO matches the skip_request_body_regexp" do
let(:skip_request_body_regexp) { /books/ }

it "logs a request in the database but without a body" do
it "logs a request in the database but without a request body" do
expect(response.status).to eq(200)
expect(response.body).to eq("Hello World")
expect(InboundRequestLog.count).to eq(1)
inbound_request_log = InboundRequestLog.first
expect(inbound_request_log.method).to eq("POST")
expect(inbound_request_log.path).to eq("/api/v1/books")
expect(inbound_request_log.request_body).to eq("[Skipped]")
expect(inbound_request_log.response_code).to eq(200)
expect(inbound_request_log.response_body).to eq("Hello World")
end
end

context "when the PATH_INFO matches the skip_response_body_regexp" do
let(:skip_response_body_regexp) { /books/ }

it "logs a request in the database but without a response body" do
expect(response.status).to eq(200)
expect(response.body).to eq("Hello World")
expect(InboundRequestLog.count).to eq(1)
Expand All @@ -66,7 +86,7 @@ def request
expect(inbound_request_log.path).to eq("/api/v1/books")
expect(inbound_request_log.request_body).to eq("")
expect(inbound_request_log.response_code).to eq(200)
expect(inbound_request_log.response_body).to be_nil
expect(inbound_request_log.response_body).to eq("[Skipped]")
end
end

Expand Down
53 changes: 41 additions & 12 deletions spec/outbound_request_log_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,45 @@
OutboundRequestLog.delete_all
end

it "logs a request in the database" do
uri = URI("http://example.com/some_path?query=string")
http = Net::HTTP.new(uri.host, uri.port)
request = Net::HTTP::Get.new(uri)
RailsApiLogger.new.call(uri, request) { http.start { |http| http.request(request) } }
expect(OutboundRequestLog.count).to eq(1)
log = OutboundRequestLog.last
expect(log.started_at).to be_present
expect(log.ended_at).to be_present
describe "logging of a request" do
let(:skip_request_body) { false }
let(:skip_response_body) { false }

before do
uri = URI("https://httpbin.org/anything?query=string")
http = Net::HTTP.new(uri.host, uri.port)
request = Net::HTTP::Post.new(uri)
request.body = {"my" => {"request" => "body"}}.to_json
RailsApiLogger.new(skip_request_body: skip_request_body, skip_response_body: skip_response_body)
.call(uri, request) { http.start { |http| http.request(request) } }
end

it "logs all information" do
expect(OutboundRequestLog.count).to eq(1)
log = OutboundRequestLog.last
expect(log.started_at).to be_present
expect(log.ended_at).to be_present
expect(log.request_body).to be_present
expect(log.response_body).to be_present
end

describe "if the skip_request_body option is set" do
let(:skip_request_body) { true }

it "does not log the request body" do
log = OutboundRequestLog.last
expect(log.request_body).to eq("[Skipped]")
end
end

describe "if the skip_response_body option is set" do
let(:skip_response_body) { true }

it "does not log the response body" do
log = OutboundRequestLog.last
expect(log.response_body).to eq("[Skipped]")
end
end
end

describe "if the request fails" do
Expand Down Expand Up @@ -42,14 +72,13 @@
describe "#formatted_request_body" do
it "renders the request body in a nice format" do
outbound_request_log = OutboundRequestLog.new(request_body: {"my" => {"request" => "body"}})
puts outbound_request_log.formatted_request_body
expect { outbound_request_log.formatted_request_body }.not_to raise_error
outbound_request_log.request_body = "simple text"
puts outbound_request_log.formatted_request_body
expect { outbound_request_log.formatted_request_body }.not_to raise_error
outbound_request_log.request_body = "<i><a>Hello</a><b>From</b><c>XML</c></i>"
puts outbound_request_log.formatted_request_body
expect { outbound_request_log.formatted_request_body }.not_to raise_error
outbound_request_log.request_body = ""
expect(outbound_request_log.formatted_request_body).to eq("")
end
end

Expand Down
11 changes: 6 additions & 5 deletions spec/request_log_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,20 @@
let(:request) { Net::HTTP::Get.new(uri) }
let(:response) { http.start { |http| http.request(request) } }

before { RailsApiLogger.new(skip_body: skip_body).call(uri, request) { response } }
before { RailsApiLogger.new(skip_response_body: skip_response_body).call(uri, request) { response } }

context "when skip_body is set to false" do
let(:skip_body) { false }
context "when skip_response_body is set to false" do
let(:skip_response_body) { false }

it "sets the response_body to the original request's response body" do
log = OutboundRequestLog.last
expect(log.response_body).to eq(response.body)
expect(log.response_body).to be_present
end
end

context "when skip_body is set to true" do
let(:skip_body) { true }
context "when skip_response_body is set to true" do
let(:skip_response_body) { true }

it "sets the response_body to [Skipped]" do
log = OutboundRequestLog.last
Expand Down

0 comments on commit 0bcf1f2

Please sign in to comment.