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.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.travis.yml +6 -0
- data/Gemfile +5 -0
- data/Guardfile +24 -0
- data/LICENSE.txt +22 -0
- data/README.md +23 -0
- data/Rakefile +20 -0
- data/errordite-rack.gemspec +21 -0
- data/errordite-rails.gemspec +21 -0
- data/errordite.gemspec +28 -0
- data/examples/rack/Gemfile +4 -0
- data/examples/rack/config.ru +7 -0
- data/examples/rails/.gitignore +16 -0
- data/examples/rails/Gemfile +36 -0
- data/examples/rails/README.rdoc +28 -0
- data/examples/rails/Rakefile +6 -0
- data/examples/rails/app/assets/images/.keep +0 -0
- data/examples/rails/app/assets/javascripts/application.js +16 -0
- data/examples/rails/app/assets/stylesheets/application.css +13 -0
- data/examples/rails/app/controllers/application_controller.rb +5 -0
- data/examples/rails/app/controllers/concerns/.keep +0 -0
- data/examples/rails/app/helpers/application_helper.rb +2 -0
- data/examples/rails/app/mailers/.keep +0 -0
- data/examples/rails/app/models/.keep +0 -0
- data/examples/rails/app/models/concerns/.keep +0 -0
- data/examples/rails/app/views/layouts/application.html.erb +14 -0
- data/examples/rails/bin/bundle +3 -0
- data/examples/rails/bin/rails +4 -0
- data/examples/rails/bin/rake +4 -0
- data/examples/rails/config.ru +4 -0
- data/examples/rails/config/application.rb +23 -0
- data/examples/rails/config/boot.rb +4 -0
- data/examples/rails/config/database.yml +25 -0
- data/examples/rails/config/environment.rb +5 -0
- data/examples/rails/config/environments/development.rb +29 -0
- data/examples/rails/config/environments/production.rb +80 -0
- data/examples/rails/config/environments/test.rb +36 -0
- data/examples/rails/config/initializers/backtrace_silencers.rb +7 -0
- data/examples/rails/config/initializers/filter_parameter_logging.rb +4 -0
- data/examples/rails/config/initializers/inflections.rb +16 -0
- data/examples/rails/config/initializers/mime_types.rb +5 -0
- data/examples/rails/config/initializers/secret_token.rb +12 -0
- data/examples/rails/config/initializers/session_store.rb +3 -0
- data/examples/rails/config/initializers/wrap_parameters.rb +14 -0
- data/examples/rails/config/locales/en.yml +23 -0
- data/examples/rails/config/routes.rb +56 -0
- data/examples/rails/db/seeds.rb +7 -0
- data/examples/rails/lib/assets/.keep +0 -0
- data/examples/rails/lib/tasks/.keep +0 -0
- data/examples/rails/log/.keep +0 -0
- data/examples/rails/public/404.html +58 -0
- data/examples/rails/public/422.html +58 -0
- data/examples/rails/public/500.html +57 -0
- data/examples/rails/public/favicon.ico +0 -0
- data/examples/rails/public/robots.txt +5 -0
- data/lib/errordite-rails.rb +7 -0
- data/lib/errordite.rb +33 -0
- data/lib/errordite/client.rb +44 -0
- data/lib/errordite/config.rb +44 -0
- data/lib/errordite/rack.rb +22 -0
- data/lib/errordite/serializer.rb +62 -0
- data/lib/errordite/version.rb +3 -0
- data/spec/lib/errordite/client_spec.rb +64 -0
- data/spec/lib/errordite/config_spec.rb +61 -0
- data/spec/lib/errordite/serializer_spec.rb +66 -0
- data/spec/lib/errordite_spec.rb +34 -0
- data/spec/spec_helper.rb +1 -0
- metadata +214 -0
File without changes
|
data/lib/errordite.rb
ADDED
@@ -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,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
|