honeybadger 1.0.0

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