honeybadger 1.0.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.
Files changed (65) hide show
  1. data/Gemfile +13 -0
  2. data/Gemfile.lock +114 -0
  3. data/Guardfile +5 -0
  4. data/MIT-LICENSE +22 -0
  5. data/README.md +271 -0
  6. data/Rakefile +261 -0
  7. data/SUPPORTED_RAILS_VERSIONS +26 -0
  8. data/TESTING.md +33 -0
  9. data/features/metal.feature +18 -0
  10. data/features/rack.feature +56 -0
  11. data/features/rails.feature +211 -0
  12. data/features/rake.feature +27 -0
  13. data/features/sinatra.feature +29 -0
  14. data/features/step_definitions/file_steps.rb +10 -0
  15. data/features/step_definitions/metal_steps.rb +23 -0
  16. data/features/step_definitions/rack_steps.rb +23 -0
  17. data/features/step_definitions/rails_application_steps.rb +394 -0
  18. data/features/step_definitions/rake_steps.rb +17 -0
  19. data/features/support/env.rb +17 -0
  20. data/features/support/honeybadger_shim.rb.template +8 -0
  21. data/features/support/rails.rb +201 -0
  22. data/features/support/rake/Rakefile +68 -0
  23. data/features/support/terminal.rb +107 -0
  24. data/generators/honeybadger/honeybadger_generator.rb +94 -0
  25. data/generators/honeybadger/lib/insert_commands.rb +34 -0
  26. data/generators/honeybadger/lib/rake_commands.rb +24 -0
  27. data/generators/honeybadger/templates/capistrano_hook.rb +6 -0
  28. data/generators/honeybadger/templates/honeybadger_tasks.rake +25 -0
  29. data/generators/honeybadger/templates/initializer.rb +6 -0
  30. data/honeybadger.gemspec +109 -0
  31. data/lib/honeybadger.rb +162 -0
  32. data/lib/honeybadger/backtrace.rb +123 -0
  33. data/lib/honeybadger/capistrano.rb +43 -0
  34. data/lib/honeybadger/configuration.rb +273 -0
  35. data/lib/honeybadger/notice.rb +314 -0
  36. data/lib/honeybadger/rack.rb +55 -0
  37. data/lib/honeybadger/rails.rb +34 -0
  38. data/lib/honeybadger/rails/action_controller_catcher.rb +30 -0
  39. data/lib/honeybadger/rails/controller_methods.rb +69 -0
  40. data/lib/honeybadger/rails/middleware/exceptions_catcher.rb +29 -0
  41. data/lib/honeybadger/rails3_tasks.rb +84 -0
  42. data/lib/honeybadger/railtie.rb +45 -0
  43. data/lib/honeybadger/rake_handler.rb +65 -0
  44. data/lib/honeybadger/sender.rb +120 -0
  45. data/lib/honeybadger/shared_tasks.rb +36 -0
  46. data/lib/honeybadger/tasks.rb +82 -0
  47. data/lib/honeybadger_tasks.rb +65 -0
  48. data/lib/rails/generators/honeybadger/honeybadger_generator.rb +99 -0
  49. data/rails/init.rb +1 -0
  50. data/resources/README.md +34 -0
  51. data/resources/ca-bundle.crt +3376 -0
  52. data/script/integration_test.rb +38 -0
  53. data/test/test_helper.rb +143 -0
  54. data/test/unit/backtrace_test.rb +180 -0
  55. data/test/unit/capistrano_test.rb +34 -0
  56. data/test/unit/configuration_test.rb +201 -0
  57. data/test/unit/honeybadger_tasks_test.rb +163 -0
  58. data/test/unit/logger_test.rb +72 -0
  59. data/test/unit/notice_test.rb +406 -0
  60. data/test/unit/notifier_test.rb +245 -0
  61. data/test/unit/rack_test.rb +56 -0
  62. data/test/unit/rails/action_controller_catcher_test.rb +300 -0
  63. data/test/unit/rails_test.rb +35 -0
  64. data/test/unit/sender_test.rb +257 -0
  65. metadata +315 -0
@@ -0,0 +1,34 @@
1
+ # Mostly pinched from http://github.com/ryanb/nifty-generators/tree/master
2
+
3
+ Rails::Generator::Commands::Base.class_eval do
4
+ def file_contains?(relative_destination, line)
5
+ File.read(destination_path(relative_destination)).include?(line)
6
+ end
7
+ end
8
+
9
+ Rails::Generator::Commands::Create.class_eval do
10
+ def append_to(file, line)
11
+ logger.insert "#{line} appended to #{file}"
12
+ unless options[:pretend] || file_contains?(file, line)
13
+ File.open(file, "a") do |file|
14
+ file.puts
15
+ file.puts line
16
+ end
17
+ end
18
+ end
19
+ end
20
+
21
+ Rails::Generator::Commands::Destroy.class_eval do
22
+ def append_to(file, line)
23
+ logger.remove "#{line} removed from #{file}"
24
+ unless options[:pretend]
25
+ gsub_file file, "\n#{line}", ''
26
+ end
27
+ end
28
+ end
29
+
30
+ Rails::Generator::Commands::List.class_eval do
31
+ def append_to(file, line)
32
+ logger.insert "#{line} appended to #{file}"
33
+ end
34
+ end
@@ -0,0 +1,24 @@
1
+ Rails::Generator::Commands::Create.class_eval do
2
+ def rake(cmd, opts = {})
3
+ logger.rake "rake #{cmd}"
4
+ unless system("rake #{cmd}")
5
+ logger.rake "#{cmd} failed. Rolling back"
6
+ command(:destroy).invoke!
7
+ end
8
+ end
9
+ end
10
+
11
+ Rails::Generator::Commands::Destroy.class_eval do
12
+ def rake(cmd, opts = {})
13
+ unless opts[:generate_only]
14
+ logger.rake "rake #{cmd}"
15
+ system "rake #{cmd}"
16
+ end
17
+ end
18
+ end
19
+
20
+ Rails::Generator::Commands::List.class_eval do
21
+ def rake(cmd, opts = {})
22
+ logger.rake "rake #{cmd}"
23
+ end
24
+ end
@@ -0,0 +1,6 @@
1
+
2
+ Dir[File.join(File.dirname(__FILE__), '..', 'vendor', 'gems', 'honeybadger-*')].each do |vendored_notifier|
3
+ $: << File.join(vendored_notifier, 'lib')
4
+ end
5
+
6
+ require 'honeybadger/capistrano'
@@ -0,0 +1,25 @@
1
+ # Don't load anything when running the gems:* tasks.
2
+ # Otherwise, honeybadger will be considered a framework gem.
3
+ # https://thoughtbot.lighthouseapp.com/projects/14221/tickets/629
4
+ unless ARGV.any? {|a| a =~ /^gems/}
5
+
6
+ Dir[File.join(Rails.root, 'vendor', 'gems', 'honeybadger-*')].each do |vendored_notifier|
7
+ $: << File.join(vendored_notifier, 'lib')
8
+ end
9
+
10
+ begin
11
+ require 'honeybadger/tasks'
12
+ rescue LoadError => exception
13
+ namespace :honeybadger do
14
+ %w(deploy test log_stdout).each do |task_name|
15
+ desc "Missing dependency for honeybadger:#{task_name}"
16
+ task task_name do
17
+ $stderr.puts "Failed to run honeybadger:#{task_name} because of missing dependency."
18
+ $stderr.puts "You probably need to run `rake gems:install` to install the honeybadger gem"
19
+ abort exception.inspect
20
+ end
21
+ end
22
+ end
23
+ end
24
+
25
+ end
@@ -0,0 +1,6 @@
1
+ <% if Rails::VERSION::MAJOR < 3 && Rails::VERSION::MINOR < 2 -%>
2
+ require 'honeybadger/rails'
3
+ <% end -%>
4
+ Honeybadger.configure do |config|
5
+ config.api_key = <%= api_key_expression %>
6
+ end
@@ -0,0 +1,109 @@
1
+ Gem::Specification.new do |s|
2
+ s.specification_version = 2 if s.respond_to? :specification_version=
3
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
4
+ s.rubygems_version = '1.3.5'
5
+
6
+ s.name = 'honeybadger'
7
+ s.version = '1.0.0'
8
+ s.date = '2012-06-27'
9
+
10
+ s.summary = "Error reports you can be happy about."
11
+ s.description = "Make managing application errors a more pleasant experience."
12
+
13
+ s.authors = ["Joshua Wood"]
14
+ s.email = 'josh@honeybadger.io'
15
+ s.homepage = 'http://www.honeybadger.io'
16
+
17
+ s.require_paths = %w[lib]
18
+
19
+ s.rdoc_options = ["--charset=UTF-8", "--markup tomdoc"]
20
+ s.extra_rdoc_files = %w[README.md MIT-LICENSE]
21
+
22
+ s.add_dependency("json")
23
+ s.add_dependency("activesupport")
24
+
25
+ s.add_development_dependency("actionpack", "~> 2.3.8")
26
+ s.add_development_dependency("activerecord", "~> 2.3.8")
27
+ s.add_development_dependency("activesupport", "~> 2.3.8")
28
+ s.add_development_dependency("bourne", ">= 1.0")
29
+ s.add_development_dependency("cucumber", "~> 0.10.6")
30
+ s.add_development_dependency("rspec", "~> 2.6.0")
31
+ s.add_development_dependency("fakeweb", "~> 1.3.0")
32
+ s.add_development_dependency("sham_rack", "~> 1.3.0")
33
+ s.add_development_dependency("shoulda", "~> 2.11.3")
34
+ s.add_development_dependency("capistrano", "~> 2.8.0")
35
+
36
+ ## Leave this section as-is. It will be automatically generated from the
37
+ ## contents of your Git repository via the gemspec task. DO NOT REMOVE
38
+ ## THE MANIFEST COMMENTS, they are used as delimiters by the task.
39
+ # = MANIFEST =
40
+ s.files = %w[
41
+ Gemfile
42
+ Gemfile.lock
43
+ Guardfile
44
+ MIT-LICENSE
45
+ README.md
46
+ Rakefile
47
+ SUPPORTED_RAILS_VERSIONS
48
+ TESTING.md
49
+ features/metal.feature
50
+ features/rack.feature
51
+ features/rails.feature
52
+ features/rake.feature
53
+ features/sinatra.feature
54
+ features/step_definitions/file_steps.rb
55
+ features/step_definitions/metal_steps.rb
56
+ features/step_definitions/rack_steps.rb
57
+ features/step_definitions/rails_application_steps.rb
58
+ features/step_definitions/rake_steps.rb
59
+ features/support/env.rb
60
+ features/support/honeybadger_shim.rb.template
61
+ features/support/rails.rb
62
+ features/support/rake/Rakefile
63
+ features/support/terminal.rb
64
+ generators/honeybadger/honeybadger_generator.rb
65
+ generators/honeybadger/lib/insert_commands.rb
66
+ generators/honeybadger/lib/rake_commands.rb
67
+ generators/honeybadger/templates/capistrano_hook.rb
68
+ generators/honeybadger/templates/honeybadger_tasks.rake
69
+ generators/honeybadger/templates/initializer.rb
70
+ honeybadger.gemspec
71
+ lib/honeybadger.rb
72
+ lib/honeybadger/backtrace.rb
73
+ lib/honeybadger/capistrano.rb
74
+ lib/honeybadger/configuration.rb
75
+ lib/honeybadger/notice.rb
76
+ lib/honeybadger/rack.rb
77
+ lib/honeybadger/rails.rb
78
+ lib/honeybadger/rails/action_controller_catcher.rb
79
+ lib/honeybadger/rails/controller_methods.rb
80
+ lib/honeybadger/rails/middleware/exceptions_catcher.rb
81
+ lib/honeybadger/rails3_tasks.rb
82
+ lib/honeybadger/railtie.rb
83
+ lib/honeybadger/rake_handler.rb
84
+ lib/honeybadger/sender.rb
85
+ lib/honeybadger/shared_tasks.rb
86
+ lib/honeybadger/tasks.rb
87
+ lib/honeybadger_tasks.rb
88
+ lib/rails/generators/honeybadger/honeybadger_generator.rb
89
+ rails/init.rb
90
+ resources/README.md
91
+ resources/ca-bundle.crt
92
+ script/integration_test.rb
93
+ test/test_helper.rb
94
+ test/unit/backtrace_test.rb
95
+ test/unit/capistrano_test.rb
96
+ test/unit/configuration_test.rb
97
+ test/unit/honeybadger_tasks_test.rb
98
+ test/unit/logger_test.rb
99
+ test/unit/notice_test.rb
100
+ test/unit/notifier_test.rb
101
+ test/unit/rack_test.rb
102
+ test/unit/rails/action_controller_catcher_test.rb
103
+ test/unit/rails_test.rb
104
+ test/unit/sender_test.rb
105
+ ]
106
+ # = MANIFEST =
107
+
108
+ s.test_files = s.files.select { |path| path =~ /^test\/.*_test\.rb/ }
109
+ end
@@ -0,0 +1,162 @@
1
+ require 'net/http'
2
+ require 'net/https'
3
+ require 'json'
4
+
5
+ begin
6
+ require 'active_support'
7
+ require 'active_support/core_ext'
8
+ rescue LoadError
9
+ require 'activesupport'
10
+ require 'activesupport/core_ext'
11
+ end
12
+
13
+ require 'honeybadger/configuration'
14
+ require 'honeybadger/backtrace'
15
+ require 'honeybadger/notice'
16
+ require 'honeybadger/rack'
17
+ require 'honeybadger/sender'
18
+
19
+ require 'honeybadger/railtie' if defined?(Rails::Railtie)
20
+
21
+ module Honeybadger
22
+ VERSION = '0.0.1'
23
+ LOG_PREFIX = "** [Honeybadger] "
24
+
25
+ HEADERS = {
26
+ 'Content-type' => 'application/json',
27
+ 'Accept' => 'text/json, application/json'
28
+ }
29
+
30
+ class << self
31
+ # The sender object is responsible for delivering formatted data to the
32
+ # Honeybadger server. Must respond to #send_to_honeybadger. See Honeybadger::Sender.
33
+ attr_accessor :sender
34
+
35
+ # A Honeybadger configuration object. Must act like a hash and return sensible
36
+ # values for all Honeybadger configuration options. See Honeybadger::Configuration.
37
+ attr_writer :configuration
38
+ end
39
+
40
+ # Tell the log that the Notifier is good to go
41
+ def self.report_ready
42
+ write_verbose_log("Notifier #{VERSION} ready to catch errors")
43
+ end
44
+
45
+ # Prints out the environment info to the log for debugging help
46
+ def self.report_environment_info
47
+ write_verbose_log("Environment Info: #{environment_info}")
48
+ end
49
+
50
+ # Prints out the response body from Honeybadger for debugging help
51
+ def self.report_response_body(response)
52
+ write_verbose_log("Response from Honeybadger: \n#{response}")
53
+ end
54
+
55
+ # Returns the Ruby version, Rails version, and current Rails environment
56
+ def self.environment_info
57
+ info = "[Ruby: #{RUBY_VERSION}]"
58
+ info << " [#{configuration.framework}]" if configuration.framework
59
+ info << " [Env: #{configuration.environment_name}]" if configuration.environment_name
60
+ end
61
+
62
+ # Writes out the given message to the #logger
63
+ def self.write_verbose_log(message)
64
+ logger.info LOG_PREFIX + message if logger
65
+ end
66
+
67
+ # Look for the Rails logger currently defined
68
+ def self.logger
69
+ self.configuration.logger
70
+ end
71
+
72
+ # Public: Call this method to modify defaults in your initializers.
73
+ #
74
+ # Examples:
75
+ #
76
+ # Honeybadger.configure do |config|
77
+ # config.api_key = '1234567890abcdef'
78
+ # config.secure = false
79
+ # end
80
+ #
81
+ # Yields Honeybadger configuration
82
+ def self.configure(silent = false)
83
+ yield(configuration)
84
+ self.sender = Sender.new(configuration)
85
+ report_ready unless silent
86
+ self.sender
87
+ end
88
+
89
+ # Public: The configuration object.
90
+ # See Honeybadger.configure
91
+ #
92
+ # Returns Honeybadger configuration
93
+ def self.configuration
94
+ @configuration ||= Configuration.new
95
+ end
96
+
97
+ # Public: Sends an exception manually using this method, even when you are not in a controller.
98
+ #
99
+ # exception - The exception you want to notify Honeybadger about.
100
+ # options - Data that will be sent to Honeybadger.
101
+ # :api_key - The API key for this project. The API key is a unique identifier
102
+ # that Honeybadger uses for identification.
103
+ # :error_message - The error returned by the exception (or the message you want to log).
104
+ # :backtrace - A backtrace, usually obtained with +caller+.
105
+ # :rack_env - The Rack environment.
106
+ # :session - The contents of the user's session.
107
+ # :environment_name - The application environment name.
108
+ #
109
+ # Returns exception ID from Honeybadger on success, false on failure
110
+ def self.notify(exception, options = {})
111
+ send_notice(build_notice_for(exception, options))
112
+ end
113
+
114
+ # Public: Sends the notice unless it is one of the default ignored exceptions
115
+ # see Honeybadger.notify
116
+ def self.notify_or_ignore(exception, opts = {})
117
+ notice = build_notice_for(exception, opts)
118
+ send_notice(notice) unless notice.ignore?
119
+ end
120
+
121
+ def self.build_lookup_hash_for(exception, options = {})
122
+ notice = build_notice_for(exception, options)
123
+
124
+ result = {}
125
+ result[:action] = notice.action rescue nil
126
+ result[:component] = notice.component rescue nil
127
+ result[:error_class] = notice.error_class if notice.error_class
128
+ result[:environment_name] = 'production'
129
+
130
+ unless notice.backtrace.lines.empty?
131
+ result[:file] = notice.backtrace.lines.first.file
132
+ result[:line_number] = notice.backtrace.lines.first.number
133
+ end
134
+
135
+ result
136
+ end
137
+
138
+ private
139
+
140
+ def self.send_notice(notice)
141
+ if configuration.public?
142
+ sender.send_to_honeybadger(notice.to_json)
143
+ end
144
+ end
145
+
146
+ def self.build_notice_for(exception, opts = {})
147
+ exception = unwrap_exception(exception)
148
+ opts = opts.merge(:exception => exception) if exception.is_a?(Exception)
149
+ opts = opts.merge(exception.to_hash) if exception.respond_to?(:to_hash)
150
+ Notice.new(configuration.merge(opts))
151
+ end
152
+
153
+ def self.unwrap_exception(exception)
154
+ if exception.respond_to?(:original_exception)
155
+ exception.original_exception
156
+ elsif exception.respond_to?(:continued_exception)
157
+ exception.continued_exception
158
+ else
159
+ exception
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,123 @@
1
+ module Honeybadger
2
+ # Public: Front end to parsing the backtrace for each notice
3
+ class Backtrace
4
+
5
+ # Public: Handles backtrace parsing line by line
6
+ class Line
7
+ # regexp (optionnally allowing leading X: for windows support)
8
+ INPUT_FORMAT = %r{^((?:[a-zA-Z]:)?[^:]+):(\d+)(?::in `([^']+)')?$}.freeze
9
+
10
+ # Public: The file portion of the line (such as app/models/user.rb)
11
+ attr_reader :file
12
+
13
+ # Public: The line number portion of the line
14
+ attr_reader :number
15
+
16
+ # Public: The method of the line (such as index)
17
+ attr_reader :method
18
+
19
+ # Public: Parses a single line of a given backtrace
20
+ #
21
+ # unparsed_line - The raw line from +caller+ or some backtrace
22
+ #
23
+ # Returns the parsed backtrace line
24
+ def self.parse(unparsed_line)
25
+ _, file, number, method = unparsed_line.match(INPUT_FORMAT).to_a
26
+ new(file, number, method)
27
+ end
28
+
29
+ def initialize(file, number, method)
30
+ self.file = file
31
+ self.number = number
32
+ self.method = method
33
+ end
34
+
35
+ # Public: Reconstructs the line in a readable fashion
36
+ def to_s
37
+ "#{file}:#{number}:in `#{method}'"
38
+ end
39
+
40
+ def ==(other)
41
+ to_s == other.to_s
42
+ end
43
+
44
+ def inspect
45
+ "<Line:#{to_s}>"
46
+ end
47
+
48
+ private
49
+
50
+ attr_writer :file, :number, :method
51
+ end
52
+
53
+ # Public: holder for an Array of Backtrace::Line instances
54
+ attr_reader :lines
55
+
56
+ def self.parse(ruby_backtrace, opts = {})
57
+ ruby_lines = split_multiline_backtrace(ruby_backtrace)
58
+
59
+ filters = opts[:filters] || []
60
+ filtered_lines = ruby_lines.to_a.map do |line|
61
+ filters.inject(line) do |line, proc|
62
+ proc.call(line)
63
+ end
64
+ end.compact
65
+
66
+ lines = filtered_lines.collect do |unparsed_line|
67
+ Line.parse(unparsed_line)
68
+ end
69
+
70
+ instance = new(lines)
71
+ end
72
+
73
+ def initialize(lines)
74
+ self.lines = lines
75
+ end
76
+
77
+ # Public
78
+ #
79
+ # Returns array containing backtrace lines
80
+ def to_ary
81
+ lines.map { |l| { :number => l.number, :file => l.file, :method => l.method } }
82
+ end
83
+ alias :to_a :to_ary
84
+
85
+ # Public: JSON support
86
+ #
87
+ # Returns JSON representation of backtrace
88
+ def as_json(options = {})
89
+ to_ary
90
+ end
91
+
92
+ # Public: Creates JSON
93
+ #
94
+ # Returns valid JSON representation of backtrace
95
+ def to_json(*a)
96
+ as_json.to_json(*a)
97
+ end
98
+
99
+ def inspect
100
+ "<Backtrace: " + lines.collect { |line| line.inspect }.join(", ") + ">"
101
+ end
102
+
103
+ def ==(other)
104
+ if other.respond_to?(:lines)
105
+ lines == other.lines
106
+ else
107
+ false
108
+ end
109
+ end
110
+
111
+ private
112
+
113
+ attr_writer :lines
114
+
115
+ def self.split_multiline_backtrace(backtrace)
116
+ if backtrace.to_a.size == 1
117
+ backtrace.to_a.first.split(/\n\s*/)
118
+ else
119
+ backtrace
120
+ end
121
+ end
122
+ end
123
+ end