Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add the log formatter that is easy to override and safe to inherit #889

Merged
merged 1 commit into from Mar 6, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
17 changes: 17 additions & 0 deletions README.md
Expand Up @@ -73,6 +73,23 @@ conn = Faraday.new(:url => 'http://sushi.com/api_key=s3cr3t') do |faraday|
end
faraday.adapter Faraday.default_adapter # make requests with Net::HTTP
end

# Override the log formatting on demand

class MyFormatter < Faraday::Response::Logger::Formatter
def request(env)
info('Request', env)
end

def request(env)
info('Response', env)
end
end

conn = Faraday.new(:url => 'http://sushi.com/api_key=s3cr3t') do |faraday|
faraday.response :logger, StructLogger.new(STDOUT), formatter: MyFormatter
end

```

Once you have the connection object, use it to make HTTP requests. You can pass parameters to it in a few different ways:
Expand Down
76 changes: 76 additions & 0 deletions lib/faraday/logging/formatter.rb
@@ -0,0 +1,76 @@
# frozen_string_literal: true

require 'pp'
module Faraday
module Logging
# Serves as an integration point to customize logging
class Formatter
extend Forwardable

DEFAULT_OPTIONS = { headers: true, bodies: false }.freeze

def initialize(logger:, options:)
@logger = logger
@filter = []
@options = DEFAULT_OPTIONS.merge(options)
end

def_delegators :@logger, :debug, :info, :warn, :error, :fatal

def request(env)
info('request') { "#{env.method.upcase} #{apply_filters(env.url.to_s)}" }
debug('request') { apply_filters(dump_headers(env.request_headers)) } if log_headers?(:request)
debug('request') { apply_filters(dump_body(env[:body])) } if env[:body] && log_body?(:request)
end

def response(env)
info('response') { "Status #{env.status}" }
debug('response') { apply_filters(dump_headers(env.response_headers)) } if log_headers?(:response)
debug('response') { apply_filters(dump_body(env[:body])) } if env[:body] && log_body?(:response)
end

def filter(filter_word, filter_replacement)
@filter.push([filter_word, filter_replacement])
end

private

def dump_headers(headers)
headers.map { |k, v| "#{k}: #{v.inspect}" }.join("\n")
end

def dump_body(body)
if body.respond_to?(:to_str)
body.to_str
else
pretty_inspect(body)
end
end

def pretty_inspect(body)
body.pretty_inspect
end

def log_headers?(type)
case @options[:headers]
when Hash then @options[:headers][type]
else @options[:headers]
end
end

def log_body?(type)
case @options[:bodies]
when Hash then @options[:bodies][type]
else @options[:bodies]
end
end

def apply_filters(output)
@filter.each do |pattern, replacement|
output = output.to_s.gsub(pattern, replacement)
end
output
end
end
end
end
67 changes: 7 additions & 60 deletions lib/faraday/response/logger.rb
@@ -1,82 +1,29 @@
# frozen_string_literal: true

require 'forwardable'
require 'faraday/logging/formatter'

module Faraday
class Response
class Logger < Middleware
extend Forwardable

DEFAULT_OPTIONS = { headers: true, bodies: false }.freeze

def initialize(app, logger = nil, options = {})
super(app)
@logger = logger || begin
logger ||= begin
require 'logger'
::Logger.new($stdout)
end
@filter = []
@options = DEFAULT_OPTIONS.merge(options)
yield self if block_given?
formatter_class = options.delete(:formatter) || Logging::Formatter
@formatter = formatter_class.new(logger: logger, options: options)
yield @formatter if block_given?
end

def_delegators :@logger, :debug, :info, :warn, :error, :fatal

def call(env)
info('request') { "#{env.method.upcase} #{apply_filters(env.url.to_s)}" }
debug('request') { apply_filters(dump_headers(env.request_headers)) } if log_headers?(:request)
debug('request') { apply_filters(dump_body(env[:body])) } if env[:body] && log_body?(:request)
@formatter.request(env)
super
end

def on_complete(env)
info('response') { "Status #{env.status}" }
debug('response') { apply_filters(dump_headers(env.response_headers)) } if log_headers?(:response)
debug('response') { apply_filters(dump_body(env[:body])) } if env[:body] && log_body?(:response)
end

def filter(filter_word, filter_replacement)
@filter.push([filter_word, filter_replacement])
end

private

def dump_headers(headers)
headers.map { |k, v| "#{k}: #{v.inspect}" }.join("\n")
end

def dump_body(body)
if body.respond_to?(:to_str)
body.to_str
else
pretty_inspect(body)
end
end

def pretty_inspect(body)
require 'pp' unless body.respond_to?(:pretty_inspect)
body.pretty_inspect
end

def log_headers?(type)
case @options[:headers]
when Hash then @options[:headers][type]
else @options[:headers]
end
end

def log_body?(type)
case @options[:bodies]
when Hash then @options[:bodies][type]
else @options[:bodies]
end
end

def apply_filters(output)
@filter.each do |pattern, replacement|
output = output.to_s.gsub(pattern, replacement)
end
output
@formatter.response(env)
end
end
end
Expand Down
55 changes: 55 additions & 0 deletions spec/faraday/response/logger_spec.rb
Expand Up @@ -38,6 +38,61 @@
expect(resp.body).to eq('hello')
end

context 'without configuration' do
let(:conn) do
Faraday.new do |b|
b.response :logger
b.adapter :test do |stubs|
stubs.get('/hello') { [200, { 'Content-Type' => 'text/html' }, 'hello'] }
end
end
end

it 'defaults to stdout' do
expect(Logger).to receive(:new).with($stdout).and_return(Logger.new(nil))
conn.get('/hello')
end
end

context 'with default formatter' do
let(:formatter) { instance_double(Faraday::Logging::Formatter, request: true, response: true, filter: []) }

before { allow(Faraday::Logging::Formatter).to receive(:new).and_return(formatter) }

it 'delegates logging to the formatter' do
expect(formatter).to receive(:request).with(an_instance_of(Faraday::Env))
expect(formatter).to receive(:response).with(an_instance_of(Faraday::Env))
conn.get '/hello'
end
end

context 'with custom formatter' do
let(:formatter_class) do
Class.new(Faraday::Logging::Formatter) do
def initialize(*args)
super
end

def request(_env)
info 'Custom log formatter request'
end

def response(_env)
info 'Custom log formatter response'
end
end
end

let(:logger_options) { { formatter: formatter_class } }

it 'logs with custom formatter' do
conn.get '/hello'

expect(string_io.string).to match('Custom log formatter request')
expect(string_io.string).to match('Custom log formatter response')
end
end

it 'logs method and url' do
conn.get '/hello', nil, accept: 'text/html'
expect(string_io.string).to match('GET http:/hello')
Expand Down