samuel 0.1.0 → 0.2.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/README.rdoc CHANGED
@@ -7,24 +7,56 @@ quotidian and remarkable.
7
7
  Should a Great Plague, Fire, or Whale befall an important external web service
8
8
  you use, you'll be sure to have a tidy record of it.
9
9
 
10
- == Usage in Rails:
10
+ == Usage:
11
+
12
+ When Rails is loaded, Samuel configures a few things automatically. So all you
13
+ need to do is this:
11
14
 
12
15
  # config/environment.rb
13
16
  config.gem "samuel"
14
17
 
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:
18
+ And Samuel will automatically use Rails's logger and an ActiveRecord-like format.
20
19
 
21
- When Rails isn't loaded, you'll have to manually configure logging, like this:
20
+ For non-Rails projects, you'll have to manually configure logging, like this:
22
21
 
23
22
  require 'samuel'
24
23
  Samuel.logger = Logger.new('http_requests.log')
25
24
 
26
25
  If you don't assign a logger, Samuel will configure a default logger on +STDOUT+.
27
26
 
27
+ == Configuration
28
+
29
+ There are two ways to specify configuration options for Samuel: global and
30
+ inline. Global configs look like this:
31
+
32
+ Samuel.config[:label] = "Twitter API"
33
+ Samuel.config[:filtered_params] = :password
34
+
35
+ You should put global configuration somewhere early-on in your program. If
36
+ you're using Rails, <tt>config/initializers/samuel.rb</tt> will do the trick.
37
+
38
+ Alternatively, an inline configuration block temporarily overrides any global
39
+ configuration for a set of HTTP requests:
40
+
41
+ Samuel.with_config :label => "Twitter API" do
42
+ Net::HTTP.start("twitter.com") { |http| http.get("/help/test") }
43
+ end
44
+
45
+ Right now, there are two configuration changes you can make in either style:
46
+
47
+ * +:label+ - This is the very first part of each log entry, and it gets
48
+ <tt>"Request"</tt> appended to it. By default, it's <tt>"HTTP"</tt> -- but if
49
+ you want your log to say +Twitter API Request+ instead of the default +HTTP
50
+ Request+, set this to <tt>"Twitter API"</tt>.
51
+ * +:filtered_params+ - This works just like Rails's +filter_parameter_logging+
52
+ method. Set it to a symbol, string, or array of them, and Samuel will filter
53
+ the value of query parameters that have any of these patterns as a substring
54
+ by replacing the value with <tt>[FILTERED]</tt> in your logs. By default, no
55
+ filtering is enabled.
56
+
57
+ Samuel logs successful HTTP requests at the +INFO+ level; Failed requests log at
58
+ the +WARN+ level. This isn't currently configurable, but it's on the list.
59
+
28
60
  == License
29
61
 
30
62
  Copyright 2009 Chris Kampmeier. See +LICENSE+ for details.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.0
1
+ 0.2.0
@@ -25,7 +25,8 @@ module Samuel
25
25
  blue = "\e[34m"
26
26
  underline = "\e[4m"
27
27
  reset = "\e[0m"
28
- " #{bold}#{blue}#{underline}HTTP request (#{milliseconds}ms) " +
28
+ label = Samuel.config[:label]
29
+ " #{bold}#{blue}#{underline}#{label} request (#{milliseconds}ms) " +
29
30
  "#{response_summary}#{reset} #{method} #{uri}"
30
31
  end
31
32
 
@@ -34,7 +35,21 @@ module Samuel
34
35
  end
35
36
 
36
37
  def uri
37
- "#{scheme}://#{@http.address}#{port_if_not_default}#{@request.path}"
38
+ "#{scheme}://#{@http.address}#{port_if_not_default}#{filtered_path}"
39
+ end
40
+
41
+ def filtered_path
42
+ path_without_query, query = @request.path.split("?")
43
+ if query
44
+ patterns = [Samuel.config[:filtered_params]].flatten
45
+ patterns.map { |pattern|
46
+ pattern_for_regex = Regexp.escape(pattern.to_s)
47
+ [/([^&]*#{pattern_for_regex}[^&=]*)=(?:[^&]+)/, '\1=[FILTERED]']
48
+ }.each { |filter| query.gsub!(*filter) }
49
+ "#{path_without_query}?#{query}"
50
+ else
51
+ @request.path
52
+ end
38
53
  end
39
54
 
40
55
  def scheme
data/lib/samuel.rb CHANGED
@@ -10,11 +10,10 @@ require "samuel/request"
10
10
  module Samuel
11
11
  extend self
12
12
 
13
- def logger=(new_logger)
14
- @logger = new_logger
15
- end
13
+ attr_writer :config, :logger
16
14
 
17
15
  def logger
16
+ @logger = nil if !defined?(@logger)
18
17
  return @logger if !@logger.nil?
19
18
 
20
19
  if defined?(RAILS_DEFAULT_LOGGER)
@@ -24,10 +23,30 @@ module Samuel
24
23
  end
25
24
  end
26
25
 
26
+ def config
27
+ Thread.current[:__samuel_config] ? Thread.current[:__samuel_config] : @config
28
+ end
29
+
27
30
  def log_request(http, request, &block)
28
31
  request = Request.new(http, request, block)
29
32
  request.execute_and_log!
30
33
  request.response
31
34
  end
32
35
 
36
+ def with_config(options = {})
37
+ original_config = config.dup
38
+ nested = !Thread.current[:__samuel_config].nil?
39
+
40
+ Thread.current[:__samuel_config] = original_config.merge(options)
41
+ yield
42
+ Thread.current[:__samuel_config] = nested ? original_config : nil
43
+ end
44
+
45
+ def reset_config
46
+ Thread.current[:__samuel_config] = nil
47
+ @config = {:label => "HTTP", :filtered_params => []}
48
+ end
49
+
33
50
  end
51
+
52
+ Samuel.reset_config
data/samuel.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{samuel}
8
- s.version = "0.1.0"
8
+ s.version = "0.2.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Chris Kampmeier"]
12
- s.date = %q{2009-09-11}
12
+ s.date = %q{2009-09-13}
13
13
  s.description = %q{An automatic logger for HTTP requests in Ruby. Adds Net::HTTP request logging to your Rails logs, and more.}
14
14
  s.email = %q{chris@kampers.net}
15
15
  s.extra_rdoc_files = [
@@ -27,8 +27,10 @@ Gem::Specification.new do |s|
27
27
  "lib/samuel/net_http.rb",
28
28
  "lib/samuel/request.rb",
29
29
  "samuel.gemspec",
30
+ "test/request_test.rb",
30
31
  "test/samuel_test.rb",
31
- "test/test_helper.rb"
32
+ "test/test_helper.rb",
33
+ "test/thread_test.rb"
32
34
  ]
33
35
  s.homepage = %q{http://github.com/chrisk/samuel}
34
36
  s.rdoc_options = ["--charset=UTF-8"]
@@ -37,8 +39,10 @@ Gem::Specification.new do |s|
37
39
  s.rubygems_version = %q{1.3.5}
38
40
  s.summary = %q{An automatic logger for HTTP requests in Ruby}
39
41
  s.test_files = [
40
- "test/samuel_test.rb",
41
- "test/test_helper.rb"
42
+ "test/request_test.rb",
43
+ "test/samuel_test.rb",
44
+ "test/test_helper.rb",
45
+ "test/thread_test.rb"
42
46
  ]
43
47
 
44
48
  if s.respond_to? :specification_version then
@@ -0,0 +1,178 @@
1
+ require 'test_helper'
2
+
3
+ class RequestTest < Test::Unit::TestCase
4
+
5
+ context "making an HTTP request" do
6
+ setup { setup_test_logger
7
+ FakeWeb.clean_registry
8
+ Samuel.reset_config }
9
+ teardown { teardown_test_logger }
10
+
11
+ context "to GET http://example.com/test, responding with a 200 in 53ms" do
12
+ setup do
13
+ FakeWeb.register_uri(:get, "http://example.com/test", :status => [200, "OK"])
14
+ Benchmark.stubs(:realtime).yields.returns(0.053)
15
+ open "http://example.com/test"
16
+ end
17
+
18
+ should_log_lines 1
19
+ should_log_at_level :info
20
+ should_log_including "HTTP request"
21
+ should_log_including "(53ms)"
22
+ should_log_including "[200 OK]"
23
+ should_log_including "GET http://example.com/test"
24
+ end
25
+
26
+ context "on a non-standard port" do
27
+ setup do
28
+ FakeWeb.register_uri(:get, "http://example.com:8080/test", :status => [200, "OK"])
29
+ open "http://example.com:8080/test"
30
+ end
31
+
32
+ should_log_including "GET http://example.com:8080/test"
33
+ end
34
+
35
+ context "with SSL" do
36
+ setup do
37
+ FakeWeb.register_uri(:get, "https://example.com/test", :status => [200, "OK"])
38
+ open "https://example.com/test"
39
+ end
40
+
41
+ should_log_including "HTTP request"
42
+ should_log_including "GET https://example.com/test"
43
+ end
44
+
45
+ context "with SSL on a non-standard port" do
46
+ setup do
47
+ FakeWeb.register_uri(:get, "https://example.com:80/test", :status => [200, "OK"])
48
+ open "https://example.com:80/test"
49
+ end
50
+
51
+ should_log_including "HTTP request"
52
+ should_log_including "GET https://example.com:80/test"
53
+ end
54
+
55
+ context "that raises" do
56
+ setup do
57
+ FakeWeb.register_uri(:get, "http://example.com/test", :exception => Errno::ECONNREFUSED)
58
+ begin
59
+ Net::HTTP.start("example.com") { |http| http.get("/test") }
60
+ rescue Errno::ECONNREFUSED => @exception
61
+ end
62
+ end
63
+
64
+ should_log_at_level :warn
65
+ should_log_including "HTTP request"
66
+ should_log_including "GET http://example.com/test"
67
+ should_log_including "Errno::ECONNREFUSED"
68
+ should_log_including %r|\d+ms|
69
+ should_raise_exception Errno::ECONNREFUSED
70
+ end
71
+
72
+ context "that responds with a 500-level code" do
73
+ setup do
74
+ FakeWeb.register_uri(:get, "http://example.com/test", :status => [502, "Bad Gateway"])
75
+ Net::HTTP.start("example.com") { |http| http.get("/test") }
76
+ end
77
+
78
+ should_log_at_level :warn
79
+ end
80
+
81
+ context "that responds with a 400-level code" do
82
+ setup do
83
+ FakeWeb.register_uri(:get, "http://example.com/test", :status => [404, "Not Found"])
84
+ Net::HTTP.start("example.com") { |http| http.get("/test") }
85
+ end
86
+
87
+ should_log_at_level :warn
88
+ end
89
+
90
+ context "inside a configuration block with :label => 'Example'" do
91
+ setup do
92
+ FakeWeb.register_uri(:get, "http://example.com/test", :status => [200, "OK"])
93
+ Samuel.with_config :label => "Example" do
94
+ open "http://example.com/test"
95
+ end
96
+ end
97
+
98
+ should_log_including "Example request"
99
+ should_have_config_afterwards_including :label => "HTTP"
100
+ end
101
+
102
+ context "inside a configuration block with :filter_params" do
103
+ setup do
104
+ FakeWeb.register_uri(:get, "http://example.com/test?password=secret&username=chrisk",
105
+ :status => [200, "OK"])
106
+ @uri = "http://example.com/test?password=secret&username=chrisk"
107
+ end
108
+
109
+ context "=> :password" do
110
+ setup { Samuel.with_config(:filtered_params => :password) { open @uri } }
111
+ should_log_including "http://example.com/test?password=[FILTERED]&username=chrisk"
112
+ end
113
+
114
+ context "=> :ass" do
115
+ setup { Samuel.with_config(:filtered_params => :ass) { open @uri } }
116
+ should_log_including "http://example.com/test?password=[FILTERED]&username=chrisk"
117
+ end
118
+
119
+ context "=> ['pass', 'name']" do
120
+ setup { Samuel.with_config(:filtered_params => %w(pass name)) { open @uri } }
121
+ should_log_including "http://example.com/test?password=[FILTERED]&username=[FILTERED]"
122
+ end
123
+ end
124
+
125
+ context "with a global config including :label => 'Example'" do
126
+ setup do
127
+ FakeWeb.register_uri(:get, "http://example.com/test", :status => [200, "OK"])
128
+ Samuel.config[:label] = "Example"
129
+ open "http://example.com/test"
130
+ end
131
+
132
+ should_log_including "Example request"
133
+ should_have_config_afterwards_including :label => "Example"
134
+ end
135
+
136
+ context "with a global config including :label => 'Example' but inside config block that changes it to 'Example 2'" do
137
+ setup do
138
+ FakeWeb.register_uri(:get, "http://example.com/test", :status => [200, "OK"])
139
+ Samuel.config[:label] = "Example"
140
+ Samuel.with_config(:label => "Example 2") { open "http://example.com/test" }
141
+ end
142
+
143
+ should_log_including "Example 2 request"
144
+ should_have_config_afterwards_including :label => "Example"
145
+ end
146
+
147
+ context "inside a config block of :label => 'Example 2' nested inside a config block of :label => 'Example'" do
148
+ setup do
149
+ FakeWeb.register_uri(:get, "http://example.com/test", :status => [200, "OK"])
150
+ Samuel.with_config :label => "Example" do
151
+ Samuel.with_config :label => "Example 2" do
152
+ open "http://example.com/test"
153
+ end
154
+ end
155
+ end
156
+
157
+ should_log_including "Example 2 request"
158
+ should_have_config_afterwards_including :label => "HTTP"
159
+ end
160
+
161
+ context "wth a global config including :label => 'Example' but inside a config block of :label => 'Example 3' nested inside a config block of :label => 'Example 2'" do
162
+ setup do
163
+ FakeWeb.register_uri(:get, "http://example.com/test", :status => [200, "OK"])
164
+ Samuel.config[:label] = "Example"
165
+ Samuel.with_config :label => "Example 2" do
166
+ Samuel.with_config :label => "Example 3" do
167
+ open "http://example.com/test"
168
+ end
169
+ end
170
+ end
171
+
172
+ should_log_including "Example 3 request"
173
+ should_have_config_afterwards_including :label => "Example"
174
+ end
175
+
176
+ end
177
+
178
+ end
data/test/samuel_test.rb CHANGED
@@ -2,88 +2,40 @@ require 'test_helper'
2
2
 
3
3
  class SamuelTest < Test::Unit::TestCase
4
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"
5
+ context "logger configuration" do
6
+ setup do
7
+ Samuel.logger = nil
8
+ if Object.const_defined?(:RAILS_DEFAULT_LOGGER)
9
+ Object.send(:remove_const, :RAILS_DEFAULT_LOGGER)
15
10
  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
11
  end
24
12
 
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"
13
+ teardown do
14
+ Samuel.logger = nil
32
15
  end
33
16
 
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
17
+ context "when Rails's logger is available" do
18
+ setup { Object.const_set(:RAILS_DEFAULT_LOGGER, :mock_logger) }
43
19
 
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"
20
+ should "use the same logger" do
21
+ assert_equal :mock_logger, Samuel.logger
48
22
  end
49
-
50
- should_log_including "HTTP request"
51
- should_log_including "GET https://example.com:80/test"
52
23
  end
53
24
 
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
25
+ context "when Rails's logger is not available" do
26
+ should "use a new Logger instance pointed to STDOUT" do
27
+ assert_instance_of Logger, Samuel.logger
28
+ assert_equal STDOUT, Samuel.logger.instance_variable_get(:"@logdev").dev
61
29
  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
30
  end
31
+ end
79
32
 
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
33
 
86
- should_log_at_level :warn
34
+ context ".reset_config" do
35
+ should "reset the config to default vaules" do
36
+ Samuel.config = {:foo => "bar"}
37
+ Samuel.reset_config
38
+ assert_equal({:label => "HTTP", :filtered_params => []}, Samuel.config)
87
39
  end
88
40
  end
89
41
 
data/test/test_helper.rb CHANGED
@@ -27,7 +27,8 @@ class Test::Unit::TestCase
27
27
  if what.is_a?(Regexp)
28
28
  assert_match what, contents
29
29
  else
30
- assert contents.include?(what)
30
+ assert contents.include?(what),
31
+ "Expected #{contents.inspect} to include #{what.inspect}"
31
32
  end
32
33
  end
33
34
  end
@@ -45,6 +46,14 @@ class Test::Unit::TestCase
45
46
  end
46
47
  end
47
48
 
49
+ def self.should_have_config_afterwards_including(config)
50
+ config.each_pair do |key, value|
51
+ should "continue afterwards with Samuel.config[#{key.inspect}] set to #{value.inspect}" do
52
+ assert_equal value, Samuel.config[key]
53
+ end
54
+ end
55
+ end
56
+
48
57
  def setup_test_logger
49
58
  FileUtils.rm_rf TEST_LOG_PATH
50
59
  FileUtils.touch TEST_LOG_PATH
@@ -0,0 +1,32 @@
1
+ require 'test_helper'
2
+
3
+ class ThreadTest < Test::Unit::TestCase
4
+
5
+ context "when logging multiple requests at once" do
6
+ setup do
7
+ @log = StringIO.new
8
+ Samuel.logger = Logger.new(@log)
9
+ FakeWeb.register_uri(:get, /example\.com/, :status => [200, "OK"])
10
+ threads = []
11
+ 5.times do |i|
12
+ threads << Thread.new(i) do |n|
13
+ Samuel.with_config :label => "Example #{n}" do
14
+ Thread.pass
15
+ open "http://example.com/#{n}"
16
+ end
17
+ end
18
+ end
19
+ threads.each { |t| t.join }
20
+ @log.rewind
21
+ end
22
+
23
+ should "not let configuration blocks interfere with eachother" do
24
+ @log.each_line do |line|
25
+ matches = %r|Example (\d+).*example\.com/(\d+)|.match(line)
26
+ assert_not_nil matches
27
+ assert_equal matches[1], matches[2]
28
+ end
29
+ end
30
+ end
31
+
32
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: samuel
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Kampmeier
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-09-11 00:00:00 -07:00
12
+ date: 2009-09-13 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -72,8 +72,10 @@ files:
72
72
  - lib/samuel/net_http.rb
73
73
  - lib/samuel/request.rb
74
74
  - samuel.gemspec
75
+ - test/request_test.rb
75
76
  - test/samuel_test.rb
76
77
  - test/test_helper.rb
78
+ - test/thread_test.rb
77
79
  has_rdoc: true
78
80
  homepage: http://github.com/chrisk/samuel
79
81
  licenses: []
@@ -103,5 +105,7 @@ signing_key:
103
105
  specification_version: 3
104
106
  summary: An automatic logger for HTTP requests in Ruby
105
107
  test_files:
108
+ - test/request_test.rb
106
109
  - test/samuel_test.rb
107
110
  - test/test_helper.rb
111
+ - test/thread_test.rb