never-forget 0.1.0
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.
- data/lib/never-forget.rb +1 -0
- data/lib/never_forget.rb +23 -0
- data/lib/never_forget/exception.rb +179 -0
- data/lib/never_forget/exception_handler.rb +95 -0
- data/lib/never_forget/list_exceptions.erb +111 -0
- data/lib/never_forget/railtie.rb +26 -0
- data/lib/never_forget/sinatra.rb +17 -0
- metadata +103 -0
data/lib/never-forget.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'never_forget'
|
data/lib/never_forget.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
module NeverForget
|
2
|
+
autoload :ExceptionHandler, 'never_forget/exception_handler'
|
3
|
+
autoload :Exception, 'never_forget/exception'
|
4
|
+
|
5
|
+
class << self
|
6
|
+
attr_writer :enabled
|
7
|
+
def enabled?() @enabled end
|
8
|
+
end
|
9
|
+
self.enabled = true
|
10
|
+
|
11
|
+
def self.log(error, env = {}, &block)
|
12
|
+
Exception.create(error, env, &block) if enabled?
|
13
|
+
rescue
|
14
|
+
warn "NeverForget: error saving exception (#{$!.class} #{$!})"
|
15
|
+
warn $!.backtrace.first
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
require 'never_forget/railtie' if defined? Rails::Railtie
|
20
|
+
|
21
|
+
if defined?(Sinatra) and Sinatra.respond_to? :register
|
22
|
+
require 'never_forget/sinatra'
|
23
|
+
end
|
@@ -0,0 +1,179 @@
|
|
1
|
+
require 'mingo'
|
2
|
+
require 'active_support/memoizable'
|
3
|
+
require 'active_support/core_ext/kernel/singleton_class'
|
4
|
+
require 'active_support/core_ext/enumerable'
|
5
|
+
require 'active_support/core_ext/hash'
|
6
|
+
require 'rack/request'
|
7
|
+
require 'rack/utils'
|
8
|
+
|
9
|
+
module NeverForget
|
10
|
+
class Exception < ::Mingo
|
11
|
+
def self.collection_name() "NeverForget" end
|
12
|
+
def self.collection
|
13
|
+
unless defined? @collection
|
14
|
+
db.create_collection collection_name, capped: true, size: 1.megabyte
|
15
|
+
end
|
16
|
+
super
|
17
|
+
end
|
18
|
+
|
19
|
+
extend ::ActiveSupport::Memoizable
|
20
|
+
include ::Mingo::Timestamps
|
21
|
+
|
22
|
+
def self.create(error, env)
|
23
|
+
if connected?
|
24
|
+
record = new.init(error, env)
|
25
|
+
yield record if block_given?
|
26
|
+
record.save
|
27
|
+
record
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.recent
|
32
|
+
find.sort('$natural', :desc).limit(20)
|
33
|
+
end
|
34
|
+
|
35
|
+
KEEP = %w[rack.url_scheme action_dispatch.remote_ip]
|
36
|
+
EXCLUDE = %w[HTTP_COOKIE QUERY_STRING SERVER_ADDR]
|
37
|
+
KNOWN_MODULES = %w[
|
38
|
+
ActiveSupport::Dependencies::Blamable
|
39
|
+
JSON::Ext::Generator::GeneratorMethods::Object
|
40
|
+
ActiveSupport::Dependencies::Loadable
|
41
|
+
PP::ObjectMixin
|
42
|
+
Kernel
|
43
|
+
]
|
44
|
+
|
45
|
+
attr_reader :exception, :env
|
46
|
+
|
47
|
+
def init(ex, env_hash)
|
48
|
+
@exception = unwrap_exception(ex)
|
49
|
+
@env = env_hash
|
50
|
+
self['name'] = exception.class.name
|
51
|
+
self['modules'] = tag_modules
|
52
|
+
self['backtrace'] = exception.backtrace.join("\n")
|
53
|
+
self['message'] = exception.message
|
54
|
+
|
55
|
+
if env['REQUEST_METHOD']
|
56
|
+
self['env'] = sanitized_env
|
57
|
+
self['params'] = extract_params
|
58
|
+
self['session'] = extract_session
|
59
|
+
self['cookies'] = extract_cookies
|
60
|
+
else
|
61
|
+
self['params'] = clean_unserializable_data(env)
|
62
|
+
end
|
63
|
+
self
|
64
|
+
end
|
65
|
+
|
66
|
+
def request
|
67
|
+
@request ||= Rack::Request.new(env)
|
68
|
+
end
|
69
|
+
|
70
|
+
def request?
|
71
|
+
self['env'].present?
|
72
|
+
end
|
73
|
+
|
74
|
+
def request_url
|
75
|
+
env = self['env']
|
76
|
+
scheme = env['rack::url_scheme']
|
77
|
+
host, port = env['HTTP_HOST'], env['SERVER_PORT'].to_i
|
78
|
+
host += ":#{port}" if 'http' == scheme && port != 80 or 'https' == scheme && port != 443
|
79
|
+
|
80
|
+
url = scheme + '://' + File.join(host, env['SCRIPT_NAME'], env['PATH_INFO'])
|
81
|
+
url << '?' << Rack::Utils::build_nested_query(self['params']) if get_request? and self['params'].present?
|
82
|
+
url
|
83
|
+
end
|
84
|
+
|
85
|
+
def request_method
|
86
|
+
self['env']['REQUEST_METHOD']
|
87
|
+
end
|
88
|
+
|
89
|
+
def get_request?
|
90
|
+
'GET' == request_method
|
91
|
+
end
|
92
|
+
|
93
|
+
def xhr?
|
94
|
+
self['env']['HTTP_X_REQUESTED_WITH'] =~ /XMLHttpRequest/i
|
95
|
+
end
|
96
|
+
|
97
|
+
def remote_ip
|
98
|
+
self['env']['action_dispatch::remote_ip'] || self['env']['REMOTE_ADDR']
|
99
|
+
end
|
100
|
+
|
101
|
+
def tag_modules
|
102
|
+
Array(exception.singleton_class.included_modules).map(&:to_s) - KNOWN_MODULES
|
103
|
+
end
|
104
|
+
memoize :tag_modules
|
105
|
+
|
106
|
+
def unwrap_exception(exception)
|
107
|
+
if exception.respond_to?(:original_exception)
|
108
|
+
exception.original_exception
|
109
|
+
elsif exception.respond_to?(:continued_exception)
|
110
|
+
exception.continued_exception
|
111
|
+
else
|
112
|
+
exception
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def extract_session
|
117
|
+
if session = env['rack.session']
|
118
|
+
session_hash = session.to_hash.stringify_keys.except('session_id', '_csrf_token')
|
119
|
+
clean_unserializable_data session_hash
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def exclude_params
|
124
|
+
Array(env['action_dispatch.parameter_filter']).map(&:to_s)
|
125
|
+
end
|
126
|
+
memoize :exclude_params
|
127
|
+
|
128
|
+
def extract_params
|
129
|
+
if params = request.params and params.any?
|
130
|
+
filtered = params.each_with_object({}) { |(key, value), keep|
|
131
|
+
keep[key] = exclude_params.include?(key.to_s) ? '[FILTERED]' : value
|
132
|
+
}
|
133
|
+
clean_unserializable_data filtered.except('utf8')
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def extract_cookies
|
138
|
+
if cookies = env['rack.request.cookie_hash']
|
139
|
+
if options = env['rack.session.options']
|
140
|
+
cookies = cookies.except(options[:key])
|
141
|
+
end
|
142
|
+
clean_unserializable_data cookies
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def sanitized_env
|
147
|
+
clean_unserializable_data env.select { |key, _| keep_env? key }
|
148
|
+
end
|
149
|
+
|
150
|
+
def keep_env?(key)
|
151
|
+
( key !~ /[a-z]/ or KEEP.include?(key) ) and not discard_env?(key)
|
152
|
+
end
|
153
|
+
|
154
|
+
def discard_env?(key)
|
155
|
+
EXCLUDE.include?(key) or
|
156
|
+
( key == 'REMOTE_ADDR' and env['action_dispatch.remote_ip'] )
|
157
|
+
end
|
158
|
+
|
159
|
+
def sanitize_key(key)
|
160
|
+
key.to_s.gsub('.', '::').sub(/^\$/, 'DOLLAR::')
|
161
|
+
end
|
162
|
+
|
163
|
+
def clean_unserializable_data(data, stack = [])
|
164
|
+
return "[possible infinite recursion halted]" if stack.any?{|item| item == data.object_id }
|
165
|
+
|
166
|
+
if data.respond_to?(:to_hash)
|
167
|
+
data.to_hash.each_with_object({}) do |(key, value), result|
|
168
|
+
result[sanitize_key(key)] = clean_unserializable_data(value, stack + [data.object_id])
|
169
|
+
end
|
170
|
+
elsif data.respond_to?(:to_ary)
|
171
|
+
data.collect do |value|
|
172
|
+
clean_unserializable_data(value, stack + [data.object_id])
|
173
|
+
end
|
174
|
+
else
|
175
|
+
data.to_s
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'rbconfig'
|
2
|
+
require 'erubis'
|
3
|
+
require 'active_support/memoizable'
|
4
|
+
|
5
|
+
module NeverForget
|
6
|
+
class ExceptionHandler
|
7
|
+
TEMPLATE_FILE = File.expand_path('../list_exceptions.erb', __FILE__)
|
8
|
+
|
9
|
+
def initialize(app, options = {})
|
10
|
+
@app = app
|
11
|
+
@options = {:list_path => '/_exceptions'}.update(options)
|
12
|
+
end
|
13
|
+
|
14
|
+
def forward(env)
|
15
|
+
begin
|
16
|
+
@app.call(env)
|
17
|
+
rescue StandardError, ScriptError => error
|
18
|
+
NeverForget.log(error, env)
|
19
|
+
raise error
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def call(env)
|
24
|
+
if env['PATH_INFO'] == File.join('/', @options[:list_path])
|
25
|
+
body = render_exceptions_view
|
26
|
+
[200, {'content-type' => 'text/html'}, [body]]
|
27
|
+
else
|
28
|
+
forward(env)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def render_exceptions_view
|
33
|
+
template = Erubis::Eruby.new(exceptions_template)
|
34
|
+
context = Erubis::Context.new(:recent => Exception.recent)
|
35
|
+
context.extend TemplateHelpers
|
36
|
+
template.evaluate(context)
|
37
|
+
end
|
38
|
+
|
39
|
+
def exceptions_template
|
40
|
+
File.read(TEMPLATE_FILE)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
module TemplateHelpers
|
45
|
+
include RbConfig
|
46
|
+
extend ActiveSupport::Memoizable
|
47
|
+
|
48
|
+
def gem_path
|
49
|
+
paths = []
|
50
|
+
paths << Bundler.bundle_path << Bundler.user_bundle_path if defined? Bundler
|
51
|
+
paths << Gem.path if defined? Gem
|
52
|
+
paths.flatten.uniq
|
53
|
+
end
|
54
|
+
|
55
|
+
SYSDIRS = %w[ vendor site rubylib arch sitelib sitearch vendorlib vendorarch top ]
|
56
|
+
|
57
|
+
def system_path
|
58
|
+
SYSDIRS.map { |name| CONFIG["#{name}dir"] }.compact
|
59
|
+
end
|
60
|
+
|
61
|
+
def external_path
|
62
|
+
['/usr/ruby1.9.2', '/home/heroku_rack', gem_path, system_path].flatten.uniq
|
63
|
+
end
|
64
|
+
memoize :external_path
|
65
|
+
|
66
|
+
def collapse_line?(line)
|
67
|
+
external_path.any? {|p| line.start_with? p }
|
68
|
+
end
|
69
|
+
|
70
|
+
def ignore_line?(line)
|
71
|
+
line.include? '/Library/Application Support/Pow/'
|
72
|
+
end
|
73
|
+
|
74
|
+
def strip_root(line)
|
75
|
+
if line =~ %r{/gems/([^/]+)-(\d[\w.]*)/}
|
76
|
+
gem_name, gem_version = $1, $2
|
77
|
+
path = line.split($&, 2).last
|
78
|
+
"#{gem_name} (#{gem_version}) #{path}"
|
79
|
+
elsif path = "#{root_path}/" and line.start_with? path
|
80
|
+
line.sub(path, '')
|
81
|
+
else
|
82
|
+
line
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def root_path
|
87
|
+
if defined? Bundler then Bundler.root
|
88
|
+
elsif defined? Rails then Rails.root
|
89
|
+
elsif defined? Sinatra::Application then Sinatra::Application.root
|
90
|
+
else Dir.pwd
|
91
|
+
end
|
92
|
+
end
|
93
|
+
memoize :root_path
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<meta charset="utf-8">
|
3
|
+
<meta name="viewport" content="initial-scale=1.0; maximum-scale=1.0; user-scalable=0;">
|
4
|
+
<title>Exceptions</title>
|
5
|
+
<style>
|
6
|
+
body {
|
7
|
+
margin: 2em;
|
8
|
+
font: medium/1.6 Helvetica, sans-serif;
|
9
|
+
}
|
10
|
+
a:link, a:visited { color: darkblue; }
|
11
|
+
a:hover { color: crimson; }
|
12
|
+
.url, .ua { margin: .2em 0; }
|
13
|
+
.ua { color: gray; font-size: 85%; }
|
14
|
+
ol.trace {
|
15
|
+
margin: .5em 0;
|
16
|
+
padding: 0;
|
17
|
+
list-style: none;
|
18
|
+
font-size: 85%;
|
19
|
+
line-height: 1.2;
|
20
|
+
}
|
21
|
+
.trace .num { font-weight: bold; }
|
22
|
+
.trace .sys { display: none; }
|
23
|
+
.trace .expand a {
|
24
|
+
font-size: 80%; text-transform: uppercase;
|
25
|
+
}
|
26
|
+
.trace.expanded .sys { display: list-item; }
|
27
|
+
.trace.expanded .expand { display: none; }
|
28
|
+
|
29
|
+
h1 { margin: 0; padding: 0; }
|
30
|
+
article {
|
31
|
+
margin-top: 1.5em;
|
32
|
+
padding-top: 1em;
|
33
|
+
border-top: 2px solid #eee;
|
34
|
+
}
|
35
|
+
h2 {
|
36
|
+
margin: 0 0 .2em 0;
|
37
|
+
padding: 0 0 .1em 0;
|
38
|
+
display: inline-block;
|
39
|
+
}
|
40
|
+
time {
|
41
|
+
display: inline-block;
|
42
|
+
font: 80% "American Typewriter", monospace;
|
43
|
+
color: gray;
|
44
|
+
margin-left: 1em;
|
45
|
+
}
|
46
|
+
h2 span { font-weight: normal; }
|
47
|
+
h3 { margin: .5em 0 .2em 0; padding: .1em 0; font-size: 1.1em; }
|
48
|
+
pre { margin: .2em; }
|
49
|
+
|
50
|
+
@media only screen and (max-device-width:480px) {
|
51
|
+
body { margin: 7px; font-size: small; }
|
52
|
+
h1 { font-size: 22px; text-align: center; margin: 0; padding: 0; }
|
53
|
+
h2 { font-size: 18px; }
|
54
|
+
article { margin-top: .2em; padding-top: .2em; padding-bottom: .5em; }
|
55
|
+
}
|
56
|
+
</style>
|
57
|
+
|
58
|
+
<h1>Exceptions</h1>
|
59
|
+
|
60
|
+
<% if @recent.has_next? %>
|
61
|
+
<% for ex in @recent %>
|
62
|
+
<article>
|
63
|
+
<header><h2><%= ex['name'] %>: <span><%== ex['message'] %></span></h2>
|
64
|
+
<time><%== ex.created_at.strftime('%a, %b %e %T') %></time>
|
65
|
+
</header>
|
66
|
+
|
67
|
+
<% if ex.request? %>
|
68
|
+
<% url = ex.request_url %>
|
69
|
+
<p class="url">
|
70
|
+
<% if ex.xhr? %>Ajax<% end %> <%= ex.request_method %>
|
71
|
+
<a href="<%== url %>"><%== url.split('://').last %></a>
|
72
|
+
</p>
|
73
|
+
<p class="ua">
|
74
|
+
<span class="ip"><%= ex.remote_ip %></span> —
|
75
|
+
<%== ex['env']['HTTP_USER_AGENT'] %>
|
76
|
+
</p>
|
77
|
+
<% end %>
|
78
|
+
|
79
|
+
<% if ex['params'].present? %>
|
80
|
+
<h3>Params:</h3>
|
81
|
+
<pre class="params"><%== YAML.dump ex['params'] %></pre>
|
82
|
+
<% end %>
|
83
|
+
|
84
|
+
<% if ex['session'].present? %>
|
85
|
+
<h3>Session:</h3>
|
86
|
+
<pre class="session"><%== YAML.dump ex['session'] %></pre>
|
87
|
+
<% end %>
|
88
|
+
|
89
|
+
<h3>Backtrace:</h3>
|
90
|
+
<ol class="trace">
|
91
|
+
<% for line in ex['backtrace'].split("\n") %>
|
92
|
+
<% next if ignore_line? line %>
|
93
|
+
<li<%= collapse_line?(line) ? ' class="sys"' : '' %>><%= strip_root(line).sub(/:(\d+):/, ' <span class="num">\1</span> ') %></li>
|
94
|
+
<% end %>
|
95
|
+
<li class="expand"><a href="#expand">full trace</a></li>
|
96
|
+
</ol>
|
97
|
+
</article>
|
98
|
+
<% end %>
|
99
|
+
|
100
|
+
<script>
|
101
|
+
document.addEventListener('click', function(e) {
|
102
|
+
var el = e.target
|
103
|
+
if (el.nodeName == 'A' && el.getAttribute('href') == '#expand') {
|
104
|
+
e.preventDefault()
|
105
|
+
el.parentNode.parentNode.className += " expanded"
|
106
|
+
}
|
107
|
+
}, false)
|
108
|
+
</script>
|
109
|
+
<% else %>
|
110
|
+
<p><i>No exceptions captured yet.</i></p>
|
111
|
+
<% end %>
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module NeverForget
|
2
|
+
class Railtie < ::Rails::Railtie
|
3
|
+
config.never_forget = ActiveSupport::OrderedOptions.new
|
4
|
+
config.never_forget.enabled = ::Rails.env.production? || ::Rails.env.staging?
|
5
|
+
config.never_forget.list_path = '/_exceptions'
|
6
|
+
|
7
|
+
initializer "never_forget" do |app|
|
8
|
+
if NeverForget.enabled = app.config.never_forget.enabled
|
9
|
+
app.config.middleware.insert_after 'ActionDispatch::ShowExceptions',
|
10
|
+
ExceptionHandler, :list_path => app.config.never_forget.list_path
|
11
|
+
|
12
|
+
::ActiveSupport.on_load(:action_controller) { include NeverForget::ControllerRescue }
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
module ControllerRescue
|
18
|
+
def rescue_with_handler(exception)
|
19
|
+
if super
|
20
|
+
# the exception was handled, but we still want to save it
|
21
|
+
NeverForget.log(exception, request.env)
|
22
|
+
true
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module NeverForget
|
2
|
+
module Sinatra
|
3
|
+
module Helpers
|
4
|
+
def log_error(boom = $!, env = nil)
|
5
|
+
env = request.env if env.nil? and respond_to? :request
|
6
|
+
NeverForget.log(boom, env || {})
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.registered(app)
|
11
|
+
app.use NeverForget::ExceptionHandler
|
12
|
+
app.helpers Helpers
|
13
|
+
end
|
14
|
+
|
15
|
+
::Sinatra.register self
|
16
|
+
end
|
17
|
+
end
|
metadata
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: never-forget
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.1.0
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- "Mislav Marohni\xC4\x87"
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-09-28 00:00:00 Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: mingo
|
17
|
+
prerelease: false
|
18
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
19
|
+
none: false
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0.2"
|
24
|
+
type: :runtime
|
25
|
+
version_requirements: *id001
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: activesupport
|
28
|
+
prerelease: false
|
29
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
30
|
+
none: false
|
31
|
+
requirements:
|
32
|
+
- - ">="
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: "0"
|
35
|
+
type: :runtime
|
36
|
+
version_requirements: *id002
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: rack
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: "0"
|
46
|
+
type: :runtime
|
47
|
+
version_requirements: *id003
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: erubis
|
50
|
+
prerelease: false
|
51
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: "0"
|
57
|
+
type: :runtime
|
58
|
+
version_requirements: *id004
|
59
|
+
description: Never Forget is a layer of persistence for exceptions thrown in a Rack application at runtime.
|
60
|
+
email: mislav.marohnic@gmail.com
|
61
|
+
executables: []
|
62
|
+
|
63
|
+
extensions: []
|
64
|
+
|
65
|
+
extra_rdoc_files: []
|
66
|
+
|
67
|
+
files:
|
68
|
+
- lib/never-forget.rb
|
69
|
+
- lib/never_forget/exception.rb
|
70
|
+
- lib/never_forget/exception_handler.rb
|
71
|
+
- lib/never_forget/list_exceptions.erb
|
72
|
+
- lib/never_forget/railtie.rb
|
73
|
+
- lib/never_forget/sinatra.rb
|
74
|
+
- lib/never_forget.rb
|
75
|
+
homepage: https://github.com/mislav/never-forget
|
76
|
+
licenses: []
|
77
|
+
|
78
|
+
post_install_message:
|
79
|
+
rdoc_options: []
|
80
|
+
|
81
|
+
require_paths:
|
82
|
+
- lib
|
83
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
84
|
+
none: false
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: "0"
|
89
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
90
|
+
none: false
|
91
|
+
requirements:
|
92
|
+
- - ">="
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: "0"
|
95
|
+
requirements: []
|
96
|
+
|
97
|
+
rubyforge_project:
|
98
|
+
rubygems_version: 1.8.8
|
99
|
+
signing_key:
|
100
|
+
specification_version: 3
|
101
|
+
summary: Saves exceptions to MongoDB
|
102
|
+
test_files: []
|
103
|
+
|