whoops 0.0.5 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.asciidoc +73 -56
- data/app/controllers/event_groups_controller.rb +7 -1
- data/app/controllers/events_controller.rb +1 -1
- data/app/helpers/events_helper.rb +3 -3
- data/app/models/whoops/event.rb +29 -2
- data/app/models/whoops/event_group.rb +1 -1
- data/app/models/whoops/{search_parser.rb → mongoid_search_parser.rb} +4 -2
- data/app/views/event_groups/index.html.haml +6 -0
- metadata +21 -35
data/README.asciidoc
CHANGED
@@ -3,37 +3,81 @@ Whoops
|
|
3
3
|
Daniel Higginbotham <daniel@flyingmachinestudios.com>
|
4
4
|
2011-07-09
|
5
5
|
|
6
|
-
==
|
6
|
+
== What is Whoops?
|
7
7
|
|
8
|
-
|
8
|
+
Whoops is a logging system. It consists of a Rails engine (which records logs and provides an interface to them) and a logger. Both are described below, along with how they compare to Hoptoad. Note that the comparisons aren't meant to disparage Hoptoad - it's a great product. They're only meant to help describe Whoops and help people decide whether it would be useful to them.
|
9
9
|
|
10
|
-
|
10
|
+
== Whoops Server
|
11
11
|
|
12
|
-
|
13
|
-
* *Log arbitrary details.* Events are stored in http://www.mongodb.org[mongodb], giving you the flexibility of a document database.
|
14
|
-
* *Search event details.* The search parser maps text to mongoid conditions, allowing you to do queries like +details.current_user_id#in [123, 423]+
|
15
|
-
* *Extend the app.* Since it's a rails engine, you can make changes to your base rails app without worrying about merge difficulties when you upgrade whoops.
|
12
|
+
The Whoops server is a Rails engine which records logs and provides an interface to filter, search, and view them. Below is a listing of its features and how it compares to Hoptoad:
|
16
13
|
|
17
|
-
|
14
|
+
=== Log Arbitrary Events
|
18
15
|
|
19
|
-
|
16
|
+
With Hoptoad, you only log exceptions. With Whoops, it's up to you to tell the Whoops server what you're logging, be it an exception, notification, warning, or whatever. Internally, Whoops EventGroups use the event_type field to store the event type. You can filter on this field when viewing a listing of all events.
|
20
17
|
|
21
|
-
whoops
|
18
|
+
image::https://github.com/flyingmachine/whoops/raw/master/doc/images/dash-filters.png[Filters]
|
19
|
+
|
20
|
+
=== Log Arbitrary Details
|
21
|
+
|
22
|
+
With Hoptoad, the fields which you can log are pre-defined. They also reflect an assumption that your error happened within the context of handling an HTTP request. Whoops uses mongodb as its database and this allows you to log whatever details you want. For example, you could log the following:
|
23
|
+
|
24
|
+
----
|
25
|
+
{
|
26
|
+
:start_time => 1310748754,
|
27
|
+
:end_time => 1310949834,
|
28
|
+
:users_imported => [
|
29
|
+
{ :id => 413, :succeeded => false },
|
30
|
+
{ :id => 835, :succeeded => true },
|
31
|
+
{ :id => 894, :succeeded => true },
|
32
|
+
{ :id => 124, :succeeded => true },
|
33
|
+
],
|
34
|
+
}
|
35
|
+
----
|
36
|
+
|
37
|
+
This gets stored as-is in Whoops. You can also search these details, as explained below:
|
38
|
+
|
39
|
+
=== Search Event Details
|
40
|
+
|
41
|
+
As far I know, you can't search Hoptoad. Whoops let's you search all Events within an EventGroup. Eventually, keyword search over all events will be implemented.
|
42
|
+
|
43
|
+
Below is example text you would write, and below that is essentially the ruby code that ends up getting run by the server.
|
44
|
+
|
45
|
+
----
|
46
|
+
details.current_user_id#in [3, 54, 532] <1>
|
47
|
+
details.num_failures#gt 3 <2>
|
48
|
+
details.current_user.first_name Voldemort <3>
|
49
|
+
message#in !r/(yesterday|today)/ <4>
|
50
|
+
----
|
51
|
+
|
52
|
+
<1> `Event.where( {:"details.current_user_id".in => [3, 54, 532]} )`
|
53
|
+
<2> `Event.where( {:"details.num_failure".gt => 3} )`
|
54
|
+
<3> `Event.where( {:"details.current_user.first_name" => "Voldemort"} )`
|
55
|
+
<4> `Event.where( {:message.in /(yesterday|today/)} )` Note that regular expressions must start with !r.
|
56
|
+
|
57
|
+
The general format is +key[#mongoid_method] query+ . As you can see, +query+ can be a string, number, regex, or array of these values. Hashes are allowed too. If you're not familiar with querying mongo, you can http://www.mongodb.org/display/DOCS/Querying[read more in the mongodb docs]. The http://mongoid.org/docs/querying/criteria.html#where[Mongoid] docs are useful as well.
|
58
|
+
|
59
|
+
=== Extend the App
|
60
|
+
|
61
|
+
Since Whoops is a Rails engine, you can make changes to your base rails app without worrying about merge difficulties when you upgrade Whoops.
|
62
|
+
|
63
|
+
=== No Users or Projects
|
64
|
+
|
65
|
+
In Hoptoad, errors are assigned to projects, and access to projects is given to users. In Whoops, there are no users, so it's not necessary to manage access rights or even to log in. Additionally, there is no Project model within the code or database. Instead, each EventGroup has a +service+ field which you can filter on. Services can be namespaced, so that if you have the services "godzilla.web" and "godzilla.background", you can set a filter to show events related to either service or to their common name, "godzilla".
|
22
66
|
|
23
|
-
|
24
|
-
* An end point for receiving event notifications
|
25
|
-
* A mailer for sending email notifications
|
26
|
-
* http://www.mongoid.org[Mongoid] models
|
67
|
+
Note that you can add users and/or login to the base rails app if you really want to.
|
27
68
|
|
28
|
-
|
69
|
+
=== Notifications
|
29
70
|
|
30
|
-
|
71
|
+
Since Whoops doesn't have users, email notification of events is handled by entering an email address along with a newline-separated list of services to receive notifications for. This isn't 100% implemented yet.
|
31
72
|
|
32
|
-
===
|
73
|
+
=== You Manage the Rails App
|
33
74
|
|
34
|
-
|
35
|
-
|
36
|
-
|
75
|
+
If you use Whoops you'll have to manage the Rails app yourself. You'll have to set up mongodb and all that. Heroku has a http://addons.heroku.com/mongolab[great mongodb addon] that gives you 240mb of space for free. Hoptoad doesn't require you to host or manage anything.
|
76
|
+
|
77
|
+
== Demos
|
78
|
+
|
79
|
+
* http://whoops-example.heroku.com[Example of the Whoops Rails engine]
|
80
|
+
* http://whoops-rails-notifier-example.heroku.com/[Example site which sends logs to whoops]
|
37
81
|
|
38
82
|
== Usage
|
39
83
|
|
@@ -42,9 +86,10 @@ For example, you could use the https://github.com/flyingmachine/whoops_rails_not
|
|
42
86
|
. create a new rails app
|
43
87
|
. add +gem "whoops"+ to your Gemfile
|
44
88
|
. run +bundle+
|
89
|
+
. add http://mongoid.org/docs/installation/configuration.html[+config/mongoid.yml+]
|
45
90
|
. run +bundle exec rails g whoops:assets+ - this copies assets to your public directory (whoops isn't 3.1 compatible yet)
|
46
91
|
. _optional_ add `root :to => "event_groups#index"` to your routes file to make the event group listing your home page
|
47
|
-
. add https://github.com/flyingmachine/
|
92
|
+
. add https://github.com/flyingmachine/whoops_logger[loggers] to the code you want to monitor
|
48
93
|
|
49
94
|
=== Filtering
|
50
95
|
|
@@ -55,45 +100,17 @@ When viewing the Event Group list, you can filter by service, environment, and e
|
|
55
100
|
|
56
101
|
When you set a filter, its value is stored in a session and won't be changed until you click "reset". This is so that you won't lose your filter after, for example, viewing a specific event.
|
57
102
|
|
58
|
-
=== Searching
|
59
|
-
|
60
|
-
.Search
|
61
|
-
image::https://github.com/flyingmachine/whoops/raw/master/doc/images/details-search.png[Search]
|
62
|
-
|
63
|
-
For now, you need to enter your queries into the textarea shown above. Currently, searches only apply to the events within the current event group.
|
64
|
-
|
65
|
-
.Search Syntax
|
66
|
-
----
|
67
|
-
key[#method] query
|
68
|
-
----
|
69
|
-
|
70
|
-
* *key* the Event field to search.
|
71
|
-
* *method* _(optional)_ a http://mongoid.org/docs/querying/criteria.html#where[Mongoid] where criteria method
|
72
|
-
* *query* a YAML string
|
73
|
-
|
74
|
-
.Examples
|
75
|
-
|
76
|
-
Below is the text you would write, and below that is essentially the ruby code that ends up getting run by the server
|
77
|
-
|
78
|
-
----
|
79
|
-
details.current_user_id#in [3, 54, 532] <1>
|
80
|
-
details.num_failures#gt 3 <2>
|
81
|
-
details.current_user.first_name Voldemort <3>
|
82
|
-
message#in !r/(yesterday|today)/ <4>
|
83
|
-
----
|
84
|
-
|
85
|
-
<1> `Event.where( {:"details.current_user_id".in => [3, 54, 532]} )`
|
86
|
-
<2> `Event.where( {:"details.num_failure".gt => 3} )`
|
87
|
-
<3> `Event.where( {:"details.current_user.first_name" => "Voldemort"} )`
|
88
|
-
<4> `Event.where( {:message.in /(yesterday|today/)} )` Note that regular expressions must start with !r.
|
89
|
-
|
90
|
-
This syntax is kind of goofy, but it lets you enter constant numbers, strings, arrays, hashes, and regular expressions, just like you could if you were typing in the code directly.
|
91
|
-
|
92
103
|
== TODO
|
93
104
|
|
94
|
-
* finish
|
105
|
+
* finish email notification of events
|
95
106
|
* site-wide search
|
96
107
|
* graphing
|
108
|
+
* logger documentation
|
109
|
+
|
110
|
+
== Alternatives
|
111
|
+
|
112
|
+
* http://airbrakeapp.com/pages/home[Airbrake (the app formerly known as Hoptoad)]
|
113
|
+
* https://papertrailapp.com/[papertrail]
|
97
114
|
|
98
115
|
== License
|
99
116
|
|
@@ -4,7 +4,13 @@ class EventGroupsController < ApplicationController
|
|
4
4
|
helper_method :event_group_filter
|
5
5
|
|
6
6
|
def index
|
7
|
-
|
7
|
+
finder = if params[:query].blank?
|
8
|
+
Whoops::EventGroup.where(event_group_filter.to_query_document)
|
9
|
+
else
|
10
|
+
Whoops::EventGroup.where(:_id.in => Whoops::Event.where(:keywords => /#{params[:query]}/).collect{|e| e.event_group_id}.uniq)
|
11
|
+
end
|
12
|
+
|
13
|
+
@event_groups = finder.desc(:last_recorded_at).paginate(
|
8
14
|
:page => params[:page],
|
9
15
|
:per_page => 20
|
10
16
|
)
|
@@ -6,7 +6,7 @@ class EventsController < ApplicationController
|
|
6
6
|
|
7
7
|
events_base = @event_group.events
|
8
8
|
unless params[:query].blank?
|
9
|
-
conditions = Whoops::
|
9
|
+
conditions = Whoops::MongoidSearchParser.new(params[:query]).conditions
|
10
10
|
events_base = events_base.where(conditions)
|
11
11
|
end
|
12
12
|
|
@@ -1,9 +1,9 @@
|
|
1
1
|
module EventsHelper
|
2
2
|
def format_detail(detail)
|
3
3
|
case detail
|
4
|
-
when String, Numeric
|
5
|
-
when Array
|
6
|
-
when Hash
|
4
|
+
when String, Numeric then detail
|
5
|
+
when Array then simple_format(detail.join("\n"), :class => "simple")
|
6
|
+
when Hash then detail_table(detail)
|
7
7
|
else
|
8
8
|
detail.to_s
|
9
9
|
end
|
data/app/models/whoops/event.rb
CHANGED
@@ -5,10 +5,13 @@ class Whoops::Event
|
|
5
5
|
belongs_to :event_group, :class_name => "Whoops::EventGroup"
|
6
6
|
|
7
7
|
field :details
|
8
|
+
field :keywords, :type => String
|
8
9
|
field :message, :type => String
|
9
10
|
field :event_time, :type => DateTime
|
11
|
+
|
12
|
+
validates_presence_of :message
|
10
13
|
|
11
|
-
|
14
|
+
before_save :set_keywords
|
12
15
|
|
13
16
|
def self.record(params)
|
14
17
|
params = params.with_indifferent_access
|
@@ -30,8 +33,32 @@ class Whoops::Event
|
|
30
33
|
end
|
31
34
|
|
32
35
|
def self.search(query)
|
33
|
-
conditions = Whoops::
|
36
|
+
conditions = Whoops::MongoidSearchParser.new(query).conditions
|
34
37
|
where(conditions)
|
35
38
|
end
|
36
39
|
|
40
|
+
def set_keywords
|
41
|
+
keywords_array = []
|
42
|
+
keywords_array << self.message
|
43
|
+
add_details_to_keywords(keywords_array)
|
44
|
+
self.keywords = keywords_array.join(" ")
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def add_details_to_keywords(keywords_array)
|
50
|
+
flattened = details.to_a.flatten
|
51
|
+
flattened -= details.keys if details.respond_to?(:keys)
|
52
|
+
|
53
|
+
until flattened.empty?
|
54
|
+
non_hash = flattened.select{ |i| !i.is_a?(Hash) }
|
55
|
+
keywords_array.replace(keywords_array | non_hash)
|
56
|
+
flattened -= non_hash
|
57
|
+
|
58
|
+
flattened.collect! do |i|
|
59
|
+
i.to_a.flatten - i.keys
|
60
|
+
end.flatten!
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
37
64
|
end
|
@@ -3,7 +3,7 @@ class Whoops::EventGroup
|
|
3
3
|
include Mongoid::Document
|
4
4
|
include FieldNames
|
5
5
|
|
6
|
-
[:service, :environment, :event_type, :message, :identifier].each do |string_field|
|
6
|
+
[:service, :environment, :event_type, :message, :identifier, :logging_strategy_name].each do |string_field|
|
7
7
|
field string_field, :type => String
|
8
8
|
end
|
9
9
|
field :last_recorded_at, :type => DateTime
|
@@ -1,10 +1,12 @@
|
|
1
|
-
|
1
|
+
# Parses text to create corresponding mongoid conditions.
|
2
|
+
# See README for details on syntax.
|
3
|
+
class Whoops::MongoidSearchParser
|
2
4
|
attr_accessor :query
|
3
5
|
def initialize(query)
|
4
6
|
self.query = query
|
5
7
|
end
|
6
8
|
|
7
|
-
def
|
9
|
+
def conditions
|
8
10
|
self.query.split("\n").inject({}) do |conditions, line|
|
9
11
|
line.strip!
|
10
12
|
next(conditions) if line.empty?
|
@@ -7,6 +7,12 @@
|
|
7
7
|
%li= f.select field_name, options_from_collection_for_select(options, :first, :last, event_group_filter.send(field_name))
|
8
8
|
%li
|
9
9
|
%button#reset reset
|
10
|
+
.space
|
11
|
+
%h3 Search
|
12
|
+
- form_tag whoops_event_groups_path, :method => "get" do |f|
|
13
|
+
%ul.search
|
14
|
+
%li= text_field_tag :query
|
15
|
+
%li= submit_tag "Submit"
|
10
16
|
%article.module.width_full
|
11
17
|
%header
|
12
18
|
%h3 Events
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: whoops
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 27
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
+
- 1
|
8
9
|
- 0
|
9
|
-
|
10
|
-
version: 0.0.5
|
10
|
+
version: 0.1.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Daniel Higginbotham
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2011-07-
|
18
|
+
date: 2011-07-21 00:00:00 -04:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
@@ -104,24 +104,10 @@ dependencies:
|
|
104
104
|
version: "0"
|
105
105
|
type: :runtime
|
106
106
|
version_requirements: *id006
|
107
|
-
- !ruby/object:Gem::Dependency
|
108
|
-
name: json
|
109
|
-
prerelease: false
|
110
|
-
requirement: &id007 !ruby/object:Gem::Requirement
|
111
|
-
none: false
|
112
|
-
requirements:
|
113
|
-
- - ">="
|
114
|
-
- !ruby/object:Gem::Version
|
115
|
-
hash: 3
|
116
|
-
segments:
|
117
|
-
- 0
|
118
|
-
version: "0"
|
119
|
-
type: :runtime
|
120
|
-
version_requirements: *id007
|
121
107
|
- !ruby/object:Gem::Dependency
|
122
108
|
name: mongoid
|
123
109
|
prerelease: false
|
124
|
-
requirement: &
|
110
|
+
requirement: &id007 !ruby/object:Gem::Requirement
|
125
111
|
none: false
|
126
112
|
requirements:
|
127
113
|
- - "="
|
@@ -133,11 +119,11 @@ dependencies:
|
|
133
119
|
- 1
|
134
120
|
version: 2.0.1
|
135
121
|
type: :runtime
|
136
|
-
version_requirements: *
|
122
|
+
version_requirements: *id007
|
137
123
|
- !ruby/object:Gem::Dependency
|
138
124
|
name: will_paginate
|
139
125
|
prerelease: false
|
140
|
-
requirement: &
|
126
|
+
requirement: &id008 !ruby/object:Gem::Requirement
|
141
127
|
none: false
|
142
128
|
requirements:
|
143
129
|
- - ~>
|
@@ -149,11 +135,11 @@ dependencies:
|
|
149
135
|
- pre2
|
150
136
|
version: 3.0.pre2
|
151
137
|
type: :runtime
|
152
|
-
version_requirements: *
|
138
|
+
version_requirements: *id008
|
153
139
|
- !ruby/object:Gem::Dependency
|
154
140
|
name: rspec-rails
|
155
141
|
prerelease: false
|
156
|
-
requirement: &
|
142
|
+
requirement: &id009 !ruby/object:Gem::Requirement
|
157
143
|
none: false
|
158
144
|
requirements:
|
159
145
|
- - ">="
|
@@ -163,11 +149,11 @@ dependencies:
|
|
163
149
|
- 0
|
164
150
|
version: "0"
|
165
151
|
type: :development
|
166
|
-
version_requirements: *
|
152
|
+
version_requirements: *id009
|
167
153
|
- !ruby/object:Gem::Dependency
|
168
154
|
name: mocha
|
169
155
|
prerelease: false
|
170
|
-
requirement: &
|
156
|
+
requirement: &id010 !ruby/object:Gem::Requirement
|
171
157
|
none: false
|
172
158
|
requirements:
|
173
159
|
- - ">="
|
@@ -177,11 +163,11 @@ dependencies:
|
|
177
163
|
- 0
|
178
164
|
version: "0"
|
179
165
|
type: :development
|
180
|
-
version_requirements: *
|
166
|
+
version_requirements: *id010
|
181
167
|
- !ruby/object:Gem::Dependency
|
182
168
|
name: fabrication
|
183
169
|
prerelease: false
|
184
|
-
requirement: &
|
170
|
+
requirement: &id011 !ruby/object:Gem::Requirement
|
185
171
|
none: false
|
186
172
|
requirements:
|
187
173
|
- - ">="
|
@@ -191,11 +177,11 @@ dependencies:
|
|
191
177
|
- 0
|
192
178
|
version: "0"
|
193
179
|
type: :development
|
194
|
-
version_requirements: *
|
180
|
+
version_requirements: *id011
|
195
181
|
- !ruby/object:Gem::Dependency
|
196
182
|
name: ruby-debug
|
197
183
|
prerelease: false
|
198
|
-
requirement: &
|
184
|
+
requirement: &id012 !ruby/object:Gem::Requirement
|
199
185
|
none: false
|
200
186
|
requirements:
|
201
187
|
- - ">="
|
@@ -205,11 +191,11 @@ dependencies:
|
|
205
191
|
- 0
|
206
192
|
version: "0"
|
207
193
|
type: :development
|
208
|
-
version_requirements: *
|
194
|
+
version_requirements: *id012
|
209
195
|
- !ruby/object:Gem::Dependency
|
210
196
|
name: faker
|
211
197
|
prerelease: false
|
212
|
-
requirement: &
|
198
|
+
requirement: &id013 !ruby/object:Gem::Requirement
|
213
199
|
none: false
|
214
200
|
requirements:
|
215
201
|
- - ">="
|
@@ -219,11 +205,11 @@ dependencies:
|
|
219
205
|
- 0
|
220
206
|
version: "0"
|
221
207
|
type: :development
|
222
|
-
version_requirements: *
|
208
|
+
version_requirements: *id013
|
223
209
|
- !ruby/object:Gem::Dependency
|
224
210
|
name: capybara
|
225
211
|
prerelease: false
|
226
|
-
requirement: &
|
212
|
+
requirement: &id014 !ruby/object:Gem::Requirement
|
227
213
|
none: false
|
228
214
|
requirements:
|
229
215
|
- - ">="
|
@@ -235,7 +221,7 @@ dependencies:
|
|
235
221
|
- 0
|
236
222
|
version: 0.4.0
|
237
223
|
type: :development
|
238
|
-
version_requirements: *
|
224
|
+
version_requirements: *id014
|
239
225
|
description: A logger
|
240
226
|
email:
|
241
227
|
executables: []
|
@@ -255,8 +241,8 @@ files:
|
|
255
241
|
- app/models/whoops/event.rb
|
256
242
|
- app/models/whoops/event_group.rb
|
257
243
|
- app/models/whoops/filter.rb
|
244
|
+
- app/models/whoops/mongoid_search_parser.rb
|
258
245
|
- app/models/whoops/notification_rule.rb
|
259
|
-
- app/models/whoops/search_parser.rb
|
260
246
|
- app/views/event_groups/_list.html.haml
|
261
247
|
- app/views/event_groups/index.html.haml
|
262
248
|
- app/views/events/_detail.html.haml
|