rack-request_logger 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- 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
|