request_log 0.0.1

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