Skip to content

Commit

Permalink
Add the log formatter that is easy to override and safe to inherit
Browse files Browse the repository at this point in the history
  • Loading branch information
prikha committed Mar 6, 2019
1 parent b9ea1d9 commit ebb7c36
Show file tree
Hide file tree
Showing 4 changed files with 155 additions and 60 deletions.
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

0 comments on commit ebb7c36

Please sign in to comment.