request_recorder 0.0.1
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/.travis.yml +4 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +34 -0
- data/MIGRATION +12 -0
- data/Rakefile +22 -0
- data/Readme.md +22 -0
- data/lib/request_recorder/log.rb +7 -0
- data/lib/request_recorder/middleware.rb +77 -0
- data/lib/request_recorder/recorder.rb +13 -0
- data/lib/request_recorder/version.rb +3 -0
- data/lib/request_recorder.rb +7 -0
- data/request_recorder.gemspec +14 -0
- data/spec/request_recorder_spec.rb +84 -0
- data/spec/spec_helper.rb +23 -0
- metadata +97 -0
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
request_recorder (0.0.1)
|
5
|
+
activerecord (= 2.3.14)
|
6
|
+
rack
|
7
|
+
|
8
|
+
GEM
|
9
|
+
remote: http://rubygems.org/
|
10
|
+
specs:
|
11
|
+
activerecord (2.3.14)
|
12
|
+
activesupport (= 2.3.14)
|
13
|
+
activesupport (2.3.14)
|
14
|
+
diff-lcs (1.1.3)
|
15
|
+
rack (1.4.1)
|
16
|
+
rake (0.9.2)
|
17
|
+
rspec (2.6.0)
|
18
|
+
rspec-core (~> 2.6.0)
|
19
|
+
rspec-expectations (~> 2.6.0)
|
20
|
+
rspec-mocks (~> 2.6.0)
|
21
|
+
rspec-core (2.6.4)
|
22
|
+
rspec-expectations (2.6.0)
|
23
|
+
diff-lcs (~> 1.1.2)
|
24
|
+
rspec-mocks (2.6.0)
|
25
|
+
sqlite3 (1.3.6)
|
26
|
+
|
27
|
+
PLATFORMS
|
28
|
+
ruby
|
29
|
+
|
30
|
+
DEPENDENCIES
|
31
|
+
rake
|
32
|
+
request_recorder!
|
33
|
+
rspec (~> 2)
|
34
|
+
sqlite3
|
data/MIGRATION
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
|
3
|
+
task :default do
|
4
|
+
sh "rspec spec/"
|
5
|
+
end
|
6
|
+
|
7
|
+
# extracted from https://github.com/grosser/project_template
|
8
|
+
rule /^version:bump:.*/ do |t|
|
9
|
+
file = "lib/request_recorder/version.rb"
|
10
|
+
|
11
|
+
sh "git status | grep 'nothing to commit'" # ensure we are not dirty
|
12
|
+
index = ["major", "minor", "patch"].index(t.name.split(':').last)
|
13
|
+
version_file = File.read(file)
|
14
|
+
old_version, *version_parts = version_file.match(/(\d+)\.(\d+)\.(\d+)/).to_a
|
15
|
+
version_parts[index] = version_parts[index].to_i + 1
|
16
|
+
version_parts[2] = 0 if index < 2 # remove patch for minor
|
17
|
+
version_parts[1] = 0 if index < 1 # remove minor for major
|
18
|
+
new_version = version_parts * '.'
|
19
|
+
|
20
|
+
File.open(file,"w"){|f| f.write(version_file.sub(old_version, new_version)) }
|
21
|
+
sh "bundle && git add #{file} Gemfile.lock && git commit -m 'bump version to #{new_version}'"
|
22
|
+
end
|
data/Readme.md
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Record your rack/rails requests and store them for future inspection
|
2
|
+
|
3
|
+
Install
|
4
|
+
=======
|
5
|
+
|
6
|
+
gem install request_recorder
|
7
|
+
# generate a add_recorded_requests migration and paste content of MIGRATION
|
8
|
+
|
9
|
+
Usage
|
10
|
+
=====
|
11
|
+
|
12
|
+
- Add it to the bottom of you middleware stack
|
13
|
+
- request a page with __request_recording=10 -> record next 10 requests from my browser
|
14
|
+
- RequestRecorded::Requests gets a new entry with all the logging info from rails + activerecord
|
15
|
+
- go to the database or build a nice frontend for RequestRecorded::Requests
|
16
|
+
|
17
|
+
Author
|
18
|
+
======
|
19
|
+
[Michael Grosser](http://grosser.it)<br/>
|
20
|
+
michael@grosser.it<br/>
|
21
|
+
License: MIT<br/>
|
22
|
+
[](https://travis-ci.org/grosser/request_recorder)
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require "rack/request"
|
2
|
+
require "rack/response"
|
3
|
+
|
4
|
+
module RequestRecorder
|
5
|
+
class Middleware
|
6
|
+
MARKER = "__request_recording"
|
7
|
+
MAX_STEPS = 100
|
8
|
+
|
9
|
+
def initialize(app, options={})
|
10
|
+
@app = app
|
11
|
+
end
|
12
|
+
|
13
|
+
def call(env)
|
14
|
+
# keep this part as fast as possible, since 99.99999% of requests will not need it
|
15
|
+
return @app.call(env) unless (
|
16
|
+
(env["QUERY_STRING"] && env["QUERY_STRING"].include?(MARKER)) or
|
17
|
+
(env["HTTP_COOKIE"] && env["HTTP_COOKIE"].include?(MARKER))
|
18
|
+
)
|
19
|
+
|
20
|
+
result = logging_to_recorded do
|
21
|
+
@app.call(env)
|
22
|
+
end
|
23
|
+
|
24
|
+
steps_left, id = read_state_from_env(env)
|
25
|
+
return [500, {}, "__request_recording exceeded maximum value #{MAX_STEPS}"] if steps_left > MAX_STEPS
|
26
|
+
id = persist_log(id, @recorder.log)
|
27
|
+
response_with_data_in_cookie(result, steps_left, id)
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def persist_log(id, log)
|
33
|
+
record = (id ? Log.find(id) : Log.new(:log => ""))
|
34
|
+
record.log += log.join("\n") + "\n\n"
|
35
|
+
record.save!
|
36
|
+
record.id
|
37
|
+
end
|
38
|
+
|
39
|
+
def read_state_from_env(env)
|
40
|
+
request = Rack::Request.new(env)
|
41
|
+
if request.cookies[MARKER]
|
42
|
+
request.cookies[MARKER].split(":").map(&:to_i)
|
43
|
+
else
|
44
|
+
[env["QUERY_STRING"][/#{MARKER}=(\d+)/, 1].to_i, nil]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def response_with_data_in_cookie(result, to_go, id)
|
49
|
+
status, headers, body = result
|
50
|
+
response = Rack::Response.new(body, status, headers)
|
51
|
+
if to_go <= 1
|
52
|
+
response.delete_cookie(MARKER)
|
53
|
+
else
|
54
|
+
response.set_cookie(MARKER, {:value => "#{to_go.to_i - 1}:#{id}", :path => "/", :expires => Time.now+24*60*60})
|
55
|
+
end
|
56
|
+
|
57
|
+
response.finish # finish writes out the response in the expected format.
|
58
|
+
end
|
59
|
+
|
60
|
+
def logging_to_recorded
|
61
|
+
@recorder = Recorder.new
|
62
|
+
old = [
|
63
|
+
ActiveRecord::Base.logger.instance_variable_get("@log"),
|
64
|
+
ActiveRecord::Base.logger.auto_flushing,
|
65
|
+
ActiveRecord::Base.logger.level
|
66
|
+
]
|
67
|
+
ActiveRecord::Base.logger.instance_variable_set("@log", @recorder)
|
68
|
+
ActiveRecord::Base.logger.auto_flushing = true
|
69
|
+
ActiveRecord::Base.logger.level = Logger::DEBUG
|
70
|
+
yield
|
71
|
+
ensure
|
72
|
+
ActiveRecord::Base.logger.instance_variable_set("@log", old[0])
|
73
|
+
ActiveRecord::Base.logger.auto_flushing = old[1]
|
74
|
+
ActiveRecord::Base.logger.level = old[2]
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
$LOAD_PATH.unshift File.expand_path("../lib", __FILE__)
|
2
|
+
name = "request_recorder"
|
3
|
+
require "#{name}/version"
|
4
|
+
|
5
|
+
Gem::Specification.new name, RequestRecorder::VERSION do |s|
|
6
|
+
s.summary = "Record your rack/rails requests and store them for future inspection"
|
7
|
+
s.authors = ["Michael Grosser"]
|
8
|
+
s.email = "michael@grosser.it"
|
9
|
+
s.homepage = "http://github.com/grosser/#{name}"
|
10
|
+
s.files = `git ls-files`.split("\n")
|
11
|
+
s.license = "MIT"
|
12
|
+
s.add_dependency "activerecord", "2.3.14"
|
13
|
+
s.add_dependency "rack"
|
14
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe RequestRecorder do
|
4
|
+
let(:original_logger){ ActiveSupport::BufferedLogger.new("/dev/null") }
|
5
|
+
let(:activate_logger){ {"QUERY_STRING" => "__request_recording=10"} }
|
6
|
+
let(:inner_app){ lambda{|env|
|
7
|
+
Car.first
|
8
|
+
[200, {}, "assadasd"]
|
9
|
+
} }
|
10
|
+
let(:existing_request){ RequestRecorder::Log.create(:log => "BEFORE") }
|
11
|
+
|
12
|
+
before do
|
13
|
+
ActiveRecord::Base.logger.instance_variable_set("@log", original_logger)
|
14
|
+
ActiveRecord::Base.logger.auto_flushing = 1000
|
15
|
+
ActiveRecord::Base.logger.level = Logger::ERROR
|
16
|
+
end
|
17
|
+
|
18
|
+
after do
|
19
|
+
RequestRecorder::Log.delete_all
|
20
|
+
end
|
21
|
+
|
22
|
+
it "has a VERSION" do
|
23
|
+
RequestRecorder::VERSION.should =~ /^[\.\da-z]+$/
|
24
|
+
end
|
25
|
+
|
26
|
+
it "records activerecord queries" do
|
27
|
+
middleware = RequestRecorder::Middleware.new(inner_app)
|
28
|
+
middleware.call(activate_logger)
|
29
|
+
RequestRecorder::Log.last.log.should include "SELECT"
|
30
|
+
end
|
31
|
+
|
32
|
+
it "blows up if you go over the maximum" do
|
33
|
+
middleware = RequestRecorder::Middleware.new(inner_app)
|
34
|
+
status, headers, body = middleware.call("QUERY_STRING" => "__request_recording=99999")
|
35
|
+
status.should == 500
|
36
|
+
body.should include "maximum"
|
37
|
+
end
|
38
|
+
|
39
|
+
context "subsequent requests" do
|
40
|
+
it "sets cookie in first step" do
|
41
|
+
middleware = RequestRecorder::Middleware.new(inner_app)
|
42
|
+
status, headers, body = middleware.call(activate_logger)
|
43
|
+
generated = RequestRecorder::Log.last
|
44
|
+
headers["Set-Cookie"].should include "__request_recording=9%3A#{generated.id}; path=/; expires="
|
45
|
+
end
|
46
|
+
|
47
|
+
it "appends to existing log" do
|
48
|
+
middleware = RequestRecorder::Middleware.new(inner_app)
|
49
|
+
middleware.call("HTTP_COOKIE" => "__request_recording=8:#{existing_request.id}")
|
50
|
+
existing_request.reload
|
51
|
+
existing_request.log.should include "SELECT"
|
52
|
+
existing_request.log.should include "BEFORE"
|
53
|
+
end
|
54
|
+
|
55
|
+
it "decrements cookie on each step" do
|
56
|
+
middleware = RequestRecorder::Middleware.new(inner_app)
|
57
|
+
status, headers, body = middleware.call("HTTP_COOKIE" => "__request_recording=2:#{existing_request.id};foo=bar")
|
58
|
+
headers["Set-Cookie"].should include "__request_recording=1%3A#{existing_request.id}; path=/; expires="
|
59
|
+
end
|
60
|
+
|
61
|
+
it "removes cookie if final step is reached" do
|
62
|
+
middleware = RequestRecorder::Middleware.new(inner_app)
|
63
|
+
status, headers, body = middleware.call("HTTP_COOKIE" => "__request_recording=1:#{existing_request.id};foo=bar")
|
64
|
+
headers["Set-Cookie"].should include "__request_recording=; expires="
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
it "should not record if __request_recording is not given" do
|
69
|
+
middleware = RequestRecorder::Middleware.new(inner_app)
|
70
|
+
middleware.call(
|
71
|
+
"QUERY_STRING" => "stuff=hello", "HTTP_COOKIE" => "bar=foo"
|
72
|
+
)
|
73
|
+
RequestRecorder::Log.count.should == 0
|
74
|
+
end
|
75
|
+
|
76
|
+
it "restores the AR logger after executing" do
|
77
|
+
middleware = RequestRecorder::Middleware.new(inner_app)
|
78
|
+
middleware.call(activate_logger)
|
79
|
+
|
80
|
+
ActiveRecord::Base.logger.instance_variable_get("@log").object_id.should == original_logger.object_id
|
81
|
+
ActiveRecord::Base.logger.auto_flushing.should == 1000
|
82
|
+
ActiveRecord::Base.logger.level.should == Logger::ERROR
|
83
|
+
end
|
84
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require "request_recorder"
|
2
|
+
|
3
|
+
ActiveRecord::Base.logger = ActiveSupport::BufferedLogger.new("/dev/null")
|
4
|
+
|
5
|
+
# connect
|
6
|
+
ActiveRecord::Base.establish_connection(
|
7
|
+
:adapter => "sqlite3",
|
8
|
+
:database => ":memory:"
|
9
|
+
)
|
10
|
+
|
11
|
+
# create tables
|
12
|
+
ActiveRecord::Schema.verbose = false
|
13
|
+
ActiveRecord::Schema.define(:version => 1) do
|
14
|
+
eval(File.read(File.expand_path("../../MIGRATION", __FILE__)))
|
15
|
+
AddRecordedRequests.up
|
16
|
+
|
17
|
+
create_table :cars do |t|
|
18
|
+
t.timestamps
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class Car < ActiveRecord::Base
|
23
|
+
end
|
metadata
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: request_recorder
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Michael Grosser
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-10-19 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: activerecord
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - '='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 2.3.14
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - '='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 2.3.14
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rack
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
description:
|
47
|
+
email: michael@grosser.it
|
48
|
+
executables: []
|
49
|
+
extensions: []
|
50
|
+
extra_rdoc_files: []
|
51
|
+
files:
|
52
|
+
- .travis.yml
|
53
|
+
- Gemfile
|
54
|
+
- Gemfile.lock
|
55
|
+
- MIGRATION
|
56
|
+
- Rakefile
|
57
|
+
- Readme.md
|
58
|
+
- lib/request_recorder.rb
|
59
|
+
- lib/request_recorder/log.rb
|
60
|
+
- lib/request_recorder/middleware.rb
|
61
|
+
- lib/request_recorder/recorder.rb
|
62
|
+
- lib/request_recorder/version.rb
|
63
|
+
- request_recorder.gemspec
|
64
|
+
- spec/request_recorder_spec.rb
|
65
|
+
- spec/spec_helper.rb
|
66
|
+
homepage: http://github.com/grosser/request_recorder
|
67
|
+
licenses:
|
68
|
+
- MIT
|
69
|
+
post_install_message:
|
70
|
+
rdoc_options: []
|
71
|
+
require_paths:
|
72
|
+
- lib
|
73
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
74
|
+
none: false
|
75
|
+
requirements:
|
76
|
+
- - ! '>='
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: '0'
|
79
|
+
segments:
|
80
|
+
- 0
|
81
|
+
hash: -1997442405915248740
|
82
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
83
|
+
none: false
|
84
|
+
requirements:
|
85
|
+
- - ! '>='
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '0'
|
88
|
+
segments:
|
89
|
+
- 0
|
90
|
+
hash: -1997442405915248740
|
91
|
+
requirements: []
|
92
|
+
rubyforge_project:
|
93
|
+
rubygems_version: 1.8.24
|
94
|
+
signing_key:
|
95
|
+
specification_version: 3
|
96
|
+
summary: Record your rack/rails requests and store them for future inspection
|
97
|
+
test_files: []
|