samuel 0.1.0

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/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ .DS_Store
2
+ .yardoc
3
+ /coverage
4
+ /doc
5
+ /pkg
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2009 Chris Kampmeier
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,30 @@
1
+ = Samuel
2
+
3
+ Samuel is a gem for automatic logging of your Net::HTTP requests. It's named for
4
+ the serial diarist Mr. Pepys, who was known to reliably record events both
5
+ quotidian and remarkable.
6
+
7
+ Should a Great Plague, Fire, or Whale befall an important external web service
8
+ you use, you'll be sure to have a tidy record of it.
9
+
10
+ == Usage in Rails:
11
+
12
+ # config/environment.rb
13
+ config.gem "samuel"
14
+
15
+ That's about it! For now, Samuel automatically uses Rails' logger, and logs at
16
+ the +INFO+ level using an ActiveRecord-like format. Failed requests log at the
17
+ +WARN+ level.
18
+
19
+ == Usage in general:
20
+
21
+ When Rails isn't loaded, you'll have to manually configure logging, like this:
22
+
23
+ require 'samuel'
24
+ Samuel.logger = Logger.new('http_requests.log')
25
+
26
+ If you don't assign a logger, Samuel will configure a default logger on +STDOUT+.
27
+
28
+ == License
29
+
30
+ Copyright 2009 Chris Kampmeier. See +LICENSE+ for details.
data/Rakefile ADDED
@@ -0,0 +1,62 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "samuel"
8
+ gem.summary = %Q{An automatic logger for HTTP requests in Ruby}
9
+ gem.description = %Q{An automatic logger for HTTP requests in Ruby. Adds Net::HTTP request logging to your Rails logs, and more.}
10
+ gem.email = "chris@kampers.net"
11
+ gem.homepage = "http://github.com/chrisk/samuel"
12
+ gem.authors = ["Chris Kampmeier"]
13
+ gem.rubyforge_project = "samuel"
14
+ gem.add_development_dependency "thoughtbot-shoulda"
15
+ gem.add_development_dependency "yard"
16
+ gem.add_development_dependency "mocha"
17
+ gem.add_development_dependency "fakeweb"
18
+ end
19
+ Jeweler::GemcutterTasks.new
20
+ Jeweler::RubyforgeTasks.new do |rubyforge|
21
+ rubyforge.doc_task = "yardoc"
22
+ end
23
+ rescue LoadError
24
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
25
+ end
26
+
27
+ require 'rake/testtask'
28
+ Rake::TestTask.new(:test) do |test|
29
+ test.libs << 'lib' << 'test'
30
+ test.pattern = 'test/**/*_test.rb'
31
+ test.verbose = false
32
+ test.warning = true
33
+ end
34
+
35
+ begin
36
+ require 'rcov/rcovtask'
37
+ Rcov::RcovTask.new do |test|
38
+ test.libs << 'test'
39
+ test.pattern = 'test/**/*_test.rb'
40
+ test.rcov_opts << "--sort coverage"
41
+ test.rcov_opts << "--exclude gems"
42
+ test.verbose = false
43
+ test.warning = true
44
+ end
45
+ rescue LoadError
46
+ task :rcov do
47
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
48
+ end
49
+ end
50
+
51
+ task :test => :check_dependencies
52
+
53
+ task :default => :test
54
+
55
+ begin
56
+ require 'yard'
57
+ YARD::Rake::YardocTask.new
58
+ rescue LoadError
59
+ task :yardoc do
60
+ abort "YARD is not available. In order to run yardoc, you must: sudo gem install yard"
61
+ end
62
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
data/lib/samuel.rb ADDED
@@ -0,0 +1,33 @@
1
+ require "logger"
2
+ require "net/http"
3
+ require "net/https"
4
+ require "benchmark"
5
+
6
+ require "samuel/net_http"
7
+ require "samuel/request"
8
+
9
+
10
+ module Samuel
11
+ extend self
12
+
13
+ def logger=(new_logger)
14
+ @logger = new_logger
15
+ end
16
+
17
+ def logger
18
+ return @logger if !@logger.nil?
19
+
20
+ if defined?(RAILS_DEFAULT_LOGGER)
21
+ @logger = RAILS_DEFAULT_LOGGER
22
+ else
23
+ @logger = Logger.new(STDOUT)
24
+ end
25
+ end
26
+
27
+ def log_request(http, request, &block)
28
+ request = Request.new(http, request, block)
29
+ request.execute_and_log!
30
+ request.response
31
+ end
32
+
33
+ end
@@ -0,0 +1,10 @@
1
+ class Net::HTTP
2
+
3
+ alias request_without_samuel request
4
+ def request(req, body = nil, &block)
5
+ Samuel.log_request(self, req) do
6
+ request_without_samuel(req, body, &block)
7
+ end
8
+ end
9
+
10
+ end
@@ -0,0 +1,75 @@
1
+ module Samuel
2
+ class Request
3
+
4
+ attr_accessor :response
5
+
6
+ def initialize(http, request, proc)
7
+ @http, @request, @proc = http, request, proc
8
+ end
9
+
10
+ def execute_and_log!
11
+ # If an exception is raised in the Benchmark block, it'll interrupt the
12
+ # benchmark. Instead, use an inner block to record it as the "response"
13
+ # for raising after the benchmark (and logging) is done.
14
+ @seconds = Benchmark.realtime do
15
+ begin; @response = @proc.call; rescue Exception => @response; end
16
+ end
17
+ Samuel.logger.add(log_level, log_message)
18
+ raise @response if @response.is_a?(Exception)
19
+ end
20
+
21
+ private
22
+
23
+ def log_message
24
+ bold = "\e[1m"
25
+ blue = "\e[34m"
26
+ underline = "\e[4m"
27
+ reset = "\e[0m"
28
+ " #{bold}#{blue}#{underline}HTTP request (#{milliseconds}ms) " +
29
+ "#{response_summary}#{reset} #{method} #{uri}"
30
+ end
31
+
32
+ def milliseconds
33
+ (@seconds * 1000).round
34
+ end
35
+
36
+ def uri
37
+ "#{scheme}://#{@http.address}#{port_if_not_default}#{@request.path}"
38
+ end
39
+
40
+ def scheme
41
+ @http.use_ssl? ? "https" : "http"
42
+ end
43
+
44
+ def port_if_not_default
45
+ ssl, port = @http.use_ssl?, @http.port
46
+ if (!ssl && port == 80) || (ssl && port == 443)
47
+ ""
48
+ else
49
+ ":#{port}"
50
+ end
51
+ end
52
+
53
+ def method
54
+ @request.method.to_s.upcase
55
+ end
56
+
57
+ def response_summary
58
+ if response.is_a?(Exception)
59
+ response.class
60
+ else
61
+ "[#{response.code} #{response.message}]"
62
+ end
63
+ end
64
+
65
+ def log_level
66
+ error_classes = [Exception, Net::HTTPClientError, Net::HTTPServerError]
67
+ if error_classes.any? { |klass| response.is_a?(klass) }
68
+ level = Logger::WARN
69
+ else
70
+ level = Logger::INFO
71
+ end
72
+ end
73
+
74
+ end
75
+ end
data/samuel.gemspec ADDED
@@ -0,0 +1,65 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{samuel}
8
+ s.version = "0.1.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Chris Kampmeier"]
12
+ s.date = %q{2009-09-11}
13
+ s.description = %q{An automatic logger for HTTP requests in Ruby. Adds Net::HTTP request logging to your Rails logs, and more.}
14
+ s.email = %q{chris@kampers.net}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ ".gitignore",
22
+ "LICENSE",
23
+ "README.rdoc",
24
+ "Rakefile",
25
+ "VERSION",
26
+ "lib/samuel.rb",
27
+ "lib/samuel/net_http.rb",
28
+ "lib/samuel/request.rb",
29
+ "samuel.gemspec",
30
+ "test/samuel_test.rb",
31
+ "test/test_helper.rb"
32
+ ]
33
+ s.homepage = %q{http://github.com/chrisk/samuel}
34
+ s.rdoc_options = ["--charset=UTF-8"]
35
+ s.require_paths = ["lib"]
36
+ s.rubyforge_project = %q{samuel}
37
+ s.rubygems_version = %q{1.3.5}
38
+ s.summary = %q{An automatic logger for HTTP requests in Ruby}
39
+ s.test_files = [
40
+ "test/samuel_test.rb",
41
+ "test/test_helper.rb"
42
+ ]
43
+
44
+ if s.respond_to? :specification_version then
45
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
46
+ s.specification_version = 3
47
+
48
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
49
+ s.add_development_dependency(%q<thoughtbot-shoulda>, [">= 0"])
50
+ s.add_development_dependency(%q<yard>, [">= 0"])
51
+ s.add_development_dependency(%q<mocha>, [">= 0"])
52
+ s.add_development_dependency(%q<fakeweb>, [">= 0"])
53
+ else
54
+ s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
55
+ s.add_dependency(%q<yard>, [">= 0"])
56
+ s.add_dependency(%q<mocha>, [">= 0"])
57
+ s.add_dependency(%q<fakeweb>, [">= 0"])
58
+ end
59
+ else
60
+ s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
61
+ s.add_dependency(%q<yard>, [">= 0"])
62
+ s.add_dependency(%q<mocha>, [">= 0"])
63
+ s.add_dependency(%q<fakeweb>, [">= 0"])
64
+ end
65
+ end
@@ -0,0 +1,90 @@
1
+ require 'test_helper'
2
+
3
+ class SamuelTest < Test::Unit::TestCase
4
+
5
+ context "making an HTTP request" do
6
+ setup { setup_test_logger
7
+ FakeWeb.clean_registry }
8
+ teardown { teardown_test_logger }
9
+
10
+ context "to GET http://example.com/test, responding with a 200 in 53ms" do
11
+ setup do
12
+ FakeWeb.register_uri(:get, "http://example.com/test", :status => [200, "OK"])
13
+ Benchmark.stubs(:realtime).yields.returns(0.053)
14
+ open "http://example.com/test"
15
+ end
16
+
17
+ should_log_lines 1
18
+ should_log_at_level :info
19
+ should_log_including "HTTP request"
20
+ should_log_including "(53ms)"
21
+ should_log_including "[200 OK]"
22
+ should_log_including "GET http://example.com/test"
23
+ end
24
+
25
+ context "on a non-standard port" do
26
+ setup do
27
+ FakeWeb.register_uri(:get, "http://example.com:8080/test", :status => [200, "OK"])
28
+ open "http://example.com:8080/test"
29
+ end
30
+
31
+ should_log_including "GET http://example.com:8080/test"
32
+ end
33
+
34
+ context "with SSL" do
35
+ setup do
36
+ FakeWeb.register_uri(:get, "https://example.com/test", :status => [200, "OK"])
37
+ open "https://example.com/test"
38
+ end
39
+
40
+ should_log_including "HTTP request"
41
+ should_log_including "GET https://example.com/test"
42
+ end
43
+
44
+ context "with SSL on a non-standard port" do
45
+ setup do
46
+ FakeWeb.register_uri(:get, "https://example.com:80/test", :status => [200, "OK"])
47
+ open "https://example.com:80/test"
48
+ end
49
+
50
+ should_log_including "HTTP request"
51
+ should_log_including "GET https://example.com:80/test"
52
+ end
53
+
54
+ context "that raises" do
55
+ setup do
56
+ FakeWeb.register_uri(:get, "http://example.com/test", :exception => Errno::ECONNREFUSED)
57
+ begin
58
+ Net::HTTP.start("example.com") { |http| http.get("/test") }
59
+ rescue Errno::ECONNREFUSED => @exception
60
+ end
61
+ end
62
+
63
+ should_log_at_level :warn
64
+ should_log_including "HTTP request"
65
+ should_log_including "GET http://example.com/test"
66
+ should_log_including "Errno::ECONNREFUSED"
67
+ should_log_including %r|\d+ms|
68
+ should_raise_exception Errno::ECONNREFUSED
69
+ end
70
+
71
+ context "that responds with a 500-level code" do
72
+ setup do
73
+ FakeWeb.register_uri(:get, "http://example.com/test", :status => [502, "Bad Gateway"])
74
+ Net::HTTP.start("example.com") { |http| http.get("/test") }
75
+ end
76
+
77
+ should_log_at_level :warn
78
+ end
79
+
80
+ context "that responds with a 400-level code" do
81
+ setup do
82
+ FakeWeb.register_uri(:get, "http://example.com/test", :status => [404, "Not Found"])
83
+ Net::HTTP.start("example.com") { |http| http.get("/test") }
84
+ end
85
+
86
+ should_log_at_level :warn
87
+ end
88
+ end
89
+
90
+ end
@@ -0,0 +1,57 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+ require 'mocha'
5
+ require 'open-uri'
6
+ require 'fakeweb'
7
+
8
+ FakeWeb.allow_net_connect = false
9
+
10
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
11
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
12
+ require 'samuel'
13
+
14
+ class Test::Unit::TestCase
15
+ TEST_LOG_PATH = File.join(File.dirname(__FILE__), 'test.log')
16
+
17
+ def self.should_log_lines(expected_count)
18
+ should "log #{expected_count} line#{'s' unless expected_count == 1}" do
19
+ lines = File.readlines(TEST_LOG_PATH)
20
+ assert_equal expected_count, lines.length
21
+ end
22
+ end
23
+
24
+ def self.should_log_including(what)
25
+ should "log a line including #{what.inspect}" do
26
+ contents = File.read(TEST_LOG_PATH)
27
+ if what.is_a?(Regexp)
28
+ assert_match what, contents
29
+ else
30
+ assert contents.include?(what)
31
+ end
32
+ end
33
+ end
34
+
35
+ def self.should_log_at_level(level)
36
+ level = level.to_s.upcase
37
+ should "log at the #{level} level" do
38
+ assert File.read(TEST_LOG_PATH).include?(" #{level} -- :")
39
+ end
40
+ end
41
+
42
+ def self.should_raise_exception(klass)
43
+ should "raise an #{klass} exception" do
44
+ assert @exception.is_a?(klass)
45
+ end
46
+ end
47
+
48
+ def setup_test_logger
49
+ FileUtils.rm_rf TEST_LOG_PATH
50
+ FileUtils.touch TEST_LOG_PATH
51
+ Samuel.logger = Logger.new(TEST_LOG_PATH)
52
+ end
53
+
54
+ def teardown_test_logger
55
+ FileUtils.rm_rf TEST_LOG_PATH
56
+ end
57
+ end
metadata ADDED
@@ -0,0 +1,107 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: samuel
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Chris Kampmeier
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-09-11 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: thoughtbot-shoulda
17
+ type: :development
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: yard
27
+ type: :development
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: "0"
34
+ version:
35
+ - !ruby/object:Gem::Dependency
36
+ name: mocha
37
+ type: :development
38
+ version_requirement:
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: "0"
44
+ version:
45
+ - !ruby/object:Gem::Dependency
46
+ name: fakeweb
47
+ type: :development
48
+ version_requirement:
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: "0"
54
+ version:
55
+ description: An automatic logger for HTTP requests in Ruby. Adds Net::HTTP request logging to your Rails logs, and more.
56
+ email: chris@kampers.net
57
+ executables: []
58
+
59
+ extensions: []
60
+
61
+ extra_rdoc_files:
62
+ - LICENSE
63
+ - README.rdoc
64
+ files:
65
+ - .document
66
+ - .gitignore
67
+ - LICENSE
68
+ - README.rdoc
69
+ - Rakefile
70
+ - VERSION
71
+ - lib/samuel.rb
72
+ - lib/samuel/net_http.rb
73
+ - lib/samuel/request.rb
74
+ - samuel.gemspec
75
+ - test/samuel_test.rb
76
+ - test/test_helper.rb
77
+ has_rdoc: true
78
+ homepage: http://github.com/chrisk/samuel
79
+ licenses: []
80
+
81
+ post_install_message:
82
+ rdoc_options:
83
+ - --charset=UTF-8
84
+ require_paths:
85
+ - lib
86
+ required_ruby_version: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: "0"
91
+ version:
92
+ required_rubygems_version: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: "0"
97
+ version:
98
+ requirements: []
99
+
100
+ rubyforge_project: samuel
101
+ rubygems_version: 1.3.5
102
+ signing_key:
103
+ specification_version: 3
104
+ summary: An automatic logger for HTTP requests in Ruby
105
+ test_files:
106
+ - test/samuel_test.rb
107
+ - test/test_helper.rb