crash_hook 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,41 @@
1
+ !.gitignore
2
+ *.gem
3
+ *.rbc
4
+ *.sw[a-p]
5
+ *.tmproj
6
+ *.tmproject
7
+ *.un~
8
+ *~
9
+ .DS_Store
10
+ .Spotlight-V100
11
+ .Trashes
12
+ ._*
13
+ .bundle
14
+ .config
15
+ .directory
16
+ .elc
17
+ .redcar
18
+ .yardoc
19
+ /.emacs.desktop
20
+ /.emacs.desktop.lock
21
+ Desktop.ini
22
+ Gemfile.lock
23
+ Icon?
24
+ InstalledFiles
25
+ Session.vim
26
+ Thumbs.db
27
+ \#*\#
28
+ _yardoc
29
+ auto-save-list
30
+ coverage
31
+ doc/
32
+ lib/bundler/man
33
+ pkg
34
+ pkg/*
35
+ rdoc
36
+ spec/reports
37
+ test/tmp
38
+ test/version_tmp
39
+ tmp
40
+ tmtags
41
+ tramp
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --format=nested
3
+ --backtrace
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'http://rubygems.org'
2
+
3
+ gemspec
@@ -0,0 +1,95 @@
1
+ # CrashHook
2
+
3
+ CrashHook is a rack middleware for handling exception in your rack-based application.
4
+
5
+ It sends a http request with exception information, backtrace and environment details to your url via specified method.
6
+
7
+ ## Installation
8
+
9
+ gem install crash_hook
10
+
11
+ ## Usage
12
+
13
+ ### Rails 3
14
+
15
+ ``` ruby
16
+ YOUR_RAILS_APP::Application.config.middleware.use CrashHook::Middleware, {
17
+ :url => 'YOUR_URL'
18
+ }
19
+ ```
20
+
21
+ You can add any exceptions into ignore list:
22
+
23
+ ``` ruby
24
+ YOUR_RAILS_APP::Application.config.middleware.use CrashHook::Middleware, {
25
+ :url => 'YOUR_URL',
26
+ :ignore => [
27
+ 'NoMethodError'
28
+ ]
29
+ }
30
+ ```
31
+
32
+ ### Sinatra
33
+
34
+ ```ruby
35
+ errors do
36
+ CrashHook.notify(request.env['sinatra.error'], request.env)
37
+ # your stuff, render 500, etc
38
+ end
39
+ ```
40
+
41
+ Please note that this will work only in production mode,
42
+ since in development mode Sinatra is overriding this method with a nice looking exception backtrace page.
43
+
44
+ ### General
45
+
46
+ In other rack apps (config.ru):
47
+
48
+ ``` ruby
49
+ require 'crash_hook'
50
+
51
+ use CrashHook::Middleware {
52
+ :url => 'YOUR_URL',
53
+ :method => :post
54
+ }
55
+ ```
56
+
57
+ ## Options
58
+
59
+ You can use these options during configuration:
60
+
61
+ - :url — Target URL (**required**)
62
+ - :method — Request method *(default: :post)*
63
+ - :params — Extra parameters for the request *(ex.: api key, app ID, etc.)*
64
+ - :format — Report format (:form, :json, :yaml), *(default: json)*
65
+ - :ignore — Array of exception class names you want to ignore, *(default: [])*
66
+ - :logger — Set a logger for delivery errors, *(default: nil)*
67
+
68
+ ## Payload
69
+
70
+ Each request has a following structure:
71
+
72
+ crash:
73
+ exception:
74
+ class_name: "Exception class name"
75
+ message: "Exception message"
76
+ backtrace: "Exception backtrace"
77
+ environment: "rack request environment"
78
+ framework: "rails"
79
+ version: "crash_hook version"
80
+
81
+ Framework is being auto-detected and could one of the following:
82
+
83
+ - rack (default)
84
+ - rails
85
+ - sinatra
86
+
87
+ ## License
88
+
89
+ Copyright © 2011 Dan Sosedoff.
90
+
91
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
92
+
93
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
94
+
95
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env rake
2
+
3
+ require "bundler"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec) do |spec|
7
+ spec.pattern = 'spec/*_spec.rb'
8
+ end
9
+
10
+ task :default => :spec
11
+ task :test => :spec
@@ -0,0 +1,26 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/crash_hook/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.name = 'crash_hook'
6
+ gem.version = CrashHook::VERSION.dup
7
+ gem.author = 'Dan Sosedoff'
8
+ gem.email = 'dan.sosedoff@gmail.com'
9
+ gem.homepage = 'https://github.com/sosedoff/crash_hook'
10
+ gem.summary = %q{Exception notifications via HTTP}
11
+ gem.description = %q{Rack middleware to notify HTTP endpoint with application notifications}
12
+
13
+ gem.files = `git ls-files`.split("\n")
14
+ gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
15
+ gem.executables = `git ls-files -- bin/*`.split("\n").map{|f| File.basename(f)}
16
+ gem.require_paths = ['lib']
17
+
18
+ gem.add_development_dependency 'rack', '~> 1.2'
19
+ gem.add_development_dependency 'rake', '~> 0.9'
20
+ gem.add_development_dependency 'rspec', '~> 2.6'
21
+ gem.add_development_dependency 'simplecov', '~> 0.4'
22
+ gem.add_development_dependency 'fakeweb', '~> 1.3'
23
+
24
+ gem.add_runtime_dependency 'rest-client', '~> 1.6'
25
+ gem.add_runtime_dependency 'multi_json', '~> 1.0'
26
+ end
@@ -0,0 +1,28 @@
1
+ require 'crash_hook/version'
2
+ require 'crash_hook/errors'
3
+ require 'crash_hook/configuration'
4
+ require 'crash_hook/middleware'
5
+ require 'crash_hook/request'
6
+ require 'crash_hook/serializer'
7
+ require 'crash_hook/payload'
8
+ require 'crash_hook/crash'
9
+
10
+ module CrashHook
11
+ # Set global configuration
12
+ #
13
+ def self.configure(options)
14
+ @@config = CrashHook::Configuration.new(options)
15
+ @@config
16
+ end
17
+
18
+ # Manually sent notification
19
+ # exception => Exception object
20
+ # env => Environment hash
21
+ #
22
+ def self.notify(exception, env)
23
+ if @@config.nil?
24
+ raise CrashHook::ConfigurationError, "No configuration were provided."
25
+ end
26
+ CrashHook::Crash.new(@@config, exception, env).notify
27
+ end
28
+ end
@@ -0,0 +1,61 @@
1
+ require 'uri'
2
+
3
+ module CrashHook
4
+ @@config = nil
5
+
6
+ class Configuration
7
+ ALLOWED_METHODS = [:get, :post, :put, :delete].freeze
8
+ ALLOWED_FORMATS = [:form, :json, :yaml]
9
+
10
+ attr_reader :url
11
+ attr_reader :method
12
+ attr_reader :ignore
13
+ attr_reader :extra_params
14
+ attr_reader :format
15
+ attr_reader :logger
16
+
17
+ # Initialize configuration. Options:
18
+ # :url => Target url (required)
19
+ # :method => Request method (default: post)
20
+ # :format => Request format (default: json)
21
+ # :params => Additional parameters for the request
22
+ # :ignore => Set of exception classes to ignore
23
+ # :logger => Set logger class (default: none)
24
+ #
25
+ def initialize(options={})
26
+ if options[:url].to_s.strip.empty?
27
+ raise CrashHook::ConfigurationError, ":url option required!"
28
+ end
29
+
30
+ @url = options[:url].to_s
31
+ @method = options.key?(:method) ? options[:method].to_sym : :post
32
+ @format = options.key?(:format) ? options[:format].to_sym : :json
33
+ @extra_params = options[:params].kind_of?(Hash) ? options[:params] : {}
34
+ @logger = options[:logger]
35
+
36
+ unless ALLOWED_METHODS.include?(@method)
37
+ raise CrashHook::ConfigurationError, "#{@method} is not a valid :method option."
38
+ end
39
+
40
+ unless ALLOWED_FORMATS.include?(@format)
41
+ raise CrashHook::ConfigurationError, "#{@format} is not a valid :format option."
42
+ end
43
+
44
+ @ignore = []
45
+ @ignore += options[:ignore] if options[:ignore].kind_of?(Array)
46
+ @ignore.map! { |c| c.to_s }
47
+ @ignore.uniq!
48
+ end
49
+
50
+ # Returns true if specified exception class is ignored
51
+ #
52
+ def ignore_exception?(ex)
53
+ @ignore.include?(ex.to_s)
54
+ end
55
+
56
+ # Returns true if configuration has a logger
57
+ def has_logger?
58
+ !@logger.nil?
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,60 @@
1
+ require 'rest-client'
2
+
3
+ module CrashHook
4
+ class Crash
5
+ include CrashHook::Request
6
+
7
+ # Initialize a new Crash object
8
+ # config => CrashHook::Configuration object
9
+ # exception => Exception raised from middleware
10
+ # env => Environment variable
11
+ #
12
+ def initialize(config, exception=nil, env={})
13
+ unless config.kind_of?(CrashHook::Configuration)
14
+ raise ArgumentError, "CrashHook::Configuration required!"
15
+ end
16
+
17
+ raise ArgumentError, "Exception required!" if exception.nil?
18
+ raise ArgumentError, "Environment required!" if env.nil?
19
+
20
+ @config = config
21
+ @payload = CrashHook::Payload.new(exception, env, @config.extra_params)
22
+ end
23
+
24
+ # Send notification to the endpoint
25
+ def notify
26
+ begin
27
+ request(@config.method, @config.url, payload_data, @config.format)
28
+ true
29
+ rescue Exception => ex
30
+ log_error(ex) if @config.has_logger?
31
+ false
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ # Log CrashHook delivery error
38
+ #
39
+ def log_error(ex)
40
+ if @config.logger.respond_to?(:error)
41
+ @config.logger.error("CrashHook Error: #{ex.inspect}")
42
+ elsif @config.logger.kind_of?(IO)
43
+ @config.logger.puts("CrashHook Error: #{ex.inspect}")
44
+ end
45
+ end
46
+
47
+ # Returns data formatted into config's format
48
+ #
49
+ def payload_data
50
+ case @config.format
51
+ when :json
52
+ @payload.to_json
53
+ when :yaml
54
+ @payload.to_yaml
55
+ else
56
+ @payload.to_hash
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,5 @@
1
+ module CrashHook
2
+ class Error < StandardError ; end
3
+ class ConfigurationError < Error ; end
4
+ class DeliveryError < Error ; end
5
+ end
@@ -0,0 +1,19 @@
1
+ module CrashHook
2
+ class Middleware
3
+ attr_reader :config
4
+
5
+ def initialize(app, options={})
6
+ @app = app
7
+ @config = CrashHook.configure(options)
8
+ end
9
+
10
+ def call(env)
11
+ @app.call(env)
12
+ rescue Exception => exception
13
+ unless @config.ignore_exception?(exception.class.to_s)
14
+ CrashHook::Crash.new(@config, exception, env).notify
15
+ end
16
+ raise exception
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,65 @@
1
+ require 'multi_json'
2
+ require 'yaml'
3
+
4
+ module CrashHook
5
+ class Payload
6
+ include CrashHook::Serializer
7
+
8
+ attr_reader :exception
9
+ attr_reader :environment
10
+ attr_reader :framework
11
+ attr_reader :version
12
+ attr_reader :extra_params
13
+
14
+ # Initialize a new CrashHook::Payload object
15
+ # exception => Exception object instance
16
+ # env => Environment hash
17
+ # extra_params => Additional parameters
18
+ #
19
+ def initialize(exception, env, extra_params={})
20
+ @exception = {
21
+ :class_name => exception.class.to_s,
22
+ :message => exception.message,
23
+ :backtrace => exception.backtrace,
24
+ :timestamp => Time.now
25
+ }
26
+
27
+ @environment = clean_non_serializable_data(env)
28
+ @version = CrashHook::VERSION
29
+ @framework = 'rack'
30
+ @framework = 'rails' if defined?(Rails)
31
+ @framework = 'sinatra' if defined?(Sinatra)
32
+ @extra_params = extra_params.kind_of?(Hash) ? extra_params : {}
33
+ end
34
+
35
+ # Returns HASH representation of payload
36
+ def to_hash
37
+ {
38
+ :exception => @exception,
39
+ :environment => @environment,
40
+ :version => @version,
41
+ :framework => @framework
42
+ }
43
+ end
44
+
45
+ # Returns JSON representation of payload
46
+ #
47
+ def to_json
48
+ @extra_params.delete(:crash)
49
+ @extra_params.delete('crash')
50
+ MultiJson.encode({:crash => self.to_hash}.merge(@extra_params))
51
+ end
52
+
53
+ # Returns YAML representation of payload
54
+ #
55
+ def to_yaml
56
+ YAML.dump({:crash => self.to_hash})
57
+ end
58
+
59
+ # Returns XML representation of payload
60
+ #
61
+ def to_xml
62
+ # Not Implemented Yet
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,43 @@
1
+ module CrashHook
2
+ module Request
3
+ TIMEOUT = 4
4
+ OPEN_TIMEOUT = 4
5
+
6
+ CONTENT_TYPES = {
7
+ :form => 'application/x-www-form-urlencoded',
8
+ :json => 'application/json',
9
+ :yaml => 'application/x-yaml'
10
+ }.freeze
11
+
12
+ def get(url, payload={}, format=:json)
13
+ request(:get, url, payload, format)
14
+ end
15
+
16
+ def post(url, payload={}, format=:json)
17
+ request(:post, url, payload, format)
18
+ end
19
+
20
+ def put(url, payload={}, format=:json)
21
+ request(:put, url, payload, format)
22
+ end
23
+
24
+ def delete(url, payload={}, format=:json)
25
+ request(:delete, url, payload, format)
26
+ end
27
+
28
+ protected
29
+
30
+ def request(method, url, payload, format)
31
+ opts = {
32
+ :method => method,
33
+ :url => url,
34
+ :payload => payload,
35
+ :headers => {:content_type => CONTENT_TYPES[format]},
36
+ :timeout => TIMEOUT,
37
+ :open_timeout => OPEN_TIMEOUT
38
+ }
39
+
40
+ RestClient::Request.execute(opts)
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,18 @@
1
+ module CrashHook
2
+ module Serializer
3
+ def serializable?(value)
4
+ value.is_a?(Fixnum) ||
5
+ value.is_a?(Array) ||
6
+ value.is_a?(String) ||
7
+ value.is_a?(Hash) ||
8
+ value.is_a?(Bignum)
9
+ end
10
+
11
+ def clean_non_serializable_data(data)
12
+ data.select{|k,v| serializable?(v) }.inject({}) do |h, pair|
13
+ h[pair.first] = pair.last.is_a?(Hash) ? clean_non_serializable_data(pair.last) : pair.last
14
+ h
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,3 @@
1
+ module CrashHook
2
+ VERSION = "0.2.0".freeze unless defined? ::CrashHook::VERSION
3
+ end
@@ -0,0 +1,30 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'CrashHook::Configuration' do
4
+ it 'raises an exception if no url were provided' do
5
+ proc { CrashHook::Configuration.new }.
6
+ should raise_error CrashHook::ConfigurationError, ":url option required!"
7
+ end
8
+
9
+ it 'raises an exception on invalid url method' do
10
+ proc {
11
+ CrashHook::Configuration.new(
12
+ :url => 'http://foo.com',
13
+ :method => :foo
14
+ )
15
+ }.should raise_error CrashHook::ConfigurationError, "foo is not a valid :method option."
16
+ end
17
+
18
+ it 'has an unique list of ignored exceptions' do
19
+ c = CrashHook::Configuration.new(
20
+ :url => 'http://foo.com',
21
+ :ignore => [
22
+ 'RuntimeError',
23
+ RuntimeError
24
+ ]
25
+ )
26
+
27
+ c.ignore.size.should == 1
28
+ c.ignore_exception?(RuntimeError).should == true
29
+ end
30
+ end
@@ -0,0 +1,20 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'CrashHook::Crash' do
4
+ it 'raises error if no configuration were provided' do
5
+ proc { CrashHook::Crash.new(nil) }.
6
+ should raise_error ArgumentError, "CrashHook::Configuration required!"
7
+ end
8
+
9
+ it 'raises error if no exception were provided' do
10
+ proc { CrashHook::Crash.new(sample_configuration) }.
11
+ should raise_error ArgumentError, "Exception required!"
12
+ end
13
+
14
+ it 'raises error if no environment configuration were provided' do
15
+ e = ArgumentError.new("Sample message")
16
+
17
+ proc { CrashHook::Crash.new(sample_configuration, e, nil) }.
18
+ should raise_error ArgumentError, "Environment required!"
19
+ end
20
+ end
@@ -0,0 +1 @@
1
+ crash[exception]=missing%20something&crash[backtrace][]=%2FUsers%2Fsosedoff%2FProjects%2Fcrash_hook%2Fspec%2Fspec_helper.rb%3A23%3Ain%20%60block%20in%20raise_endpoint'&crash[backtrace][]=%2FUsers%2Fsosedoff%2FProjects%2Fcrash_hook%2Flib%2Fcrash_hook%2Fmiddleware.rb%3A11%3Ain%20%60call'&crash[backtrace][]=%2FUsers%2Fsosedoff%2FProjects%2Fcrash_hook%2Flib%2Fcrash_hook%2Fmiddleware.rb%3A11%3Ain%20%60call'&crash[backtrace][]=%2FUsers%2Fsosedoff%2F.rvm%2Fgems%2Fruby-1.9.2-p180%2Fgems%2Frack-1.2.3%2Flib%2Frack%2Fbuilder.rb%3A77%3Ain%20%60call'&crash[backtrace][]=%2FUsers%2Fsosedoff%2FProjects%2Fcrash_hook%2Fspec%2Fmiddleware_spec.rb%3A26%3Ain%20%60block%20(2%20levels)%20in%20%3Ctop%20(required)%3E'&crash[backtrace][]=%2FUsers%2Fsosedoff%2F.rvm%2Fgems%2Fruby-1.9.2-p180%2Fgems%2Frspec-core-2.6.4%2Flib%2Frspec%2Fcore%2Fexample.rb%3A48%3Ain%20%60instance_eval'&crash[backtrace][]=%2FUsers%2Fsosedoff%2F.rvm%2Fgems%2Fruby-1.9.2-p180%2Fgems%2Frspec-core-2.6.4%2Flib%2Frspec%2Fcore%2Fexample.rb%3A48%3Ain%20%60block%20in%20run'&crash[backtrace][]=%2FUsers%2Fsosedoff%2F.rvm%2Fgems%2Fruby-1.9.2-p180%2Fgems%2Frspec-core-2.6.4%2Flib%2Frspec%2Fcore%2Fexample.rb%3A107%3Ain%20%60with_around_hooks'&crash[backtrace][]=%2FUsers%2Fsosedoff%2F.rvm%2Fgems%2Fruby-1.9.2-p180%2Fgems%2Frspec-core-2.6.4%2Flib%2Frspec%2Fcore%2Fexample.rb%3A45%3Ain%20%60run'&crash[backtrace][]=%2FUsers%2Fsosedoff%2F.rvm%2Fgems%2Fruby-1.9.2-p180%2Fgems%2Frspec-core-2.6.4%2Flib%2Frspec%2Fcore%2Fexample_group.rb%3A294%3Ain%20%60block%20in%20run_examples'&crash[backtrace][]=%2FUsers%2Fsosedoff%2F.rvm%2Fgems%2Fruby-1.9.2-p180%2Fgems%2Frspec-core-2.6.4%2Flib%2Frspec%2Fcore%2Fexample_group.rb%3A290%3Ain%20%60map'&crash[backtrace][]=%2FUsers%2Fsosedoff%2F.rvm%2Fgems%2Fruby-1.9.2-p180%2Fgems%2Frspec-core-2.6.4%2Flib%2Frspec%2Fcore%2Fexample_group.rb%3A290%3Ain%20%60run_examples'&crash[backtrace][]=%2FUsers%2Fsosedoff%2F.rvm%2Fgems%2Fruby-1.9.2-p180%2Fgems%2Frspec-core-2.6.4%2Flib%2Frspec%2Fcore%2Fexample_group.rb%3A262%3Ain%20%60run'&crash[backtrace][]=%2FUsers%2Fsosedoff%2F.rvm%2Fgems%2Fruby-1.9.2-p180%2Fgems%2Frspec-core-2.6.4%2Flib%2Frspec%2Fcore%2Fcommand_line.rb%3A24%3Ain%20%60block%20(2%20levels)%20in%20run'&crash[backtrace][]=%2FUsers%2Fsosedoff%2F.rvm%2Fgems%2Fruby-1.9.2-p180%2Fgems%2Frspec-core-2.6.4%2Flib%2Frspec%2Fcore%2Fcommand_line.rb%3A24%3Ain%20%60map'&crash[backtrace][]=%2FUsers%2Fsosedoff%2F.rvm%2Fgems%2Fruby-1.9.2-p180%2Fgems%2Frspec-core-2.6.4%2Flib%2Frspec%2Fcore%2Fcommand_line.rb%3A24%3Ain%20%60block%20in%20run'&crash[backtrace][]=%2FUsers%2Fsosedoff%2F.rvm%2Fgems%2Fruby-1.9.2-p180%2Fgems%2Frspec-core-2.6.4%2Flib%2Frspec%2Fcore%2Freporter.rb%3A12%3Ain%20%60report'&crash[backtrace][]=%2FUsers%2Fsosedoff%2F.rvm%2Fgems%2Fruby-1.9.2-p180%2Fgems%2Frspec-core-2.6.4%2Flib%2Frspec%2Fcore%2Fcommand_line.rb%3A21%3Ain%20%60run'&crash[backtrace][]=%2FUsers%2Fsosedoff%2F.rvm%2Fgems%2Fruby-1.9.2-p180%2Fgems%2Frspec-core-2.6.4%2Flib%2Frspec%2Fcore%2Frunner.rb%3A80%3Ain%20%60run_in_process'&crash[backtrace][]=%2FUsers%2Fsosedoff%2F.rvm%2Fgems%2Fruby-1.9.2-p180%2Fgems%2Frspec-core-2.6.4%2Flib%2Frspec%2Fcore%2Frunner.rb%3A69%3Ain%20%60run'&crash[backtrace][]=%2FUsers%2Fsosedoff%2F.rvm%2Fgems%2Fruby-1.9.2-p180%2Fgems%2Frspec-core-2.6.4%2Flib%2Frspec%2Fcore%2Frunner.rb%3A11%3Ain%20%60block%20in%20autorun'&crash[environment][rack.version][]=1&crash[environment][rack.version][]=1&crash[environment][rack.input]=%23%3CStringIO%3A0x00000100a83fd0%3E&crash[environment][rack.errors]=%23%3CStringIO%3A0x00000100a840e8%3E&crash[environment][rack.multithread]=true&crash[environment][rack.multiprocess]=true&crash[environment][rack.run_once]=false&crash[environment][REQUEST_METHOD]=GET&crash[environment][SERVER_NAME]=example.org&crash[environment][SERVER_PORT]=80&crash[environment][QUERY_STRING]=&crash[environment][PATH_INFO]=%2F&crash[environment][rack.url_scheme]=http&crash[environment][HTTPS]=off&crash[environment][SCRIPT_NAME]=&crash[environment][CONTENT_LENGTH]=0
@@ -0,0 +1,30 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'CrashHook::Middleware' do
4
+ before do
5
+ @env = Rack::MockRequest.env_for("/")
6
+ end
7
+
8
+ it 'does nothing if no errors occurred' do
9
+ result = build_stack.call(@env)
10
+ result.status.should == 200
11
+ end
12
+
13
+ it 'sends notification to the url on exception' do
14
+ FakeWeb.register_uri(:post, "http://localhost:4567/exception", :body => fixture('stack.txt'))
15
+
16
+ proc { build_stack(raise_endpoint(ArgumentError, "missing something")).call(@env) }.
17
+ should raise_error ArgumentError, "missing something"
18
+ end
19
+
20
+ it 'sends notification in json format' do
21
+ proc { build_stack(raise_endpoint(ArgumentError, "missing something")).call(@env) }.
22
+ should raise_error ArgumentError, "missing something"
23
+ end
24
+
25
+ it 'does not produce any errors on invalid http endpoint' do
26
+ FakeWeb.register_uri(:post, "http://localhost:4567/exception", :body => fixture('stack.txt'), :status => ["404", "Not Found"])
27
+ proc { build_stack(raise_endpoint(ArgumentError, "missing something")).call(@env) }.
28
+ should raise_error ArgumentError, "missing something"
29
+ end
30
+ end
@@ -0,0 +1,29 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'CrashHook::Request' do
4
+ class RequestTester
5
+ extend CrashHook::Request
6
+ end
7
+
8
+ before :all do
9
+ CrashHook::Configuration::ALLOWED_METHODS.each do |m|
10
+ FakeWeb.register_uri(m, "http://localhost:4567/test", :body => "")
11
+ end
12
+ end
13
+
14
+ it 'sends a GET request' do
15
+ RequestTester.get('http://localhost:4567/test', {:foo => 'bar'})
16
+ end
17
+
18
+ it 'sends a POST request' do
19
+ RequestTester.post('http://localhost:4567/test', {:foo => 'bar'})
20
+ end
21
+
22
+ it 'sends a PUT request' do
23
+ RequestTester.put('http://localhost:4567/test', {:foo => 'bar'})
24
+ end
25
+
26
+ it 'sends a DELETE request' do
27
+ RequestTester.delete('http://localhost:4567/test', {:foo => 'bar'})
28
+ end
29
+ end
@@ -0,0 +1,40 @@
1
+ $:.unshift File.expand_path("../..", __FILE__)
2
+
3
+ require 'rack/mock'
4
+ require 'fakeweb'
5
+ require 'crash_hook'
6
+
7
+ FakeWeb.allow_net_connect = false
8
+
9
+ def fixture_path(file=nil)
10
+ path = File.expand_path("../fixtures", __FILE__)
11
+ path = File.join(path, file) unless file.nil?
12
+ path
13
+ end
14
+
15
+ def fixture(file)
16
+ File.read(File.join(fixture_path, file))
17
+ end
18
+
19
+ def safe_endpoint(msg = "OK")
20
+ proc { |e| Rack::Response.new(msg) }
21
+ end
22
+
23
+ def raise_endpoint(exception, msg)
24
+ proc { |e| raise exception, msg }
25
+ end
26
+
27
+ def sample_configuration
28
+ CrashHook::Configuration.new(
29
+ :url => 'http://localhost:4567/sample'
30
+ )
31
+ end
32
+
33
+ def build_stack(endpoint = safe_endpoint, options={})
34
+ options[:url] ||= 'http://localhost:4567/exception'
35
+
36
+ Rack::Builder.new do
37
+ use CrashHook::Middleware, options
38
+ run endpoint
39
+ end
40
+ end
metadata ADDED
@@ -0,0 +1,157 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: crash_hook
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.2.0
6
+ platform: ruby
7
+ authors:
8
+ - Dan Sosedoff
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2011-07-18 00:00:00 -05:00
14
+ default_executable:
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: rack
18
+ prerelease: false
19
+ requirement: &id001 !ruby/object:Gem::Requirement
20
+ none: false
21
+ requirements:
22
+ - - ~>
23
+ - !ruby/object:Gem::Version
24
+ version: "1.2"
25
+ type: :development
26
+ version_requirements: *id001
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ prerelease: false
30
+ requirement: &id002 !ruby/object:Gem::Requirement
31
+ none: false
32
+ requirements:
33
+ - - ~>
34
+ - !ruby/object:Gem::Version
35
+ version: "0.9"
36
+ type: :development
37
+ version_requirements: *id002
38
+ - !ruby/object:Gem::Dependency
39
+ name: rspec
40
+ prerelease: false
41
+ requirement: &id003 !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ~>
45
+ - !ruby/object:Gem::Version
46
+ version: "2.6"
47
+ type: :development
48
+ version_requirements: *id003
49
+ - !ruby/object:Gem::Dependency
50
+ name: simplecov
51
+ prerelease: false
52
+ requirement: &id004 !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ~>
56
+ - !ruby/object:Gem::Version
57
+ version: "0.4"
58
+ type: :development
59
+ version_requirements: *id004
60
+ - !ruby/object:Gem::Dependency
61
+ name: fakeweb
62
+ prerelease: false
63
+ requirement: &id005 !ruby/object:Gem::Requirement
64
+ none: false
65
+ requirements:
66
+ - - ~>
67
+ - !ruby/object:Gem::Version
68
+ version: "1.3"
69
+ type: :development
70
+ version_requirements: *id005
71
+ - !ruby/object:Gem::Dependency
72
+ name: rest-client
73
+ prerelease: false
74
+ requirement: &id006 !ruby/object:Gem::Requirement
75
+ none: false
76
+ requirements:
77
+ - - ~>
78
+ - !ruby/object:Gem::Version
79
+ version: "1.6"
80
+ type: :runtime
81
+ version_requirements: *id006
82
+ - !ruby/object:Gem::Dependency
83
+ name: multi_json
84
+ prerelease: false
85
+ requirement: &id007 !ruby/object:Gem::Requirement
86
+ none: false
87
+ requirements:
88
+ - - ~>
89
+ - !ruby/object:Gem::Version
90
+ version: "1.0"
91
+ type: :runtime
92
+ version_requirements: *id007
93
+ description: Rack middleware to notify HTTP endpoint with application notifications
94
+ email: dan.sosedoff@gmail.com
95
+ executables: []
96
+
97
+ extensions: []
98
+
99
+ extra_rdoc_files: []
100
+
101
+ files:
102
+ - .gitignore
103
+ - .rspec
104
+ - Gemfile
105
+ - README.md
106
+ - Rakefile
107
+ - crash_hook.gemspec
108
+ - lib/crash_hook.rb
109
+ - lib/crash_hook/configuration.rb
110
+ - lib/crash_hook/crash.rb
111
+ - lib/crash_hook/errors.rb
112
+ - lib/crash_hook/middleware.rb
113
+ - lib/crash_hook/payload.rb
114
+ - lib/crash_hook/request.rb
115
+ - lib/crash_hook/serializer.rb
116
+ - lib/crash_hook/version.rb
117
+ - spec/configuration_spec.rb
118
+ - spec/crash_spec.rb
119
+ - spec/fixtures/stack.txt
120
+ - spec/middleware_spec.rb
121
+ - spec/request_spec.rb
122
+ - spec/spec_helper.rb
123
+ has_rdoc: true
124
+ homepage: https://github.com/sosedoff/crash_hook
125
+ licenses: []
126
+
127
+ post_install_message:
128
+ rdoc_options: []
129
+
130
+ require_paths:
131
+ - lib
132
+ required_ruby_version: !ruby/object:Gem::Requirement
133
+ none: false
134
+ requirements:
135
+ - - ">="
136
+ - !ruby/object:Gem::Version
137
+ version: "0"
138
+ required_rubygems_version: !ruby/object:Gem::Requirement
139
+ none: false
140
+ requirements:
141
+ - - ">="
142
+ - !ruby/object:Gem::Version
143
+ version: "0"
144
+ requirements: []
145
+
146
+ rubyforge_project:
147
+ rubygems_version: 1.6.2
148
+ signing_key:
149
+ specification_version: 3
150
+ summary: Exception notifications via HTTP
151
+ test_files:
152
+ - spec/configuration_spec.rb
153
+ - spec/crash_spec.rb
154
+ - spec/fixtures/stack.txt
155
+ - spec/middleware_spec.rb
156
+ - spec/request_spec.rb
157
+ - spec/spec_helper.rb