Compare commits

..

No commits in common. "master" and "spec-for-after_exception" have entirely different histories.

16 changed files with 49 additions and 319 deletions

View File

@ -1,5 +0,0 @@
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

@ -1,8 +1,9 @@
language: ruby
rvm:
- 2.3.8
- 2.4.5
- 2.5.3
- 1.9.3
- 2.1.5
- 2.2.4
- 2.3.0
before_install:
- gem install bundler
- gem install mime-types
- gem install bundler -v 1.10.5
- gem install mime-types -v 2.6.2

View File

@ -1,19 +1,3 @@
1.12.0 (5/13/2019)
==================
* [#25] Support Rails 6.0.0 (Thanks [@serggl](https://github.com/serggl))
1.9.0 (7/7/2017)
==================
* [#19] Support Grape 1.0.0 (Thanks [@badlamer](https://github.com/badlamer))
1.8.0 (4/22/2017)
==================
* [#17] Add a `:headers` option, which can be either `:all` or an array of strings. (Thanks [@yamamotok](https://github.com/yamamotok))
1.7.1 (11/1/2016)
==================
* Log the error class name (https://github.com/ridiculous/grape-middleware-logger/pull/13)
1.7.0 (8/2/2016)
==================

View File

@ -1,30 +0,0 @@
# Grape::Middleware::logger Code of Conduct
The Grape::Middleware::logger project strongly values contributors from anywhere, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, physical appearance, body size, race, ethnicity, age, religion, or nationality. As a result, the Grape::Middleware::logger team has agreed to and enforces this code of conduct in order to provide a harassment-free experience for everyone who participates in the development of Grape::Middleware::logger.
### Summary
Harassment in code and discussion or violation of physical boundaries is completely unacceptable anywhere in the Grape::Middleware::logger projects codebases, issue trackers, chat rooms, mailing lists, meetups, and any other events. Violators will be warned and then blocked or banned by the core team at or before the 3rd violation.
### In detail
Harassment includes offensive verbal comments related to level of experience, gender, gender identity and expression, sexual orientation, disability, physical appearance, body size, race, ethnicity, age, religion, nationality, the use of sexualized language or imagery, deliberate intimidation, stalking, sustained disruption, and unwelcome sexual attention.
Individuals asked to stop any harassing behavior are expected to comply immediately.
Maintainers, including the core team, are also subject to the anti-harassment policy.
If anyone engages in abusive, harassing, or otherwise unacceptable behavior, including maintainers, we may take appropriate action, up to and including warning the offender, deletion of comments, removal from the projects codebase and communication systems, and escalation to Github support.
If you are being harassed, notice that someone else is being harassed, or have any other concerns, please contact a member of the core team immediately.
We expect everyone to follow these rules anywhere in the logger projects codebases, issue trackers, IRC channel, group chat, and mailing lists.
This code of conduct applies both within project spaces and in public spaces when an individual is representing the project or its community.
Finally, don't forget that it is human to make mistakes! We all do. Lets work together to help each other, resolve issues, and learn from the mistakes that we will all inevitably make from time to time.
### License
MIT

View File

@ -6,5 +6,5 @@ gemspec
group :test do
gem 'rake'
gem "factory_girl", "~> 4.0"
gem "rails", "~> 5.2"
gem "rails", "~> 4.2"
end

View File

@ -22,9 +22,6 @@ gem 'grape-middleware-logger'
## Usage
```ruby
require 'grape'
require 'grape/middleware/logger'
class API < Grape::API
# @note Make sure this is above your first +mount+
insert_after Grape::Middleware::Formatter, Grape::Middleware::Logger
@ -55,29 +52,23 @@ Completed 422 in 6.29ms
The middleware logger can be customized with the following options:
* 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 `:headers` option can be either `:all` or array of strings.
+ If `:all`, all request headers will be output.
+ If array, output will be filtered by names in the array. (case-insensitive)
For example:
```ruby
insert_after Grape::Middleware::Formatter, Grape::Middleware::Logger, {
logger: Logger.new(STDERR),
condensed: true,
filter: Class.new { def filter(opts) opts.reject { |k, _| k.to_s == 'password' } end }.new,
headers: %w(version cache-control)
filter: Class.new { def filter(opts) opts.reject { |k, _| k.to_s == 'password' } end }.new
}
```
## 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
`: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:
```ruby

View File

@ -1,5 +1,4 @@
require 'rspec/core/rake_task'
require 'bundler/gem_tasks'
RSpec::Core::RakeTask.new(:spec) do |config|
config.pattern = 'spec/lib/**/*_spec.rb'

View File

@ -2,7 +2,7 @@
Gem::Specification.new do |spec|
spec.name = 'grape-middleware-logger'
spec.version = '1.12.0'
spec.version = '1.7.0'
spec.platform = Gem::Platform::RUBY
spec.authors = ['Ryan Buckley']
spec.email = ['arebuckley@gmail.com']
@ -11,21 +11,22 @@ Gem::Specification.new do |spec|
spec.homepage = 'https://github.com/ridiculous/grape-middleware-logger'
spec.license = 'MIT'
spec.files = `git ls-files -z`.split("\x0").grep(%r{^lib/|gemspec})
spec.files = `git ls-files -z`.split("\x0")
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
spec.require_paths = ['lib']
spec.post_install_message = %q{
GrapeMiddlewareLogger v1.7+ should be mounted with +insert_after+ to properly include POST request params:
Grape::Middleware::Logger 1.7+ should be mounted with +insert_after+ to properly include POST params:
insert_after Grape::Middleware::Formatter, Grape::Middleware::Logger
insert_after Grape::Middleware::Formatter, Grape::Middleware::Logger
}
spec.add_dependency 'grape', '>= 0.17'
spec.add_dependency 'grape', '>= 0.17', '< 1'
spec.add_development_dependency 'bundler', '~> 1.7'
spec.add_development_dependency 'mime-types', '~> 2'
spec.add_development_dependency 'rake', '>= 12.3.3'
spec.add_development_dependency 'rake', '~> 10.0'
spec.add_development_dependency 'rspec', '>= 3.2', '< 4'
end

View File

@ -7,7 +7,7 @@ class Grape::Middleware::Logger < Grape::Middleware::Globals
attr_reader :logger
class << self
attr_accessor :logger, :filter, :headers, :condensed
attr_accessor :logger, :filter
def default_logger
default = Logger.new(STDOUT)
@ -19,8 +19,6 @@ class Grape::Middleware::Logger < Grape::Middleware::Globals
def initialize(_, options = {})
super
@options[:filter] ||= self.class.filter
@options[:headers] ||= self.class.headers
@options[:condensed] ||= false
@logger = options[:logger] || self.class.logger || self.class.default_logger
end
@ -28,19 +26,14 @@ class Grape::Middleware::Logger < Grape::Middleware::Globals
start_time
# sets env['grape.*']
super
log_statements = [
'',
%Q(Started %s "%s" at %s) % [
env[Grape::Env::GRAPE_REQUEST].request_method,
env[Grape::Env::GRAPE_REQUEST].path,
start_time.to_s
],
%Q(Processing by #{processed_by}),
%Q( Parameters: #{parameters})]
log_statements.append(%Q( Headers: #{headers})) if @options[:headers]
log_info(log_statements)
logger.info ''
logger.info %Q(Started %s "%s" at %s) % [
env[Grape::Env::GRAPE_REQUEST].request_method,
env[Grape::Env::GRAPE_REQUEST].path,
start_time.to_s
]
logger.info %Q(Processing by #{processed_by})
logger.info %Q( Parameters: #{parameters})
end
# @note Error and exception handling are required for the +after+ hooks
@ -69,12 +62,8 @@ class Grape::Middleware::Logger < Grape::Middleware::Globals
end
def after(status)
log_info(
[
"Completed #{status} in #{((Time.now - start_time) * 1000).round(2)}ms",
''
]
)
logger.info "Completed #{status} in #{((Time.now - start_time) * 1000).round(2)}ms"
logger.info ''
end
#
@ -102,18 +91,6 @@ class Grape::Middleware::Logger < Grape::Middleware::Globals
end
end
def headers
request_headers = env[Grape::Env::GRAPE_REQUEST_HEADERS].to_hash
return Hash[request_headers.sort] if @options[:headers] == :all
headers_needed = Array(@options[:headers])
result = {}
headers_needed.each do |need|
result.merge!(request_headers.select { |key, value| need.to_s.casecmp(key).zero? })
end
Hash[result.sort]
end
def start_time
@start_time ||= Time.now
end
@ -129,14 +106,6 @@ class Grape::Middleware::Logger < Grape::Middleware::Globals
result.concat endpoint.options[:path].map { |path| path.to_s.sub(BACKSLASH, '') }
endpoint.options[:for].to_s << result.join(BACKSLASH)
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
require_relative 'logger/railtie' if defined?(Rails)

View File

@ -1,12 +1,6 @@
class Grape::Middleware::Logger::Railtie < Rails::Railtie
options = Rails::VERSION::MAJOR < 5 ? { after: :load_config_initializers } : {}
initializer 'grape.middleware.logger', options do
initializer 'grape.middleware.logger', after: :load_config_initializers do
Grape::Middleware::Logger.logger = Rails.application.config.logger || Rails.logger.presence
parameter_filter_class = if Rails::VERSION::MAJOR >= 6
ActiveSupport::ParameterFilter
else
ActionDispatch::Http::ParameterFilter
end
Grape::Middleware::Logger.filter = parameter_filter_class.new Rails.application.config.filter_parameters
Grape::Middleware::Logger.filter = ActionDispatch::Http::ParameterFilter.new Rails.application.config.filter_parameters
end
end

View File

@ -32,10 +32,8 @@ FactoryGirl.define do
grape_request { build :grape_request }
grape_endpoint { build(:grape_endpoint) }
params { grape_request.params }
headers { grape_request.headers }
post_params { { 'secret' => 'key', 'customer' => [] } }
rails_post_params { { 'name' => 'foo', 'password' => 'access' } }
other_env_params { {} }
initialize_with do
new.merge(
@ -44,21 +42,10 @@ FactoryGirl.define do
'action_dispatch.request.request_parameters' => rails_post_params,
Grape::Env::GRAPE_REQUEST => grape_request,
Grape::Env::GRAPE_REQUEST_PARAMS => params,
Grape::Env::GRAPE_REQUEST_HEADERS => headers,
Grape::Env::RACK_REQUEST_FORM_HASH => post_params,
Grape::Env::API_ENDPOINT => grape_endpoint
).merge(other_env_params)
)
end
trait :prefixed_basic_headers do
other_env_params { {
'HTTP_VERSION' => 'HTTP/1.1',
'HTTP_CACHE_CONTROL' => 'max-age=0',
'HTTP_USER_AGENT' => 'Mozilla/5.0',
'HTTP_ACCEPT_LANGUAGE' => 'en-US'
} }
end
end
factory :grape_endpoint, class: Grape::Endpoint do
@ -97,20 +84,9 @@ FactoryGirl.define do
end
factory :grape_request, class: OpenStruct do
headers { {} }
initialize_with {
new(request_method: 'POST', path: '/api/1.0/users', headers: headers, params: { 'id' => '101001' })
new(request_method: 'POST', path: '/api/1.0/users', headers: {}, params: { 'id' => '101001' })
}
trait :basic_headers do
headers { {
'Version' => 'HTTP/1.1',
'Cache-Control' => 'max-age=0',
'User-Agent' => 'Mozilla/5.0',
'Accept-Language' => 'en-US'
} }
end
end
factory :app do

View File

@ -1,4 +1,3 @@
require 'tempfile'
class RailsApp < Rails::Application
RailsLogger = Class.new Logger
config.logger = RailsLogger.new(Tempfile.new '')

View File

@ -1,65 +0,0 @@
require 'spec_helper'
describe Grape::Middleware::Logger, type: :integration do
let(:app) { build :app }
subject { described_class.new(app, options) }
let(:grape_endpoint) { build(:grape_endpoint) }
let(:env) { build(:expected_env, :prefixed_basic_headers, grape_endpoint: grape_endpoint) }
context ':all option is set to option headers' do
let(:options) { {
filter: build(:param_filter),
headers: :all,
logger: Logger.new(Tempfile.new('logger'))
} }
it 'all headers will be shown, headers will be sorted by name' do
expect(subject.logger).to receive(:info).with ''
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)
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 %Q( Headers: {"Accept-Language"=>"en-US", "Cache-Control"=>"max-age=0", "User-Agent"=>"Mozilla/5.0", "Version"=>"HTTP/1.1"})
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 'list of names ["User-Agent", "Cache-Control"] is set to option headers' do
let(:options) { {
filter: build(:param_filter),
headers: %w(User-Agent Cache-Control),
logger: Logger.new(Tempfile.new('logger'))
} }
it 'two headers will be shown' do
expect(subject.logger).to receive(:info).with ''
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)
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 %Q( Headers: {"Cache-Control"=>"max-age=0", "User-Agent"=>"Mozilla/5.0"})
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 'a single string "Cache-Control" is set to option headers' do
let(:options) { {
filter: build(:param_filter),
headers: 'Cache-Control',
logger: Logger.new(Tempfile.new('logger'))
} }
it 'only Cache-Control header will be shown' do
expect(subject.logger).to receive(:info).with ''
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)
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 %Q( Headers: {"Cache-Control"=>"max-age=0"})
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
end

View File

@ -11,28 +11,14 @@ describe Grape::Middleware::Logger, type: :integration do
let(:grape_endpoint) { build(:grape_endpoint) }
let(:env) { build(:expected_env, grape_endpoint: grape_endpoint) }
context 'when the option[:condensed] is false' do
let(:options) { { filter: build(:param_filter), logger: Logger.new(Tempfile.new('logger')), condensed: false } }
it 'logs all parts of the request on multiple lines' do
expect(subject.logger).to receive(:info).with ''
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)
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
it 'logs all parts of the request' do
expect(subject.logger).to receive(:info).with ''
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)
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
context 'when an exception occurs' do

View File

@ -42,27 +42,14 @@ describe Grape::Middleware::Logger, type: :rails_integration do
end
end
context 'when the option[:condensed] is false' do
let(:options) { { condensed: false } }
it 'logs all parts of the request on multiple lines' do
expect(subject.logger).to receive(:info).with ''
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)
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
it 'logs all parts of the request' do
expect(subject.logger).to receive(:info).with ''
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)
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
describe 'the "processing by" section' do

View File

@ -1,57 +0,0 @@
require 'spec_helper'
describe Grape::Middleware::Logger do
let(:app) { double('app') }
subject { described_class.new(app, options) }
describe '#headers' do
let(:grape_request) { build :grape_request, :basic_headers }
let(:env) { build :expected_env, grape_request: grape_request }
before { subject.instance_variable_set(:@env, env) }
context 'when @options[:headers] has a symbol :all' do
let(:options) { { headers: :all, logger: Object.new } }
it 'all request headers should be retrieved' do
expect(subject.headers.fetch('Accept-Language')).to eq('en-US')
expect(subject.headers.fetch('Cache-Control')).to eq('max-age=0')
expect(subject.headers.fetch('User-Agent')).to eq('Mozilla/5.0')
expect(subject.headers.fetch('Version')).to eq('HTTP/1.1')
end
end
context 'when @options[:headers] is a string "user-agent"' do
let(:options) { { headers: 'user-agent', logger: Object.new } }
it 'only "User-Agent" should be retrieved' do
expect(subject.headers.fetch('User-Agent')).to eq('Mozilla/5.0')
expect(subject.headers.length).to eq(1)
end
end
context 'when @options[:headers] is an array of ["user-agent", "Cache-Control", "Unknown"]' do
let(:options) { { headers: %w(user-agent Cache-Control Unknown), logger: Object.new } }
it '"User-Agent" and "Cache-Control" should be retrieved' do
expect(subject.headers.fetch('Cache-Control')).to eq('max-age=0')
expect(subject.headers.fetch('User-Agent')).to eq('Mozilla/5.0')
end
it '"Unknown" name does not make any effect' do
expect(subject.headers.length).to eq(2)
end
end
end
describe '#headers if no request header' do
let(:env) { build :expected_env }
before { subject.instance_variable_set(:@env, env) }
context 'when @options[:headers] is set, but no request header is there' do
let(:options) { { headers: %w(user-agent Cache-Control), logger: Object.new } }
it 'subject.headers should return empty hash' do
expect(subject.headers.length).to eq(0)
end
end
end
end