errordite 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.travis.yml +6 -0
  4. data/Gemfile +5 -0
  5. data/Guardfile +24 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +23 -0
  8. data/Rakefile +20 -0
  9. data/errordite-rack.gemspec +21 -0
  10. data/errordite-rails.gemspec +21 -0
  11. data/errordite.gemspec +28 -0
  12. data/examples/rack/Gemfile +4 -0
  13. data/examples/rack/config.ru +7 -0
  14. data/examples/rails/.gitignore +16 -0
  15. data/examples/rails/Gemfile +36 -0
  16. data/examples/rails/README.rdoc +28 -0
  17. data/examples/rails/Rakefile +6 -0
  18. data/examples/rails/app/assets/images/.keep +0 -0
  19. data/examples/rails/app/assets/javascripts/application.js +16 -0
  20. data/examples/rails/app/assets/stylesheets/application.css +13 -0
  21. data/examples/rails/app/controllers/application_controller.rb +5 -0
  22. data/examples/rails/app/controllers/concerns/.keep +0 -0
  23. data/examples/rails/app/helpers/application_helper.rb +2 -0
  24. data/examples/rails/app/mailers/.keep +0 -0
  25. data/examples/rails/app/models/.keep +0 -0
  26. data/examples/rails/app/models/concerns/.keep +0 -0
  27. data/examples/rails/app/views/layouts/application.html.erb +14 -0
  28. data/examples/rails/bin/bundle +3 -0
  29. data/examples/rails/bin/rails +4 -0
  30. data/examples/rails/bin/rake +4 -0
  31. data/examples/rails/config.ru +4 -0
  32. data/examples/rails/config/application.rb +23 -0
  33. data/examples/rails/config/boot.rb +4 -0
  34. data/examples/rails/config/database.yml +25 -0
  35. data/examples/rails/config/environment.rb +5 -0
  36. data/examples/rails/config/environments/development.rb +29 -0
  37. data/examples/rails/config/environments/production.rb +80 -0
  38. data/examples/rails/config/environments/test.rb +36 -0
  39. data/examples/rails/config/initializers/backtrace_silencers.rb +7 -0
  40. data/examples/rails/config/initializers/filter_parameter_logging.rb +4 -0
  41. data/examples/rails/config/initializers/inflections.rb +16 -0
  42. data/examples/rails/config/initializers/mime_types.rb +5 -0
  43. data/examples/rails/config/initializers/secret_token.rb +12 -0
  44. data/examples/rails/config/initializers/session_store.rb +3 -0
  45. data/examples/rails/config/initializers/wrap_parameters.rb +14 -0
  46. data/examples/rails/config/locales/en.yml +23 -0
  47. data/examples/rails/config/routes.rb +56 -0
  48. data/examples/rails/db/seeds.rb +7 -0
  49. data/examples/rails/lib/assets/.keep +0 -0
  50. data/examples/rails/lib/tasks/.keep +0 -0
  51. data/examples/rails/log/.keep +0 -0
  52. data/examples/rails/public/404.html +58 -0
  53. data/examples/rails/public/422.html +58 -0
  54. data/examples/rails/public/500.html +57 -0
  55. data/examples/rails/public/favicon.ico +0 -0
  56. data/examples/rails/public/robots.txt +5 -0
  57. data/lib/errordite-rails.rb +7 -0
  58. data/lib/errordite.rb +33 -0
  59. data/lib/errordite/client.rb +44 -0
  60. data/lib/errordite/config.rb +44 -0
  61. data/lib/errordite/rack.rb +22 -0
  62. data/lib/errordite/serializer.rb +62 -0
  63. data/lib/errordite/version.rb +3 -0
  64. data/spec/lib/errordite/client_spec.rb +64 -0
  65. data/spec/lib/errordite/config_spec.rb +61 -0
  66. data/spec/lib/errordite/serializer_spec.rb +66 -0
  67. data/spec/lib/errordite_spec.rb +34 -0
  68. data/spec/spec_helper.rb +1 -0
  69. metadata +214 -0
File without changes
@@ -0,0 +1,5 @@
1
+ # See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file
2
+ #
3
+ # To ban all spiders from the entire site uncomment the next two lines:
4
+ # User-agent: *
5
+ # Disallow: /
@@ -0,0 +1,7 @@
1
+ require 'errordite'
2
+
3
+ class Errordite::Railtie < Rails::Railtie
4
+ initializer "errordite.middleware" do |app|
5
+ app.config.middleware.insert 0, "Errordite::Rack"
6
+ end
7
+ end
@@ -0,0 +1,33 @@
1
+ require 'errordite/version'
2
+
3
+ module Errordite
4
+ autoload :Client, 'errordite/client'
5
+ autoload :Config, 'errordite/config'
6
+ autoload :Rack, 'errordite/rack'
7
+ autoload :Serializer, 'errordite/serializer'
8
+
9
+ class << self
10
+ def config
11
+ @config ||= Config.new
12
+ end
13
+
14
+ def config=(config)
15
+ @config = config
16
+ end
17
+
18
+ def method_missing(method, *args)
19
+ if config.respond_to?(method)
20
+ config.__send__ method, *args
21
+ else
22
+ super
23
+ end
24
+ end
25
+
26
+ def monitor(context = {})
27
+ yield
28
+ rescue Exception => e
29
+ client.record e, context
30
+ raise e
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,44 @@
1
+ require 'net/http'
2
+
3
+ class Errordite::Client
4
+ attr_reader :server, :port, :logger
5
+
6
+ def initialize(server = 'www.errordite.com', port = 443, logger = Errordite.logger)
7
+ @server = server
8
+ @port = port
9
+ @logger = logger
10
+ end
11
+
12
+ def record(error, context)
13
+ post_json '/receiveerror', Errordite::Serializer.new(error, context).to_json
14
+ end
15
+
16
+ def post_json(path, body)
17
+ headers = {'Content-Type' => 'application/json; charset=utf-8', 'Content-Length' => body.size.to_s}
18
+ response = Connection.new(server, port).post path, body, headers
19
+ log_response response
20
+ response
21
+ end
22
+
23
+ private
24
+
25
+ def log_response(response)
26
+ if response.code != "201"
27
+ logger.warn "Failed to log error: #{response.code} #{response.message}"
28
+ elsif logger.debug?
29
+ logger.debug "Error logged: #{response.code} #{response.message}"
30
+ end
31
+ end
32
+
33
+ class Connection
34
+ def initialize(server, port)
35
+ @http = Net::HTTP.new server, port
36
+ @http.use_ssl = true
37
+ @http.verify_mode = OpenSSL::SSL::VERIFY_PEER
38
+ end
39
+
40
+ def post(*args)
41
+ @http.post(*args)
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,44 @@
1
+ require 'errordite'
2
+ require 'logger'
3
+
4
+ class Errordite::Config
5
+ def api_token
6
+ @api_token ||= ENV['ERRORDITE_TOKEN']
7
+ end
8
+
9
+ def api_token=(token)
10
+ @api_token = token
11
+ end
12
+
13
+ def server
14
+ @server ||= 'www.errordite.com'
15
+ end
16
+
17
+ def server=(s)
18
+ @server = s
19
+ end
20
+
21
+ def port
22
+ @port ||= 443
23
+ end
24
+
25
+ def port=(p)
26
+ @port = p
27
+ end
28
+
29
+ def logger
30
+ @logger ||= begin
31
+ l = Logger.new(STDOUT)
32
+ l.level = Logger::WARN
33
+ l
34
+ end
35
+ end
36
+
37
+ def client
38
+ @client ||= Errordite::Client.new(server, port)
39
+ end
40
+
41
+ def client=(client)
42
+ @client = client
43
+ end
44
+ end
@@ -0,0 +1,22 @@
1
+ require 'errordite'
2
+ require 'rack/request'
3
+
4
+ class Errordite::Rack
5
+ def initialize(app, options = {})
6
+ @app = app
7
+ @context = options[:context] || {}
8
+ end
9
+
10
+ def call(env)
11
+ Errordite.monitor rack_context(env).merge(@context) do
12
+ @app.call(env)
13
+ end
14
+ end
15
+
16
+ private
17
+
18
+ def rack_context(env)
19
+ request = Rack::Request.new(env)
20
+ {'Url' => request.url, 'UserAgent' => env['HTTP_USER_AGENT']}
21
+ end
22
+ end
@@ -0,0 +1,62 @@
1
+ require 'errordite'
2
+ require 'json'
3
+
4
+ class Errordite::Serializer
5
+ SPECIAL_CONTEXT_ATTRIBUTES = ['MachineName', 'Url', 'UserAgent', 'Version']
6
+
7
+ attr_reader :exception, :context
8
+
9
+ def initialize(exception, context = {})
10
+ @exception = exception
11
+ @context = context
12
+ end
13
+
14
+ def as_json
15
+ special_attributes.merge(
16
+ "Token" => Errordite.api_token,
17
+ "TimestampUtc" => Time.now.strftime("%Y-%m-%d %H:%M:%S"),
18
+ "ExceptionInfo" => {
19
+ "Source" => source,
20
+ "Message" => exception.message,
21
+ "MethodName" => method_name,
22
+ "ExceptionType" => exception.class.name,
23
+ "StackTrace" => stack_trace,
24
+ "Data" => exception_data
25
+ }
26
+ )
27
+ end
28
+
29
+ def to_json
30
+ JSON.dump(as_json)
31
+ end
32
+
33
+ private
34
+
35
+ def special_attributes
36
+ Hash[context.select {|k, v| SPECIAL_CONTEXT_ATTRIBUTES.include?(k) }]
37
+ end
38
+
39
+ def exception_data
40
+ Hash[context.reject {|k, v| SPECIAL_CONTEXT_ATTRIBUTES.include?(k) }]
41
+ end
42
+
43
+ def timestamp
44
+ Time.now.strftime("%Y-%m-%d %H:%M:%S")
45
+ end
46
+
47
+ def stack_trace
48
+ exception.backtrace && exception.backtrace.join("\n")
49
+ end
50
+
51
+ def source
52
+ split_first_line && split_first_line[1]
53
+ end
54
+
55
+ def method_name
56
+ split_first_line && split_first_line[3]
57
+ end
58
+
59
+ def split_first_line
60
+ @split_first_line ||= exception.backtrace && exception.backtrace[0] && exception.backtrace[0].match(/^(.*?)(?:\:(\d+))(?:\:in `(.*)')?$/)
61
+ end
62
+ end
@@ -0,0 +1,3 @@
1
+ module Errordite
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,64 @@
1
+ require 'spec_helper'
2
+
3
+ describe Errordite::Client do
4
+ let(:logger) { double(:logger) }
5
+ let(:connection) { double(:connection) }
6
+ let(:response) { double(:response, code: '201', message: 'Created') }
7
+
8
+ before :each do
9
+ Errordite::Client::Connection.stub(:new).and_return(connection)
10
+ connection.stub(:post).and_return(response)
11
+ end
12
+
13
+ it 'records errors by posting them to /receiveerror' do
14
+ error = double(:error)
15
+ context = double(:context)
16
+ serializer = double(:serializer, to_json: 'json-representation')
17
+ Errordite::Serializer.stub(:new).with(error, context).and_return(serializer)
18
+ subject.should_receive(:post_json).with('/receiveerror', 'json-representation')
19
+ subject.record(error, context)
20
+ end
21
+
22
+ it 'sends json to the server by posting through a new connection' do
23
+ path = '/receiveerror'
24
+ body = '{}'
25
+ connection.should_receive(:post).with(
26
+ '/receiveerror', '{}', 'Content-Type' => 'application/json; charset=utf-8', 'Content-Length' => '2'
27
+ ).and_return(response)
28
+ Errordite::Client.new.post_json(path, body)
29
+ end
30
+
31
+ it 'logs response if code != 201' do
32
+ client = Errordite::Client.new('server.example.com', 443, logger)
33
+ response.stub(:code).and_return('500')
34
+ response.stub(:message).and_return('Server Error')
35
+ logger.should_receive(:warn)
36
+ client.post_json '/receiveerror', ''
37
+ end
38
+
39
+ it 'logs response if log level is debug' do
40
+ client = Errordite::Client.new('server.example.com', 443, logger)
41
+ response.stub(:code).and_return('500')
42
+ response.stub(:message).and_return('Server Error')
43
+ logger.should_receive(:warn)
44
+ client.post_json '/receiveerror', ''
45
+ end
46
+ end
47
+
48
+ describe Errordite::Client::Connection do
49
+ let(:http) { double(:http).as_null_object }
50
+
51
+ it 'creates a new verified https connection to the given server and port' do
52
+ Net::HTTP.stub(:new).with('one.example.com', 443).and_return(http)
53
+ http.should_receive(:use_ssl=).with(true)
54
+ http.should_receive(:verify_mode=).with(OpenSSL::SSL::VERIFY_PEER)
55
+ Errordite::Client::Connection.new('one.example.com', 443)
56
+ end
57
+
58
+ it 'posts data through the https connection' do
59
+ Net::HTTP.stub(:new).and_return(http)
60
+ arguments = double('arguments')
61
+ http.should_receive(:post).with(arguments)
62
+ Errordite::Client::Connection.new('one.example.com', 443).post(arguments)
63
+ end
64
+ end
@@ -0,0 +1,61 @@
1
+ require 'spec_helper'
2
+
3
+ describe Errordite::Config do
4
+ subject { Errordite::Config.new }
5
+ let(:client) { double('client') }
6
+
7
+ it 'uses www.errordite.com as default server' do
8
+ expect(subject.server).to eql('www.errordite.com')
9
+ end
10
+
11
+ it 'allows server to be set' do
12
+ subject.server = 'errordite.example.com'
13
+ expect(subject.server).to eql('errordite.example.com')
14
+ end
15
+
16
+ it 'uses 443 as default port' do
17
+ expect(subject.port).to eql(443)
18
+ end
19
+
20
+ it 'allows port to be set' do
21
+ subject.port = 80
22
+ expect(subject.port).to eql(80)
23
+ end
24
+
25
+ it 'uses ERRORDITE_TOKEN environment variable as default api token' do
26
+ ENV['ERRORDITE_TOKEN'] = 'token-from-environment'
27
+ expect(subject.api_token).to eql('token-from-environment')
28
+ end
29
+
30
+ it 'allows api token to be set' do
31
+ ENV['ERRORDITE_TOKEN'] = 'token-from-environment'
32
+ subject.api_token = 'overidden-token'
33
+ expect(subject.api_token).to eql('overidden-token')
34
+ end
35
+
36
+ it 'logs to STDOUT with log level of WARN by default' do
37
+ logger = double('logger')
38
+ Logger.should_receive(:new).with(STDOUT).and_return(logger)
39
+ logger.should_receive(:level=).with(Logger::WARN)
40
+ expect(subject.logger).to eql(logger)
41
+ end
42
+
43
+ it 'builds client with server and port' do
44
+ subject.server = 'errordite.example.com'
45
+ subject.port = 123
46
+ Errordite::Client.should_receive(:new).with('errordite.example.com', 123).and_return(client)
47
+ expect(subject.client).to eql(client)
48
+ end
49
+
50
+ it 'memoizes client' do
51
+ Errordite::Client.stub(:new).and_return(client)
52
+ expect(subject.client).to eql(client)
53
+ expect(subject.client).to eql(client)
54
+ end
55
+
56
+ it 'allows custom client to be set' do
57
+ custom_client = double('custom-client')
58
+ subject.client = custom_client
59
+ expect(subject.client).to eql(custom_client)
60
+ end
61
+ end
@@ -0,0 +1,66 @@
1
+ require 'spec_helper'
2
+
3
+ describe Errordite::Serializer do
4
+ describe '(in general)' do
5
+ let(:context) { Hash.new }
6
+ let(:exception) { Exception.new }
7
+ let(:serializer) { Errordite::Serializer.new(exception, context) }
8
+
9
+ it 'includes Token taken from Errordite.api_token' do
10
+ Errordite.stub(:api_token).and_return("AnApiToken")
11
+ expect(serializer.as_json['Token']).to eql('AnApiToken')
12
+ end
13
+
14
+ it 'takes TimestampUtc from the current time' do
15
+ Time.stub(:now).and_return(Time.utc(2013, 3, 4, 12, 34, 56))
16
+ expect(serializer.as_json['TimestampUtc']).to eql('2013-03-04 12:34:56')
17
+ end
18
+
19
+ it 'takes Source from backtrace file' do
20
+ exception.stub(:backtrace).and_return(["/path/to/file/example.rb:80:in `hello'"])
21
+ expect(serializer.as_json['ExceptionInfo']['Source']).to eql('/path/to/file/example.rb')
22
+ end
23
+
24
+ it 'takes MethodName from backtrace method' do
25
+ exception.stub(:backtrace).and_return(["/path/to/file/example.rb:80:in `hello'"])
26
+ expect(serializer.as_json['ExceptionInfo']['MethodName']).to eql('hello')
27
+ end
28
+
29
+ it 'takes ExceptionType from exception class' do
30
+ exception.stub(:class).and_return(FloatDomainError)
31
+ expect(serializer.as_json['ExceptionInfo']['ExceptionType']).to eql('FloatDomainError')
32
+ end
33
+
34
+ it 'takes Message from exception message' do
35
+ exception.stub(:message).and_return("My message is this")
36
+ expect(serializer.as_json['ExceptionInfo']['Message']).to eql('My message is this')
37
+ end
38
+
39
+ ['MachineName', 'Url', 'UserAgent', 'Version'].each do |special_attribute|
40
+ it "adds #{special_attribute} to top level if included in context" do
41
+ context[special_attribute] = "special-attribute-value"
42
+ expect(serializer.as_json[special_attribute]).to eql("special-attribute-value")
43
+ end
44
+ end
45
+
46
+ it "adds other unknown attributes to exception data when included in context" do
47
+ context["Anything"] = "anything-value"
48
+ context["Something"] = "something-value"
49
+ expect(serializer.as_json["ExceptionInfo"]["Data"]).to eql({
50
+ "Anything" => "anything-value",
51
+ "Something" => "something-value"
52
+ })
53
+ end
54
+
55
+ it 'forms StackTrace using backtrace joined with newlines' do
56
+ exception.stub(:backtrace).and_return(["first-line", "second-line"])
57
+ expect(serializer.as_json['ExceptionInfo']['StackTrace']).to eql("first-line\nsecond-line")
58
+ end
59
+
60
+ it 'is serializable as JSON' do
61
+ serializer.stub(:as_json).and_return(:as_json_result)
62
+ JSON.should_receive(:dump).with(:as_json_result).and_return(:serialized_json)
63
+ expect(serializer.to_json).to eql(:serialized_json)
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,34 @@
1
+ require 'spec_helper'
2
+
3
+ describe Errordite do
4
+ let(:config) { double('config') }
5
+
6
+ before :each do
7
+ Errordite.config = nil
8
+ Errordite::Config.stub(:new).and_return(config)
9
+ end
10
+
11
+ it 'builds a new Config instance if none set' do
12
+ Errordite::Config.should_receive(:new).and_return(config)
13
+ expect(Errordite.config).to eql(config)
14
+ end
15
+
16
+ it 'returns same Config instance if asked multiple times' do
17
+ expect(Errordite.config).to equal(Errordite.config)
18
+ end
19
+
20
+ [:api_token].each do |method|
21
+ it "delegates getting #{method} to config instance" do
22
+ result = double('result')
23
+ config.should_receive(method).and_return(result)
24
+ expect(Errordite.__send__(method)).to equal(result)
25
+ end
26
+
27
+ it "delegates setting #{method} to config instance" do
28
+ argument = double('argument')
29
+ result = double('result')
30
+ config.should_receive("#{method}=").with(argument).and_return(result)
31
+ expect(Errordite.__send__("#{method}=", argument)).to equal(result)
32
+ end
33
+ end
34
+ end