toadhopper 0.8 → 0.9

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