samuel 0.1.0 → 0.2.0

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