request_log 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.
@@ -0,0 +1,3 @@
1
+ pkg/*
2
+ *.gem
3
+ .bundle
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in request_log.gemspec
4
+ gemspec
@@ -0,0 +1,32 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ request_log (0.0.1)
5
+
6
+ GEM
7
+ remote: http://rubygems.org/
8
+ specs:
9
+ diff-lcs (1.1.2)
10
+ mocha (0.9.8)
11
+ rake
12
+ rack (1.2.1)
13
+ rake (0.8.7)
14
+ rspec (2.0.0)
15
+ rspec-core (= 2.0.0)
16
+ rspec-expectations (= 2.0.0)
17
+ rspec-mocks (= 2.0.0)
18
+ rspec-core (2.0.0)
19
+ rspec-expectations (2.0.0)
20
+ diff-lcs (>= 1.1.2)
21
+ rspec-mocks (2.0.0)
22
+ rspec-core (= 2.0.0)
23
+ rspec-expectations (= 2.0.0)
24
+
25
+ PLATFORMS
26
+ ruby
27
+
28
+ DEPENDENCIES
29
+ mocha (~> 0.9.8)
30
+ rack (~> 1.2.1)
31
+ request_log!
32
+ rspec (= 2.0.0)
@@ -0,0 +1,83 @@
1
+ = Request Log - Logging of web requests to MongoDB
2
+
3
+ Request Log is a Rack middleware for logging web requests to MongoDB. Each web request becomes a document in MongoDB
4
+ with fields like path, method, params, status, time, ip, runtime etc.
5
+ The gem offers support for monitoring the time overhead (usually very small) that the logging incurs.
6
+ The advantages of logging to MongoDB over logging to a plain text file are huge because of the query
7
+ capabilities of MongoDB. Here is an example of what a log document can look like:
8
+
9
+ summary: "GET / - 200 0.000303"
10
+ method: "GET"
11
+ path: "/"
12
+ ip: "10.218.1.177"
13
+ time: 2010-10-28 21:43:38 UTC
14
+ params: {"hello_world"=>"1"}
15
+ status: 200
16
+ runtime: 0.000303
17
+
18
+ You can easily customize which fields are stored in the log.
19
+
20
+ == Installation
21
+
22
+ Add gem dependencies with appropriate version numbers to your Gemfile (assuming you use Bundler):
23
+
24
+ gem 'mongo', '~> <latest-version-here>'
25
+ gem 'bson_ext', '~> <latest-version-here>'
26
+ gem 'request_log', :git => "http://github.com/peter/request_log.git"
27
+
28
+ Install with:
29
+
30
+ bundle install
31
+
32
+ Note that it's up to your application how it wants to connect to MongoDB (if at all) and the suggested
33
+ mongo and bson_ext gems are just suggestions.
34
+
35
+ Next you need to setup a MongoDB connection. Here is a MongoHQ example that in Rails would belong in config/initializers/request_log.rb:
36
+
37
+ if ENV['MONGOHQ_URL']
38
+ require 'uri'
39
+ require 'mongo'
40
+ uri = URI.parse(ENV['MONGOHQ_URL'])
41
+ connection = Mongo::Connection.from_uri(uri.to_s)
42
+ RequestLog::Db.mongo_db = connection.db(uri.path.gsub(/^\//, ''))
43
+ end
44
+
45
+ Now setup the Middleware in your config.ru file:
46
+
47
+ use RequestLog::Middleware
48
+
49
+ Here is an example of how you can customize the middleware:
50
+
51
+ use RequestLog::Middleware,
52
+ :logger => lambda { |data| ::RequestLog::Db.requests.insert(data.attributes.except(:summary)) },
53
+ :timeout => 0.5
54
+
55
+ In order to use the Rake tasks you need to make sure you have the MongoDB connection setup and that you
56
+ require the tasks in your Rakefile, like this:
57
+
58
+ require 'request_log'
59
+ require 'config/initializers/request_log.rb' # The file where you setup the mongo db connection
60
+ require 'request_log/tasks'
61
+
62
+ == Accessing the logs
63
+
64
+ You can tail the log like this:
65
+
66
+ rake request_log:tail
67
+
68
+ If you want to query the log and print a certain time period you can use request_log:print:
69
+
70
+ rake request_log:print from="2010-10-28 17:06:08" to="2010-10-28 17:06:10" conditions='status: 200'
71
+
72
+ If you are using MONGOHQ, remember to set the MONGOHQ_URL environment variable.
73
+
74
+ == Profiling
75
+
76
+ To monitor the time consumption and reliability of the MongoDB logging you can use the RequestLog::Profiler class.
77
+ It records number of failed and successful loggings, average and maximum logging times etc. To persist the profiling
78
+ information to MongoDB you can configure the profiler like this:
79
+
80
+ RequestLog::Profiler.persist_enabled = true
81
+ RequestLog::Profiler.persist_frequency = 1000 # persist profiling info every 1000 requests
82
+
83
+ The profiling info will then be written to a table (request_log_profiling) in the MongoDB database.
@@ -0,0 +1,8 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+ Bundler.require
4
+
5
+ require 'rspec/core'
6
+ require 'rspec/core/rake_task'
7
+
8
+ RSpec::Core::RakeTask.new(:default)
@@ -0,0 +1,5 @@
1
+ require 'request_log/version'
2
+ require 'request_log/db'
3
+ require 'request_log/data'
4
+ require 'request_log/profiler'
5
+ require 'request_log/middleware'
@@ -0,0 +1,37 @@
1
+ module RequestLog
2
+ class Data
3
+ attr_accessor :env, :status, :headers, :response, :app_time
4
+
5
+ def initialize(env, rack_response, app_time)
6
+ self.env = env
7
+ self.status = rack_response[0]
8
+ self.headers = rack_response[1]
9
+ self.response = rack_response[2]
10
+ self.app_time = app_time
11
+ end
12
+
13
+ def self.request_path(env)
14
+ env['PATH_INFO'] || env['REQUEST_PATH'] || "/"
15
+ end
16
+
17
+ def attributes
18
+ method = env['REQUEST_METHOD']
19
+ path = self.class.request_path(env)
20
+ {
21
+ :summary => "#{method} #{path} - #{status} #{app_time}",
22
+ :method => method,
23
+ :path => path,
24
+ :ip => env['REMOTE_ADDR'],
25
+ :time => Time.now.utc,
26
+ :params => params,
27
+ :status => status,
28
+ :runtime => app_time
29
+ }
30
+ end
31
+
32
+ def params
33
+ # NOTE: It seems getting POST params with ::Rack::Request.new(env).params does not work well with Rails
34
+ (env['action_controller.instance'] || ::Rack::Request.new(env)).params
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,41 @@
1
+ module RequestLog
2
+ class Db
3
+ @@mongo_db = nil
4
+
5
+ def self.mongo_db=(mongo_db)
6
+ @@mongo_db = mongo_db
7
+ end
8
+
9
+ def self.mongo_db
10
+ @@mongo_db
11
+ end
12
+
13
+ def self.requests
14
+ mongo_db['requests']
15
+ end
16
+
17
+ def self.profiling
18
+ mongo_db['request_log_profiling']
19
+ end
20
+
21
+ def self.printable_request(request)
22
+ request.keys.reject { |key| key == "_id" }.map do |key|
23
+ "#{key}: #{request[key].inspect}"
24
+ end.join("\n")
25
+ end
26
+
27
+ def self.filtered_requests(start_time, end_time, conditions = {})
28
+ start_time = Time.parse(start_time).utc if start_time.is_a?(String)
29
+ end_time = Time.parse(end_time).utc if end_time.is_a?(String)
30
+ time_condition = {"time" => {"$gt" => start_time, "$lt" => end_time}}
31
+ requests.find(time_condition.merge(conditions))
32
+ end
33
+
34
+ def self.print_requests(start_time, end_time, conditions = {})
35
+ filtered_requests(start_time, end_time, conditions).each do |request|
36
+ puts printable_request(request)
37
+ puts
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,40 @@
1
+ module RequestLog
2
+ class Middleware
3
+ def initialize(app, options = {})
4
+ @app = app
5
+ @logger = options[:logger] || lambda { |data| ::RequestLog::Db.requests.insert(data.attributes) }
6
+ @profiler = options[:profiler] || ::RequestLog::Profiler
7
+ @timeout = options[:timeout] || 0.3
8
+ @only_path = options[:only_path]
9
+ end
10
+
11
+ def call(env)
12
+ app_start = Time.now
13
+ rack_response = @app.call(env)
14
+ app_time = Time.now - app_start
15
+ return rack_response unless should_log?(env)
16
+ begin
17
+ logger_start = Time.now
18
+ Timeout::timeout(@timeout) do
19
+ @logger.call(::RequestLog::Data.new(env, rack_response, app_time))
20
+ end
21
+ @profiler.call(:result => :success, :elapsed_time => (Time.now - logger_start))
22
+ rescue Exception => e
23
+ @profiler.call(:result => :failure, :exception => e)
24
+ $stderr.puts("#{self.class}: exception #{e} #{e.backtrace.join("\n")}")
25
+ ensure
26
+ return rack_response
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def should_log?(env)
33
+ if @only_path && ::RequestLog::Data.request_path(env) !~ @only_path
34
+ false
35
+ else
36
+ true
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,106 @@
1
+ module RequestLog
2
+ class Profiler
3
+ def self.default_values
4
+ {
5
+ :success_count => 0,
6
+ :failure_count => 0,
7
+ :failure_exceptions => {},
8
+ :min_time => nil,
9
+ :max_time => nil,
10
+ :avg_time => nil,
11
+ :persist_enabled => false,
12
+ :persist_frequency => 2000
13
+ }
14
+ end
15
+
16
+ def self.attribute_names
17
+ default_values.keys
18
+ end
19
+
20
+ attribute_names.each do |attribute|
21
+ # cattr_accessor from ActiveSupport library
22
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
23
+ @@#{attribute} = nil
24
+
25
+ def self.#{attribute}=(obj)
26
+ @@#{attribute} = obj
27
+ end
28
+
29
+ def self.#{attribute}
30
+ @@#{attribute}
31
+ end
32
+ EOS
33
+ end
34
+
35
+ def self.reset
36
+ attribute_names.each do |attribute|
37
+ send("#{attribute}=", default_values[attribute])
38
+ end
39
+ self
40
+ end
41
+
42
+ reset
43
+
44
+ def self.call(options = {})
45
+ if options[:result] == :success
46
+ self.success_count += 1
47
+ update_times(options[:elapsed_time])
48
+ else
49
+ self.failure_count += 1
50
+ if options[:exception]
51
+ failure_exceptions[options[:exception].class.name] ||= 0
52
+ failure_exceptions[options[:exception].class.name] += 1
53
+ end
54
+ end
55
+ persist! if should_persist?
56
+ end
57
+
58
+ def self.total_count
59
+ success_count + failure_count
60
+ end
61
+
62
+ def self.failure_ratio
63
+ failure_count.to_f/total_count
64
+ end
65
+
66
+ def self.attributes
67
+ attribute_names.inject({}) do |hash, attribute|
68
+ hash[attribute] = send(attribute)
69
+ hash
70
+ end
71
+ end
72
+
73
+ def self.persist!
74
+ ::RequestLog::Db.profiling.insert(
75
+ :total_count => total_count,
76
+ :failure_ratio => failure_ratio,
77
+ :max_time => max_time,
78
+ :avg_time => avg_time,
79
+ :data => attributes
80
+ )
81
+ end
82
+
83
+ def self.should_persist?
84
+ persist_enabled && (total_count % persist_frequency == 0)
85
+ end
86
+
87
+ def self.to_s
88
+ attributes.inspect
89
+ end
90
+
91
+ private
92
+
93
+ def self.update_times(elapsed_time)
94
+ initialize_times(elapsed_time)
95
+ self.min_time = elapsed_time if elapsed_time < min_time
96
+ self.max_time = elapsed_time if elapsed_time > max_time
97
+ self.avg_time = (avg_time*(success_count - 1) + elapsed_time)/success_count
98
+ end
99
+
100
+ def self.initialize_times(elapsed_time)
101
+ self.min_time ||= elapsed_time
102
+ self.max_time ||= elapsed_time
103
+ self.avg_time ||= elapsed_time
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,2 @@
1
+ require 'request_log'
2
+ Dir[File.join(File.dirname(__FILE__), "..", "tasks", "*.rake")].each { |rake_file| load rake_file }
@@ -0,0 +1,3 @@
1
+ module RequestLog
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,33 @@
1
+ require 'set'
2
+
3
+ namespace :request_log do
4
+ desc "Tail the request log"
5
+ task :tail do
6
+ wait_time = 10
7
+ printed_ids = Set.new
8
+ while(true)
9
+ RequestLog::Db.requests.find("time" => {"$gt" => (Time.now - wait_time).utc}).each do |r|
10
+ unless printed_ids.include?(r['_id'])
11
+ puts
12
+ puts RequestLog::Db.printable_request(r)
13
+ printed_ids << r['_id']
14
+ end
15
+ end
16
+ sleep (wait_time-1)
17
+ end
18
+ end
19
+
20
+ desc %q{Print a part of the log. Parameters: from='YYYY-MM-DD HH:MM' to='YYYY-MM-DD HH:MM' conditions=<ruby-hash-with-mongo-db-conditions>}
21
+ task :print do
22
+ from = ENV['from'] || (Time.now-600).utc
23
+ to = ENV['to'] || Time.now.utc
24
+ if conditions = ENV['conditions']
25
+ # We need to parse a Ruby hash here, let's not require braces
26
+ conditions = "{#{conditions}}" unless conditions[0] == "{"
27
+ conditions = eval(conditions)
28
+ else
29
+ conditions = {}
30
+ end
31
+ RequestLog::Db.print_requests(from, to, conditions)
32
+ end
33
+ end
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "request_log/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "request_log"
7
+ s.version = RequestLog::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Peter Marklund"]
10
+ s.email = ["peter@marklunds.com"]
11
+ s.homepage = ""
12
+ s.summary = %q{Rack middleware for logging web requests to a MongoDB database. Provides a profiler for monitoring logging overhead.}
13
+
14
+ s.rubyforge_project = "request_log"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ s.add_development_dependency "rspec", "2.0.0"
22
+ s.add_development_dependency "mocha", "~> 0.9.8"
23
+ s.add_development_dependency "rack", "~> 1.2.1"
24
+ end
@@ -0,0 +1,28 @@
1
+ require 'spec_helper'
2
+
3
+ describe RequestLog::Data do
4
+ describe "attributes" do
5
+ before(:each) do
6
+ @env = env
7
+ @rack_response = [200, {"Content-Type" => "text/html"}, "Hello, World!"]
8
+ @app_time = 0.22666
9
+ @data = RequestLog::Data.new(@env, @rack_response, @app_time)
10
+ end
11
+
12
+ it "returns a hash with information about a request" do
13
+ puts "running spec"
14
+ attributes = @data.attributes
15
+ attributes[:summary].should =~ %r{GET /images/logo.png - 200 \d+\.\d+}
16
+ attributes[:runtime].to_s.should == attributes[:summary][/\d+\.\d+$/]
17
+ attributes[:time].should >= (Time.now-1).utc
18
+ attributes[:time].should <= Time.now.utc
19
+ attributes[:method].should == "GET"
20
+ attributes[:path].should == "/images/logo.png"
21
+ attributes[:ip].should == "127.0.0.1"
22
+ end
23
+ end
24
+
25
+ def env
26
+ {"GATEWAY_INTERFACE"=>"CGI/1.1", "PATH_INFO"=>"/images/logo.png", "QUERY_STRING"=>"", "REMOTE_ADDR"=>"127.0.0.1", "REMOTE_HOST"=>"www.publish.newsdesk.local", "REQUEST_METHOD"=>"GET", "REQUEST_URI"=>"http://publish.lvh.me:3000/images/logo.png", "SCRIPT_NAME"=>"", "SERVER_NAME"=>"publish.lvh.me", "SERVER_PORT"=>"3000", "SERVER_PROTOCOL"=>"HTTP/1.1", "SERVER_SOFTWARE"=>"WEBrick/1.3.1 (Ruby/1.9.2/2010-08-18)", "HTTP_HOST"=>"publish.lvh.me:3000", "HTTP_USER_AGENT"=>"Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; en-US; rv:1.9.2.2) Gecko/20100316 Firefox/3.6.2", "HTTP_ACCEPT"=>"image/png,image/*;q=0.8,*/*;q=0.5", "HTTP_ACCEPT_LANGUAGE"=>"en-us,en;q=0.5", "HTTP_ACCEPT_ENCODING"=>"gzip,deflate", "HTTP_ACCEPT_CHARSET"=>"ISO-8859-1,utf-8;q=0.7,*;q=0.7", "HTTP_KEEP_ALIVE"=>"115", "HTTP_CONNECTION"=>"keep-alive", "HTTP_REFERER"=>"http://publish.lvh.me:3000/system/profiling", "HTTP_AUTHORIZATION"=>"Basic YWRtaW46aWx3d3NwYTIwMTA=", "rack.version"=>[1, 1], "rack.input"=>StringIO.new, "rack.errors"=>$stderr, "rack.multithread"=>true, "rack.multiprocess"=>false, "rack.run_once"=>false, "rack.url_scheme"=>"http", "HTTP_VERSION"=>"HTTP/1.1", "REQUEST_PATH"=>"/"}
27
+ end
28
+ end
@@ -0,0 +1,10 @@
1
+ require 'spec_helper'
2
+
3
+ describe RequestLog::Db do
4
+ describe "mongo_db" do
5
+ it "can be set to som custom Mongo DB" do
6
+ RequestLog::Db.mongo_db = "foobar"
7
+ RequestLog::Db.mongo_db.should == "foobar"
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,65 @@
1
+ require 'spec_helper'
2
+
3
+ describe RequestLog::Middleware do
4
+ describe "call" do
5
+ it "invokes the logger with the data and the profiler in case of success" do
6
+ @env = {"REQUEST_PATH" => "/v1/foobar", "HTTP_USER_AGENT" => "Mozilla/5.0"}
7
+ @logger = mock('logger')
8
+ @logger.expects(:call).with() { |data| data.is_a?(RequestLog::Data) && data.status == 200 && data.env == @env }
9
+ @profiler = mock('profiler')
10
+ @profiler.expects(:call).with() { |options| options[:result] == :success && options[:elapsed_time] > 0 }
11
+ @rack_response = [200, {"Content-Type" => "text/html"}, "Hello, World!"]
12
+ @app = lambda { |env| @rack_response }
13
+ @middleware = RequestLog::Middleware.new(@app, :logger => @logger, :profiler => @profiler, :only_path => %r{\A/v\d})
14
+ @middleware.call(@env).should == @rack_response
15
+ end
16
+
17
+ it "uses RequestLog::Db.requests.insert as a default logger if no logger is specified" do
18
+ @env = env.merge("REQUEST_PATH" => "/v1/foobar", "HTTP_USER_AGENT" => "Mozilla/5.0")
19
+ requests = mock('requests')
20
+ requests.expects(:insert).with() { |attributes| attributes.is_a?(Hash) && attributes[:status] == 200 && attributes[:path] == '/v1/foobar' }
21
+ ::RequestLog::Db.stubs(:requests).returns(requests)
22
+ @rack_response = [200, {"Content-Type" => "text/html"}, "Hello, World!"]
23
+ @app = lambda { |env| @rack_response }
24
+ @middleware = RequestLog::Middleware.new(@app)
25
+ @middleware.call(@env).should == @rack_response
26
+ end
27
+
28
+ it "never calls logger if response content type doesn't match only_path option" do
29
+ @env = {"REQUEST_PATH" => "/foobar", "HTTP_USER_AGENT" => "Mozilla/5.0"}
30
+ @logger = mock('logger')
31
+ @logger.expects(:call).never
32
+ @profiler = mock('profiler')
33
+ @profiler.expects(:call).never
34
+ @rack_response = [200, {"Content-Type" => "text/html"}, "Hello, World!"]
35
+ @app = lambda { |env| @rack_response }
36
+ @middleware = RequestLog::Middleware.new(@app, :logger => @logger, :profiler => @profiler, :only_path => %r{\A/v\d})
37
+ @middleware.call(@env).should == @rack_response
38
+ end
39
+
40
+ it "invokes the logger with the data and the profiler if RuntimeError is raised" do
41
+ call_middleware_with_exception(RuntimeError)
42
+ end
43
+
44
+ it "invokes the logger with the data and the profiler if Timeout::Error is raised" do
45
+ call_middleware_with_exception(Timeout::Error)
46
+ end
47
+
48
+ def call_middleware_with_exception(exception_class)
49
+ @env = {"REQUEST_PATH" => "/", "HTTP_USER_AGENT" => "Mozilla/5.0"}
50
+ @logger = mock('logger')
51
+ @logger.expects(:call).raises(exception_class)
52
+ @profiler = mock('profiler')
53
+ @profiler.expects(:call).with() { |options| options[:result] == :failure && options[:exception].is_a?(exception_class) }
54
+ @rack_response = [200, {"Content-Type" => "text/html"}, "Hello, World!"]
55
+ @app = lambda { |env| @rack_response }
56
+ $stderr.expects(:puts).with() { |error_string| error_string =~ /#{exception_class.name}/ }
57
+ @middleware = RequestLog::Middleware.new(@app, :logger => @logger, :profiler => @profiler)
58
+ @middleware.call(@env).should == @rack_response
59
+ end
60
+ end
61
+
62
+ def env
63
+ {"GATEWAY_INTERFACE"=>"CGI/1.1", "QUERY_STRING"=>"", "REMOTE_ADDR"=>"127.0.0.1", "REMOTE_HOST"=>"www.publish.newsdesk.local", "REQUEST_METHOD"=>"GET", "REQUEST_URI"=>"http://publish.lvh.me:3000/images/logo.png", "SCRIPT_NAME"=>"", "SERVER_NAME"=>"publish.lvh.me", "SERVER_PORT"=>"3000", "SERVER_PROTOCOL"=>"HTTP/1.1", "SERVER_SOFTWARE"=>"WEBrick/1.3.1 (Ruby/1.9.2/2010-08-18)", "HTTP_HOST"=>"publish.lvh.me:3000", "HTTP_USER_AGENT"=>"Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; en-US; rv:1.9.2.2) Gecko/20100316 Firefox/3.6.2", "HTTP_ACCEPT"=>"image/png,image/*;q=0.8,*/*;q=0.5", "HTTP_ACCEPT_LANGUAGE"=>"en-us,en;q=0.5", "HTTP_ACCEPT_ENCODING"=>"gzip,deflate", "HTTP_ACCEPT_CHARSET"=>"ISO-8859-1,utf-8;q=0.7,*;q=0.7", "HTTP_KEEP_ALIVE"=>"115", "HTTP_CONNECTION"=>"keep-alive", "HTTP_REFERER"=>"http://publish.lvh.me:3000/system/profiling", "HTTP_AUTHORIZATION"=>"Basic YWRtaW46aWx3d3NwYTIwMTA=", "rack.version"=>[1, 1], "rack.input"=>StringIO.new, "rack.errors"=>$stderr, "rack.multithread"=>true, "rack.multiprocess"=>false, "rack.run_once"=>false, "rack.url_scheme"=>"http", "HTTP_VERSION"=>"HTTP/1.1", "REQUEST_PATH"=>"/"}
64
+ end
65
+ end
@@ -0,0 +1,200 @@
1
+ require 'spec_helper'
2
+
3
+ describe RequestLog::Profiler do
4
+ before(:each) do
5
+ profiler.reset
6
+ end
7
+
8
+ describe "call" do
9
+ describe "result => success" do
10
+ it "increments success_count and time stats" do
11
+ profiler.success_count.should == 0
12
+ profiler.call(:result => :success, :elapsed_time => 0.5)
13
+ profiler.success_count.should == 1
14
+ profiler.total_count.should == 1
15
+ profiler.min_time.should == 0.5
16
+ profiler.max_time.should == 0.5
17
+ profiler.avg_time.should == 0.5
18
+ profiler.call(:result => :success, :elapsed_time => 0.6)
19
+ profiler.success_count.should == 2
20
+ profiler.total_count.should == 2
21
+ profiler.min_time.should == 0.5
22
+ profiler.max_time.should == 0.6
23
+ profiler.avg_time.should == 0.55
24
+ profiler.call(:result => :success, :elapsed_time => 0.7)
25
+ profiler.success_count.should == 3
26
+ profiler.min_time.should == 0.5
27
+ profiler.max_time.should == 0.7
28
+ profiler.avg_time.should == 0.6
29
+ end
30
+ end
31
+
32
+ describe "result => failure" do
33
+ it "increments failure_count" do
34
+ profiler.failure_count.should == 0
35
+ profiler.call(:result => :failure)
36
+ profiler.failure_count.should == 1
37
+ profiler.total_count.should == 1
38
+ profiler.call(:result => :failure)
39
+ profiler.failure_count.should == 2
40
+ profiler.total_count.should == 2
41
+ end
42
+
43
+ it "sets failure_exceptions if an exception is passed" do
44
+ profiler.failure_count.should == 0
45
+ profiler.failure_exceptions.should == {}
46
+ profiler.call(:result => :failure, :exception => RuntimeError.new("some exception"))
47
+ profiler.failure_count.should == 1
48
+ profiler.failure_exceptions.should == {"RuntimeError" => 1}
49
+ profiler.call(:result => :failure, :exception => Timeout::Error.new("some timeout"))
50
+ profiler.failure_count.should == 2
51
+ profiler.failure_exceptions.should == {"RuntimeError" => 1, "Timeout::Error" => 1}
52
+ profiler.call(:result => :failure, :exception => Timeout::Error.new("some timeout"))
53
+ profiler.failure_count.should == 3
54
+ profiler.failure_exceptions.should == {"RuntimeError" => 1, "Timeout::Error" => 2}
55
+ end
56
+ end
57
+
58
+ describe "persist!" do
59
+ it "does not get invoked if should_persist? == false" do
60
+ profiler.stubs(:should_persist?).returns(false)
61
+ profiler.expects(:persist!).never
62
+ profiler.call(:result => :success, :elapsed_time => 0.6)
63
+ end
64
+
65
+ it "does get invoked if should_persist? == true" do
66
+ profiler.stubs(:should_persist?).returns(true)
67
+ profiler.expects(:persist!).once
68
+ profiler.call(:result => :success, :elapsed_time => 0.6)
69
+ end
70
+ end
71
+ end
72
+
73
+ describe "total_count" do
74
+ it "is failure_count + success_count" do
75
+ profiler.failure_count = 427
76
+ profiler.success_count = 10000
77
+ profiler.total_count.should == 10427
78
+ end
79
+ end
80
+
81
+ describe "failure_ratio" do
82
+ it "is failure_count/total_count" do
83
+ profiler.failure_count = 1
84
+ profiler.success_count = 0
85
+ profiler.failure_ratio.should == 1.0
86
+
87
+ profiler.failure_count = 1
88
+ profiler.success_count = 1
89
+ profiler.failure_ratio.should == 0.5
90
+
91
+ profiler.failure_count = 1
92
+ profiler.success_count = 9
93
+ profiler.failure_ratio.should == 0.1
94
+ end
95
+ end
96
+
97
+ describe "persist!" do
98
+ it "stores profiling info in mongo db" do
99
+ profiler.success_count = 80
100
+ profiler.failure_count = 20
101
+ profiler.avg_time = 0.0003
102
+ profiler.max_time = 0.09
103
+ profiling = mock('profiling')
104
+ profiling.expects(:insert).with(
105
+ :total_count => profiler.total_count,
106
+ :failure_ratio => profiler.failure_ratio,
107
+ :max_time => profiler.max_time,
108
+ :avg_time => profiler.avg_time,
109
+ :data => profiler.attributes
110
+ )
111
+ RequestLog::Db.expects(:profiling).returns(profiling)
112
+ profiler.persist!
113
+ end
114
+ end
115
+
116
+ describe "persist_enabled" do
117
+ it "defaults to false" do
118
+ profiler.reset.persist_enabled.should == false
119
+ end
120
+
121
+ it "can be set to true" do
122
+ profiler.persist_enabled = true
123
+ profiler.persist_enabled.should == true
124
+ end
125
+ end
126
+
127
+ describe "should_persist?" do
128
+ before(:each) do
129
+ profiler.persist_enabled = true
130
+ end
131
+
132
+ it "returns true if total_count % persist_frequency == 0" do
133
+ profiler.persist_frequency = 10
134
+ profiler.success_count = 5
135
+ profiler.failure_count = 5
136
+ profiler.should_persist?.should be_true
137
+
138
+ profiler.persist_frequency = 3
139
+ profiler.success_count = 80
140
+ profiler.failure_count = 10
141
+ profiler.should_persist?.should be_true
142
+ end
143
+
144
+ it "returns false if total_count % persist_frequency != 0" do
145
+ profiler.persist_frequency = 3
146
+ profiler.success_count = 5
147
+ profiler.failure_count = 5
148
+ profiler.should_persist?.should be_false
149
+ end
150
+
151
+ it "returns false if persist_enabled = false" do
152
+ profiler.persist_frequency = 10
153
+ profiler.success_count = 5
154
+ profiler.failure_count = 5
155
+ profiler.persist_enabled = false
156
+ profiler.should_persist?.should be_false
157
+ end
158
+ end
159
+
160
+ describe "attribute_names" do
161
+ it "has accessors" do
162
+ profiler.attribute_names.each do |attribute|
163
+ profiler.send(attribute).should == profiler.default_values[attribute]
164
+ profiler.send("#{attribute}=", "foobar")
165
+ profiler.send(attribute).should == "foobar"
166
+ end
167
+ end
168
+ end
169
+
170
+ describe "attributes" do
171
+ it "returns a hash with the attribute values" do
172
+ profiler.min_time = 3.0
173
+ profiler.attributes.should == profiler.default_values.merge(:min_time => 3.0)
174
+ end
175
+ end
176
+
177
+ describe "to_s" do
178
+ it "returns the inspect of the attributes" do
179
+ profiler.to_s.should == profiler.attributes.inspect
180
+ end
181
+ end
182
+
183
+ describe "reset" do
184
+ it "resets all attributes to default values" do
185
+ profiler.attribute_names.each do |attribute|
186
+ profiler.send("#{attribute}=", "foobar")
187
+ end
188
+ profiler.reset
189
+ profiler.attribute_names.each do |attribute|
190
+ profiler.send(attribute).should == profiler.default_values[attribute]
191
+ end
192
+ end
193
+ end
194
+
195
+
196
+
197
+ def profiler
198
+ RequestLog::Profiler
199
+ end
200
+ end
@@ -0,0 +1,9 @@
1
+ require 'bundler'
2
+ Bundler.require(:development)
3
+
4
+ $:.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
5
+ require 'request_log'
6
+
7
+ RSpec.configure do |config|
8
+ config.mock_with :mocha
9
+ end
metadata ADDED
@@ -0,0 +1,133 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: request_log
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 0
8
+ - 1
9
+ version: 0.0.1
10
+ platform: ruby
11
+ authors:
12
+ - Peter Marklund
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-10-29 00:00:00 +02:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: rspec
22
+ requirement: &id001 !ruby/object:Gem::Requirement
23
+ none: false
24
+ requirements:
25
+ - - "="
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 2
29
+ - 0
30
+ - 0
31
+ version: 2.0.0
32
+ type: :development
33
+ prerelease: false
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: mocha
37
+ requirement: &id002 !ruby/object:Gem::Requirement
38
+ none: false
39
+ requirements:
40
+ - - ~>
41
+ - !ruby/object:Gem::Version
42
+ segments:
43
+ - 0
44
+ - 9
45
+ - 8
46
+ version: 0.9.8
47
+ type: :development
48
+ prerelease: false
49
+ version_requirements: *id002
50
+ - !ruby/object:Gem::Dependency
51
+ name: rack
52
+ requirement: &id003 !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ~>
56
+ - !ruby/object:Gem::Version
57
+ segments:
58
+ - 1
59
+ - 2
60
+ - 1
61
+ version: 1.2.1
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: *id003
65
+ description:
66
+ email:
67
+ - peter@marklunds.com
68
+ executables: []
69
+
70
+ extensions: []
71
+
72
+ extra_rdoc_files: []
73
+
74
+ files:
75
+ - .gitignore
76
+ - Gemfile
77
+ - Gemfile.lock
78
+ - README.rdoc
79
+ - Rakefile
80
+ - lib/request_log.rb
81
+ - lib/request_log/data.rb
82
+ - lib/request_log/db.rb
83
+ - lib/request_log/middleware.rb
84
+ - lib/request_log/profiler.rb
85
+ - lib/request_log/tasks.rb
86
+ - lib/request_log/version.rb
87
+ - lib/tasks/request_log.rake
88
+ - request_log.gemspec
89
+ - spec/data_spec.rb
90
+ - spec/db_spec.rb
91
+ - spec/middleware_spec.rb
92
+ - spec/profiler_spec.rb
93
+ - spec/spec_helper.rb
94
+ has_rdoc: true
95
+ homepage: ""
96
+ licenses: []
97
+
98
+ post_install_message:
99
+ rdoc_options: []
100
+
101
+ require_paths:
102
+ - lib
103
+ required_ruby_version: !ruby/object:Gem::Requirement
104
+ none: false
105
+ requirements:
106
+ - - ">="
107
+ - !ruby/object:Gem::Version
108
+ hash: -1014350961
109
+ segments:
110
+ - 0
111
+ version: "0"
112
+ required_rubygems_version: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ hash: -1014350961
118
+ segments:
119
+ - 0
120
+ version: "0"
121
+ requirements: []
122
+
123
+ rubyforge_project: request_log
124
+ rubygems_version: 1.3.7
125
+ signing_key:
126
+ specification_version: 3
127
+ summary: Rack middleware for logging web requests to a MongoDB database. Provides a profiler for monitoring logging overhead.
128
+ test_files:
129
+ - spec/data_spec.rb
130
+ - spec/db_spec.rb
131
+ - spec/middleware_spec.rb
132
+ - spec/profiler_spec.rb
133
+ - spec/spec_helper.rb