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 CHANGED
@@ -3,37 +3,81 @@ Whoops
3
3
  Daniel Higginbotham <daniel@flyingmachinestudios.com>
4
4
  2011-07-09
5
5
 
6
- == Overview
6
+ == What is Whoops?
7
7
 
8
- === Purpose
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
- whoops was originally conceived as an alternative to Hoptoad. Its allows you to:
10
+ == Whoops Server
11
11
 
12
- * *Log arbitrary events.* Whoops events use an event_type field which can be any string - exception, notification, warning, etc.
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
- If you find yourself bumping up against limitations in Hoptoad, whoops might be right for you.
14
+ === Log Arbitrary Events
18
15
 
19
- === Design
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 consists of two main components: the rails 3 engine and the notifier. Hosting is up to you. The engine provides the following:
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
- * Controllers and views for viewing, filtering, and searching events.
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
- The notifier is documented separately. Briefly, it allows you to create strategies for building an event notification. It also allows you to modify existing strategies.
69
+ === Notifications
29
70
 
30
- For example, you could use the https://github.com/flyingmachine/whoops_rails_notifier[rails 3 notifier] , but modify it to include the ID of the currently logged in user. If your app ran background processes, you could create a notification strategy specific to your processes. Both strategies can coexist in the same app.
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
- === Terms / Models
73
+ === You Manage the Rails App
33
74
 
34
- * _Event_ A specific occurrence of an event. The Event model stores details specific to an event.
35
- * _Event Group_ All Events are associated with an Event Group. Event Groups store details common to all events in the group.
36
- * _Notification_ The whoops notifier sends Notifications to whoops. A notification contains both Event and Event Group information; whoops sorts it out when it receives a Notification.
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/whoops_notifier[notifiers] to the code you want to monitor
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 notifications
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
- @event_groups = Whoops::EventGroup.where(event_group_filter.to_query_document).desc(:last_recorded_at).paginate(
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::SearchParser.new(params[:query]).mongoid_conditions
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: detail
5
- when Array: simple_format(detail.join("\n"), :class => "simple")
6
- when Hash: detail_table(detail)
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
@@ -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
- validates_presence_of :message
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::SearchParser.new(query).mongoid_conditions
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
- class Whoops::SearchParser
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 mongoid_conditions
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: 21
4
+ hash: 27
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
+ - 1
8
9
  - 0
9
- - 5
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-09 00:00:00 -04:00
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: &id008 !ruby/object:Gem::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: *id008
122
+ version_requirements: *id007
137
123
  - !ruby/object:Gem::Dependency
138
124
  name: will_paginate
139
125
  prerelease: false
140
- requirement: &id009 !ruby/object:Gem::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: *id009
138
+ version_requirements: *id008
153
139
  - !ruby/object:Gem::Dependency
154
140
  name: rspec-rails
155
141
  prerelease: false
156
- requirement: &id010 !ruby/object:Gem::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: *id010
152
+ version_requirements: *id009
167
153
  - !ruby/object:Gem::Dependency
168
154
  name: mocha
169
155
  prerelease: false
170
- requirement: &id011 !ruby/object:Gem::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: *id011
166
+ version_requirements: *id010
181
167
  - !ruby/object:Gem::Dependency
182
168
  name: fabrication
183
169
  prerelease: false
184
- requirement: &id012 !ruby/object:Gem::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: *id012
180
+ version_requirements: *id011
195
181
  - !ruby/object:Gem::Dependency
196
182
  name: ruby-debug
197
183
  prerelease: false
198
- requirement: &id013 !ruby/object:Gem::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: *id013
194
+ version_requirements: *id012
209
195
  - !ruby/object:Gem::Dependency
210
196
  name: faker
211
197
  prerelease: false
212
- requirement: &id014 !ruby/object:Gem::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: *id014
208
+ version_requirements: *id013
223
209
  - !ruby/object:Gem::Dependency
224
210
  name: capybara
225
211
  prerelease: false
226
- requirement: &id015 !ruby/object:Gem::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: *id015
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