grape-middleware-lograge 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.travis.yml +7 -0
- data/Gemfile +10 -0
- data/LICENSE.txt +22 -0
- data/README.md +81 -0
- data/Rakefile +15 -0
- data/grape-middleware-lograge.gemspec +25 -0
- data/lib/grape/middleware/lograge.rb +113 -0
- data/lib/grape/middleware/lograge/railtie.rb +8 -0
- data/lib/lograge/formatters/rails_logger.rb +39 -0
- data/spec/factories.rb +90 -0
- data/spec/fixtures/rails_app.rb +8 -0
- data/spec/integration/lib/grape/middleware/lograge_spec.rb +55 -0
- data/spec/integration_rails/lib/grape/middleware/lograge_spec.rb +54 -0
- data/spec/lib/grape/middleware/lograge_spec.rb +107 -0
- data/spec/rails_helper.rb +4 -0
- data/spec/spec_helper.rb +13 -0
- metadata +152 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: dd7eb695c21bda678be84d2daf06a7eb2c964668
|
4
|
+
data.tar.gz: bee831a1a57cfcf2dd0291722f78bf29173b9486
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b37a71953c1d02595cecf6c45417e2e050ebcb4959411c320db4a1fbc1a3bb68ba73bbf4972164a75635d9e15fe95ba6600e733c0a7e0c16931da983f846ae9d
|
7
|
+
data.tar.gz: 61ff8930c61798b7ba78a22ac8140548b6d38656948f27901b65870d8c07a951fdff50d0bfce35685b1bc655c7080332980d293adc58b6fc0eb7612bc06b6b05
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2015 Ryan Buckley
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
# A logger for Grape apps
|
2
|
+
[](https://codeclimate.com/github/ridiculous/grape-middleware-logger) [](http://badge.fury.io/rb/grape-middleware-logger)
|
3
|
+
[](https://travis-ci.org/ridiculous/grape-middleware-logger)
|
4
|
+
|
5
|
+
Logs:
|
6
|
+
* Request path
|
7
|
+
* Parameters
|
8
|
+
* Endpoint class name and handler
|
9
|
+
* Response status
|
10
|
+
* Duration of the request
|
11
|
+
* Exceptions
|
12
|
+
* Error responses from `error!`
|
13
|
+
|
14
|
+
## Installation
|
15
|
+
|
16
|
+
Add this line to your application's Gemfile:
|
17
|
+
|
18
|
+
```ruby
|
19
|
+
gem 'grape', '>= 0.14.0'
|
20
|
+
gem 'grape-middleware-logger'
|
21
|
+
```
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
```ruby
|
25
|
+
class API < Grape::API
|
26
|
+
# @note Make sure this above you're first +mount+
|
27
|
+
use Grape::Middleware::Lograge
|
28
|
+
end
|
29
|
+
```
|
30
|
+
|
31
|
+
Server requests will be logged to STDOUT by default.
|
32
|
+
|
33
|
+
## Custom setup
|
34
|
+
Customize the logging by passing the `filter` option. Example using parameter sanitization:
|
35
|
+
```ruby
|
36
|
+
use Grape::Middleware::Lograge, {
|
37
|
+
filter: CustomFilter.new
|
38
|
+
}
|
39
|
+
```
|
40
|
+
|
41
|
+
The `filter` option can be any object that responds to `.filter(params_hash)`
|
42
|
+
|
43
|
+
## Example output
|
44
|
+
Get
|
45
|
+
```
|
46
|
+
Started GET "/v1/reports/101" at 2015-12-11 15:40:51 -0800
|
47
|
+
Processing by ReportsAPI#reports/:id
|
48
|
+
Parameters: {"id"=>"101"}
|
49
|
+
Completed 200 in 6.29ms
|
50
|
+
```
|
51
|
+
Error
|
52
|
+
```
|
53
|
+
Started POST "/v1/reports" at 2015-12-11 15:42:33 -0800
|
54
|
+
Processing by ReportsAPI#reports
|
55
|
+
Parameters: {"name"=>"foo", "password"=>"[FILTERED]"}
|
56
|
+
Error: {:error=>"undefined something something bad", :detail=>"Whoops"}
|
57
|
+
Completed 422 in 6.29ms
|
58
|
+
```
|
59
|
+
|
60
|
+
## Using Rails?
|
61
|
+
`Rails.application.config.filter_parameters` will be used automatically as the default param filterer.
|
62
|
+
|
63
|
+
## Rack
|
64
|
+
|
65
|
+
If you're using the `rackup` command to run your server in development, pass the `-q` flag to silence the default rack logger.
|
66
|
+
|
67
|
+
## Credits
|
68
|
+
|
69
|
+
This code is forked from [grape-middleware-logger](https://github.com/ridiculous/grape-middleware-logger).
|
70
|
+
|
71
|
+
Big thanks to jadent's question/answer on [stackoverflow](http://stackoverflow.com/questions/25048163/grape-using-error-and-grapemiddleware-after-callback)
|
72
|
+
for easily logging error responses. Borrowed some motivation from the [grape_logging](https://github.com/aserafin/grape_logging) gem
|
73
|
+
and would love to see these two consolidated at some point.
|
74
|
+
|
75
|
+
## Contributing
|
76
|
+
|
77
|
+
1. Fork it ( https://github.com/tchak/grape-middleware-lograge/fork )
|
78
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
79
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
80
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
81
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'rspec/core/rake_task'
|
2
|
+
|
3
|
+
RSpec::Core::RakeTask.new(:spec) do |config|
|
4
|
+
config.pattern = 'spec/lib/**/*_spec.rb'
|
5
|
+
end
|
6
|
+
|
7
|
+
RSpec::Core::RakeTask.new(:integration) do |config|
|
8
|
+
config.pattern = 'spec/integration/**/*_spec.rb'
|
9
|
+
end
|
10
|
+
|
11
|
+
RSpec::Core::RakeTask.new(:integration_rails) do |config|
|
12
|
+
config.pattern = 'spec/integration_rails/**/*_spec.rb'
|
13
|
+
end
|
14
|
+
|
15
|
+
task default: [:spec, :integration, :integration_rails]
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
Gem::Specification.new do |spec|
|
4
|
+
spec.name = 'grape-middleware-lograge'
|
5
|
+
spec.version = '1.0.0'
|
6
|
+
spec.platform = Gem::Platform::RUBY
|
7
|
+
spec.authors = ['Ryan Buckley', 'Paul Chavard']
|
8
|
+
spec.email = ['arebuckley@gmail.com', 'paul+github@chavard.net']
|
9
|
+
spec.summary = %q{A logger for the Grape framework}
|
10
|
+
spec.description = %q{Logging middleware for the Grape framework, that uses Lograge}
|
11
|
+
spec.homepage = 'https://github.com/tchak/grape-middleware-lograge'
|
12
|
+
spec.license = 'MIT'
|
13
|
+
|
14
|
+
spec.files = `git ls-files -z`.split("\x0")
|
15
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
16
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
17
|
+
spec.require_paths = ['lib']
|
18
|
+
|
19
|
+
spec.add_dependency 'grape', '>= 0.14', '< 1'
|
20
|
+
spec.add_dependency 'lograge', '~> 0.3'
|
21
|
+
|
22
|
+
spec.add_development_dependency 'bundler', '~> 1.7'
|
23
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
24
|
+
spec.add_development_dependency 'rspec', '>= 3.2', '< 4'
|
25
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
require 'grape'
|
2
|
+
|
3
|
+
class Grape::Middleware::Lograge < Grape::Middleware::Globals
|
4
|
+
BACKSLASH = '/'.freeze
|
5
|
+
|
6
|
+
STATUS_CODE_TO_SYMBOL = Rack::Utils::SYMBOL_TO_STATUS_CODE.each_with_object({}) do |(symbol, status_code), hash|
|
7
|
+
hash[status_code] = symbol
|
8
|
+
end
|
9
|
+
|
10
|
+
class << self
|
11
|
+
attr_accessor :filter
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(_, options = {})
|
15
|
+
super
|
16
|
+
@options[:filter] ||= self.class.filter
|
17
|
+
end
|
18
|
+
|
19
|
+
def before
|
20
|
+
super
|
21
|
+
|
22
|
+
ActiveSupport::Notifications.instrument("start_processing.grape", raw_payload)
|
23
|
+
end
|
24
|
+
|
25
|
+
# @note Error and exception handling are required for the +after+ hooks
|
26
|
+
# Exceptions are logged as a 500 status and re-raised
|
27
|
+
# Other "errors" are caught, logged and re-thrown
|
28
|
+
def call!(env)
|
29
|
+
@env = env
|
30
|
+
|
31
|
+
before
|
32
|
+
|
33
|
+
ActiveSupport::Notifications.instrument("process_action.grape", raw_payload) do |payload|
|
34
|
+
error = catch(:error) do
|
35
|
+
begin
|
36
|
+
@app_response = @app.call(@env)
|
37
|
+
rescue => e
|
38
|
+
after_exception(payload, e)
|
39
|
+
raise e
|
40
|
+
end
|
41
|
+
|
42
|
+
nil
|
43
|
+
end
|
44
|
+
|
45
|
+
if error
|
46
|
+
after_failure(payload, error)
|
47
|
+
throw(:error, error)
|
48
|
+
else
|
49
|
+
after(payload, response.status)
|
50
|
+
end
|
51
|
+
|
52
|
+
@app_response
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def after(payload, status)
|
57
|
+
payload[:status] = status
|
58
|
+
payload[:format] = env['api.format']
|
59
|
+
payload[:version] = env['api.version']
|
60
|
+
end
|
61
|
+
|
62
|
+
def after_exception(payload, e)
|
63
|
+
class_name = e.class.name
|
64
|
+
status = e.respond_to?(:status) ? e.status : 500
|
65
|
+
|
66
|
+
payload[:exception] = [class_name, e.message]
|
67
|
+
payload[:backtrace] = e.backtrace
|
68
|
+
|
69
|
+
ActionDispatch::ExceptionWrapper.rescue_responses[class_name] = STATUS_CODE_TO_SYMBOL[status]
|
70
|
+
end
|
71
|
+
|
72
|
+
def after_failure(payload, error)
|
73
|
+
after(payload, error[:status])
|
74
|
+
end
|
75
|
+
|
76
|
+
def parameters
|
77
|
+
request_params = env[Grape::Env::GRAPE_REQUEST_PARAMS].to_hash
|
78
|
+
request_params.merge!(env['action_dispatch.request.request_parameters'.freeze] || {}) # for Rails
|
79
|
+
if @options[:filter]
|
80
|
+
@options[:filter].filter(request_params)
|
81
|
+
else
|
82
|
+
request_params
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def raw_payload
|
87
|
+
{
|
88
|
+
params: parameters.merge(
|
89
|
+
'action' => action_name || 'index',
|
90
|
+
'controller' => controller
|
91
|
+
),
|
92
|
+
method: env[Grape::Env::GRAPE_REQUEST].request_method,
|
93
|
+
path: env[Grape::Env::GRAPE_REQUEST].path,
|
94
|
+
user_agent: env['HTTP_USER_AGENT'],
|
95
|
+
request_id: env['action_dispatch.request_id'],
|
96
|
+
remote_ip: env['action_dispatch.remote_ip']
|
97
|
+
}
|
98
|
+
end
|
99
|
+
|
100
|
+
def endpoint
|
101
|
+
env[Grape::Env::API_ENDPOINT]
|
102
|
+
end
|
103
|
+
|
104
|
+
def controller
|
105
|
+
endpoint.options[:for].to_s
|
106
|
+
end
|
107
|
+
|
108
|
+
def action_name
|
109
|
+
endpoint.options[:path].map { |path| path.to_s.sub(BACKSLASH, '') }.join(BACKSLASH)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
require_relative 'lograge/railtie' if defined?(Rails)
|
@@ -0,0 +1,8 @@
|
|
1
|
+
class Grape::Middleware::Lograge::Railtie < Rails::Railtie
|
2
|
+
initializer 'grape.middleware.lograge', after: :load_config_initializers do
|
3
|
+
Grape::Middleware::Lograge.filter = ActionDispatch::Http::ParameterFilter.new Rails.application.config.filter_parameters
|
4
|
+
|
5
|
+
require 'lograge'
|
6
|
+
::Lograge::RequestLogSubscriber.attach_to :grape
|
7
|
+
end
|
8
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Lograge
|
2
|
+
module Formatters
|
3
|
+
class RailsLogger
|
4
|
+
INTERNAL_PARAMS = %w(controller action format _method only_path)
|
5
|
+
|
6
|
+
def call(data)
|
7
|
+
lines = start_processing(data)
|
8
|
+
lines << completed(data)
|
9
|
+
|
10
|
+
lines.join("\n")
|
11
|
+
end
|
12
|
+
|
13
|
+
def start_processing(data)
|
14
|
+
params = data[:params].except(*INTERNAL_PARAMS)
|
15
|
+
format = data[:format]
|
16
|
+
format = format.to_s.upcase if format.is_a?(Symbol)
|
17
|
+
|
18
|
+
lines = []
|
19
|
+
|
20
|
+
lines << "Processing by #{data[:controller]}##{data[:action]} as #{format}"
|
21
|
+
lines << " Parameters: #{params.inspect}" unless params.empty?
|
22
|
+
|
23
|
+
lines
|
24
|
+
end
|
25
|
+
|
26
|
+
def completed(data)
|
27
|
+
status = data[:status]
|
28
|
+
|
29
|
+
puts ActionDispatch::ExceptionWrapper.rescue_responses
|
30
|
+
|
31
|
+
if data[:error]
|
32
|
+
"Exception #{status} #{data[:error]} after #{data[:duration].round}ms"
|
33
|
+
else
|
34
|
+
"Completed #{status} #{Rack::Utils::HTTP_STATUS_CODES[status]} in #{data[:duration].round}ms"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/spec/factories.rb
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
FactoryGirl.define do
|
2
|
+
class ExpectedEnv < Hash
|
3
|
+
attr_accessor :grape_request, :params, :post_params, :grape_endpoint
|
4
|
+
end
|
5
|
+
|
6
|
+
class ParamFilter
|
7
|
+
def filter(opts)
|
8
|
+
opts.each_pair { |key, val| val[0..-1] = '[FILTERED]' if key == 'password' }
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class TestAPI
|
13
|
+
end
|
14
|
+
|
15
|
+
class App
|
16
|
+
attr_accessor :response
|
17
|
+
|
18
|
+
def call(_env)
|
19
|
+
response
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
factory :param_filter
|
24
|
+
|
25
|
+
require 'rack/rewindable_input'
|
26
|
+
|
27
|
+
factory :expected_env do
|
28
|
+
grape_request { build :grape_request }
|
29
|
+
params { grape_request.params }
|
30
|
+
post_params { { 'name' => 'foo', 'password' => 'access' } }
|
31
|
+
grape_endpoint { build(:grape_endpoint) }
|
32
|
+
|
33
|
+
initialize_with do
|
34
|
+
new.merge(
|
35
|
+
'REQUEST_METHOD' => 'POST',
|
36
|
+
'PATH_INFO' => '/api/1.0/users',
|
37
|
+
'action_dispatch.request.request_parameters' => post_params,
|
38
|
+
Grape::Env::GRAPE_REQUEST => grape_request,
|
39
|
+
Grape::Env::GRAPE_REQUEST_PARAMS => params,
|
40
|
+
Grape::Env::API_ENDPOINT => grape_endpoint
|
41
|
+
)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
factory :grape_endpoint, class: Grape::Endpoint do
|
46
|
+
settings { Grape::Util::InheritableSetting.new }
|
47
|
+
options {
|
48
|
+
{
|
49
|
+
path: [:users],
|
50
|
+
method: 'get',
|
51
|
+
for: TestAPI
|
52
|
+
}
|
53
|
+
}
|
54
|
+
|
55
|
+
initialize_with { new(settings, options) }
|
56
|
+
|
57
|
+
trait :complex do
|
58
|
+
options {
|
59
|
+
{
|
60
|
+
path: ['/users/:name/profile'],
|
61
|
+
method: 'put',
|
62
|
+
for: TestAPI
|
63
|
+
}
|
64
|
+
}
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
factory :namespaced_endpoint, parent: :grape_endpoint do
|
69
|
+
initialize_with do
|
70
|
+
new(settings, options).tap do |me|
|
71
|
+
me.namespace_stackable(:namespace, Grape::Namespace.new('/admin', {}))
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
factory :app_response, class: Rack::Response do
|
77
|
+
initialize_with { new('Hello World', 200, {}) }
|
78
|
+
end
|
79
|
+
|
80
|
+
factory :grape_request, class: OpenStruct do
|
81
|
+
initialize_with {
|
82
|
+
new(request_method: 'POST', path: '/api/1.0/users', headers: {}, params: { 'id' => '101001' })
|
83
|
+
}
|
84
|
+
end
|
85
|
+
|
86
|
+
factory :app do
|
87
|
+
response { build :app_response }
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Grape::Middleware::Lograge, type: :integration do
|
4
|
+
let(:app) { build :app }
|
5
|
+
let(:options) { { filter: build(:param_filter) } }
|
6
|
+
|
7
|
+
subject { described_class.new(app, options) }
|
8
|
+
|
9
|
+
let(:app_response) { build :app_response }
|
10
|
+
let(:grape_request) { build :grape_request }
|
11
|
+
let(:grape_endpoint) { build(:grape_endpoint) }
|
12
|
+
let(:env) { build(:expected_env, grape_endpoint: grape_endpoint) }
|
13
|
+
|
14
|
+
it 'logs all parts of the request' do
|
15
|
+
pending('test payload object')
|
16
|
+
expect(subject.logger).to receive(:info).with ''
|
17
|
+
expect(subject.logger).to receive(:info).with %Q(Started POST "/api/1.0/users" at #{subject.start_time})
|
18
|
+
expect(subject.logger).to receive(:info).with %Q(Processing by TestAPI#users)
|
19
|
+
expect(subject.logger).to receive(:info).with %Q( Parameters: {"id"=>"101001", "name"=>"foo", "password"=>"[FILTERED]"})
|
20
|
+
expect(subject.logger).to receive(:info).with /Completed 200 in \d+.\d+ms/
|
21
|
+
expect(subject.logger).to receive(:info).with ''
|
22
|
+
subject.call!(env)
|
23
|
+
end
|
24
|
+
|
25
|
+
describe 'the "processing by" section' do
|
26
|
+
before { subject.call!(env) }
|
27
|
+
|
28
|
+
context 'namespacing' do
|
29
|
+
let(:grape_endpoint) { build(:namespaced_endpoint) }
|
30
|
+
|
31
|
+
it 'ignores the namespacing' do
|
32
|
+
expect(subject.controller).to eq 'TestAPI'
|
33
|
+
expect(subject.action_name).to eq 'users'
|
34
|
+
end
|
35
|
+
|
36
|
+
context 'with more complex route' do
|
37
|
+
let(:grape_endpoint) { build(:namespaced_endpoint, :complex) }
|
38
|
+
|
39
|
+
it 'only escapes the first slash and leaves the rest of the untouched' do
|
40
|
+
expect(subject.controller).to eq 'TestAPI'
|
41
|
+
expect(subject.action_name).to eq 'users/:name/profile'
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
context 'with more complex route' do
|
47
|
+
let(:grape_endpoint) { build(:grape_endpoint, :complex) }
|
48
|
+
|
49
|
+
it 'only escapes the first slash and leaves the rest of the untouched' do
|
50
|
+
expect(subject.action_name).to eq 'users/:name/profile'
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'rails_helper'
|
2
|
+
|
3
|
+
describe Grape::Middleware::Lograge, type: :rails_integration do
|
4
|
+
let(:app) { build :app }
|
5
|
+
let(:options) { {} }
|
6
|
+
|
7
|
+
subject { described_class.new(app, options) }
|
8
|
+
|
9
|
+
let(:app_response) { build :app_response }
|
10
|
+
let(:grape_request) { build :grape_request }
|
11
|
+
let(:grape_endpoint) { build(:grape_endpoint) }
|
12
|
+
let(:env) { build(:expected_env, grape_endpoint: grape_endpoint) }
|
13
|
+
|
14
|
+
it 'logs all parts of the request' do
|
15
|
+
pending('test payload object')
|
16
|
+
expect(subject.logger).to receive(:info).with ''
|
17
|
+
expect(subject.logger).to receive(:info).with %Q(Started POST "/api/1.0/users" at #{subject.start_time})
|
18
|
+
expect(subject.logger).to receive(:info).with %Q(Processing by TestAPI#users)
|
19
|
+
expect(subject.logger).to receive(:info).with %Q( Parameters: {"id"=>"101001", "name"=>"foo", "password"=>"[FILTERED]"})
|
20
|
+
expect(subject.logger).to receive(:info).with /Completed 200 in \d+.\d+ms/
|
21
|
+
expect(subject.logger).to receive(:info).with ''
|
22
|
+
subject.call!(env)
|
23
|
+
end
|
24
|
+
|
25
|
+
describe 'the "processing by" section' do
|
26
|
+
before { subject.call!(env) }
|
27
|
+
|
28
|
+
context 'namespacing' do
|
29
|
+
let(:grape_endpoint) { build(:namespaced_endpoint) }
|
30
|
+
|
31
|
+
it 'ignores the namespacing' do
|
32
|
+
expect(subject.controller).to eq 'TestAPI'
|
33
|
+
expect(subject.action_name).to eq 'users'
|
34
|
+
end
|
35
|
+
|
36
|
+
context 'with more complex route' do
|
37
|
+
let(:grape_endpoint) { build(:namespaced_endpoint, :complex) }
|
38
|
+
|
39
|
+
it 'only escapes the first slash and leaves the rest of the untouched' do
|
40
|
+
expect(subject.controller).to eq 'TestAPI'
|
41
|
+
expect(subject.action_name).to eq 'users/:name/profile'
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
context 'with more complex route' do
|
47
|
+
let(:grape_endpoint) { build(:grape_endpoint, :complex) }
|
48
|
+
|
49
|
+
it 'only escapes the first slash and leaves the rest of the untouched' do
|
50
|
+
expect(subject.action_name).to eq 'users/:name/profile'
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Grape::Middleware::Lograge do
|
4
|
+
let(:app) { double('app') }
|
5
|
+
let(:options) { { filter: build(:param_filter), logger: Object.new } }
|
6
|
+
|
7
|
+
subject { described_class.new(app, options) }
|
8
|
+
|
9
|
+
let(:app_response) { build :app_response }
|
10
|
+
let(:grape_request) { build :grape_request }
|
11
|
+
let(:env) { build(:expected_env) }
|
12
|
+
|
13
|
+
describe '#call!' do
|
14
|
+
context 'when calling the app results in an error response' do
|
15
|
+
let(:error) { { status: 400 } }
|
16
|
+
|
17
|
+
it 'calls +after_failure+ and rethrows the error' do
|
18
|
+
expect(app).to receive(:call).with(env).and_throw(:error, error)
|
19
|
+
expect(subject).to receive(:before)
|
20
|
+
expect(subject).to receive(:after_failure).with(hash_including(method: 'POST'), error)
|
21
|
+
expect(subject).to receive(:throw).with(:error, error)
|
22
|
+
subject.call!(env)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
context 'when there is no error' do
|
27
|
+
it 'calls +after+ with the correct status' do
|
28
|
+
expect(app).to receive(:call).with(env).and_return(app_response)
|
29
|
+
expect(subject).to receive(:before).and_call_original
|
30
|
+
expect(subject).to receive(:after).with(hash_including(method: 'POST'), 200)
|
31
|
+
subject.call!(env)
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'returns the @app_response' do
|
35
|
+
expect(app).to receive(:call).with(env).and_return(app_response)
|
36
|
+
allow(subject).to receive(:before)
|
37
|
+
allow(subject).to receive(:after)
|
38
|
+
expect(subject.call!(env)).to eq app_response
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
context 'when calling the app results in an array response' do
|
43
|
+
let(:app_response) { [401, {}, []] }
|
44
|
+
|
45
|
+
it 'calls +after+ with the correct status' do
|
46
|
+
expect(app).to receive(:call).with(env).and_return(app_response)
|
47
|
+
expect(subject).to receive(:before).and_call_original
|
48
|
+
expect(subject).to receive(:after).with(hash_including(method: 'POST'), 401)
|
49
|
+
subject.call!(env)
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'returns the @app_response' do
|
53
|
+
expect(app).to receive(:call).with(env).and_return(app_response)
|
54
|
+
allow(subject).to receive(:before)
|
55
|
+
allow(subject).to receive(:after)
|
56
|
+
expect(subject.call!(env)).to eq app_response
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe '#after_failure' do
|
62
|
+
let(:error) { { status: 403 } }
|
63
|
+
let(:payload) { {} }
|
64
|
+
|
65
|
+
it 'calls +after+ with the :status' do
|
66
|
+
expect(subject).to receive(:after).with(payload, 403).and_call_original
|
67
|
+
expect(subject).to receive(:env).twice.and_return(env)
|
68
|
+
subject.after_failure(payload, error)
|
69
|
+
expect(payload[:status]).to eq(403)
|
70
|
+
end
|
71
|
+
|
72
|
+
context 'when :message is set in the error object' do
|
73
|
+
let(:error) { { message: 'Oops, not found' } }
|
74
|
+
|
75
|
+
it 'logs the error message' do
|
76
|
+
pending('implement error messages')
|
77
|
+
expect(subject).to receive(:after).with(payload, nil).and_call_original
|
78
|
+
expect(subject).to receive(:env).twice.and_return(env)
|
79
|
+
subject.after_failure(payload, error)
|
80
|
+
expect(payload[:message]).to match(Regexp.new(error[:message]))
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
describe '#parameters' do
|
86
|
+
before { subject.instance_variable_set(:@env, env) }
|
87
|
+
|
88
|
+
context 'when @options[:filter] is set' do
|
89
|
+
it 'calls +filter+ with the raw parameters' do
|
90
|
+
expect(subject.options[:filter]).to receive(:filter).with({ "id" => '101001', "name" => "foo", "password" => "access" })
|
91
|
+
subject.parameters
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'returns the filtered results' do
|
95
|
+
expect(subject.parameters).to eq({ "id" => '101001', "name" => "foo", "password" => "[FILTERED]" })
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
context 'when @options[:filter] is nil' do
|
100
|
+
let(:options) { {} }
|
101
|
+
|
102
|
+
it 'returns the params extracted out of @env' do
|
103
|
+
expect(subject.parameters).to eq({ "id" => '101001', "name" => "foo", "password" => "access" })
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
2
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
3
|
+
|
4
|
+
require 'ostruct'
|
5
|
+
require 'factory_girl'
|
6
|
+
require 'grape/middleware/lograge'
|
7
|
+
|
8
|
+
FactoryGirl.find_definitions
|
9
|
+
|
10
|
+
RSpec.configure do |config|
|
11
|
+
config.raise_errors_for_deprecations!
|
12
|
+
config.include FactoryGirl::Syntax::Methods
|
13
|
+
end
|
metadata
ADDED
@@ -0,0 +1,152 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: grape-middleware-lograge
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ryan Buckley
|
8
|
+
- Paul Chavard
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2015-12-24 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: grape
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - ">="
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '0.14'
|
21
|
+
- - "<"
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: '1'
|
24
|
+
type: :runtime
|
25
|
+
prerelease: false
|
26
|
+
version_requirements: !ruby/object:Gem::Requirement
|
27
|
+
requirements:
|
28
|
+
- - ">="
|
29
|
+
- !ruby/object:Gem::Version
|
30
|
+
version: '0.14'
|
31
|
+
- - "<"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1'
|
34
|
+
- !ruby/object:Gem::Dependency
|
35
|
+
name: lograge
|
36
|
+
requirement: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0.3'
|
41
|
+
type: :runtime
|
42
|
+
prerelease: false
|
43
|
+
version_requirements: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0.3'
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: bundler
|
50
|
+
requirement: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.7'
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.7'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: rake
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '10.0'
|
69
|
+
type: :development
|
70
|
+
prerelease: false
|
71
|
+
version_requirements: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '10.0'
|
76
|
+
- !ruby/object:Gem::Dependency
|
77
|
+
name: rspec
|
78
|
+
requirement: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '3.2'
|
83
|
+
- - "<"
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '4'
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
requirements:
|
90
|
+
- - ">="
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: '3.2'
|
93
|
+
- - "<"
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '4'
|
96
|
+
description: Logging middleware for the Grape framework, that uses Lograge
|
97
|
+
email:
|
98
|
+
- arebuckley@gmail.com
|
99
|
+
- paul+github@chavard.net
|
100
|
+
executables: []
|
101
|
+
extensions: []
|
102
|
+
extra_rdoc_files: []
|
103
|
+
files:
|
104
|
+
- ".gitignore"
|
105
|
+
- ".travis.yml"
|
106
|
+
- Gemfile
|
107
|
+
- LICENSE.txt
|
108
|
+
- README.md
|
109
|
+
- Rakefile
|
110
|
+
- grape-middleware-lograge.gemspec
|
111
|
+
- lib/grape/middleware/lograge.rb
|
112
|
+
- lib/grape/middleware/lograge/railtie.rb
|
113
|
+
- lib/lograge/formatters/rails_logger.rb
|
114
|
+
- spec/factories.rb
|
115
|
+
- spec/fixtures/rails_app.rb
|
116
|
+
- spec/integration/lib/grape/middleware/lograge_spec.rb
|
117
|
+
- spec/integration_rails/lib/grape/middleware/lograge_spec.rb
|
118
|
+
- spec/lib/grape/middleware/lograge_spec.rb
|
119
|
+
- spec/rails_helper.rb
|
120
|
+
- spec/spec_helper.rb
|
121
|
+
homepage: https://github.com/tchak/grape-middleware-lograge
|
122
|
+
licenses:
|
123
|
+
- MIT
|
124
|
+
metadata: {}
|
125
|
+
post_install_message:
|
126
|
+
rdoc_options: []
|
127
|
+
require_paths:
|
128
|
+
- lib
|
129
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
130
|
+
requirements:
|
131
|
+
- - ">="
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: '0'
|
134
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
requirements: []
|
140
|
+
rubyforge_project:
|
141
|
+
rubygems_version: 2.4.5.1
|
142
|
+
signing_key:
|
143
|
+
specification_version: 4
|
144
|
+
summary: A logger for the Grape framework
|
145
|
+
test_files:
|
146
|
+
- spec/factories.rb
|
147
|
+
- spec/fixtures/rails_app.rb
|
148
|
+
- spec/integration/lib/grape/middleware/lograge_spec.rb
|
149
|
+
- spec/integration_rails/lib/grape/middleware/lograge_spec.rb
|
150
|
+
- spec/lib/grape/middleware/lograge_spec.rb
|
151
|
+
- spec/rails_helper.rb
|
152
|
+
- spec/spec_helper.rb
|