request_recorder 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ rvm:
2
+ - ree
3
+ - 1.9.2
4
+ - 1.9.3
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source :rubygems
2
+ gemspec
3
+
4
+ gem "rake"
5
+ gem "rspec", "~>2"
6
+ gem "sqlite3"
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
@@ -0,0 +1,12 @@
1
+ class AddRecordedRequests < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :recorded_request_log do |t|
4
+ t.text "log"
5
+ t.timestamp :created_at
6
+ end
7
+ end
8
+
9
+ def self.down
10
+ drop_table :recorded_request_log
11
+ end
12
+ end
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
+ [![Build Status](https://travis-ci.org/grosser/request_recorder.png)](https://travis-ci.org/grosser/request_recorder)
@@ -0,0 +1,7 @@
1
+ require "active_record"
2
+
3
+ module RequestRecorder
4
+ class Log < ActiveRecord::Base
5
+ set_table_name "recorded_request_log" # TODO use self.table_name = in rails 3
6
+ end
7
+ end
@@ -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,13 @@
1
+ module RequestRecorder
2
+ class Recorder
3
+ attr_reader :log
4
+
5
+ def initialize
6
+ @log = []
7
+ end
8
+
9
+ def write(s)
10
+ @log << s
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,3 @@
1
+ module RequestRecorder
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,7 @@
1
+ require "request_recorder/version"
2
+ require "request_recorder/log"
3
+ require "request_recorder/middleware"
4
+ require "request_recorder/recorder"
5
+
6
+ module RequestRecorder
7
+ 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
@@ -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: []