Skip to content

Commit 1d3d675

Browse files
prikhaiMacTia
authored andcommitted
Add the log formatter that is easy to override and safe to inherit (#889)
1 parent b9ea1d9 commit 1d3d675

4 files changed

Lines changed: 155 additions & 60 deletions

File tree

README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,23 @@ conn = Faraday.new(:url => 'http://sushi.com/api_key=s3cr3t') do |faraday|
7373
end
7474
faraday.adapter Faraday.default_adapter # make requests with Net::HTTP
7575
end
76+
77+
# Override the log formatting on demand
78+
79+
class MyFormatter < Faraday::Response::Logger::Formatter
80+
def request(env)
81+
info('Request', env)
82+
end
83+
84+
def request(env)
85+
info('Response', env)
86+
end
87+
end
88+
89+
conn = Faraday.new(:url => 'http://sushi.com/api_key=s3cr3t') do |faraday|
90+
faraday.response :logger, StructLogger.new(STDOUT), formatter: MyFormatter
91+
end
92+
7693
```
7794

7895
Once you have the connection object, use it to make HTTP requests. You can pass parameters to it in a few different ways:

lib/faraday/logging/formatter.rb

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# frozen_string_literal: true
2+
3+
require 'pp'
4+
module Faraday
5+
module Logging
6+
# Serves as an integration point to customize logging
7+
class Formatter
8+
extend Forwardable
9+
10+
DEFAULT_OPTIONS = { headers: true, bodies: false }.freeze
11+
12+
def initialize(logger:, options:)
13+
@logger = logger
14+
@filter = []
15+
@options = DEFAULT_OPTIONS.merge(options)
16+
end
17+
18+
def_delegators :@logger, :debug, :info, :warn, :error, :fatal
19+
20+
def request(env)
21+
info('request') { "#{env.method.upcase} #{apply_filters(env.url.to_s)}" }
22+
debug('request') { apply_filters(dump_headers(env.request_headers)) } if log_headers?(:request)
23+
debug('request') { apply_filters(dump_body(env[:body])) } if env[:body] && log_body?(:request)
24+
end
25+
26+
def response(env)
27+
info('response') { "Status #{env.status}" }
28+
debug('response') { apply_filters(dump_headers(env.response_headers)) } if log_headers?(:response)
29+
debug('response') { apply_filters(dump_body(env[:body])) } if env[:body] && log_body?(:response)
30+
end
31+
32+
def filter(filter_word, filter_replacement)
33+
@filter.push([filter_word, filter_replacement])
34+
end
35+
36+
private
37+
38+
def dump_headers(headers)
39+
headers.map { |k, v| "#{k}: #{v.inspect}" }.join("\n")
40+
end
41+
42+
def dump_body(body)
43+
if body.respond_to?(:to_str)
44+
body.to_str
45+
else
46+
pretty_inspect(body)
47+
end
48+
end
49+
50+
def pretty_inspect(body)
51+
body.pretty_inspect
52+
end
53+
54+
def log_headers?(type)
55+
case @options[:headers]
56+
when Hash then @options[:headers][type]
57+
else @options[:headers]
58+
end
59+
end
60+
61+
def log_body?(type)
62+
case @options[:bodies]
63+
when Hash then @options[:bodies][type]
64+
else @options[:bodies]
65+
end
66+
end
67+
68+
def apply_filters(output)
69+
@filter.each do |pattern, replacement|
70+
output = output.to_s.gsub(pattern, replacement)
71+
end
72+
output
73+
end
74+
end
75+
end
76+
end

lib/faraday/response/logger.rb

Lines changed: 7 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,82 +1,29 @@
11
# frozen_string_literal: true
22

33
require 'forwardable'
4+
require 'faraday/logging/formatter'
45

56
module Faraday
67
class Response
78
class Logger < Middleware
8-
extend Forwardable
9-
10-
DEFAULT_OPTIONS = { headers: true, bodies: false }.freeze
11-
129
def initialize(app, logger = nil, options = {})
1310
super(app)
14-
@logger = logger || begin
11+
logger ||= begin
1512
require 'logger'
1613
::Logger.new($stdout)
1714
end
18-
@filter = []
19-
@options = DEFAULT_OPTIONS.merge(options)
20-
yield self if block_given?
15+
formatter_class = options.delete(:formatter) || Logging::Formatter
16+
@formatter = formatter_class.new(logger: logger, options: options)
17+
yield @formatter if block_given?
2118
end
2219

23-
def_delegators :@logger, :debug, :info, :warn, :error, :fatal
24-
2520
def call(env)
26-
info('request') { "#{env.method.upcase} #{apply_filters(env.url.to_s)}" }
27-
debug('request') { apply_filters(dump_headers(env.request_headers)) } if log_headers?(:request)
28-
debug('request') { apply_filters(dump_body(env[:body])) } if env[:body] && log_body?(:request)
21+
@formatter.request(env)
2922
super
3023
end
3124

3225
def on_complete(env)
33-
info('response') { "Status #{env.status}" }
34-
debug('response') { apply_filters(dump_headers(env.response_headers)) } if log_headers?(:response)
35-
debug('response') { apply_filters(dump_body(env[:body])) } if env[:body] && log_body?(:response)
36-
end
37-
38-
def filter(filter_word, filter_replacement)
39-
@filter.push([filter_word, filter_replacement])
40-
end
41-
42-
private
43-
44-
def dump_headers(headers)
45-
headers.map { |k, v| "#{k}: #{v.inspect}" }.join("\n")
46-
end
47-
48-
def dump_body(body)
49-
if body.respond_to?(:to_str)
50-
body.to_str
51-
else
52-
pretty_inspect(body)
53-
end
54-
end
55-
56-
def pretty_inspect(body)
57-
require 'pp' unless body.respond_to?(:pretty_inspect)
58-
body.pretty_inspect
59-
end
60-
61-
def log_headers?(type)
62-
case @options[:headers]
63-
when Hash then @options[:headers][type]
64-
else @options[:headers]
65-
end
66-
end
67-
68-
def log_body?(type)
69-
case @options[:bodies]
70-
when Hash then @options[:bodies][type]
71-
else @options[:bodies]
72-
end
73-
end
74-
75-
def apply_filters(output)
76-
@filter.each do |pattern, replacement|
77-
output = output.to_s.gsub(pattern, replacement)
78-
end
79-
output
26+
@formatter.response(env)
8027
end
8128
end
8229
end

spec/faraday/response/logger_spec.rb

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,61 @@
3838
expect(resp.body).to eq('hello')
3939
end
4040

41+
context 'without configuration' do
42+
let(:conn) do
43+
Faraday.new do |b|
44+
b.response :logger
45+
b.adapter :test do |stubs|
46+
stubs.get('/hello') { [200, { 'Content-Type' => 'text/html' }, 'hello'] }
47+
end
48+
end
49+
end
50+
51+
it 'defaults to stdout' do
52+
expect(Logger).to receive(:new).with($stdout).and_return(Logger.new(nil))
53+
conn.get('/hello')
54+
end
55+
end
56+
57+
context 'with default formatter' do
58+
let(:formatter) { instance_double(Faraday::Logging::Formatter, request: true, response: true, filter: []) }
59+
60+
before { allow(Faraday::Logging::Formatter).to receive(:new).and_return(formatter) }
61+
62+
it 'delegates logging to the formatter' do
63+
expect(formatter).to receive(:request).with(an_instance_of(Faraday::Env))
64+
expect(formatter).to receive(:response).with(an_instance_of(Faraday::Env))
65+
conn.get '/hello'
66+
end
67+
end
68+
69+
context 'with custom formatter' do
70+
let(:formatter_class) do
71+
Class.new(Faraday::Logging::Formatter) do
72+
def initialize(*args)
73+
super
74+
end
75+
76+
def request(_env)
77+
info 'Custom log formatter request'
78+
end
79+
80+
def response(_env)
81+
info 'Custom log formatter response'
82+
end
83+
end
84+
end
85+
86+
let(:logger_options) { { formatter: formatter_class } }
87+
88+
it 'logs with custom formatter' do
89+
conn.get '/hello'
90+
91+
expect(string_io.string).to match('Custom log formatter request')
92+
expect(string_io.string).to match('Custom log formatter response')
93+
end
94+
end
95+
4196
it 'logs method and url' do
4297
conn.get '/hello', nil, accept: 'text/html'
4398
expect(string_io.string).to match('GET http:/hello')

0 commit comments

Comments
 (0)