Merge pull request #16 from mmclead/one_line_log_option

adding an option to format log message to be one line
This commit is contained in:
Ryan Buckley 2019-03-07 15:15:21 -08:00 committed by GitHub
commit 3e64a80ab7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 81 additions and 30 deletions

5
.byebug_history Normal file
View File

@ -0,0 +1,5 @@
exit
c
log_statements.compact.delete_if(&:empty?).each(&:strip!)
log_statements.compact.delete_if(&:empty?).each(&:lstrip)
log_statements.compact.delete_if(&:empty?).each(&:strip)

View File

@ -55,6 +55,7 @@ Completed 422 in 6.29ms
The middleware logger can be customized with the following options: The middleware logger can be customized with the following options:
* The `:logger` option can be any object that responds to `.info(String)` * The `:logger` option can be any object that responds to `.info(String)`
* The `:condensed` option configures the log output to be on one line instead of multiple. It accepts `true` or `false`. The default configuration is `false`
* The `:filter` option can be any object that responds to `.filter(Hash)` and returns a hash. * The `:filter` option can be any object that responds to `.filter(Hash)` and returns a hash.
* The `:headers` option can be either `:all` or array of strings. * The `:headers` option can be either `:all` or array of strings.
+ If `:all`, all request headers will be output. + If `:all`, all request headers will be output.
@ -65,17 +66,18 @@ For example:
```ruby ```ruby
insert_after Grape::Middleware::Formatter, Grape::Middleware::Logger, { insert_after Grape::Middleware::Formatter, Grape::Middleware::Logger, {
logger: Logger.new(STDERR), logger: Logger.new(STDERR),
condensed: true,
filter: Class.new { def filter(opts) opts.reject { |k, _| k.to_s == 'password' } end }.new, filter: Class.new { def filter(opts) opts.reject { |k, _| k.to_s == 'password' } end }.new,
headers: %w(version cache-control) headers: %w(version cache-control)
} }
``` ```
## Using Rails? ## Using Rails?
`Rails.logger` and `Rails.application.config.filter_parameters` will be used automatically as the default logger and `Rails.logger` and `Rails.application.config.filter_parameters` will be used automatically as the default logger and
param filterer, respectively. This behavior can be overridden by passing the `:logger` or param filterer, respectively. This behavior can be overridden by passing the `:logger` or
`:filter` option when mounting. `:filter` option when mounting.
You may want to disable Rails logging for API endpoints, so that the logging doesn't double-up. You can achieve this You may want to disable Rails logging for API endpoints, so that the logging doesn't double-up. You can achieve this
by switching around some middleware. For example: by switching around some middleware. For example:
```ruby ```ruby

View File

@ -7,7 +7,7 @@ class Grape::Middleware::Logger < Grape::Middleware::Globals
attr_reader :logger attr_reader :logger
class << self class << self
attr_accessor :logger, :filter, :headers attr_accessor :logger, :filter, :headers, :condensed
def default_logger def default_logger
default = Logger.new(STDOUT) default = Logger.new(STDOUT)
@ -20,6 +20,7 @@ class Grape::Middleware::Logger < Grape::Middleware::Globals
super super
@options[:filter] ||= self.class.filter @options[:filter] ||= self.class.filter
@options[:headers] ||= self.class.headers @options[:headers] ||= self.class.headers
@options[:condensed] ||= false
@logger = options[:logger] || self.class.logger || self.class.default_logger @logger = options[:logger] || self.class.logger || self.class.default_logger
end end
@ -27,15 +28,19 @@ class Grape::Middleware::Logger < Grape::Middleware::Globals
start_time start_time
# sets env['grape.*'] # sets env['grape.*']
super super
logger.info ''
logger.info %Q(Started %s "%s" at %s) % [ log_statements = [
env[Grape::Env::GRAPE_REQUEST].request_method, '',
env[Grape::Env::GRAPE_REQUEST].path, %Q(Started %s "%s" at %s) % [
start_time.to_s env[Grape::Env::GRAPE_REQUEST].request_method,
] env[Grape::Env::GRAPE_REQUEST].path,
logger.info %Q(Processing by #{processed_by}) start_time.to_s
logger.info %Q( Parameters: #{parameters}) ],
logger.info %Q( Headers: #{headers}) if @options[:headers] %Q(Processing by #{processed_by}),
%Q( Parameters: #{parameters})]
log_statements.append(%Q( Headers: #{headers})) if @options[:headers]
log_info(log_statements)
end end
# @note Error and exception handling are required for the +after+ hooks # @note Error and exception handling are required for the +after+ hooks
@ -64,8 +69,12 @@ class Grape::Middleware::Logger < Grape::Middleware::Globals
end end
def after(status) def after(status)
logger.info "Completed #{status} in #{((Time.now - start_time) * 1000).round(2)}ms" log_info(
logger.info '' [
"Completed #{status} in #{((Time.now - start_time) * 1000).round(2)}ms",
''
]
)
end end
# #
@ -120,6 +129,14 @@ class Grape::Middleware::Logger < Grape::Middleware::Globals
result.concat endpoint.options[:path].map { |path| path.to_s.sub(BACKSLASH, '') } result.concat endpoint.options[:path].map { |path| path.to_s.sub(BACKSLASH, '') }
endpoint.options[:for].to_s << result.join(BACKSLASH) endpoint.options[:for].to_s << result.join(BACKSLASH)
end end
def log_info(log_statements=[])
if @options[:condensed]
logger.info log_statements.compact.delete_if(&:empty?).each(&:strip!).join(" - ")
else
log_statements.each { |log_statement| logger.info log_statement }
end
end
end end
require_relative 'logger/railtie' if defined?(Rails) require_relative 'logger/railtie' if defined?(Rails)

View File

@ -11,14 +11,28 @@ describe Grape::Middleware::Logger, type: :integration do
let(:grape_endpoint) { build(:grape_endpoint) } let(:grape_endpoint) { build(:grape_endpoint) }
let(:env) { build(:expected_env, grape_endpoint: grape_endpoint) } let(:env) { build(:expected_env, grape_endpoint: grape_endpoint) }
it 'logs all parts of the request' do context 'when the option[:condensed] is false' do
expect(subject.logger).to receive(:info).with '' let(:options) { { filter: build(:param_filter), logger: Logger.new(Tempfile.new('logger')), condensed: false } }
expect(subject.logger).to receive(:info).with %Q(Started POST "/api/1.0/users" at #{subject.start_time})
expect(subject.logger).to receive(:info).with %Q(Processing by TestAPI/users) it 'logs all parts of the request on multiple lines' do
expect(subject.logger).to receive(:info).with %Q( Parameters: {"id"=>"101001", "secret"=>"[FILTERED]", "customer"=>[], "name"=>"foo", "password"=>"[FILTERED]"}) expect(subject.logger).to receive(:info).with ''
expect(subject.logger).to receive(:info).with /Completed 200 in \d+.\d+ms/ expect(subject.logger).to receive(:info).with %Q(Started POST "/api/1.0/users" at #{subject.start_time})
expect(subject.logger).to receive(:info).with '' expect(subject.logger).to receive(:info).with %Q(Processing by TestAPI/users)
subject.call!(env) expect(subject.logger).to receive(:info).with %Q( Parameters: {"id"=>"101001", "secret"=>"[FILTERED]", "customer"=>[], "name"=>"foo", "password"=>"[FILTERED]"})
expect(subject.logger).to receive(:info).with /Completed 200 in \d+.\d+ms/
expect(subject.logger).to receive(:info).with ''
subject.call!(env)
end
end
context 'when the options[:condensed is true' do
let(:options) { { filter: build(:param_filter), logger: Logger.new(Tempfile.new('logger')), condensed: true } }
it 'logs all parts of the request on one line' do
expect(subject.logger).to receive(:info).with %Q(Started POST "/api/1.0/users" at #{subject.start_time} - Processing by TestAPI/users - Parameters: {"id"=>"101001", "secret"=>"[FILTERED]", "customer"=>[], "name"=>"foo", "password"=>"[FILTERED]"})
expect(subject.logger).to receive(:info).with /Completed 200 in \d+.\d+ms/
subject.call!(env)
end
end end
context 'when an exception occurs' do context 'when an exception occurs' do

View File

@ -42,14 +42,27 @@ describe Grape::Middleware::Logger, type: :rails_integration do
end end
end end
it 'logs all parts of the request' do context 'when the option[:condensed] is false' do
expect(subject.logger).to receive(:info).with '' let(:options) { { condensed: false } }
expect(subject.logger).to receive(:info).with %Q(Started POST "/api/1.0/users" at #{subject.start_time})
expect(subject.logger).to receive(:info).with %Q(Processing by TestAPI/users) it 'logs all parts of the request on multiple lines' do
expect(subject.logger).to receive(:info).with %Q( Parameters: {"id"=>"101001", "secret"=>"key", "customer"=>[], "name"=>"foo", "password"=>"[FILTERED]"}) expect(subject.logger).to receive(:info).with ''
expect(subject.logger).to receive(:info).with /Completed 200 in \d+.\d+ms/ expect(subject.logger).to receive(:info).with %Q(Started POST "/api/1.0/users" at #{subject.start_time})
expect(subject.logger).to receive(:info).with '' expect(subject.logger).to receive(:info).with %Q(Processing by TestAPI/users)
subject.call!(env) expect(subject.logger).to receive(:info).with %Q( Parameters: {"id"=>"101001", "secret"=>"key", "customer"=>[], "name"=>"foo", "password"=>"[FILTERED]"})
expect(subject.logger).to receive(:info).with /Completed 200 in \d+.\d+ms/
expect(subject.logger).to receive(:info).with ''
subject.call!(env)
end
end
context 'when the option[:condensed] is true' do
let(:options) { { condensed: true } }
it 'logs all parts of the request on one line' do
expect(subject.logger).to receive(:info).with %Q(Started POST "/api/1.0/users" at #{subject.start_time} - Processing by TestAPI/users - Parameters: {"id"=>"101001", "secret"=>"key", "customer"=>[], "name"=>"foo", "password"=>"[FILTERED]"})
expect(subject.logger).to receive(:info).with /Completed 200 in \d+.\d+ms/
subject.call!(env)
end
end end
describe 'the "processing by" section' do describe 'the "processing by" section' do