temescal 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 668a0e9c9bb3d5400a4b9b28302607767828bc0c
4
+ data.tar.gz: 1f1a62d3e48d5db8cd32b853a06ea116be8a3b69
5
+ SHA512:
6
+ metadata.gz: 614f71816ca80c27852abe47a8aef37702fc8eced6143613ed25735dd1bc3bc051a7e9d4f4dd1eefb1709aedc6537191354abaab13a837cc57bbc70469f91564
7
+ data.tar.gz: 1bf5f51abe2f31d8e2cd7b8595f97c4d6a86083f99d34f1d800e7e96495de41bae2017b62ec1bb6b7f1ced4a8ccf4fd04062b7c7ea7edde87484a2fd3c0799e9
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2013-2014 Todd Bealmear
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,53 @@
1
+ temescal
2
+ ========
3
+
4
+ [![Build Status](https://travis-ci.org/todd/temescal.png?branch=master)](https://travis-ci.org/todd/temescal) [![Coverage Status](https://coveralls.io/repos/todd/temescal/badge.png?branch=master)](https://coveralls.io/r/todd/temescal?branch=master) [![Code Climate](https://codeclimate.com/github/todd/temescal.png)](https://codeclimate.com/github/todd/temescal) [![Dependency Status](https://gemnasium.com/todd/temescal.png)](https://gemnasium.com/todd/temescal)
5
+
6
+ Temescal is Rack middleware that will automatically rescue exceptions for JSON APIs and render a nice, clean JSON response with the error information. No need to write custom error handling logic for your apps - Temescal will take care of it for you!
7
+ ## Getting Started
8
+ Add the gem to your Gemfile and run `bundle install`:
9
+ ```
10
+ gem 'temescal'
11
+ ```
12
+ Since Temescal is just Rack middleware, adding it to your application is super easy. For Rails, add an initializer:
13
+ ```ruby
14
+ Rails.application.config.middleware.use Temescal::Middleware do |config|
15
+ config.monitors = :airbrake, :new_relic
16
+ config.default_message = "Oops! Something went kablooey!"
17
+ end
18
+ ```
19
+ For Sinatra:
20
+ ```ruby
21
+ use Temescal::Middleware do |config|
22
+ config.monitors = :airbrake, :new_relic
23
+ config.default_message = "Oops! Something went kablooey!"
24
+ end
25
+ ```
26
+ ## Default Behavior
27
+ By default, Temescal will render a JSON response formatted as such (using StandardError with a message of "Foobar" as an example):
28
+ ```json
29
+ {
30
+ meta: {
31
+ status: 500,
32
+ error: "StandardError",
33
+ message: "Foobar"
34
+ }
35
+ }
36
+ ```
37
+ Temescal will also log the error for you through STDERR.
38
+ ## Monitors
39
+ Though Temescal will log an error for you, it won't necessarily be picked up by your monitoring solution of choice in a production environment. Luckily, Temescal provides integration with popular monitoring services. At the moment, only two (Airbrake, New Relic) are supported, but more will be added if there's a need. If you use a different monitoring service that you'd like to see supported, pull requests are more than welcome!
40
+
41
+ Note that you'll need the gem for your monitor installed and configured for your application in order for Temescal to properly work with it.
42
+
43
+ ## Configuration
44
+ Temescal provides several configuration options for you. You can set these options when configuring the middleware for your application.
45
+
46
+ `monitors` to set the monitors you'd like to use with Temescal. It takes symbols of monitor names (currently `:airbrake` and `:new_relic`).
47
+
48
+ `raise_errors` to set whether you'd like to override Temescal and raise all errors without rendering a Temescal response. Set to `true` to enable.
49
+
50
+ `default_message` to set an all-encompasing message to use in responses instead of the exception's message. Takes a string.
51
+
52
+ ## License
53
+ Copyright 2013-2014 Todd Bealmear. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,5 @@
1
+ require 'rspec/core/rake_task'
2
+
3
+ RSpec::Core::RakeTask.new(:spec)
4
+
5
+ task :default => :spec
data/lib/temescal.rb ADDED
@@ -0,0 +1,6 @@
1
+ module Temescal
2
+ require 'temescal/configuration'
3
+ require 'temescal/middleware'
4
+ require 'temescal/monitors'
5
+ require 'temescal/response'
6
+ end
@@ -0,0 +1,54 @@
1
+ module Temescal
2
+ class Configuration
3
+
4
+ # Public: Getter for monitors array.
5
+ attr_reader :monitors
6
+
7
+ # Public: Setter for raise_errors option.
8
+ attr_writer :raise_errors
9
+
10
+ # Public: Getter/Setter for default JSON message.
11
+ attr_accessor :default_message
12
+
13
+ # Public: Initializes configuration and monitors option.
14
+ #
15
+ # Returns a new Configuration object.
16
+ def initialize
17
+ @monitors = []
18
+ end
19
+
20
+ # Public: Setter for monitors option.
21
+ #
22
+ # monitors - Zero or more Symbols representing monitoring services
23
+ # supported by Temescal.
24
+ #
25
+ # Raises NameError if a monitor Symbol is invalid.
26
+ def monitors=(*monitors)
27
+ monitors.flatten.each do |monitor|
28
+ monitor = camelize_symbol(monitor)
29
+ @monitors << Temescal::Monitors.const_get(monitor)
30
+ end
31
+
32
+ rescue NameError => exception
33
+ strategy = exception.message.split(" ").last
34
+ raise NameError.new("#{strategy} is not a valid monitoring strategy")
35
+ end
36
+
37
+ # Public: Getter for raise_errors.
38
+ #
39
+ # Returns true if raise_errors is configured to true or the application is
40
+ # running in a test environment, false otherwise.
41
+ def raise_errors?
42
+ @raise_errors == true || ENV["RACK_ENV"] == "test"
43
+ end
44
+
45
+ private
46
+
47
+ # Private: Converts a snake cased Symbol to a camel cased String.
48
+ #
49
+ # Returns the converted String.
50
+ def camelize_symbol(symbol)
51
+ symbol.to_s.split(/_/).map { |word| word.capitalize }.join
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,55 @@
1
+ module Temescal
2
+ class Middleware
3
+
4
+ # Public: Initializes the middleware.
5
+ #
6
+ # app - The Rack application.
7
+ # block - Optional block for configuring the middleware.
8
+ #
9
+ # Returns an instance of the middleware.
10
+ def initialize(app, &block)
11
+ @app = app
12
+ yield(configuration) if block
13
+ end
14
+
15
+ # Public: call method for Rack application. Rescues from an exception and
16
+ # does the following: 1) Logs the error 2) Reports error to configured
17
+ # monitoring services 3) Generates a JSON response with error information.
18
+ #
19
+ # env - The environment of the request.
20
+ #
21
+ # Returns an array of response data for Rack.
22
+ def call(env)
23
+ begin
24
+ @status, @headers, @response = @app.call(env)
25
+ rescue => error
26
+ raise if configuration.raise_errors?
27
+
28
+ @error = error
29
+ message = configuration.default_message || @error.message
30
+
31
+ $stderr.print formatted_error
32
+ configuration.monitors.each { |monitor| monitor.report(@error) }
33
+
34
+ @status = @error.respond_to?(:http_status) ? @error.http_status : 500
35
+ @response = Response.build(@status, @error, message)
36
+ @headers = { "Content-Type" => "application/json" }
37
+ end
38
+ [@status, @headers, @response]
39
+ end
40
+
41
+ private
42
+
43
+ # Private: Getter for middleware configuration.
44
+ def configuration
45
+ @_configuration ||= Configuration.new
46
+ end
47
+
48
+ # Private: Formats the exception for logging.
49
+ def formatted_error
50
+ message = "\n#{@error.class}: #{@error.message}\n "
51
+ message << @error.backtrace.join("\n ")
52
+ message << "\n\n"
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,35 @@
1
+ module Temescal
2
+
3
+ # Public: Collection of reporting strategies for different monitoring
4
+ # services.
5
+ module Monitors
6
+
7
+ # Public: Abstract strategy for monitor reporting strategies.
8
+ class MonitorsStrategy
9
+ # Raises NotImplementedError
10
+ def self.report(exception)
11
+ raise NotImplementedError
12
+ end
13
+ end
14
+
15
+ # Public: Reporting strategy for Airbrake.
16
+ class Airbrake < MonitorsStrategy
17
+ # Public: Reports an exception to Airbrake.
18
+ #
19
+ # exception - The caught exception.
20
+ def self.report(exception)
21
+ ::Airbrake.notify exception
22
+ end
23
+ end
24
+
25
+ # Public: Reporting strategy for New Relic.
26
+ class NewRelic < MonitorsStrategy
27
+ # Public: Reports an exception to New Relic.
28
+ #
29
+ # exception - The caught exception.
30
+ def self.report(exception)
31
+ ::NewRelic::Agent.notice_error exception
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,25 @@
1
+ require 'json'
2
+
3
+ module Temescal
4
+ module Response
5
+
6
+ # Public: Builds a response body for the Rack response.
7
+ #
8
+ # status - The HTTP status code of the response.
9
+ # exception - The caught exception.
10
+ # message - The error message.
11
+ #
12
+ # Returns an Array containing a JSON string as the response body.
13
+ def self.build(status, exception, message)
14
+ [
15
+ {
16
+ meta: {
17
+ status: status,
18
+ error: exception.class.to_s,
19
+ message: message
20
+ }
21
+ }.to_json
22
+ ]
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,3 @@
1
+ module Temescal
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,25 @@
1
+ require "spec_helper"
2
+
3
+ class Temescal::Monitors::Monitor; end
4
+ class Temescal::Monitors::FakeMonitor; end
5
+ class Temescal::Monitors::FooMonitor; end
6
+
7
+ describe Temescal::Configuration do
8
+ let(:config) { Temescal::Configuration.new }
9
+
10
+ context "#monitors=" do
11
+ it "should add a list of valid monitor classes to the monitors array from snake-cased arguments" do
12
+ config.monitors = :monitor, :fake_monitor, :foo_monitor
13
+
14
+ expect(config.monitors).to eq [
15
+ Temescal::Monitors::Monitor,
16
+ Temescal::Monitors::FakeMonitor,
17
+ Temescal::Monitors::FooMonitor
18
+ ]
19
+ end
20
+
21
+ it "should raise an error if a monitor strategy is invalid" do
22
+ expect { config.monitors = :foo }.to raise_error(NameError, /Foo is not a valid monitoring strategy/)
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,112 @@
1
+ require "spec_helper"
2
+
3
+ describe Temescal::Middleware do
4
+ context "Ok response" do
5
+ let(:app) { ->(env) { [200, env, "app" ] } }
6
+
7
+ let(:middleware) do
8
+ Temescal::Middleware.new(app)
9
+ end
10
+
11
+ it "should render the application's response" do
12
+ code, _, _ = middleware.call env_for('http://foobar.com')
13
+
14
+ expect(code).to eq 200
15
+ end
16
+ end
17
+
18
+ context "Bad response" do
19
+ before do
20
+ Temescal::Monitors::NewRelic.stub(:report)
21
+ $stderr.stub(:print)
22
+
23
+ # Travis automatically sets RACK_ENV=test
24
+ # Need to override for tests to run correctly.
25
+ ENV["RACK_ENV"] = nil
26
+ end
27
+
28
+ let(:app) { ->(env) { raise StandardError.new("Foobar") } }
29
+
30
+ let(:middleware) do
31
+ Temescal::Middleware.new(app) do |config|
32
+ config.monitors = :new_relic
33
+ end
34
+ end
35
+
36
+ it "should respond with a 500 if the exception does not have a http_status attribute" do
37
+ code, _, _ = middleware.call env_for("http://foobar.com")
38
+
39
+ expect(code).to eq 500
40
+ end
41
+
42
+ it "should respond with the appropriate status if the exception has a http_status attribute" do
43
+ StandardError.any_instance.stub(:http_status).and_return(403)
44
+
45
+ code, _, _ = middleware.call env_for("http://foobar.com")
46
+
47
+ expect(code).to eq 403
48
+ end
49
+
50
+ it "should set the correct content type header" do
51
+ _, headers, _ = middleware.call env_for("http://foobar.com")
52
+
53
+ expect(headers["Content-Type"]).to eq "application/json"
54
+ end
55
+
56
+ it "should render a JSON response for the error" do
57
+ _, _, response = middleware.call env_for("http://foobar.com")
58
+
59
+ json = JSON.parse(response.first)["meta"]
60
+ expect(json["status"]).to eq 500
61
+ expect(json["error"]).to eq "StandardError"
62
+ expect(json["message"]).to eq "Foobar"
63
+ end
64
+
65
+ it "should log the error" do
66
+ expect($stderr).to receive(:print).with(an_instance_of String)
67
+
68
+ middleware.call env_for("http://foobar.com")
69
+ end
70
+
71
+ it "should report the error to the specified monitors" do
72
+ expect(Temescal::Monitors::NewRelic).to receive(:report).with(an_instance_of StandardError)
73
+
74
+ middleware.call env_for("http://foobar.com")
75
+ end
76
+
77
+ context "with default_message set" do
78
+ it "should build a response with the specified message instead of the exception message" do
79
+ middleware = Temescal::Middleware.new(app) do |config|
80
+ config.default_message = "An error has occured - we'll get on it right away!"
81
+ end
82
+
83
+ _, _, response = middleware.call env_for("http://foobar.com")
84
+
85
+ json = JSON.parse(response.first)["meta"]
86
+ expect(json["message"]).to eq "An error has occured - we'll get on it right away!"
87
+ end
88
+ end
89
+ end
90
+
91
+ context "Override middleware to raise exception" do
92
+ before do
93
+ $stderr.stub(:print)
94
+ end
95
+
96
+ let(:app) { ->(env) { raise StandardError.new("Foobar") } }
97
+
98
+ let(:middleware) do
99
+ Temescal::Middleware.new(app) do |config|
100
+ config.raise_errors = true
101
+ end
102
+ end
103
+
104
+ it "should raise the error" do
105
+ expect { middleware.call env_for("http://foobar.com") }.to raise_error StandardError
106
+ end
107
+ end
108
+
109
+ def env_for url, opts={}
110
+ Rack::MockRequest.env_for(url, opts)
111
+ end
112
+ end
@@ -0,0 +1,29 @@
1
+ require "spec_helper"
2
+
3
+ describe Temescal::Monitors do
4
+ let(:exception) { StandardError.new }
5
+
6
+ context "MonitorsStrategy" do
7
+ it "should raise an error when the report method is called" do
8
+ expect { Temescal::Monitors::MonitorsStrategy.report(exception) }.to raise_error(NotImplementedError)
9
+ end
10
+ end
11
+
12
+ context "Airbrake" do
13
+ it "should send the exception to Airbrake when report is called" do
14
+ Airbrake.stub(:send_notice)
15
+
16
+ expect(Airbrake).to receive(:notify).with(exception)
17
+
18
+ Temescal::Monitors::Airbrake.report exception
19
+ end
20
+ end
21
+
22
+ context "NewRelic" do
23
+ it "should send the exception to New Relic when report is called" do
24
+ expect(NewRelic::Agent).to receive(:notice_error).with(exception)
25
+
26
+ Temescal::Monitors::NewRelic.report exception
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,29 @@
1
+ if ENV['CI']
2
+ require 'coveralls'
3
+ Coveralls.wear!
4
+ else
5
+ require 'awesome_print'
6
+ require 'pry-debugger'
7
+
8
+ require 'simplecov'
9
+ SimpleCov.start
10
+ end
11
+
12
+ require 'rack'
13
+
14
+ require 'airbrake'
15
+ require 'newrelic_rpm'
16
+
17
+ require 'temescal'
18
+
19
+ RSpec.configure do |config|
20
+ config.treat_symbols_as_metadata_keys_with_true_values = true
21
+ config.run_all_when_everything_filtered = true
22
+ config.filter_run :focus
23
+
24
+ # Run specs in random order to surface order dependencies. If you find an
25
+ # order dependency and want to debug it, you can fix the order by providing
26
+ # the seed, which is printed after each run.
27
+ # --seed 1234
28
+ config.order = 'random'
29
+ end
data/temescal.gemspec ADDED
@@ -0,0 +1,22 @@
1
+ # encoding: utf-8
2
+
3
+ $:.unshift File.expand_path('../lib', __FILE__)
4
+ require 'temescal/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.add_dependency 'rack'
8
+ spec.add_development_dependency 'bundler'
9
+
10
+ spec.authors = ['Todd Bealmear']
11
+ spec.description = %q{Rack Middleware for gracefully handling exceptions for JSON APIs.}
12
+ spec.email = ['todd@t0dd.io']
13
+ spec.files = %w(LICENSE README.md Rakefile temescal.gemspec)
14
+ spec.files += Dir.glob('lib/**/*.rb')
15
+ spec.files += Dir.glob('spec/**/*')
16
+ spec.homepage = 'https://github.com/todd/temescal'
17
+ spec.licenses = ['MIT']
18
+ spec.name = 'temescal'
19
+ spec.require_paths = ['lib']
20
+ spec.summary = spec.description
21
+ spec.version = Temescal::VERSION
22
+ end
metadata ADDED
@@ -0,0 +1,86 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: temescal
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Todd Bealmear
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-01-13 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rack
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: Rack Middleware for gracefully handling exceptions for JSON APIs.
42
+ email:
43
+ - todd@t0dd.io
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - LICENSE
49
+ - README.md
50
+ - Rakefile
51
+ - temescal.gemspec
52
+ - lib/temescal/configuration.rb
53
+ - lib/temescal/middleware.rb
54
+ - lib/temescal/monitors.rb
55
+ - lib/temescal/response.rb
56
+ - lib/temescal/version.rb
57
+ - lib/temescal.rb
58
+ - spec/lib/temescal/configuration_spec.rb
59
+ - spec/lib/temescal/middleware_spec.rb
60
+ - spec/lib/temescal/monitors_spec.rb
61
+ - spec/spec_helper.rb
62
+ homepage: https://github.com/todd/temescal
63
+ licenses:
64
+ - MIT
65
+ metadata: {}
66
+ post_install_message:
67
+ rdoc_options: []
68
+ require_paths:
69
+ - lib
70
+ required_ruby_version: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - '>='
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ required_rubygems_version: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - '>='
78
+ - !ruby/object:Gem::Version
79
+ version: '0'
80
+ requirements: []
81
+ rubyforge_project:
82
+ rubygems_version: 2.1.10
83
+ signing_key:
84
+ specification_version: 4
85
+ summary: Rack Middleware for gracefully handling exceptions for JSON APIs.
86
+ test_files: []