grape-middleware-lograge 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Code Climate](https://codeclimate.com/github/ridiculous/grape-middleware-logger/badges/gpa.svg)](https://codeclimate.com/github/ridiculous/grape-middleware-logger) [![Gem Version](https://badge.fury.io/rb/grape-middleware-logger.svg)](http://badge.fury.io/rb/grape-middleware-logger)
|
3
|
+
[![Build Status](https://travis-ci.org/ridiculous/grape-middleware-logger.svg)](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
|