errordite 0.0.1

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 (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