toadhopper 0.8 → 0.9

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/.gitignore CHANGED
@@ -1,7 +1,6 @@
1
- /dist/
2
- .yardoc
3
- /doc/bin
4
- vendor
5
- bin
6
- doc
7
- toadhopper.gemspec
1
+ /pkg
2
+ /.yardoc
3
+ /vendor
4
+ /bin
5
+ /doc
6
+ /toadhopper.gemspec
data/README.md CHANGED
@@ -2,13 +2,12 @@ A base library for [Hoptoad](http://www.hoptoadapp.com/) error reporting.
2
2
 
3
3
  Toadhopper can be used to report plain old Ruby exceptions, or to build a framework-specific gem such as [toadhopper-sinatra](http://github.com/toolmantim/toadhopper-sinatra).
4
4
 
5
- require 'toadhopper'
6
-
7
- dispatcher = Toadhopper::Dispatcher.new("YOURAPIKEY")
8
-
9
- error = begin; raise "Kaboom!"; rescue => e; e; end
10
-
11
- puts dispatcher.post!(error)
5
+ begin
6
+ raise "Kaboom!"
7
+ rescue => e
8
+ require 'toadhopper'
9
+ ToadHopper.new("YOURAPIKEY").post!(e)
10
+ end
12
11
 
13
12
  You can install it via rubygems:
14
13
 
data/Rakefile CHANGED
@@ -14,9 +14,11 @@ Jeweler::Tasks.new do |s|
14
14
  s.email = "t.lucas@toolmantim.com"
15
15
  s.homepage = "http://github.com/toolmantim/toadhopper"
16
16
  s.authors = ["Tim Lucas", "Samuel Tesla", "Corey Donohoe"]
17
- s.version = "0.8"
18
17
  s.extra_rdoc_files = ["README.md", "LICENSE"]
19
18
  s.executables = nil # stops jeweler automatically adding bin/*
19
+
20
+ require File.join(File.dirname(__FILE__), 'lib', 'toadhopper')
21
+ s.version = ToadHopper::VERSION
20
22
 
21
23
  require 'bundler'
22
24
  bundler_env = Bundler::Environment.load(File.dirname(__FILE__) + '/Gemfile')
data/lib/notice.haml CHANGED
@@ -4,7 +4,7 @@
4
4
  %notifier
5
5
  %name= notifier_name
6
6
  %version= notifier_version
7
- %url http://github.com/toolmantim/toadhopper
7
+ %url= notifier_url
8
8
  %error
9
9
  %class= error.class.name
10
10
  %message= "#{error.class.name}: #{error.message}"
@@ -30,5 +30,5 @@
30
30
  %var{:key => key}= value
31
31
 
32
32
  %server-environment
33
- %project-root= Dir.pwd
33
+ %project-root= project_root
34
34
  %environment-name= framework_env
data/lib/toadhopper.rb CHANGED
@@ -1,153 +1,130 @@
1
- root = File.expand_path(File.dirname(__FILE__))
2
1
  require 'net/http'
3
2
  require 'haml'
4
3
  require 'haml/engine'
5
4
  require 'nokogiri'
6
- require File.join(root, 'backtrace')
7
5
 
8
- module ToadHopper
6
+ # Posts errors to the Hoptoad API
7
+ class ToadHopper
8
+ VERSION = "0.9"
9
+
9
10
  # Hoptoad API response
10
11
  class Response < Struct.new(:status, :body, :errors); end
11
12
 
12
- # Posts errors to the Hoptoad API
13
- class Dispatcher
14
- attr_reader :api_key
13
+ attr_reader :api_key
15
14
 
16
- def initialize(api_key)
17
- @api_key = api_key
18
- end
19
-
20
- # Sets patterns to [FILTER] out sensitive data such as passwords, emails and credit card numbers.
21
- #
22
- # Toadhopper::Dispatcher.new('apikey').filters = /password/, /email/, /credit_card_number/
23
- def filters=(*filters)
24
- @filters = filters.flatten
25
- end
26
-
27
- # Filters for the Dispatcher
28
- #
29
- # @return [Regexp]
30
- def filters
31
- [@filters].flatten.compact
32
- end
15
+ def initialize(api_key)
16
+ @api_key = api_key
17
+ end
18
+
19
+ # Sets patterns to +[FILTER]+ out sensitive data such as +/password/+, +/email/+ and +/credit_card_number/+
20
+ def filters=(*filters)
21
+ @filters = filters.flatten
22
+ end
33
23
 
34
- # Posts an exception to hoptoad.
35
- # Toadhopper::Dispatcher.new('apikey').post!(exception, {:action => 'show', :controller => 'Users'})
36
- # The Following Keys are available as parameters to the document_options
37
- # error The actual exception to be reported
38
- # api_key The api key for your project
39
- # url The url for the request, required to post but not useful in a console environment
40
- # component Normally this is your Controller name in an MVC framework
41
- # action Normally the action for your request in an MVC framework
42
- # request An object that response to #params and returns a hash
43
- # notifier_name Say you're a different notifier than ToadHopper
44
- # notifier_version Specify the version of your custom notifier
45
- # session A hash of the user session in a web request
46
- # framework_env The framework environment your app is running under
47
- # backtrace Normally not needed, parsed automatically from the provided exception parameter
48
- # environment You MUST scrub your environment if you plan to use this, please do not use it though. :)
49
- #
50
- # @return Toadhopper::Response
51
- def post!(error, document_options={}, header_options={})
52
- post_document(document_for(error, document_options), header_options)
53
- end
24
+ # @private
25
+ def filters
26
+ [@filters].flatten.compact
27
+ end
54
28
 
55
- # Posts a v2 document error to Hoptoad
56
- # header_options can be passed in to indicate you're posting from a separate client
57
- # Toadhopper::Dispatcher.new('API KEY').post_document(doc, 'X-Hoptoad-Client-Name' => 'MyCustomDispatcher')
58
- #
59
- # @private
60
- def post_document(document, header_options={})
61
- uri = URI.parse("http://hoptoadapp.com:80/notifier_api/v2/notices")
29
+ # Posts an exception to hoptoad.
30
+ # Toadhopper.new('apikey').post!(error, {:action => 'show', :controller => 'Users'})
31
+ # The Following Keys are available as parameters to the document_options
32
+ # url The url for the request, required to post but not useful in a console environment
33
+ # component Normally this is your Controller name in an MVC framework
34
+ # action Normally the action for your request in an MVC framework
35
+ # request An object that response to #params and returns a hash
36
+ # notifier_name Say you're a different notifier than ToadHopper
37
+ # notifier_version Specify the version of your custom notifier
38
+ # notifier_url Specify the project URL of your custom notifier
39
+ # session A hash of the user session in a web request
40
+ # framework_env The framework environment your app is running under
41
+ # backtrace Normally not needed, parsed automatically from the provided exception parameter
42
+ # environment You MUST scrub your environment if you plan to use this, please do not use it though. :)
43
+ # project_root The root directory of your app
44
+ #
45
+ # @return Toadhopper::Response
46
+ def post!(error, document_options={}, header_options={})
47
+ post_document(document_for(error, document_options), header_options)
48
+ end
62
49
 
63
- Net::HTTP.start(uri.host, uri.port) do |http|
64
- headers = {
65
- 'Content-type' => 'text/xml',
66
- 'Accept' => 'text/xml, application/xml',
67
- 'X-Hoptoad-Client-Name' => 'Toadhopper',
68
- }.merge(header_options)
69
- http.read_timeout = 5 # seconds
70
- http.open_timeout = 2 # seconds
71
- begin
72
- response = http.post uri.path, document, headers
73
- response_for(response)
74
- rescue TimeoutError => e
75
- Response.new(500, '', ['Timeout error'])
76
- end
50
+ # Posts a v2 document error to Hoptoad
51
+ # header_options can be passed in to indicate you're posting from a separate client
52
+ # Toadhopper.new('API KEY').post_document(doc, 'X-Hoptoad-Client-Name' => 'MyCustomLib')
53
+ #
54
+ # @private
55
+ def post_document(document, header_options={})
56
+ uri = URI.parse("http://hoptoadapp.com:80/notifier_api/v2/notices")
57
+
58
+ Net::HTTP.start(uri.host, uri.port) do |http|
59
+ headers = {
60
+ 'Content-type' => 'text/xml',
61
+ 'Accept' => 'text/xml, application/xml',
62
+ 'X-Hoptoad-Client-Name' => 'Toadhopper',
63
+ }.merge(header_options)
64
+ http.read_timeout = 5 # seconds
65
+ http.open_timeout = 2 # seconds
66
+ begin
67
+ response = http.post(uri.path, document, headers)
68
+ Response.new response.code.to_i,
69
+ response.body,
70
+ Nokogiri::XML.parse(response.body).xpath('//errors/error').map {|e| e.content}
71
+ rescue TimeoutError => e
72
+ Response.new(500, '', ['Timeout error'])
77
73
  end
78
74
  end
75
+ end
79
76
 
80
- # @private
81
- def document_for(exception, options={})
82
- locals = {
83
- :error => exception,
84
- :api_key => api_key,
85
- :environment => scrub_environment(ENV.to_hash),
86
- :backtrace => Backtrace.from_exception(exception),
87
- :url => 'http://localhost/',
88
- :component => 'http://localhost/',
89
- :action => nil,
90
- :request => nil,
91
- :notifier_name => 'ToadHopper',
92
- :notifier_version => '0.8',
93
- :session => { },
94
- :framework_env => ENV['RACK_ENV'] || 'development' }.merge(options)
95
-
96
- Haml::Engine.new(notice_template).render(Object.new, locals)
97
- end
98
-
99
- # @private
100
- def response_for(http_response)
101
- status = Integer(http_response.code)
102
- case status
103
- when 422
104
- errors = Nokogiri::XML.parse(http_response.body).xpath('//errors/error')
105
- Response.new(status, http_response.body, errors.map { |error| error.content })
106
- else
107
- Response.new(status, http_response.body, [])
108
- end
109
- end
77
+ # @private
78
+ def document_for(exception, options={})
79
+ locals = {
80
+ :error => exception,
81
+ :api_key => api_key,
82
+ :environment => clean(ENV.to_hash),
83
+ :backtrace => exception.backtrace.map {|l| backtrace_line(l)},
84
+ :url => 'http://localhost/',
85
+ :component => 'http://localhost/',
86
+ :action => nil,
87
+ :request => nil,
88
+ :notifier_name => 'ToadHopper',
89
+ :notifier_version => VERSION,
90
+ :notifier_url => 'http://github.com/toolmantim/toadhopper',
91
+ :session => {},
92
+ :framework_env => ENV['RACK_ENV'] || 'development',
93
+ :project_root => Dir.pwd
94
+ }.merge(options)
95
+
96
+ Haml::Engine.new(notice_template).render(Object.new, locals)
97
+ end
98
+
99
+ # @private
100
+ def backtrace_line(line)
101
+ Struct.new(:file, :number, :method).new(*line.match(%r{^([^:]+):(\d+)(?::in `([^']+)')?$}).captures)
102
+ end
103
+
104
+ # @private
105
+ def notice_template
106
+ File.read(::File.join(::File.dirname(__FILE__), 'notice.haml'))
107
+ end
110
108
 
111
- # @private
112
- def filter(hash)
113
- hash.inject({}) do |acc, (key, val)|
114
- acc[key] = filter?(key) ? "[FILTERED]" : val
109
+ # @private
110
+ def clean(hash)
111
+ hash.inject({}) do |acc, (k, v)|
112
+ acc[k] = (v.is_a?(Hash) ? clean(v) : filtered_value(k,v)) if serializable?(v)
115
113
  acc
116
- end
117
- end
118
-
119
- # @private
120
- def filter?(key)
121
- filters.any? do |filter|
122
- key.to_s =~ Regexp.new(filter)
123
- end
124
- end
125
-
126
- # @private
127
- def scrub_environment(hash)
128
- filter(clean_non_serializable_data(hash))
129
- end
130
-
131
- # @private
132
- def clean_non_serializable_data(data)
133
- data.select{|k,v| serializable?(v) }.inject({}) do |h, pair|
134
- h[pair.first] = pair.last.is_a?(Hash) ? clean_non_serializable_data(pair.last) : pair.last
135
- h
136
- end
137
114
  end
115
+ end
138
116
 
139
- # @private
140
- def serializable?(value)
141
- value.is_a?(Fixnum) ||
142
- value.is_a?(Array) ||
143
- value.is_a?(String) ||
144
- value.is_a?(Hash) ||
145
- value.is_a?(Bignum)
117
+ # @private
118
+ def filtered_value(key, value)
119
+ if filters.any? {|f| key.to_s =~ Regexp.new(f)}
120
+ "[FILTERED]"
121
+ else
122
+ value
146
123
  end
124
+ end
147
125
 
148
- # @private
149
- def notice_template
150
- File.read(::File.join(::File.dirname(__FILE__), 'notice.haml'))
151
- end
126
+ # @private
127
+ def serializable?(value)
128
+ [Fixnum, Array, String, Hash, Bignum].any? {|c| value.is_a?(c)}
152
129
  end
153
130
  end
@@ -0,0 +1,30 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), 'helper'))
2
+
3
+ class ToadHopper::TestFilters < Test::Unit::TestCase
4
+ def toadhopper
5
+ @toadhopper ||= ToadHopper.new("test api key")
6
+ end
7
+
8
+ def test_no_filters
9
+ assert_equal( {:id => "myid", :password => "mypassword"},
10
+ toadhopper.clean(:id => "myid", :password => "mypassword"))
11
+ end
12
+
13
+ def test_string_filter
14
+ toadhopper.filters = "pass"
15
+ assert_equal( {:id => "myid", :password => "[FILTERED]"},
16
+ toadhopper.clean(:id => "myid", :password => "mypassword"))
17
+ end
18
+
19
+ def test_regex_filter
20
+ toadhopper.filters = /pas{2}/
21
+ assert_equal( {:id => "myid", :password => "[FILTERED]"},
22
+ toadhopper.clean(:id => "myid", :password => "mypassword"))
23
+ end
24
+
25
+ def test_multiple_filters
26
+ toadhopper.filters = "email", /pas{2}/
27
+ assert_equal( {:id => "myid", :email => "[FILTERED]", :password => "[FILTERED]"},
28
+ toadhopper.clean(:id => "myid", :email => "myemail", :password => "mypassword"))
29
+ end
30
+ end
data/test/test_posting.rb CHANGED
@@ -1,21 +1,21 @@
1
1
  require File.expand_path(File.join(File.dirname(__FILE__), 'helper'))
2
2
 
3
- class ToadHopper::Dispatcher::TestPosting < Test::Unit::TestCase
3
+ class ToadHopper::TestPosting < Test::Unit::TestCase
4
4
  def test_posting
5
- dispatcher = ToadHopper::Dispatcher.new("abc123")
5
+ toadhopper = ToadHopper.new("abc123")
6
6
  error = begin; raise "Kaboom!"; rescue => e; e end
7
7
 
8
- response = dispatcher.post!(error)
8
+ response = toadhopper.post!(error)
9
9
  assert_equal 422, response.status
10
10
  assert_equal ['No project exists with the given API key.'], response.errors
11
11
  end
12
12
 
13
13
  if ENV['HOPTOAD_API_KEY']
14
14
  def test_posting_integration
15
- dispatcher = ToadHopper::Dispatcher.new(ENV['HOPTOAD_API_KEY'])
15
+ toadhopper = ToadHopper.new(ENV['HOPTOAD_API_KEY'])
16
16
  error = begin; raise "Kaboom!"; rescue => e; e end
17
17
 
18
- response = dispatcher.post!(error)
18
+ response = toadhopper.post!(error)
19
19
  assert_equal 200, response.status
20
20
  assert_equal [], response.errors
21
21
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: toadhopper
3
3
  version: !ruby/object:Gem::Version
4
- version: "0.8"
4
+ version: "0.9"
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tim Lucas
@@ -40,13 +40,11 @@ files:
40
40
  - LICENSE
41
41
  - README.md
42
42
  - Rakefile
43
- - lib/backtrace.rb
44
43
  - lib/notice.haml
45
44
  - lib/toadhopper.rb
46
45
  - test/helper.rb
47
- - test/test_filter.rb
46
+ - test/test_filters.rb
48
47
  - test/test_posting.rb
49
- - test/test_setters.rb
50
48
  has_rdoc: true
51
49
  homepage: http://github.com/toolmantim/toadhopper
52
50
  licenses: []
@@ -77,6 +75,5 @@ specification_version: 3
77
75
  summary: Post error notifications to Hoptoad
78
76
  test_files:
79
77
  - test/helper.rb
80
- - test/test_filter.rb
78
+ - test/test_filters.rb
81
79
  - test/test_posting.rb
82
- - test/test_setters.rb
data/lib/backtrace.rb DELETED
@@ -1,31 +0,0 @@
1
- module ToadHopper
2
- # A line in a ruby Backtrace
3
- #
4
- # @private
5
- class BacktraceLine < Struct.new(:file, :number, :method); end
6
-
7
- # A collection of BacktraceLines representing an entire ruby backtrace
8
- #
9
- # @private
10
- class Backtrace
11
- INPUT_FORMAT = %r{^([^:]+):(\d+)(?::in `([^']+)')?$}.freeze
12
-
13
- # the collection of lines in the backtrace
14
- attr_reader :lines
15
-
16
- # create a collection of BacktraceLines from an exception
17
- def self.from_exception(exception)
18
- @lines = exception.backtrace.map do |line|
19
- _, file, number, method = line.match(INPUT_FORMAT).to_a
20
- BacktraceLine.new(file, number, method)
21
- end
22
- end
23
-
24
- # iterate over the lines in a Backtrace
25
- def each_line(&block)
26
- lines.each do |line|
27
- yield line
28
- end
29
- end
30
- end
31
- end
data/test/test_filter.rb DELETED
@@ -1,30 +0,0 @@
1
- require File.expand_path(File.join(File.dirname(__FILE__), 'helper'))
2
-
3
- class ToadHopper::Dispatcher::TestFilter < Test::Unit::TestCase
4
- def dispatcher
5
- @dispatcher ||= ToadHopper::Dispatcher.new("test api key")
6
- end
7
-
8
- def test_no_filters
9
- assert_equal( {:id => "myid", :password => "mypassword"},
10
- dispatcher.filter(:id => "myid", :password => "mypassword"))
11
- end
12
-
13
- def test_string_filter
14
- dispatcher.filters = "pass"
15
- assert_equal( {:id => "myid", :password => "[FILTERED]"},
16
- dispatcher.filter(:id => "myid", :password => "mypassword"))
17
- end
18
-
19
- def test_regex_filter
20
- dispatcher.filters = /pas{2}/
21
- assert_equal( {:id => "myid", :password => "[FILTERED]"},
22
- dispatcher.filter(:id => "myid", :password => "mypassword"))
23
- end
24
-
25
- def test_multiple_filters
26
- dispatcher.filters = "email", /pas{2}/
27
- assert_equal( {:id => "myid", :email => "[FILTERED]", :password => "[FILTERED]"},
28
- dispatcher.filter(:id => "myid", :email => "myemail", :password => "mypassword"))
29
- end
30
- end
data/test/test_setters.rb DELETED
@@ -1,18 +0,0 @@
1
- require File.expand_path(File.join(File.dirname(__FILE__), 'helper'))
2
-
3
- class ToadHopper::Dispatcher::TestSetters < Test::Unit::TestCase
4
- def test_setting_api_key
5
- dispatcher = ToadHopper::Dispatcher.new('abc123')
6
- assert_equal "abc123", dispatcher.api_key
7
- end
8
- def test_setting_single_filter
9
- dispatcher = ToadHopper::Dispatcher.new('')
10
- dispatcher.filters = /password/
11
- assert_equal [/password/], dispatcher.filters
12
- end
13
- def test_setting_multple_filters
14
- dispatcher = ToadHopper::Dispatcher.new('')
15
- dispatcher.filters = /password/, /email/
16
- assert_equal [/password/, /email/], dispatcher.filters
17
- end
18
- end