radar 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +6 -0
- data/.yardopts +2 -0
- data/Gemfile +11 -0
- data/Gemfile.lock +34 -0
- data/README.md +107 -0
- data/Rakefile +17 -0
- data/docs/user_guide.md +197 -0
- data/lib/radar.rb +18 -0
- data/lib/radar/application.rb +96 -0
- data/lib/radar/config.rb +35 -0
- data/lib/radar/data_extensions/host_environment.rb +24 -0
- data/lib/radar/error.rb +6 -0
- data/lib/radar/exception_event.rb +61 -0
- data/lib/radar/reporter.rb +29 -0
- data/lib/radar/reporter/file_reporter.rb +37 -0
- data/lib/radar/support.rb +25 -0
- data/lib/radar/version.rb +3 -0
- data/radar.gemspec +28 -0
- data/test/radar/application_test.rb +105 -0
- data/test/radar/config_test.rb +59 -0
- data/test/radar/data_extensions/host_environment_test.rb +15 -0
- data/test/radar/exception_event_test.rb +48 -0
- data/test/radar/reporter/file_reporter_test.rb +27 -0
- data/test/radar/reporter_test.rb +14 -0
- data/test/radar/support_test.rb +26 -0
- data/test/test_helper.rb +25 -0
- metadata +163 -0
data/.gitignore
ADDED
data/.yardopts
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
source :gemcutter
|
2
|
+
|
3
|
+
# Specify your gem's dependencies in radar.gemspec
|
4
|
+
gemspec
|
5
|
+
|
6
|
+
# Additional gems which I don't really want in the gemspec but
|
7
|
+
# are useful for development
|
8
|
+
group :development do
|
9
|
+
gem "yard", :git => "http://github.com/lsegal/yard.git"
|
10
|
+
gem "bluecloth"
|
11
|
+
end
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
GIT
|
2
|
+
remote: http://github.com/lsegal/yard.git
|
3
|
+
revision: bf84275
|
4
|
+
specs:
|
5
|
+
yard (0.6.0.rc1)
|
6
|
+
|
7
|
+
PATH
|
8
|
+
remote: .
|
9
|
+
specs:
|
10
|
+
radar (0.1.0)
|
11
|
+
json (>= 1.4.6)
|
12
|
+
|
13
|
+
GEM
|
14
|
+
remote: http://rubygems.org/
|
15
|
+
specs:
|
16
|
+
bluecloth (2.0.7)
|
17
|
+
json (1.4.6)
|
18
|
+
mocha (0.9.8)
|
19
|
+
rake
|
20
|
+
rake (0.8.7)
|
21
|
+
shoulda (2.11.3)
|
22
|
+
|
23
|
+
PLATFORMS
|
24
|
+
ruby
|
25
|
+
|
26
|
+
DEPENDENCIES
|
27
|
+
bluecloth
|
28
|
+
bundler (>= 1.0.0.rc.5)
|
29
|
+
json (>= 1.4.6)
|
30
|
+
mocha
|
31
|
+
radar!
|
32
|
+
rake
|
33
|
+
shoulda
|
34
|
+
yard!
|
data/README.md
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
# Radar
|
2
|
+
|
3
|
+
* Source: [http://github.com/mitchellh/radar](http://github.com/mitchellh/radar)
|
4
|
+
* IRC: `#vagrant` on Freenode
|
5
|
+
* User Guide: [http://mitchellh.github.com/radar/file.user_guide.html](http://mitchellh.github.com/radar/file.user_guide.html)
|
6
|
+
* Documentation: [http://mitchellh.github.com/radar](http://mitchellh.github.com/radar)
|
7
|
+
|
8
|
+
Radar is a tool which provides a drop-in solution to catching and reporting
|
9
|
+
errors in your Ruby applications in customizable ways.
|
10
|
+
|
11
|
+
Radar is not a typical exception notifier such as Hoptoad and Exceptional
|
12
|
+
since the former are built with Rails apps in mind, logging to a central
|
13
|
+
server. Instead, Radar was initially built for [Vagrant](http://vagrantup.com),
|
14
|
+
a command line tool. And instead of solely logging to a central server,
|
15
|
+
Radar supports logging in configurable ways (to a file, to a server, to
|
16
|
+
a growl notification, etc.)
|
17
|
+
|
18
|
+
Radar was built out of the need for an easy automated solution to catching
|
19
|
+
exceptions and gathering information, since I noticed that every time a stack
|
20
|
+
trace was outputted for [Vagrant](http://vagrantup.com), I was asking the
|
21
|
+
user all the same questions which could've easily have been gathered automatically.
|
22
|
+
Now, Vagrant users can simply send me the radar output and I don't have to
|
23
|
+
ask for any more information 90% of the time.
|
24
|
+
|
25
|
+
## Quick Start
|
26
|
+
|
27
|
+
gem install radar
|
28
|
+
|
29
|
+
Then just begin logging exceptions in your application:
|
30
|
+
|
31
|
+
r = Radar::Application.new(:my_application)
|
32
|
+
r.config.reporter Radar::Reporter::FileReporter
|
33
|
+
r.report(exception)
|
34
|
+
|
35
|
+
You can also tell Radar to attach itself to Ruby's `at_exit` hook
|
36
|
+
so it can catch application-crashing exceptions automatically:
|
37
|
+
|
38
|
+
r.rescue_at_exit!
|
39
|
+
|
40
|
+
Both of the above methods can be used together, of course.
|
41
|
+
|
42
|
+
Since the above enabled the `FileReporter` for the application, reported
|
43
|
+
exceptions will be stored in a text file on the local filesystem, by default
|
44
|
+
at `~/.radar/errors/my_application`. Sample contents of the exception
|
45
|
+
data generated by default is shown below. Note that you can add your own
|
46
|
+
custom data to this output by using data extensions (covered in the user
|
47
|
+
guide).
|
48
|
+
|
49
|
+
{
|
50
|
+
"application":{
|
51
|
+
"name":"my_application"
|
52
|
+
},
|
53
|
+
"exception":{
|
54
|
+
"klass":"RuntimeError",
|
55
|
+
"message":"This was an example exception",
|
56
|
+
"backtrace":[
|
57
|
+
"test.rb:28:in `<main>'"
|
58
|
+
],
|
59
|
+
"uniqueness_hash":"296d169e7928c4433ccfcf091b4d737aabe83dcb"
|
60
|
+
},
|
61
|
+
"occurred_at":1281894743,
|
62
|
+
"host_environment":{
|
63
|
+
"ruby_version":"1.9.2",
|
64
|
+
"ruby_pl":-1,
|
65
|
+
"ruby_release_date":"2010-07-11",
|
66
|
+
"ruby_platform":"x86_64-darwin10.4.0"
|
67
|
+
}
|
68
|
+
}
|
69
|
+
|
70
|
+
Also, instead of assigning the application to a variable, you may also
|
71
|
+
look it up later anywhere in your application:
|
72
|
+
|
73
|
+
Radar::Application.find(:my_application).report(exception)
|
74
|
+
|
75
|
+
# Or the shorthand:
|
76
|
+
Radar[:my_application].report(exception)
|
77
|
+
|
78
|
+
## Documentation and User Guide
|
79
|
+
|
80
|
+
For more details on configuring Radar, please view the
|
81
|
+
[user guide](http://mitchellh.github.com/radar/file.user_guide.html), which
|
82
|
+
is a detailed overview of Radar and all of its features.
|
83
|
+
|
84
|
+
## Reporting Bugs and Requesting Features
|
85
|
+
|
86
|
+
Please use the [issues section](http://github.com/mitchellh/radar/issues) to report
|
87
|
+
bugs and request features. This section is also used as the TODO area for the
|
88
|
+
Radar gem.
|
89
|
+
|
90
|
+
## Contributing
|
91
|
+
|
92
|
+
To hack on Radar, you'll need [bundler](http://github.com/carlhuda/bundler) which
|
93
|
+
can be installed with a simple `gem install bundler`. Then, do the following:
|
94
|
+
|
95
|
+
bundle install
|
96
|
+
rake
|
97
|
+
|
98
|
+
This will run the test suite, which should come back all green! Then you're
|
99
|
+
good to go.
|
100
|
+
|
101
|
+
The general steps to contributing a new change are:
|
102
|
+
|
103
|
+
1. Fork the repository
|
104
|
+
2. Make your changes, writing tests if necessary
|
105
|
+
3. Open an [issue](http://github.com/mitchellh/radar/issues) with the feature and
|
106
|
+
a link to your fork or a gist with a patch.
|
107
|
+
4. Wait patiently, for I am but one man.
|
data/Rakefile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler/setup'
|
3
|
+
require 'rake/testtask'
|
4
|
+
Bundler::GemHelper.install_tasks
|
5
|
+
|
6
|
+
task :default => :test
|
7
|
+
|
8
|
+
Rake::TestTask.new do |t|
|
9
|
+
t.libs << "test"
|
10
|
+
t.pattern = 'test/**/*_test.rb'
|
11
|
+
end
|
12
|
+
|
13
|
+
begin
|
14
|
+
require 'yard'
|
15
|
+
YARD::Rake::YardocTask.new
|
16
|
+
rescue LoadError
|
17
|
+
end
|
data/docs/user_guide.md
ADDED
@@ -0,0 +1,197 @@
|
|
1
|
+
# Radar User's Guide
|
2
|
+
|
3
|
+
## Overview
|
4
|
+
|
5
|
+
Radar is a tool which provides a drop-in solution to catching and reporting
|
6
|
+
errors in your Ruby application via customizable mediums.
|
7
|
+
|
8
|
+
## Installation
|
9
|
+
|
10
|
+
Install via RubyGems:
|
11
|
+
|
12
|
+
gem install radar
|
13
|
+
|
14
|
+
Or specify in your own library or application's `Gemfile` or `gemspec` so
|
15
|
+
that the gem is included properly.
|
16
|
+
|
17
|
+
## Basic Usage
|
18
|
+
|
19
|
+
### Setup
|
20
|
+
|
21
|
+
First, as early as possible in your application so that Radar can begin
|
22
|
+
catching exceptions right away, create a new {Radar::Application}
|
23
|
+
instance for your own app, replacing `my_application` with a unique name for your
|
24
|
+
application.
|
25
|
+
|
26
|
+
Radar::Application.new(:my_application)
|
27
|
+
|
28
|
+
But this alone won't do anything, since you haven't configured any reporters
|
29
|
+
for the application. Reporters are what handle taking an {Radar::ExceptionEvent ExceptionEvent}
|
30
|
+
and doing something with it (such as storing it in a file, reporting it to a
|
31
|
+
remote server, etc.). Radar comes with some built-in reporters. Below, we configure
|
32
|
+
the application to log errors to a file (by default at `~/.radar/errors/my_application`):
|
33
|
+
|
34
|
+
Radar::Application.new(:my_application) do |app|
|
35
|
+
app.config.reporter Radar::Reporter::FileReporter
|
36
|
+
end
|
37
|
+
|
38
|
+
### Reporting Errors
|
39
|
+
|
40
|
+
Once the application is setup, there are two methods to report errors:
|
41
|
+
|
42
|
+
1. Manually call the {Radar::Application#report report} method on the application.
|
43
|
+
2. Tell the application to {Radar::Application#rescue_at_exit! rescue at exit} so
|
44
|
+
Radar automatically catches any exceptions before your application crashes.
|
45
|
+
|
46
|
+
Calling the report method manually:
|
47
|
+
|
48
|
+
app = Radar::Application.new(:my_application)
|
49
|
+
app.report(exception)
|
50
|
+
|
51
|
+
The use case for this is in a `rescue` block somewhere, and forces Radar
|
52
|
+
to report the given exception.
|
53
|
+
|
54
|
+
Telling Radar to catch exceptions on exit is equally simple, and can be
|
55
|
+
used in conjunction with the above method as well:
|
56
|
+
|
57
|
+
app = Radar::Application.new(:my_application)
|
58
|
+
app.rescue_at_exit!
|
59
|
+
|
60
|
+
Now, whenever your application is about to crash (an exception not caught by
|
61
|
+
a `rescue`), Radar will catch your exception and report it just prior to
|
62
|
+
crashing.
|
63
|
+
|
64
|
+
# Features
|
65
|
+
|
66
|
+
## Reporters
|
67
|
+
|
68
|
+
On its own, Radar does nothing but catch and shuttle exceptions to reporters. Without
|
69
|
+
reporters, Radar is basically useless! A reporter is a class which takes an
|
70
|
+
{Radar::ExceptionEvent} and does something with it. Its easier to get a better idea
|
71
|
+
of what this means with a few examples:
|
72
|
+
|
73
|
+
* `FileReporter` - Stores the data from an exception on the filesystem for each
|
74
|
+
exception.
|
75
|
+
* `ServerReporter` - Transfers information about an exception to some remote
|
76
|
+
server.
|
77
|
+
|
78
|
+
### Enabling and Configuring a Reporter
|
79
|
+
|
80
|
+
Reporters are enabled using the appilication configuration:
|
81
|
+
|
82
|
+
Radar::Application.new(:my_application) do |app|
|
83
|
+
app.config.reporter FileReporter
|
84
|
+
end
|
85
|
+
|
86
|
+
And can be configured by passing a block to the reporter, which is yielded with
|
87
|
+
the instance of that reporter:
|
88
|
+
|
89
|
+
Radar::Application.new(:my_application) do |app|
|
90
|
+
app.config.reporter FileReporter do |reporter|
|
91
|
+
reporter.storage_directory = "~/.radar/exceptions"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
Radar also allows multiple reporters to be used, which are then called
|
96
|
+
in the order they are defined when an exception occurs:
|
97
|
+
|
98
|
+
Radar::Application.new(:my_application) do |app|
|
99
|
+
app.config.reporter FileReporter
|
100
|
+
app.config.reporter AnotherReporter
|
101
|
+
end
|
102
|
+
|
103
|
+
### Built-in Reporters
|
104
|
+
|
105
|
+
#### FileReporter
|
106
|
+
|
107
|
+
{Radar::Reporter::FileReporter FileReporter} outputs exception information as JSON to a file
|
108
|
+
on the local filesystem. The filename is in the format of `timestamp-uniquehash.txt`,
|
109
|
+
where `timestamp` is the time that the exception occurred and `uniquehash` is the
|
110
|
+
{Radar::ExceptionEvent#uniqueness_hash}.
|
111
|
+
|
112
|
+
The directory where these files will be stored is configurable:
|
113
|
+
|
114
|
+
Radar::Application.new(:my_application) do |app|
|
115
|
+
app.config.reporter Radar::Reporter::FileReporter do |reporter|
|
116
|
+
reporter.output_directory = "~/my_application_errors"
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
You may also use a lambda. Below is also the default value for the FileReporter:
|
121
|
+
|
122
|
+
reporter.output_directory = lambda { |event| "~/.radar/#{event.application.name}" }
|
123
|
+
|
124
|
+
A few notes:
|
125
|
+
|
126
|
+
* The FileReporter does not automatically handle cleaning up old exception files or reporting
|
127
|
+
these files to any remote server. It is up to you, if you wish, to clean up old
|
128
|
+
exception information.
|
129
|
+
* The JSON output is compressed JSON, and is not pretty printed. It is up to you to take the
|
130
|
+
JSON and pretty print it if you wish it to be easily human readable. There are
|
131
|
+
[services](http://jsonformatter.curiousconcept.com/) out there to do this.
|
132
|
+
|
133
|
+
### Custom Reporters
|
134
|
+
|
135
|
+
It is very easy to write custom reporters. A reporter is simply a class which
|
136
|
+
responds to `report` and takes a single {Radar::ExceptionEvent} as a parameter.
|
137
|
+
Below is an example of a reporter which simply prints out that an error
|
138
|
+
occurred:
|
139
|
+
|
140
|
+
class StdoutReporter
|
141
|
+
def report(event)
|
142
|
+
puts "An exception occurred! Message: #{event.exception.message}"
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
And then using that reporter is just as easy:
|
147
|
+
|
148
|
+
Radar::Application.new(:my_application) do |app|
|
149
|
+
app.config.reporter StdoutReporter
|
150
|
+
end
|
151
|
+
|
152
|
+
## Data Extensions
|
153
|
+
|
154
|
+
Data extensions allow you to easily extend the data represented by {Radar::ExceptionEvent#to_hash}.
|
155
|
+
By default, Radar only sends a small amount of information about the host environment and the
|
156
|
+
exception when an exception occurs. Often its more helpful to get more information about the
|
157
|
+
environment to more easily track down a bug. Some examples:
|
158
|
+
|
159
|
+
* HTTP request headers for a web application
|
160
|
+
* Configuration settings for a desktop application
|
161
|
+
|
162
|
+
### Defining Data Extensions
|
163
|
+
|
164
|
+
Data extensions are defined with classes which are expected to be initialized
|
165
|
+
with a reference to a {Radar::ExceptionEvent} and must implement the `to_hash`
|
166
|
+
method. Below is a data extension to add the output of `uname -a` to the event:
|
167
|
+
|
168
|
+
class UnameExtension
|
169
|
+
def initialize(event)
|
170
|
+
@event = event
|
171
|
+
end
|
172
|
+
|
173
|
+
def to_hash
|
174
|
+
{ :uname => `uname -a` }
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
### Enabling Data Extensions
|
179
|
+
|
180
|
+
Data extensions are enabled via the application configuration like most other
|
181
|
+
things:
|
182
|
+
|
183
|
+
Radar::Application.new(:my_application) do |app|
|
184
|
+
app.config.data_extension UnameExtension
|
185
|
+
end
|
186
|
+
|
187
|
+
### Built-In Data Extensions
|
188
|
+
|
189
|
+
By default, {Radar::ExceptionEvent#to_hash} (view the source) returns very little
|
190
|
+
information on its own. To be as general and extensible as possible, even the data
|
191
|
+
such as information about the host are created using built-in data extensions.
|
192
|
+
Some of these are enabled by default, which are designated by the `*` on the name.
|
193
|
+
|
194
|
+
* {Radar::DataExtensions::HostEnvironment HostEnvironment}* - Adds information about the
|
195
|
+
host such as Ruby version and operating system.
|
196
|
+
|
197
|
+
`*`: Enabled by default on every application.
|
data/lib/radar.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'radar/version'
|
2
|
+
require 'radar/error'
|
3
|
+
|
4
|
+
module Radar
|
5
|
+
autoload :Application, 'radar/application'
|
6
|
+
autoload :Config, 'radar/config'
|
7
|
+
autoload :ExceptionEvent, 'radar/exception_event'
|
8
|
+
autoload :Reporter, 'radar/reporter'
|
9
|
+
autoload :Support, 'radar/support'
|
10
|
+
|
11
|
+
module DataExtensions
|
12
|
+
autoload :HostEnvironment, 'radar/data_extensions/host_environment'
|
13
|
+
end
|
14
|
+
|
15
|
+
class Reporter
|
16
|
+
autoload :FileReporter, 'radar/reporter/file_reporter'
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
module Radar
|
4
|
+
# A shortcut for {Application.find}.
|
5
|
+
def [](*args)
|
6
|
+
Application.find(*args)
|
7
|
+
end
|
8
|
+
module_function :[]
|
9
|
+
|
10
|
+
# Represents an instance of Radar for a given application. Every
|
11
|
+
# application which uses Radar must instantiate an {Application}.
|
12
|
+
class Application
|
13
|
+
@@registered = {}
|
14
|
+
@@mutex = Mutex.new
|
15
|
+
|
16
|
+
attr_reader :name
|
17
|
+
attr_reader :creation_location
|
18
|
+
|
19
|
+
# Looks up an application which was registered with the given name.
|
20
|
+
#
|
21
|
+
# @param [String] name Application name.
|
22
|
+
# @return [Application]
|
23
|
+
def self.find(name)
|
24
|
+
@@registered[name]
|
25
|
+
end
|
26
|
+
|
27
|
+
# Removes all registered applications. **This is only exposed for testing
|
28
|
+
# purposes.**
|
29
|
+
def self.clear!
|
30
|
+
@@registered.clear
|
31
|
+
end
|
32
|
+
|
33
|
+
# Creates a new application with the given name and registers
|
34
|
+
# it for lookup later. If a block is given, it will be yielded with
|
35
|
+
# the new instantiated {Application} so you can also {#config} it
|
36
|
+
# all in one go.
|
37
|
+
#
|
38
|
+
# @param [String] name Application name. This must be unique for
|
39
|
+
# any given application or an exception will be raised.
|
40
|
+
# @return [Application]
|
41
|
+
def initialize(name, register=true)
|
42
|
+
@@mutex.synchronize do
|
43
|
+
raise ApplicationAlreadyExists.new("'#{name}' already defined at '#{self.class.find(name).creation_location}'") if self.class.find(name)
|
44
|
+
|
45
|
+
@name = name
|
46
|
+
@creation_location = caller.first
|
47
|
+
yield self if block_given?
|
48
|
+
@@registered[name] = self if register
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Configures the application by returning the configuration
|
53
|
+
# object. If a block is given, the configuration object will be
|
54
|
+
# passed into it, allowing for a cleaner way of configuring your
|
55
|
+
# applications.
|
56
|
+
#
|
57
|
+
# $app = Radar::Application.new
|
58
|
+
# $app.config do |config|
|
59
|
+
# config.storage_directory = "foo"
|
60
|
+
# end
|
61
|
+
#
|
62
|
+
# @yield [Config] Configuration object, only if block is given.
|
63
|
+
# @return [Config]
|
64
|
+
def config
|
65
|
+
@_config ||= Config.new
|
66
|
+
yield @_config if block_given?
|
67
|
+
@_config
|
68
|
+
end
|
69
|
+
|
70
|
+
# Reports an exception. This will send the exception on to the
|
71
|
+
# various reporters configured for this application.
|
72
|
+
#
|
73
|
+
# @param [Exception] exception
|
74
|
+
def report(exception)
|
75
|
+
data = ExceptionEvent.new(self, exception)
|
76
|
+
|
77
|
+
# Report the exception to each of the reporters
|
78
|
+
config.reporters.each do |reporter|
|
79
|
+
reporter.report(data)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Hooks this application into the `at_exit` handler so that
|
84
|
+
# application crashing exceptions are properly reported.
|
85
|
+
def rescue_at_exit!
|
86
|
+
at_exit { report($!) if $! }
|
87
|
+
end
|
88
|
+
|
89
|
+
# Converts application to a serialization-friendly hash.
|
90
|
+
#
|
91
|
+
# @return [Hash]
|
92
|
+
def to_hash
|
93
|
+
{ :name => name }
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
data/lib/radar/config.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
module Radar
|
2
|
+
class Config
|
3
|
+
attr_reader :reporters
|
4
|
+
attr_reader :data_extensions
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@reporters = []
|
8
|
+
@data_extensions = [DataExtensions::HostEnvironment]
|
9
|
+
end
|
10
|
+
|
11
|
+
# Add a reporter to an application. If a block is given, it
|
12
|
+
# will be yielded later (since reporters are initialized lazily)
|
13
|
+
# with the instance of the reporter.
|
14
|
+
#
|
15
|
+
# @param [Class] klass A {Reporter} class.
|
16
|
+
def reporter(klass)
|
17
|
+
instance = klass.new
|
18
|
+
yield instance if block_given?
|
19
|
+
@reporters << instance
|
20
|
+
end
|
21
|
+
|
22
|
+
# Adds a data extension to an application. Data extensions allow
|
23
|
+
# extra data to be included into an {ExceptionEvent} (they appear
|
24
|
+
# in the {ExceptionEvent#to_hash} output). For more information,
|
25
|
+
# please read the Radar user guide.
|
26
|
+
#
|
27
|
+
# This method takes a class. This class is expected to be initialized
|
28
|
+
# with an {ExceptionEvent}, and must implement the `to_hash` method.
|
29
|
+
#
|
30
|
+
# @param [Class] klass
|
31
|
+
def data_extension(klass)
|
32
|
+
@data_extensions << klass
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Radar
|
2
|
+
module DataExtensions
|
3
|
+
# Data extension which adds information about the host environment:
|
4
|
+
#
|
5
|
+
# * Operating system
|
6
|
+
# * Ruby version
|
7
|
+
#
|
8
|
+
class HostEnvironment
|
9
|
+
def initialize(event)
|
10
|
+
@event = event
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_hash
|
14
|
+
{ :host_environment => {
|
15
|
+
:ruby_version => (RUBY_VERSION rescue '?'),
|
16
|
+
:ruby_pl => (RUBY_PATCHLEVEL rescue '?'),
|
17
|
+
:ruby_release_date => (RUBY_RELEASE_DATE rescue '?'),
|
18
|
+
:ruby_platform => (RUBY_PLATFORM rescue '?')
|
19
|
+
}
|
20
|
+
}
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/radar/error.rb
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'digest/sha1'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module Radar
|
5
|
+
# Represents the event of an exception being captured. This class
|
6
|
+
# contains references to the {Application} and exception which is
|
7
|
+
# raised.
|
8
|
+
class ExceptionEvent
|
9
|
+
attr_reader :application
|
10
|
+
attr_reader :exception
|
11
|
+
attr_reader :occurred_at
|
12
|
+
|
13
|
+
def initialize(application, exception)
|
14
|
+
@application = application
|
15
|
+
@exception = exception
|
16
|
+
@occurred_at = Time.now
|
17
|
+
end
|
18
|
+
|
19
|
+
# A hash of information about this exception event. This includes
|
20
|
+
# {Application#to_hash} as well as information about the exception.
|
21
|
+
# This also includes any {Config#data_extension data_extensions} if
|
22
|
+
# specified.
|
23
|
+
#
|
24
|
+
# @return [Hash]
|
25
|
+
def to_hash
|
26
|
+
result = { :application => application.to_hash,
|
27
|
+
:exception => {
|
28
|
+
:klass => exception.class.to_s,
|
29
|
+
:message => exception.message,
|
30
|
+
:backtrace => exception.backtrace,
|
31
|
+
:uniqueness_hash => uniqueness_hash
|
32
|
+
},
|
33
|
+
:occurred_at => occurred_at.to_i
|
34
|
+
}
|
35
|
+
|
36
|
+
if !application.config.data_extensions.empty?
|
37
|
+
application.config.data_extensions.each do |extension|
|
38
|
+
Support::Hash.deep_merge!(result, extension.new(self).to_hash)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
result
|
43
|
+
end
|
44
|
+
|
45
|
+
# JSONified {#to_hash} output.
|
46
|
+
#
|
47
|
+
# @return [String]
|
48
|
+
def to_json
|
49
|
+
to_hash.to_json
|
50
|
+
end
|
51
|
+
|
52
|
+
# Returns uniqueness hash to test if one event is roughly equivalent to
|
53
|
+
# another. The uniqueness hash is generated by taking the exception
|
54
|
+
# backtrace and class and generating the SHA1 hash of those concatenated.
|
55
|
+
#
|
56
|
+
# @return [String]
|
57
|
+
def uniqueness_hash
|
58
|
+
Digest::SHA1.hexdigest("#{exception.class}-#{exception.backtrace rescue 'blank'}")
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Radar
|
2
|
+
# This class defines the interface for a {Reporter}. A reporter
|
3
|
+
# is a class which takes exception information and "reports" it
|
4
|
+
# in some way. Examples of reporters (note that not all of these
|
5
|
+
# may be implemented; they are ideas):
|
6
|
+
#
|
7
|
+
# - FileReporter - Logs the exception out to a timestamped
|
8
|
+
# file in a specified directory.
|
9
|
+
# - ServerReporter - Logs the exception to a server somewhere for
|
10
|
+
# later retrieval.
|
11
|
+
# - GrowlReporter - Causes a Growl notification to occur, noting
|
12
|
+
# that an exception occurred.
|
13
|
+
#
|
14
|
+
# From the examples above it is clear to see that the reporter doesn't
|
15
|
+
# necessarilly need to store the exception information anywhere, it
|
16
|
+
# is simply required to take some action in the event that an exception
|
17
|
+
# occurred.
|
18
|
+
#
|
19
|
+
# Radar allows for multiple reporters to be used together, so a reporter
|
20
|
+
# doesn't need to do everything. They're meant to be small units of
|
21
|
+
# functionality.
|
22
|
+
#
|
23
|
+
class Reporter
|
24
|
+
# Report the environment.
|
25
|
+
def report(environment)
|
26
|
+
raise "Implement the `report` method in #{self.class}"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
module Radar
|
4
|
+
class Reporter
|
5
|
+
# Reports exceptions by dumping the JSON data out to a file on the
|
6
|
+
# local filesystem. The reporter is configurable:
|
7
|
+
#
|
8
|
+
# * {#output_directory=}
|
9
|
+
#
|
10
|
+
class FileReporter
|
11
|
+
attr_accessor :output_directory
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@output_directory = lambda { |event| "~/.radar/errors/#{event.application.name}" }
|
15
|
+
end
|
16
|
+
|
17
|
+
def report(event)
|
18
|
+
output_file = File.join(File.expand_path(output_directory(event)), "#{event.occurred_at.to_i}-#{event.uniqueness_hash}.txt")
|
19
|
+
|
20
|
+
# Attempt to make the directory if it doesn't exist
|
21
|
+
FileUtils.mkdir_p File.dirname(output_file)
|
22
|
+
|
23
|
+
# Write out the JSON to the output file
|
24
|
+
File.open(output_file, 'w') { |f| f.write(event.to_json) }
|
25
|
+
end
|
26
|
+
|
27
|
+
# Returns the currently configured output directory. If `event` is given
|
28
|
+
# as a parameter and the currently set directory is a lambda, then the
|
29
|
+
# lambda will be evaluated then returned. If no event is given, the lambda
|
30
|
+
# is returned as-is.
|
31
|
+
def output_directory(event=nil)
|
32
|
+
return @output_directory.call(event) if event && @output_directory.is_a?(Proc)
|
33
|
+
@output_directory
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Radar
|
2
|
+
class Support
|
3
|
+
class Hash
|
4
|
+
#----------------------------------------------------------------------
|
5
|
+
# Deep Merging - Taken from Ruby on Rails ActiveSupport
|
6
|
+
#----------------------------------------------------------------------
|
7
|
+
|
8
|
+
# Returns a new hash with +self+ and +other_hash+ merged recursively.
|
9
|
+
def self.deep_merge(source, other)
|
10
|
+
deep_merge!(source.dup, other)
|
11
|
+
end
|
12
|
+
|
13
|
+
# Returns a new hash with +self+ and +other_hash+ merged recursively.
|
14
|
+
# Modifies the receiver in place.
|
15
|
+
def self.deep_merge!(source, other)
|
16
|
+
other.each_pair do |k,v|
|
17
|
+
tv = source[k]
|
18
|
+
source[k] = tv.is_a?(::Hash) && v.is_a?(::Hash) ? deep_merge(tv, v) : v
|
19
|
+
end
|
20
|
+
|
21
|
+
source
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/radar.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$LOAD_PATH.unshift File.expand_path('../lib', __FILE__)
|
3
|
+
require 'radar/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "radar"
|
7
|
+
s.version = Radar::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Mitchell Hashimoto"]
|
10
|
+
s.email = ["mitchell.hashimoto@gmail.com"]
|
11
|
+
s.homepage = "http://rubygems.org/gems/radar"
|
12
|
+
s.summary = "Easily catch and report errors in your Ruby libraries and applications any way you want!"
|
13
|
+
s.description = "Radar provides a drop-in solution to catching and reporting errors in your libraries and applications."
|
14
|
+
|
15
|
+
s.required_rubygems_version = ">= 1.3.6"
|
16
|
+
s.rubyforge_project = "radar"
|
17
|
+
|
18
|
+
s.add_dependency "json", ">= 1.4.6"
|
19
|
+
|
20
|
+
s.add_development_dependency "bundler", ">= 1.0.0.rc.5"
|
21
|
+
s.add_development_dependency "shoulda"
|
22
|
+
s.add_development_dependency "mocha"
|
23
|
+
s.add_development_dependency "rake"
|
24
|
+
|
25
|
+
s.files = `git ls-files`.split("\n")
|
26
|
+
s.executables = `git ls-files`.split("\n").select{|f| f =~ /^bin/}
|
27
|
+
s.require_path = 'lib'
|
28
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class ApplicationTest < Test::Unit::TestCase
|
4
|
+
context "application class" do
|
5
|
+
setup do
|
6
|
+
@klass = Radar::Application
|
7
|
+
@instance = @klass.new("bar", false)
|
8
|
+
end
|
9
|
+
|
10
|
+
context "initializing" do
|
11
|
+
teardown do
|
12
|
+
@klass.clear!
|
13
|
+
end
|
14
|
+
|
15
|
+
should "be able to create for a name" do
|
16
|
+
instance = @klass.new("foo")
|
17
|
+
assert_equal "foo", instance.name
|
18
|
+
end
|
19
|
+
|
20
|
+
should "be able to lookup after created" do
|
21
|
+
instance = @klass.new("foo")
|
22
|
+
assert_equal instance, @klass.find("foo")
|
23
|
+
end
|
24
|
+
|
25
|
+
should "allow creation of unregistered applications" do
|
26
|
+
instance = @klass.new("foo", false)
|
27
|
+
assert_nil @klass.find("foo")
|
28
|
+
end
|
29
|
+
|
30
|
+
should "raise an exception if duplicate name is used" do
|
31
|
+
assert_raises(Radar::ApplicationAlreadyExists) {
|
32
|
+
@klass.new("foo")
|
33
|
+
@klass.new("foo")
|
34
|
+
}
|
35
|
+
end
|
36
|
+
|
37
|
+
should "yield with the instance if a block is given" do
|
38
|
+
@klass.new("foo") do |instance|
|
39
|
+
assert instance.is_a?(@klass)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context "configuration" do
|
45
|
+
setup do
|
46
|
+
@instance.config.reporters.clear
|
47
|
+
|
48
|
+
@reporter = Class.new
|
49
|
+
end
|
50
|
+
|
51
|
+
should "be able to configure an application" do
|
52
|
+
@instance.config.reporter(@reporter)
|
53
|
+
assert !@instance.config.reporters.empty?
|
54
|
+
end
|
55
|
+
|
56
|
+
should "be able to configure using a block" do
|
57
|
+
@instance.config do |config|
|
58
|
+
config.reporter(@reporter)
|
59
|
+
end
|
60
|
+
|
61
|
+
assert !@instance.config.reporters.empty?
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
context "reporting" do
|
66
|
+
setup do
|
67
|
+
# The fake reporter class
|
68
|
+
reporter = Class.new do
|
69
|
+
def report(environment)
|
70
|
+
raise "success"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Setup the application to use the fake reporter
|
75
|
+
@instance.config do |config|
|
76
|
+
config.reporter reporter
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
should "call report on each registered reporter" do
|
81
|
+
assert_raises(RuntimeError) do
|
82
|
+
begin
|
83
|
+
@instance.report(Exception.new)
|
84
|
+
rescue => e
|
85
|
+
assert_equal "success", e.message
|
86
|
+
raise
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
context "to_hash" do
|
93
|
+
setup do
|
94
|
+
@hash = @instance.to_hash
|
95
|
+
end
|
96
|
+
|
97
|
+
should "contain name" do
|
98
|
+
assert_equal @instance.name, @hash[:name]
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# Untested: Application#rescue_at_exit! since I'm not aware of an
|
103
|
+
# [easy] way of testing it without spawning out a separate process.
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class ConfigTest < Test::Unit::TestCase
|
4
|
+
context "configuration" do
|
5
|
+
setup do
|
6
|
+
@klass = Radar::Config
|
7
|
+
@instance = @klass.new
|
8
|
+
end
|
9
|
+
|
10
|
+
context "reporters" do
|
11
|
+
setup do
|
12
|
+
@reporter_klass = Class.new
|
13
|
+
end
|
14
|
+
|
15
|
+
teardown do
|
16
|
+
@instance.reporters.clear
|
17
|
+
end
|
18
|
+
|
19
|
+
should "initially have no reporters" do
|
20
|
+
assert @instance.reporters.empty?
|
21
|
+
end
|
22
|
+
|
23
|
+
should "be able to add reporters" do
|
24
|
+
@instance.reporter @reporter_klass
|
25
|
+
assert !@instance.reporters.empty?
|
26
|
+
assert @instance.reporters.first.is_a?(@reporter_klass)
|
27
|
+
end
|
28
|
+
|
29
|
+
should "yield the reporter instance if a block is given" do
|
30
|
+
@reporter_klass.any_instance.expects(:some_method).once
|
31
|
+
@instance.reporter @reporter_klass do |reporter|
|
32
|
+
reporter.some_method
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context "data extensions" do
|
38
|
+
setup do
|
39
|
+
@extension = Class.new do
|
40
|
+
def initialize(event)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
teardown do
|
46
|
+
@instance.data_extensions.clear
|
47
|
+
end
|
48
|
+
|
49
|
+
should "initially have some data extensions" do
|
50
|
+
assert_equal [Radar::DataExtensions::HostEnvironment], @instance.data_extensions
|
51
|
+
end
|
52
|
+
|
53
|
+
should "be able to add data extensions" do
|
54
|
+
@instance.data_extension @extension
|
55
|
+
assert !@instance.data_extensions.empty?
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class HostEnvironmentDataTest < Test::Unit::TestCase
|
4
|
+
context "host environment class" do
|
5
|
+
setup do
|
6
|
+
@klass = Radar::DataExtensions::HostEnvironment
|
7
|
+
@instance = @klass.new(create_exception_event)
|
8
|
+
end
|
9
|
+
|
10
|
+
should "be able to convert to a hash" do
|
11
|
+
result = @instance.to_hash
|
12
|
+
assert result.is_a?(Hash)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class ExceptionEventTest < Test::Unit::TestCase
|
4
|
+
context "ExceptionEvent class" do
|
5
|
+
setup do
|
6
|
+
@klass = Radar::ExceptionEvent
|
7
|
+
@instance = create_exception_event
|
8
|
+
end
|
9
|
+
|
10
|
+
context "to_hash" do
|
11
|
+
context "data extensions" do
|
12
|
+
setup do
|
13
|
+
@extension = Class.new do
|
14
|
+
def initialize(event); @event = event; end
|
15
|
+
def to_hash; { :exception => { :foo => :bar } }; end
|
16
|
+
end
|
17
|
+
|
18
|
+
@instance.application.config.data_extension @extension
|
19
|
+
@result = @instance.to_hash
|
20
|
+
end
|
21
|
+
|
22
|
+
should "include data extensions if defined" do
|
23
|
+
assert @result[:exception].has_key?(:foo), "instance should have key: foo"
|
24
|
+
assert_equal :bar, @result[:exception][:foo]
|
25
|
+
end
|
26
|
+
|
27
|
+
should "deep merge information" do
|
28
|
+
assert @result[:exception].has_key?(:klass)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context "to_json" do
|
34
|
+
should "just jsonify hash output" do
|
35
|
+
assert_equal @instance.to_hash.to_json, @instance.to_json
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
should "generate a uniqueness hash" do
|
40
|
+
assert @instance.uniqueness_hash, "should have generated a uniqueness hash"
|
41
|
+
end
|
42
|
+
|
43
|
+
should "have a timestamp of when the exception occurred" do
|
44
|
+
assert @instance.occurred_at
|
45
|
+
assert @instance.occurred_at.is_a?(Time)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class FileReporterTest < Test::Unit::TestCase
|
4
|
+
context "file reporter class" do
|
5
|
+
setup do
|
6
|
+
@klass = Radar::Reporter::FileReporter
|
7
|
+
@instance = @klass.new
|
8
|
+
end
|
9
|
+
|
10
|
+
should "allow output directory to be a lambda" do
|
11
|
+
@instance.output_directory = lambda { |event| event.application.name }
|
12
|
+
event = create_exception_event
|
13
|
+
assert_equal event.application.name, @instance.output_directory(event)
|
14
|
+
end
|
15
|
+
|
16
|
+
should "allow output directory to be a string" do
|
17
|
+
value = "value"
|
18
|
+
@instance.output_directory = value
|
19
|
+
assert_equal value, @instance.output_directory
|
20
|
+
end
|
21
|
+
|
22
|
+
should "just return the lambda if no event is given to read" do
|
23
|
+
@instance.output_directory = lambda {}
|
24
|
+
assert @instance.output_directory.is_a?(Proc)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class ReporterTest < Test::Unit::TestCase
|
4
|
+
context "reporter class" do
|
5
|
+
setup do
|
6
|
+
@klass = Radar::Reporter
|
7
|
+
@instance = @klass.new
|
8
|
+
end
|
9
|
+
|
10
|
+
should "raise an exception for an unimplemented reporter" do
|
11
|
+
assert_raises(RuntimeError) { @instance.report(nil) }
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class SupportTest < Test::Unit::TestCase
|
4
|
+
context "Support class" do
|
5
|
+
setup do
|
6
|
+
@klass = Radar::Support
|
7
|
+
end
|
8
|
+
|
9
|
+
context "hash" do
|
10
|
+
should "deep merge simple hashes" do
|
11
|
+
result = @klass::Hash.deep_merge({ :a => 1 }, { :b => 2 })
|
12
|
+
assert_equal 1, result[:a]
|
13
|
+
assert_equal 2, result[:b]
|
14
|
+
end
|
15
|
+
|
16
|
+
should "support deep merging" do
|
17
|
+
a = { :a => { :a => 1 } }
|
18
|
+
b = { :a => { :b => 2 } }
|
19
|
+
|
20
|
+
result = @klass::Hash.deep_merge(a, b)
|
21
|
+
assert_equal 1, result[:a][:a]
|
22
|
+
assert_equal 2, result[:a][:b]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
$:.unshift File.expand_path('../../lib', __FILE__)
|
2
|
+
|
3
|
+
require "rubygems"
|
4
|
+
require "bundler/setup"
|
5
|
+
require "test/unit"
|
6
|
+
require "shoulda"
|
7
|
+
require "mocha"
|
8
|
+
require "radar"
|
9
|
+
|
10
|
+
class Test::Unit::TestCase
|
11
|
+
# Returns a real {Radar::ExceptionEvent} object with a newly created
|
12
|
+
# {Radar::Application} and a valid (has a backtrace) exception.
|
13
|
+
def create_exception_event
|
14
|
+
application = Radar::Application.new(:foo, false)
|
15
|
+
exception = nil
|
16
|
+
|
17
|
+
begin
|
18
|
+
raise "Something bad happened!"
|
19
|
+
rescue => e
|
20
|
+
exception = e
|
21
|
+
end
|
22
|
+
|
23
|
+
Radar::ExceptionEvent.new(application, exception)
|
24
|
+
end
|
25
|
+
end
|
metadata
ADDED
@@ -0,0 +1,163 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: radar
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
version: 0.1.0
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Mitchell Hashimoto
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-08-15 00:00:00 -07:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: json
|
22
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
23
|
+
none: false
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
segments:
|
28
|
+
- 1
|
29
|
+
- 4
|
30
|
+
- 6
|
31
|
+
version: 1.4.6
|
32
|
+
type: :runtime
|
33
|
+
prerelease: false
|
34
|
+
version_requirements: *id001
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: bundler
|
37
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
38
|
+
none: false
|
39
|
+
requirements:
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
segments:
|
43
|
+
- 1
|
44
|
+
- 0
|
45
|
+
- 0
|
46
|
+
- rc
|
47
|
+
- 5
|
48
|
+
version: 1.0.0.rc.5
|
49
|
+
type: :development
|
50
|
+
prerelease: false
|
51
|
+
version_requirements: *id002
|
52
|
+
- !ruby/object:Gem::Dependency
|
53
|
+
name: shoulda
|
54
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
55
|
+
none: false
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
segments:
|
60
|
+
- 0
|
61
|
+
version: "0"
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: *id003
|
65
|
+
- !ruby/object:Gem::Dependency
|
66
|
+
name: mocha
|
67
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
68
|
+
none: false
|
69
|
+
requirements:
|
70
|
+
- - ">="
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
segments:
|
73
|
+
- 0
|
74
|
+
version: "0"
|
75
|
+
type: :development
|
76
|
+
prerelease: false
|
77
|
+
version_requirements: *id004
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: rake
|
80
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ">="
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
segments:
|
86
|
+
- 0
|
87
|
+
version: "0"
|
88
|
+
type: :development
|
89
|
+
prerelease: false
|
90
|
+
version_requirements: *id005
|
91
|
+
description: Radar provides a drop-in solution to catching and reporting errors in your libraries and applications.
|
92
|
+
email:
|
93
|
+
- mitchell.hashimoto@gmail.com
|
94
|
+
executables: []
|
95
|
+
|
96
|
+
extensions: []
|
97
|
+
|
98
|
+
extra_rdoc_files: []
|
99
|
+
|
100
|
+
files:
|
101
|
+
- .gitignore
|
102
|
+
- .yardopts
|
103
|
+
- Gemfile
|
104
|
+
- Gemfile.lock
|
105
|
+
- README.md
|
106
|
+
- Rakefile
|
107
|
+
- docs/user_guide.md
|
108
|
+
- lib/radar.rb
|
109
|
+
- lib/radar/application.rb
|
110
|
+
- lib/radar/config.rb
|
111
|
+
- lib/radar/data_extensions/host_environment.rb
|
112
|
+
- lib/radar/error.rb
|
113
|
+
- lib/radar/exception_event.rb
|
114
|
+
- lib/radar/reporter.rb
|
115
|
+
- lib/radar/reporter/file_reporter.rb
|
116
|
+
- lib/radar/support.rb
|
117
|
+
- lib/radar/version.rb
|
118
|
+
- radar.gemspec
|
119
|
+
- test/radar/application_test.rb
|
120
|
+
- test/radar/config_test.rb
|
121
|
+
- test/radar/data_extensions/host_environment_test.rb
|
122
|
+
- test/radar/exception_event_test.rb
|
123
|
+
- test/radar/reporter/file_reporter_test.rb
|
124
|
+
- test/radar/reporter_test.rb
|
125
|
+
- test/radar/support_test.rb
|
126
|
+
- test/test_helper.rb
|
127
|
+
has_rdoc: true
|
128
|
+
homepage: http://rubygems.org/gems/radar
|
129
|
+
licenses: []
|
130
|
+
|
131
|
+
post_install_message:
|
132
|
+
rdoc_options: []
|
133
|
+
|
134
|
+
require_paths:
|
135
|
+
- lib
|
136
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
137
|
+
none: false
|
138
|
+
requirements:
|
139
|
+
- - ">="
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
hash: 4451322989948183573
|
142
|
+
segments:
|
143
|
+
- 0
|
144
|
+
version: "0"
|
145
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
146
|
+
none: false
|
147
|
+
requirements:
|
148
|
+
- - ">="
|
149
|
+
- !ruby/object:Gem::Version
|
150
|
+
segments:
|
151
|
+
- 1
|
152
|
+
- 3
|
153
|
+
- 6
|
154
|
+
version: 1.3.6
|
155
|
+
requirements: []
|
156
|
+
|
157
|
+
rubyforge_project: radar
|
158
|
+
rubygems_version: 1.3.7
|
159
|
+
signing_key:
|
160
|
+
specification_version: 3
|
161
|
+
summary: Easily catch and report errors in your Ruby libraries and applications any way you want!
|
162
|
+
test_files: []
|
163
|
+
|