rack-request_logger 0.0.4
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.
- data/lib/rack/request_logger.rb +187 -0
- data/lib/rack/request_logger_version.rb +5 -0
- data/lib/rack/tasks/request_log_readers.rb +111 -0
- data/spec/rack/request_logger_spec.rb +395 -0
- data/spec/spec_helper.rb +8 -0
- metadata +104 -0
@@ -0,0 +1,187 @@
|
|
1
|
+
# Set Rack::RequestLogger.log_handler to an object with a write(params_hash) method
|
2
|
+
# This class can be extended and have the following methods overriden as desired:
|
3
|
+
#
|
4
|
+
# For logging more fields than the defaults, override:
|
5
|
+
# add_log_params(env_hash, status, response_headers, response_body, params_hash) # => modifies params_hash
|
6
|
+
#
|
7
|
+
# For filtering information from data to be logged, override:
|
8
|
+
# filter_request_headers(hash) # => returns hash with filtered values
|
9
|
+
# filter_request_body(string) # => returns filterd string
|
10
|
+
# filter_response_headers(hash) # => returns hash with filtered values
|
11
|
+
# filter_response_body(string) # => returns filterd string
|
12
|
+
#
|
13
|
+
# For special error handling, override:
|
14
|
+
# app_error(env_hash) # => returns an error to be logged
|
15
|
+
# The default app_error will handle errors thrown by Sinatra
|
16
|
+
# To set your own errors for logging,
|
17
|
+
# In Sintatra: env['request_logger.error'] = error
|
18
|
+
# In Rails: request.env['request_logger.error'] = error
|
19
|
+
# Or, override the method and handle it as desired.
|
20
|
+
#
|
21
|
+
# To control when logging happens, override:
|
22
|
+
# log_request?(env_hash, request, status, response_headers, response_body) # => returns boolean
|
23
|
+
# By default, this is always true
|
24
|
+
|
25
|
+
require 'rack'
|
26
|
+
require 'rack/tasks/request_log_readers'
|
27
|
+
|
28
|
+
module Rack
|
29
|
+
class RequestLogger
|
30
|
+
class << self
|
31
|
+
def log_handler=(log_handler)
|
32
|
+
@log_handler = log_handler
|
33
|
+
#Explicity set the logger on this class so everything works when this is inherited from
|
34
|
+
Rack::RequestLogger.instance_variable_set(:@log_handler, log_handler)
|
35
|
+
end
|
36
|
+
|
37
|
+
def log_handler
|
38
|
+
@log_handler
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def initialize(app)
|
43
|
+
@app = app
|
44
|
+
end
|
45
|
+
|
46
|
+
def call(env)
|
47
|
+
begin
|
48
|
+
request_time = Time.now
|
49
|
+
request = Rack::Request.new env
|
50
|
+
status, response_headers, response_body = @app.call(env)
|
51
|
+
rescue => e
|
52
|
+
#TODO: Should return a better message, but this should basically never happen anyway,
|
53
|
+
# since @app.call won't throw execeptions, but rather catches and wraps them
|
54
|
+
status = 500
|
55
|
+
response_headers = {}
|
56
|
+
response_body = e.message
|
57
|
+
error = e
|
58
|
+
end
|
59
|
+
|
60
|
+
if log_handler.respond_to?(:write) && log_request?(env, request, status, response_headers, response_body)
|
61
|
+
begin
|
62
|
+
params = {
|
63
|
+
:url => request.url,
|
64
|
+
:method => request.request_method,
|
65
|
+
:request_headers => format_header_hash(filter_request_headers(request_headers(env))),
|
66
|
+
:request_body => filter_request_body(read_stream(request.body)),
|
67
|
+
:request_time => request_time,
|
68
|
+
:status => status,
|
69
|
+
:response_headers => format_header_hash(filter_response_headers(response_headers)),
|
70
|
+
:response_body => filter_response_body(response_body_to_s(response_body)),
|
71
|
+
:response_time => Time.now
|
72
|
+
}
|
73
|
+
|
74
|
+
add_log_params(env, status, response_headers, response_body, params)
|
75
|
+
|
76
|
+
remote_host = remote_host(env)
|
77
|
+
params[:remote_host] = remote_host if remote_host
|
78
|
+
|
79
|
+
error ||= app_error(env)
|
80
|
+
params[:error] = format_error(error) if error
|
81
|
+
|
82
|
+
log_handler.write params
|
83
|
+
rescue => e
|
84
|
+
begin
|
85
|
+
log_handler.write(
|
86
|
+
#TODO: Log more things that are safe, or ensure they are safe first
|
87
|
+
:error => format_error(e)
|
88
|
+
)
|
89
|
+
rescue
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
[status, response_headers, response_body]
|
95
|
+
end
|
96
|
+
|
97
|
+
protected
|
98
|
+
|
99
|
+
def log_handler
|
100
|
+
self.class.log_handler
|
101
|
+
end
|
102
|
+
|
103
|
+
def read_stream(data)
|
104
|
+
return nil unless data
|
105
|
+
string = data.read
|
106
|
+
data.rewind
|
107
|
+
string
|
108
|
+
end
|
109
|
+
|
110
|
+
def request_headers(env)
|
111
|
+
hash = {}
|
112
|
+
hash['Content-Type'] = env['CONTENT_TYPE'] if env['CONTENT_TYPE']
|
113
|
+
hash['Content-Length'] = env['CONTENT_LENGTH'] if env['CONTENT_LENGTH']
|
114
|
+
|
115
|
+
env.each do |key, value|
|
116
|
+
if header = key[/http_(.+)/i, 1]
|
117
|
+
hash[header.downcase.gsub('_','-')] = value
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
hash
|
122
|
+
end
|
123
|
+
|
124
|
+
def remote_host(env)
|
125
|
+
env['REMOTE_HOST'] || env['REMOTE_ADDR']
|
126
|
+
end
|
127
|
+
|
128
|
+
def format_header_hash(hash)
|
129
|
+
hash.map { |k, v| "#{k}: #{v}" }.join "\n" if hash
|
130
|
+
end
|
131
|
+
|
132
|
+
def format_error(error)
|
133
|
+
"#{error.class}: #{error.message}\n#{error.backtrace.join("\n") if error.backtrace}"
|
134
|
+
end
|
135
|
+
|
136
|
+
def response_body_to_s(response_body)
|
137
|
+
if sinatra? response_body
|
138
|
+
response_body.first
|
139
|
+
elsif rails? response_body
|
140
|
+
response_body.body
|
141
|
+
elsif response_body.is_a? StringIO
|
142
|
+
read_stream response_body
|
143
|
+
else
|
144
|
+
response_body
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def sinatra?(response_body)
|
149
|
+
response_body.is_a?(Array) && response_body.first.is_a?(String)
|
150
|
+
end
|
151
|
+
|
152
|
+
#Check with string to avoid Rails dependency
|
153
|
+
def rails?(response_body)
|
154
|
+
response_body.class.to_s == 'ActionDispatch::Response'
|
155
|
+
end
|
156
|
+
|
157
|
+
|
158
|
+
#Template methods
|
159
|
+
def add_log_params(env, status, response_headers, response_body, params)
|
160
|
+
end
|
161
|
+
|
162
|
+
def filter_request_headers(hash)
|
163
|
+
hash
|
164
|
+
end
|
165
|
+
|
166
|
+
def filter_request_body(string)
|
167
|
+
string
|
168
|
+
end
|
169
|
+
|
170
|
+
def filter_response_headers(hash)
|
171
|
+
hash
|
172
|
+
end
|
173
|
+
|
174
|
+
def filter_response_body(string)
|
175
|
+
string
|
176
|
+
end
|
177
|
+
|
178
|
+
def app_error(env)
|
179
|
+
env['request_logger.error'] || env['sinatra.error']
|
180
|
+
end
|
181
|
+
|
182
|
+
def log_request?(env, request, status, response_headers, response_body)
|
183
|
+
true
|
184
|
+
end
|
185
|
+
|
186
|
+
end
|
187
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
#Defines tasks for printing latest log entries
|
2
|
+
#Requires Rack::RequestLogger.log_handler to inherit from ActiveRecord::Base
|
3
|
+
|
4
|
+
if defined?(namespace)
|
5
|
+
namespace :log do
|
6
|
+
class Rack::RequestLogger
|
7
|
+
module TaskHelpers
|
8
|
+
|
9
|
+
class << self
|
10
|
+
def print_entry(entry)
|
11
|
+
unless entry
|
12
|
+
puts 'No matching log entry found!'
|
13
|
+
exit 1
|
14
|
+
end
|
15
|
+
|
16
|
+
#No guarantee of Ruby 1.9.x or greater, so hashes may not be ordered
|
17
|
+
#Use arrays and each_slice instead
|
18
|
+
small_fields = [
|
19
|
+
:id, 'ID',
|
20
|
+
:request_id, 'GUID',
|
21
|
+
:user_id, 'User ID',
|
22
|
+
[:method, :url], 'Request',
|
23
|
+
:remote_host, 'Remote host',
|
24
|
+
:request_time, 'Request time',
|
25
|
+
:response_time, 'Response time',
|
26
|
+
:status, 'Status'
|
27
|
+
]
|
28
|
+
large_fields = [
|
29
|
+
:request_headers, 'Request headers',
|
30
|
+
:request_body, 'Request body',
|
31
|
+
:response_headers, 'Response headers',
|
32
|
+
:response_body, 'Response body',
|
33
|
+
:error, 'Error'
|
34
|
+
]
|
35
|
+
|
36
|
+
name_size = 0
|
37
|
+
small_fields.each_slice(2) { |field, name| name_size = name.size if name.size > name_size }
|
38
|
+
|
39
|
+
puts
|
40
|
+
printed = []
|
41
|
+
small_fields.each_slice(2) do |field, name|
|
42
|
+
print_field entry, "#{name}:".ljust(name_size + 1), field, false, printed
|
43
|
+
end
|
44
|
+
large_fields.each_slice(2) do |field, name|
|
45
|
+
print_field entry, name, field, true, printed
|
46
|
+
end
|
47
|
+
puts
|
48
|
+
|
49
|
+
custom_fields = entry.class.columns.map(&:name) - printed.map(&:to_s)
|
50
|
+
custom_fields.each { |field| print_field entry, field + ':', field }
|
51
|
+
puts
|
52
|
+
end
|
53
|
+
|
54
|
+
def print_field(entry, field_name, field, large = nil, printed = [])
|
55
|
+
return unless entry
|
56
|
+
|
57
|
+
field = Array(field)
|
58
|
+
values = []
|
59
|
+
field.each do |f|
|
60
|
+
if entry.respond_to?(f)
|
61
|
+
values << entry.send(f)
|
62
|
+
printed << f
|
63
|
+
end
|
64
|
+
end
|
65
|
+
value = values.join(' ')
|
66
|
+
|
67
|
+
unless value.nil? || value.to_s.empty?
|
68
|
+
if large || (large.nil? && value.to_s.include?("\n"))
|
69
|
+
s = '-' * field_name.length
|
70
|
+
puts "\n#{s}\n#{field_name}\n#{s}\n#{value}\n#{s}\n\n"
|
71
|
+
else
|
72
|
+
puts "#{field_name} #{value}"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def request_logger
|
78
|
+
Rack::RequestLogger.log_handler.tap do |logger|
|
79
|
+
unless !logger.nil? && logger.respond_to?(:ancestors) && logger.ancestors.map { |a| a.to_s }.include?('ActiveRecord::Base')
|
80
|
+
puts 'Must have Rack::RequestLogger.log_handler set to an ActiveRecord based class to use this task!'
|
81
|
+
exit 1
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def conditions(args, existing = nil)
|
87
|
+
conditions = []
|
88
|
+
conditions << "(#{existing})" if existing
|
89
|
+
conditions << "(#{args[:conditions]})" if args[:conditions]
|
90
|
+
conditions.empty? ? {} : { :conditions => conditions }
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
desc 'Print the most recent request'
|
95
|
+
task :request, [:conditions] => :environment do |task, args|
|
96
|
+
print_entry request_logger.last(conditions(args))
|
97
|
+
end
|
98
|
+
|
99
|
+
namespace :request do
|
100
|
+
%w(GET POST PUT DELETE).each do |method|
|
101
|
+
desc "Print the most recent #{method} request"
|
102
|
+
task method.downcase, [:conditions] => :environment do |task, args|
|
103
|
+
print_entry request_logger.last(conditions(args, "upper(method) = '#{method}'"))
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
109
|
+
end unless defined?(Rack::RequestLogger::TaskHelpers)
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,395 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Rack
|
4
|
+
describe RequestLogger do
|
5
|
+
|
6
|
+
class TestLogHandler
|
7
|
+
def self.write(params={}) end
|
8
|
+
end
|
9
|
+
|
10
|
+
class BadLogHandler
|
11
|
+
def self.write(params={}) raise 'Should not have called write!' end
|
12
|
+
def self.respond_to?(method) method == :write ? false : super end
|
13
|
+
end
|
14
|
+
|
15
|
+
before do
|
16
|
+
@log_handler = TestLogHandler
|
17
|
+
RequestLogger.log_handler = @log_handler
|
18
|
+
|
19
|
+
@app = Object.new
|
20
|
+
@logger = RequestLogger.new @app
|
21
|
+
end
|
22
|
+
|
23
|
+
describe 'call' do
|
24
|
+
def test_write_call(expected_hash)
|
25
|
+
@log_handler.should_receive(:write).with(hash_including(expected_hash))
|
26
|
+
@logger.call @env
|
27
|
+
end
|
28
|
+
|
29
|
+
before do
|
30
|
+
@user_id = 'someuser'
|
31
|
+
@request_id = 'somerequest'
|
32
|
+
@body = "user_id=#{@user_id}&other=stuff&request_id=#{@request_id}"
|
33
|
+
|
34
|
+
@url = 'http://this.server/path'
|
35
|
+
|
36
|
+
@env = {
|
37
|
+
'rack.input' => StringIO.new(@body),
|
38
|
+
'REQUEST_METHOD' => 'INFO',
|
39
|
+
'rack.url_scheme' => 'http',
|
40
|
+
'HTTP_HOST' => 'this.server',
|
41
|
+
'PATH_INFO' => '/path',
|
42
|
+
'SERVER_PORT' => 80
|
43
|
+
}
|
44
|
+
|
45
|
+
@status = 123
|
46
|
+
@response_headers = {:response => 'headers'}
|
47
|
+
@resopnse_body = 'the response'
|
48
|
+
@app.should_receive(:call).with(@env).and_return([@status, @response_headers, @response_body])
|
49
|
+
|
50
|
+
@logger.stub!(:log_request?).and_return(true)
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'should return result of app.call' do
|
54
|
+
status, headers, body = @logger.call @env
|
55
|
+
status.should == @status
|
56
|
+
headers.should == @response_headers
|
57
|
+
body.should == @response_body
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'should not log if the log_handler is not set' do
|
61
|
+
RequestLogger.log_handler = nil
|
62
|
+
@logger.call @env
|
63
|
+
#All is well if nothing blows up
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'should not log if the log_handler does not respond to write' do
|
67
|
+
#Can't use expecatations for this, as using should_not_receive will define the method
|
68
|
+
RequestLogger.log_handler = BadLogHandler
|
69
|
+
@logger.call @env
|
70
|
+
#All is well if nothing blows up
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'should not log if log_request? returns false' do
|
74
|
+
@log_handler.should_not_receive(:write)
|
75
|
+
@logger.should_receive(:log_request?).and_return(false)
|
76
|
+
@logger.call @env
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'should call add_log_params with env, response results, and params' do
|
80
|
+
@logger.should_receive(:add_log_params).with(@env, @status, @response_headers, @response_body, hash_including(:url => @url))
|
81
|
+
@logger.call @env
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'should log url' do
|
85
|
+
test_write_call :url => @url
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'should log request_method' do
|
89
|
+
test_write_call :method => @env['REQUEST_METHOD']
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'should log obfucated and formatted request headers' do
|
93
|
+
headers = { :some => 'headers' }
|
94
|
+
filtered_headers = { :some => '*****' }
|
95
|
+
formatted_headers = 'some formatted headers'
|
96
|
+
|
97
|
+
@logger.should_receive(:request_headers).with(@env).and_return(headers)
|
98
|
+
@logger.should_receive(:filter_request_headers).with(headers).and_return(filtered_headers)
|
99
|
+
@logger.should_receive(:format_header_hash).with(filtered_headers).and_return(formatted_headers)
|
100
|
+
@logger.stub!(:format_header_hash).with(@response_headers).and_return('other headers')
|
101
|
+
|
102
|
+
test_write_call :request_headers => formatted_headers
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'should log filtered request body' do
|
106
|
+
filtered_body = 'some **** body'
|
107
|
+
|
108
|
+
@logger.should_receive(:read_stream).with(@env['rack.input']).and_return(@body)
|
109
|
+
@logger.should_receive(:filter_request_body).with(@body).and_return(filtered_body)
|
110
|
+
|
111
|
+
test_write_call :request_body => filtered_body
|
112
|
+
end
|
113
|
+
|
114
|
+
it 'should log request and response times' do
|
115
|
+
request, response = 1, 2
|
116
|
+
Time.should_receive(:now).and_return(request, response)
|
117
|
+
test_write_call :request_time => request, :response_time => response
|
118
|
+
end
|
119
|
+
|
120
|
+
it 'should log response status' do
|
121
|
+
test_write_call :status => @status
|
122
|
+
end
|
123
|
+
|
124
|
+
it 'should log filtered and formatted response headers' do
|
125
|
+
request_headers = { :other => 'request headers' }
|
126
|
+
@logger.stub!(:request_headers).and_return(request_headers)
|
127
|
+
@logger.stub!(:format_header_hash).with(request_headers).and_return('other headers')
|
128
|
+
|
129
|
+
filtered_headers = { :some => '*****' }
|
130
|
+
formatted_headers = 'some formatted headers'
|
131
|
+
@logger.should_receive(:filter_response_headers).with(@response_headers).and_return(filtered_headers)
|
132
|
+
@logger.should_receive(:format_header_hash).with(filtered_headers).and_return(formatted_headers)
|
133
|
+
|
134
|
+
test_write_call :response_headers => formatted_headers
|
135
|
+
end
|
136
|
+
|
137
|
+
it 'should log filtered response body' do
|
138
|
+
filtered_body = 'some **** body'
|
139
|
+
@logger.should_receive(:filter_response_body).with(@response_body).and_return(filtered_body)
|
140
|
+
test_write_call :response_body => filtered_body
|
141
|
+
end
|
142
|
+
|
143
|
+
it 'should not log remote host if not present' do
|
144
|
+
@logger.should_receive(:remote_host).and_return(nil)
|
145
|
+
@log_handler.should_not_receive(:write).with(hash_including(:remote_host => anything()))
|
146
|
+
@log_handler.should_receive(:write)
|
147
|
+
|
148
|
+
@logger.call @env
|
149
|
+
end
|
150
|
+
|
151
|
+
it 'should log remote host if present' do
|
152
|
+
remote_host = 'some.user.com'
|
153
|
+
@logger.should_receive(:remote_host).and_return(remote_host)
|
154
|
+
test_write_call :remote_host => remote_host
|
155
|
+
end
|
156
|
+
|
157
|
+
it 'should not log any error if none is present' do
|
158
|
+
@logger.should_receive(:app_error).and_return(nil)
|
159
|
+
@log_handler.should_not_receive(:write).with(hash_including(:error => anything()))
|
160
|
+
@log_handler.should_receive(:write)
|
161
|
+
|
162
|
+
@logger.call @env
|
163
|
+
end
|
164
|
+
|
165
|
+
it 'shoud format and log any present error' do
|
166
|
+
formatted_error = 'some formatted error'
|
167
|
+
error = RuntimeError.new 'some error'
|
168
|
+
|
169
|
+
@logger.should_receive(:app_error).and_return(error)
|
170
|
+
@logger.should_receive(:format_error).with(error).and_return(formatted_error)
|
171
|
+
|
172
|
+
test_write_call :error => formatted_error
|
173
|
+
end
|
174
|
+
|
175
|
+
it 'should not allow logging errors to propogate' do
|
176
|
+
@log_handler.should_receive(:write).twice.and_raise('some error')
|
177
|
+
@logger.call @env
|
178
|
+
#All is well if nothing blows up
|
179
|
+
end
|
180
|
+
|
181
|
+
it 'should attempt to format and log any logging errors' do
|
182
|
+
error = RuntimeError.new 'some error'
|
183
|
+
|
184
|
+
@log_handler.should_receive(:write).with(hash_including(:url => @url)).and_raise('some error')
|
185
|
+
@log_handler.should_receive(:write).with(instance_of(Hash)) do |params|
|
186
|
+
params[:error].message.should == error.message
|
187
|
+
end
|
188
|
+
|
189
|
+
@logger.call @env
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
describe 'read_stream' do
|
194
|
+
it 'should return nil if parameter is nil' do
|
195
|
+
@logger.send(:read_stream, nil).should be_nil
|
196
|
+
end
|
197
|
+
|
198
|
+
it 'should rewind stream' do
|
199
|
+
string = 'some data'
|
200
|
+
stream = StringIO.new string
|
201
|
+
value = @logger.send(:read_stream, stream)
|
202
|
+
|
203
|
+
value.should == string
|
204
|
+
stream.gets.should == string
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
describe 'filter_request_headers' do
|
209
|
+
it 'should return the same hash provided, since it is intended to be overridden' do
|
210
|
+
hash = { :login => 'login', :password => 'password' }
|
211
|
+
@logger.send(:filter_request_headers, nil).should be_nil
|
212
|
+
@logger.send(:filter_request_headers, hash).should == hash
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
describe 'filter_request_body' do
|
217
|
+
it 'should return the same string provided, since it is intended to be overridden' do
|
218
|
+
string = 'login=l&password=p&key=k&user=u'
|
219
|
+
@logger.send(:filter_request_body, nil).should be_nil
|
220
|
+
@logger.send(:filter_request_body, string).should == string
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
describe 'filter_response_headers' do
|
225
|
+
it 'should return the same hash provided, since it is intended to be overridden' do
|
226
|
+
hash = { :login => 'login', :password => 'password' }
|
227
|
+
@logger.send(:filter_response_headers, nil).should be_nil
|
228
|
+
@logger.send(:filter_response_headers, hash).should == hash
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
describe 'filter_response_body' do
|
233
|
+
it 'should return the same string provided, since it is intended to be overridden' do
|
234
|
+
string = 'login=l&password=p&key=k&user=u'
|
235
|
+
@logger.send(:filter_response_body, nil).should be_nil
|
236
|
+
@logger.send(:filter_response_body, string).should == string
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
describe 'request_headers' do
|
241
|
+
it 'should pull content type' do
|
242
|
+
content_type = 'some/type'
|
243
|
+
headers = @logger.send(:request_headers, 'CONTENT_TYPE' => content_type)
|
244
|
+
headers['Content-Type'].should == content_type
|
245
|
+
end
|
246
|
+
|
247
|
+
it 'should pull content length' do
|
248
|
+
content_length = 53188
|
249
|
+
headers = @logger.send(:request_headers, 'CONTENT_LENGTH' => content_length)
|
250
|
+
headers['Content-Length'].should == content_length
|
251
|
+
end
|
252
|
+
|
253
|
+
it 'should pull and format anything starting with "HTTP_"' do
|
254
|
+
env = {
|
255
|
+
'HTTP_SOME_HEADER' => 'something',
|
256
|
+
'HTTP_ANOTHER_HEADER' => 'another thing',
|
257
|
+
'http_lowercase_header' => 'lowercase',
|
258
|
+
'hTtp_CrAZy_HEadeR' => 'multicase'
|
259
|
+
}
|
260
|
+
|
261
|
+
headers = @logger.send(:request_headers, env)
|
262
|
+
|
263
|
+
env.each do |header, value|
|
264
|
+
formatted_header = header.downcase.sub('http_', '').gsub('_', '-')
|
265
|
+
headers[formatted_header].should == value
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
it 'should ignore anything not covered by tests above' do
|
270
|
+
@logger.send(:request_headers, 'IGNORED_VALUE' => 'JUNK').should be_empty
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
describe 'remote_host' do
|
275
|
+
before do
|
276
|
+
@host = 'some.host'
|
277
|
+
end
|
278
|
+
|
279
|
+
it 'should return remote host for Rails requests' do
|
280
|
+
@logger.send(:remote_host, 'REMOTE_ADDR' => @host).should == @host
|
281
|
+
end
|
282
|
+
|
283
|
+
it 'should return remote host for Sinatra requests' do
|
284
|
+
@logger.send(:remote_host, 'REMOTE_HOST' => @host).should == @host
|
285
|
+
end
|
286
|
+
|
287
|
+
it 'should return nil if nothing found' do
|
288
|
+
@logger.send(:remote_host, 'nothing' => 'useful').should be_nil
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
describe 'app_error' do
|
293
|
+
it 'should return sinatra.error if present in env' do
|
294
|
+
error = RuntimeError.new 'some sinatra-set error'
|
295
|
+
@logger.send(:app_error, 'sinatra.error' => error).should == error
|
296
|
+
end
|
297
|
+
|
298
|
+
it 'should return request_logger.error if present in env' do
|
299
|
+
error = RuntimeError.new 'some manually set error'
|
300
|
+
@logger.send(:app_error, 'request_logger.error' => error).should == error
|
301
|
+
end
|
302
|
+
|
303
|
+
it 'should let manually set errors take precedence over Sinatra set errors' do
|
304
|
+
sinatra_error = RuntimeError.new 'some sinatra-set error'
|
305
|
+
manual_error = RuntimeError.new 'some manually set error'
|
306
|
+
env = { 'sinatra.error' => sinatra_error, 'request_logger.error' => manual_error }
|
307
|
+
@logger.send(:app_error, env).should == manual_error
|
308
|
+
end
|
309
|
+
|
310
|
+
it 'should return nil if no error is found' do
|
311
|
+
@logger.send(:app_error, {}).should == nil
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
describe 'format_header_hash' do
|
316
|
+
it 'should return nil if hash is nil' do
|
317
|
+
@logger.send(:format_header_hash, nil).should be_nil
|
318
|
+
end
|
319
|
+
|
320
|
+
it 'should turn hash into a header string' do
|
321
|
+
hash = { :key1 => 'value1', :key2 => 'value2' }
|
322
|
+
string_possibilities = [[]]
|
323
|
+
hash.each do |k, v|
|
324
|
+
string_possibilities.first << "#{k}: #{v}"
|
325
|
+
end
|
326
|
+
string_possibilities << string_possibilities.first.reverse
|
327
|
+
string_possibilities.map! { |s| s.join "\n" }
|
328
|
+
string_possibilities.should include(@logger.send(:format_header_hash, hash))
|
329
|
+
end
|
330
|
+
end
|
331
|
+
|
332
|
+
describe 'format_error' do
|
333
|
+
before do
|
334
|
+
@message = 'the message'
|
335
|
+
@error = RuntimeError.new(@message)
|
336
|
+
end
|
337
|
+
|
338
|
+
it 'should include class' do
|
339
|
+
string = @logger.send(:format_error, @error)
|
340
|
+
string.should include(@error.class.to_s)
|
341
|
+
end
|
342
|
+
|
343
|
+
it 'should include message' do
|
344
|
+
string = @logger.send(:format_error, @error)
|
345
|
+
string.should include(@error.message)
|
346
|
+
end
|
347
|
+
|
348
|
+
it 'should include backtrace' do
|
349
|
+
begin
|
350
|
+
#Raise it to get a backtrace
|
351
|
+
raise @error
|
352
|
+
rescue => e
|
353
|
+
string = @logger.send(:format_error, e)
|
354
|
+
e.backtrace.each do |frame|
|
355
|
+
string.should include(frame)
|
356
|
+
end
|
357
|
+
end
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
361
|
+
describe 'response_body_to_s' do
|
362
|
+
it 'should handle Sinatra responses' do
|
363
|
+
body = 'An array with a single element'
|
364
|
+
sinatra_response = [ body ]
|
365
|
+
@logger.send(:response_body_to_s, sinatra_response).should == body
|
366
|
+
end
|
367
|
+
|
368
|
+
it 'should handle Rails responses' do
|
369
|
+
body = 'Part of an ActionDispatch::Response object'
|
370
|
+
hacked_rails_response = Object.new
|
371
|
+
hacked_rails_response.should_receive(:body).and_return(body)
|
372
|
+
hacked_rails_response.should_receive(:class).and_return('ActionDispatch::Response')
|
373
|
+
@logger.send(:response_body_to_s, hacked_rails_response).should == body
|
374
|
+
end
|
375
|
+
|
376
|
+
it 'should read streams' do
|
377
|
+
body = 'a stream'
|
378
|
+
stream = StringIO.new body
|
379
|
+
@logger.should_receive(:read_stream).with(stream).and_return(body)
|
380
|
+
@logger.send(:response_body_to_s, stream).should == body
|
381
|
+
end
|
382
|
+
|
383
|
+
it 'should simply return the object if it is a string' do
|
384
|
+
body = 'a string'
|
385
|
+
@logger.send(:response_body_to_s, body).should == body
|
386
|
+
end
|
387
|
+
|
388
|
+
it 'should simply return the object if it is an unknown type' do
|
389
|
+
object = { :a => 'hash' }
|
390
|
+
@logger.send(:response_body_to_s, object).should == object
|
391
|
+
end
|
392
|
+
end
|
393
|
+
|
394
|
+
end
|
395
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rack-request_logger
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 0
|
8
|
+
- 4
|
9
|
+
version: 0.0.4
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Mike Marion
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2011-08-25 00:00:00 -05:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: rack
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ~>
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
segments:
|
28
|
+
- 1
|
29
|
+
- 0
|
30
|
+
version: "1.0"
|
31
|
+
type: :runtime
|
32
|
+
version_requirements: *id001
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: rake
|
35
|
+
prerelease: false
|
36
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
segments:
|
41
|
+
- 0
|
42
|
+
version: "0"
|
43
|
+
type: :development
|
44
|
+
version_requirements: *id002
|
45
|
+
- !ruby/object:Gem::Dependency
|
46
|
+
name: rspec
|
47
|
+
prerelease: false
|
48
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
49
|
+
requirements:
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
segments:
|
53
|
+
- 2
|
54
|
+
- 6
|
55
|
+
version: "2.6"
|
56
|
+
type: :development
|
57
|
+
version_requirements: *id003
|
58
|
+
description: |-
|
59
|
+
This gem is Rack middleware that gathers information about
|
60
|
+
all requests and responses, allowing for detailed logging.
|
61
|
+
email: mike.marion@gmail.com
|
62
|
+
executables: []
|
63
|
+
|
64
|
+
extensions: []
|
65
|
+
|
66
|
+
extra_rdoc_files: []
|
67
|
+
|
68
|
+
files:
|
69
|
+
- lib/rack/request_logger.rb
|
70
|
+
- lib/rack/request_logger_version.rb
|
71
|
+
- lib/rack/tasks/request_log_readers.rb
|
72
|
+
has_rdoc: true
|
73
|
+
homepage: https://github.com/BLC/rack-request_logger
|
74
|
+
licenses: []
|
75
|
+
|
76
|
+
post_install_message:
|
77
|
+
rdoc_options: []
|
78
|
+
|
79
|
+
require_paths:
|
80
|
+
- lib
|
81
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
82
|
+
requirements:
|
83
|
+
- - ">="
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
segments:
|
86
|
+
- 0
|
87
|
+
version: "0"
|
88
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
89
|
+
requirements:
|
90
|
+
- - ">="
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
segments:
|
93
|
+
- 0
|
94
|
+
version: "0"
|
95
|
+
requirements: []
|
96
|
+
|
97
|
+
rubyforge_project:
|
98
|
+
rubygems_version: 1.3.6
|
99
|
+
signing_key:
|
100
|
+
specification_version: 3
|
101
|
+
summary: Rack Request Logger
|
102
|
+
test_files:
|
103
|
+
- spec/rack/request_logger_spec.rb
|
104
|
+
- spec/spec_helper.rb
|