├── uninstall.rb
├── install.rb
├── views
└── logged_exceptions
│ ├── destroy.rjs
│ ├── destroy_all.rjs
│ ├── show.rjs
│ ├── query.rjs
│ ├── _feed.rhtml
│ ├── _show.rhtml
│ ├── query.rxml
│ ├── _exceptions.rhtml
│ └── index.rhtml
├── tasks
└── exception_logger_tasks.rake
├── test
└── exception_logger_test.rb
├── init.rb
├── generators
└── exception_migration
│ ├── exception_migration_generator.rb
│ ├── USAGE
│ └── templates
│ └── migration.rb
├── Rakefile
├── lib
├── logged_exceptions_helper.rb
├── logged_exception.rb
├── exception_loggable.rb
└── logged_exceptions_controller.rb
├── assets
├── exception_logger.js
└── style.css
└── README
/uninstall.rb:
--------------------------------------------------------------------------------
1 | # Uninstall hook code here
2 |
--------------------------------------------------------------------------------
/install.rb:
--------------------------------------------------------------------------------
1 | puts IO.read(File.join(File.dirname(__FILE__), 'README'))
2 |
--------------------------------------------------------------------------------
/views/logged_exceptions/destroy.rjs:
--------------------------------------------------------------------------------
1 | page["exception-#{params[:id]}"].addClassName('deleted')
2 | page[:showpage].hide
--------------------------------------------------------------------------------
/views/logged_exceptions/destroy_all.rjs:
--------------------------------------------------------------------------------
1 | page[:exceptions].replace :partial => "exceptions"
2 | page[:showpage].hide
--------------------------------------------------------------------------------
/tasks/exception_logger_tasks.rake:
--------------------------------------------------------------------------------
1 | # desc "Explaining what the task does"
2 | # task :exception_logger do
3 | # # Task goes here
4 | # end
--------------------------------------------------------------------------------
/views/logged_exceptions/show.rjs:
--------------------------------------------------------------------------------
1 | page[:showpage].replace_html :partial => "show"
2 | page[:showpage].visual_effect :blind_down, :duration => 0.25
--------------------------------------------------------------------------------
/views/logged_exceptions/query.rjs:
--------------------------------------------------------------------------------
1 | page[:exceptions].replace :partial => "exceptions"
2 | page[:showpage].hide
3 | page[:feed].replace :partial => 'feed'
--------------------------------------------------------------------------------
/test/exception_logger_test.rb:
--------------------------------------------------------------------------------
1 | require 'test/unit'
2 |
3 | class ExceptionLoggerTest < Test::Unit::TestCase
4 | # Replace this with your real tests.
5 | def test_this_plugin
6 | flunk
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/init.rb:
--------------------------------------------------------------------------------
1 | $PAGINATION_TYPE = 'none'
2 | #require 'will_paginate'
3 | #$PAGINATION_TYPE = 'will_paginate'
4 | #WillPaginate.enable
5 | #require 'paginating_find'
6 | #$PAGINATION_TYPE = 'paginating_find'
7 | LoggedExceptionsController.view_paths = [File.join(directory, 'views')]
8 |
--------------------------------------------------------------------------------
/views/logged_exceptions/_feed.rhtml:
--------------------------------------------------------------------------------
1 | <%
2 | feed_params = [:id, :query, :date_ranges_filter, :exception_names_filter, :controller_actions_filter].inject({:format => 'rss'}) do |p, key|
3 | params[key].blank? ? p : p.update(key => params[key])
4 | end
5 | -%>
6 |
7 | - <%= link_to 'Rss Feed', feed_params %>
8 |
--------------------------------------------------------------------------------
/generators/exception_migration/exception_migration_generator.rb:
--------------------------------------------------------------------------------
1 | class ExceptionMigrationGenerator < Rails::Generator::NamedBase
2 | attr_reader :exception_table_name
3 | def initialize(runtime_args, runtime_options = {})
4 | @exception_table_name = (runtime_args.length < 2 ? 'logged_exceptions' : runtime_args[1]).tableize
5 | runtime_args << 'add_exception_table' if runtime_args.empty?
6 | super
7 | end
8 |
9 | def manifest
10 | record do |m|
11 | m.migration_template 'migration.rb', 'db/migrate'
12 | end
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/generators/exception_migration/USAGE:
--------------------------------------------------------------------------------
1 | Description:
2 | The exception migration generator creates a migration for the logged exceptions model.
3 |
4 | The generator takes a migration name as its argument. The migration name may be
5 | given in CamelCase or under_score. 'add_exception_table' is the default.
6 |
7 | The generator creates a migration class in db/migrate prefixed by its number
8 | in the queue.
9 |
10 | Example:
11 | ./script/generate exception_migration add_exception_table
12 |
13 | With 4 existing migrations, this will create an AddExceptionTable migration in the
14 | file db/migrate/5_add_exception_table.rb
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require 'rake'
2 | require 'rake/testtask'
3 | require 'rake/rdoctask'
4 |
5 | desc 'Default: run unit tests.'
6 | task :default => :test
7 |
8 | desc 'Test the exception_logger plugin.'
9 | Rake::TestTask.new(:test) do |t|
10 | t.libs << 'lib'
11 | t.pattern = 'test/**/*_test.rb'
12 | t.verbose = true
13 | end
14 |
15 | desc 'Generate documentation for the exception_logger plugin.'
16 | Rake::RDocTask.new(:rdoc) do |rdoc|
17 | rdoc.rdoc_dir = 'rdoc'
18 | rdoc.title = 'ExceptionLogger'
19 | rdoc.options << '--line-numbers' << '--inline-source'
20 | rdoc.rdoc_files.include('README')
21 | rdoc.rdoc_files.include('lib/**/*.rb')
22 | end
23 |
--------------------------------------------------------------------------------
/generators/exception_migration/templates/migration.rb:
--------------------------------------------------------------------------------
1 | class <%= class_name %> < ActiveRecord::Migration
2 | def self.up
3 | create_table "<%= exception_table_name %>", :force => true do |t|
4 | t.column :exception_class, :string
5 | t.column :controller_name, :string
6 | t.column :action_name, :string
7 | t.column :message, :text
8 | t.column :backtrace, :text
9 | t.column :environment, :text
10 | t.column :request, :text
11 | t.column :created_at, :datetime
12 | end
13 | end
14 |
15 | def self.down
16 | drop_table "<%= exception_table_name %>"
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/views/logged_exceptions/_show.rhtml:
--------------------------------------------------------------------------------
1 |
2 | <%= link_to_remote 'Delete', {:url => { :action => 'destroy', :id => @exc }} , :class => "util" %>
3 | |
4 | <%= link_to_function "Close", "$('showpage').style.display='none';", :class => "util" %>
5 |
6 |
7 |
8 |
9 | <%= @exc.created_at.strftime("%A, %b %d, %Y at %l:%M %p") %>
10 |
11 |
12 | <%= @exc.exception_class %> in <%= @exc.controller_name.camelcase %>/<%= @exc.action_name %>
13 |
14 |
15 | Request
16 |
17 | <%= textilize(@exc.request) %>
18 |
19 | Backtrace
20 |
21 | <%= simple_format @exc.message %>
22 |
23 |
24 | <%=h(@exc.backtrace).gsub(/\n/,"
") %>
25 |
26 |
27 | Environment
28 |
29 | <%= textilize(@exc.environment) %>
--------------------------------------------------------------------------------
/views/logged_exceptions/query.rxml:
--------------------------------------------------------------------------------
1 | xml.instruct! :xml, :version=>"1.0", :encoding=>"UTF-8"
2 |
3 | xml.rss "version" => "2.0" do
4 | xml.channel do
5 | xml.title "Recent Exceptions#{%( (filtered)) if filtered?} | #{LoggedExceptionsController.application_name}"
6 | xml.link url_for(:only_path => false, :skip_relative_url_root => false)
7 | xml.language "en-us"
8 | xml.ttl "60"
9 |
10 | @exceptions.each do |exc|
11 | xml.item do
12 | xml.title "#{exc.exception_class} in #{exc.controller_action} @ #{exc.created_at.rfc822}"
13 | xml.description exc.message
14 | xml.pubDate exc.created_at.rfc822
15 | xml.guid [request.host_with_port, 'exceptions', exc.id.to_s].join(":"), "isPermaLink" => "false"
16 | xml.link url_for(:action => 'index', :id => exc, :only_path => false, :skip_relative_url_root => false)
17 | end
18 | end
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/views/logged_exceptions/_exceptions.rhtml:
--------------------------------------------------------------------------------
1 |
2 |
3 | <%= link_to_remote 'Delete Visible', :url => { :action => 'destroy_all' }, :with => "ExceptionLogger.deleteAll()" %>
4 | <%= pagination_remote_links @exceptions %>
5 |
6 |
7 |
Exceptions <%= "(filtered)" if filtered? %>
8 |
9 |
10 |
11 |
12 |
13 | | Exception |
14 | Date |
15 |
16 |
17 |
18 |
19 | <% exceptions.each do |exc| -%>
20 | exception">
21 | |
22 |
23 |
24 | <%= link_to_remote "#{exc.exception_class} in #{exc.controller_action}", :url => { :action => 'show', :id => exc } %>
25 | <%=h exc.message %>
26 |
27 | |
28 |
29 | <%
30 | if Date.today == exc.created_at.to_date
31 | if exc.created_at > Time.now - 4.hours
32 | %>
33 | <%= time_ago_in_words(exc.created_at).gsub(/about /,"~ ") %> ago
34 | <% else %>
35 | Today, <%= exc.created_at.strftime("%l:%M %p") %>
36 | <% end %>
37 | <% else %>
38 | <%= exc.created_at.strftime("%b %d, %Y") %>
39 | <% end %>
40 | |
41 | <%= link_to_remote 'Delete', {:url => { :action => 'destroy', :id => exc }} , :class => "util" %> |
42 |
43 |
44 | <% end -%>
45 |
46 |
47 |
48 |
49 |
50 | <%= pagination_remote_links @exceptions %>
51 |
52 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/lib/logged_exceptions_helper.rb:
--------------------------------------------------------------------------------
1 | module LoggedExceptionsHelper
2 | def filtered?
3 | [:query, :date_ranges_filter, :exception_names_filter, :controller_actions_filter].any? { |p| params[p] }
4 | end
5 |
6 | def pagination_remote_links(collection)
7 | ret = ''
8 | if $PAGINATION_TYPE == 'will_paginate'
9 | pagination = will_paginate (collection,
10 | :renderer => 'LoggedExceptionsHelper::PaginationRenderer',
11 | :prev_label => '',
12 | :next_label => '',
13 | :container => false)
14 | if collection.total_pages > 1 then
15 | ret = ":: Pages : #{pagination}"
16 | end
17 | elsif $PAGINATION_TYPE == 'paginating_find' then
18 | pagination = paginating_links collection
19 | ret = ":: Pages : #{pagination}"
20 | else
21 | next_page = params[:page].to_i + 1
22 | prev_page = 0
23 | prev_link = ''
24 | if params[:page].to_i > 0 then
25 | prev_page = params[:page].to_i - 1
26 | prev_link = " Previous page"
27 | end
28 | next_link = "Next page"
29 | ret = "Pagination not available#{prev_link} - #{next_link}"
30 | end
31 | end
32 |
33 | if $PAGINATION_TYPE == 'will_paginate'
34 | class PaginationRenderer < WillPaginate::LinkRenderer
35 | def page_link_or_span(page, span_class = 'current', text = nil)
36 | text ||= page.to_s
37 | if page and page != current_page
38 | @template.link_to_function text, "ExceptionLogger.setPage(#{page})"
39 | else
40 | @template.content_tag :span, text, :class => span_class
41 | end
42 | end
43 | end
44 | end
45 | end
46 |
--------------------------------------------------------------------------------
/assets/exception_logger.js:
--------------------------------------------------------------------------------
1 | ExceptionLogger = {
2 | filters: ['exception_names', 'controller_actions', 'date_ranges'],
3 | setPage: function(num) {
4 | $('page').value = num;
5 | $('query-form').onsubmit();
6 | },
7 |
8 | setFilter: function(context, name) {
9 | var filterName = context + '_filter'
10 | $(filterName).value = ($F(filterName) == name) ? '' : name;
11 | this.deselect(context, filterName);
12 | $('page').value = '1';
13 | $('query-form').onsubmit();
14 | },
15 |
16 | deselect: function(context, filterName) {
17 | $$('#' + context + ' a').each(function(a) {
18 | var value = $(filterName) ? $F(filterName) : null;
19 | a.className = (value && (a.getAttribute('title') == value || a.innerHTML == value)) ? 'selected' : '';
20 | });
21 | },
22 |
23 | deleteAll: function() {
24 | return Form.serialize('query-form') + '&' + $$('tr.exception').collect(function(tr) { return tr.getAttribute('id').gsub(/^\w+-/, ''); }).toQueryString('ids');
25 | }
26 | }
27 |
28 | Event.observe(window, 'load', function() {
29 | ExceptionLogger.filters.each(function(context) {
30 | $(context + '_filter').value = '';
31 | });
32 | });
33 |
34 | Object.extend(Array.prototype, {
35 | toQueryString: function(name) {
36 | return this.collect(function(item) { return name + "[]=" + encodeURIComponent(item) }).join('&');
37 | }
38 | });
39 |
40 | Ajax.Responders.register({
41 | onCreate: function() {
42 | if($('activity') && Ajax.activeRequestCount > 0) $('activity').visualEffect('appear', {duration:0.25});
43 | },
44 |
45 | onComplete: function() {
46 | if($('activity') && Ajax.activeRequestCount == 0) $('activity').visualEffect('fade', {duration:0.25});
47 | }
48 | });
--------------------------------------------------------------------------------
/lib/logged_exception.rb:
--------------------------------------------------------------------------------
1 | class LoggedException < ActiveRecord::Base
2 | class << self
3 | def create_from_exception(controller, exception, data)
4 | message = exception.message.inspect
5 | message << "\n* Extra Data\n\n#{data}" unless data.blank?
6 | create! \
7 | :exception_class => exception.class.name,
8 | :controller_name => controller.controller_name,
9 | :action_name => controller.action_name,
10 | :message => message,
11 | :backtrace => exception.backtrace,
12 | :request => controller.request
13 | end
14 |
15 | def find_exception_class_names
16 | connection.select_values "SELECT DISTINCT exception_class FROM #{table_name} ORDER BY exception_class"
17 | end
18 |
19 | def find_exception_controllers_and_actions
20 | find(:all, :select => "DISTINCT controller_name, action_name", :order => "controller_name, action_name").collect(&:controller_action)
21 | end
22 |
23 | def host_name
24 | `hostname -s`.chomp
25 | end
26 | end
27 |
28 | def backtrace=(backtrace)
29 | backtrace = sanitize_backtrace(backtrace) * "\n" unless backtrace.is_a?(String)
30 | write_attribute :backtrace, backtrace
31 | end
32 |
33 | def request=(request)
34 | if request.is_a?(String)
35 | write_attribute :request, request
36 | else
37 | max = request.env.keys.max { |a,b| a.length <=> b.length }
38 | env = request.env.keys.sort.inject [] do |env, key|
39 | env << '* ' + ("%-*s: %s" % [max.length, key, request.env[key].to_s.strip])
40 | end
41 | write_attribute(:environment, (env << "* Process: #{$$}" << "* Server : #{self.class.host_name}") * "\n")
42 |
43 | write_attribute(:request, [
44 | "* URL:#{" #{request.method.to_s.upcase}" unless request.get?} #{request.protocol}#{request.env["HTTP_HOST"]}#{request.request_uri}",
45 | "* Format: #{request.format.to_s}",
46 | "* Parameters: #{request.parameters.inspect}",
47 | "* Rails Root: #{rails_root}"
48 | ] * "\n")
49 | end
50 | end
51 |
52 | def controller_action
53 | @controller_action ||= "#{controller_name.camelcase}/#{action_name}"
54 | end
55 |
56 | private
57 | @@rails_root = Pathname.new(RAILS_ROOT).cleanpath.to_s
58 | @@backtrace_regex = /^#{Regexp.escape(@@rails_root)}/
59 |
60 | def sanitize_backtrace(trace)
61 | trace.collect { |line| Pathname.new(line.gsub(@@backtrace_regex, "[RAILS_ROOT]")).cleanpath.to_s }
62 | end
63 |
64 | def rails_root
65 | @@rails_root
66 | end
67 | end
--------------------------------------------------------------------------------
/lib/exception_loggable.rb:
--------------------------------------------------------------------------------
1 | require 'ipaddr'
2 |
3 | # Copyright (c) 2005 Jamis Buck
4 | #
5 | # Permission is hereby granted, free of charge, to any person obtaining
6 | # a copy of this software and associated documentation files (the
7 | # "Software"), to deal in the Software without restriction, including
8 | # without limitation the rights to use, copy, modify, merge, publish,
9 | # distribute, sublicense, and/or sell copies of the Software, and to
10 | # permit persons to whom the Software is furnished to do so, subject to
11 | # the following conditions:
12 | #
13 | # The above copyright notice and this permission notice shall be
14 | # included in all copies or substantial portions of the Software.
15 | #
16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 | # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 | # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 | module ExceptionLoggable
24 | def self.included(target)
25 | target.extend(ClassMethods)
26 | end
27 |
28 | module ClassMethods
29 | def consider_local(*args)
30 | local_addresses.concat(args.flatten.map { |a| IPAddr.new(a) })
31 | end
32 |
33 | def local_addresses
34 | addresses = read_inheritable_attribute(:local_addresses)
35 | unless addresses
36 | addresses = [IPAddr.new("127.0.0.1")]
37 | write_inheritable_attribute(:local_addresses, addresses)
38 | end
39 | addresses
40 | end
41 |
42 | def exception_data(deliverer = self, &block)
43 | deliverer = block if block
44 | if deliverer == self
45 | read_inheritable_attribute(:exception_data)
46 | else
47 | write_inheritable_attribute(:exception_data, deliverer)
48 | end
49 | end
50 | end
51 |
52 | def local_request?
53 | remote = IPAddr.new(request.remote_ip)
54 | !self.class.local_addresses.detect { |addr| addr.include?(remote) }.nil?
55 | end
56 |
57 | def rescue_action_in_public(exception)
58 | status = response_code_for_rescue(exception)
59 | render_optional_error_file status
60 | log_exception(exception) if status != :not_found
61 | end
62 |
63 | def log_exception(exception)
64 | deliverer = self.class.exception_data
65 | data = case deliverer
66 | when nil then {}
67 | when Symbol then send(deliverer)
68 | when Proc then deliverer.call(self)
69 | end
70 |
71 | LoggedException.create_from_exception(self, exception, data)
72 | end
73 | end
74 |
--------------------------------------------------------------------------------
/views/logged_exceptions/index.rhtml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Logged Exceptions
5 |
6 | <%= javascript_include_tag 'prototype','effects' %>
7 |
10 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
Filters
21 |
22 |
23 | - <%= link_to 'Latest Exceptions', :action => 'index', :id => nil %>
24 |
25 |
26 |
Exception
27 |
28 |
29 | <% @exception_names.each do |name| -%>
30 | - <%= link_to_function name, "ExceptionLogger.setFilter('exception_names','#{escape_javascript name}')" %>
31 | <% end -%>
32 |
33 |
34 |
Controller / Action
35 |
36 |
37 | <% @controller_actions.each do |action| -%>
38 | - <%= link_to_function action, "ExceptionLogger.setFilter('controller_actions','#{escape_javascript action}')" %>
39 | <% end -%>
40 |
41 |
42 |
Dates
43 |
44 |
50 |
51 |
52 |
53 | <%= form_remote_tag :url => { :action => 'query' }, :html => { :id => 'query-form' } %>
54 |
55 |
56 | <%= text_field_tag :query, "", :size => 17 %>
57 | <%= submit_tag :Find %>
58 |
59 | <%= hidden_field_tag :exception_names_filter %>
60 | <%= hidden_field_tag :date_ranges_filter %>
61 | <%= hidden_field_tag :controller_actions_filter %>
62 | <%= hidden_field_tag :page, (params[:page] || 1) %>
63 |
64 |
65 |
66 |
67 | <%= render :partial => 'feed' %>
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 | <%= render :partial => "exceptions" %>
77 |
78 |
79 |
80 |
Busy...
81 |
82 |
83 |
84 |
85 |
86 |
--------------------------------------------------------------------------------
/README:
--------------------------------------------------------------------------------
1 | ExceptionLogger
2 | ===============
3 |
4 | The Exception Logger (forgive the horrible name) logs your Rails exceptions in the database and provides a funky web interface to manage them.
5 |
6 | First you need to generate the migration:
7 |
8 | ./script/generate exception_migration
9 |
10 | Next, you'll need to include the ExceptionLoggable module into ApplicationController. Once that's done you might want to modify key methods to customize the logging:
11 |
12 | render_404(exception) - Shows the 404 template.
13 |
14 | render_500(exception) - Shows the 500 template.
15 |
16 | log_exception(exception) - Logs the actual exception in the database.
17 |
18 | rescue_action_in_public(exception) - Does not log these exceptions: ActiveRecord::RecordNotFound, ActionController::UnknownController, ActionController::UnknownAction
19 |
20 | Now add a new route to your routes.rb:
21 |
22 | map.connect "logged_exceptions/:action/:id", :controller => "logged_exceptions"
23 |
24 | After that, visit /logged_exceptions in your application to manage the exceptions.
25 |
26 | Once you have done that, open up the vendor/plugins/init.rb file and choose your pagination,
27 | supported options are will_paginate, paginating_find, and simple mysql based pagination (Uses LIMIT)
28 | The current default is none. To use the other options you need to uncomment the $PAGINATION_TYPE line
29 | and the require for that pagination, you should comment out what you won't use etc...
30 |
31 | It's understandable that you may want to require authentication. Add this to your config/environments/production.rb:
32 |
33 | # config/environments/production.rb
34 | config.after_initialize do
35 | require 'application' unless Object.const_defined?(:ApplicationController)
36 | LoggedExceptionsController.class_eval do
37 | # set the same session key as the app
38 | session :session_key => '_beast_session_id'
39 |
40 | # include any custom auth modules you need
41 | include AuthenticationSystem
42 |
43 | before_filter :login_required
44 |
45 | # optional, sets the application name for the rss feeds
46 | self.application_name = "Beast"
47 |
48 | protected
49 | # only allow admins
50 | # this obviously depends on how your auth system works
51 | def authorized?
52 | current_user.is_a?(Admin)
53 | end
54 |
55 | # assume app's login required doesn't use http basic
56 | def login_required_with_basic
57 | respond_to do |accepts|
58 | # alias_method_chain will alias the app's login_required to login_required_without_basic
59 | accepts.html { login_required_without_basic }
60 |
61 | # access_denied_with_basic_auth is defined in LoggedExceptionsController
62 | # get_auth_data returns back the user/password pair
63 | accepts.rss do
64 | access_denied_with_basic_auth unless self.current_user = User.authenticate(*get_auth_data)
65 | end
66 | end
67 | end
68 |
69 | alias_method_chain :login_required, :basic
70 | end
71 | end
72 |
73 | The exact code of course depends on the specific needs of your application.
74 |
75 | CREDITS
76 |
77 | Jamis Buck - original exception_notification plugin
78 | Rick Olson - model/controller code
79 | Josh Goebel - design
80 | Jason Knight - Pagination support, built on/inspired by Ryanb's willpaginate support.
81 |
--------------------------------------------------------------------------------
/lib/logged_exceptions_controller.rb:
--------------------------------------------------------------------------------
1 | class LoggedExceptionsController < ActionController::Base
2 | cattr_accessor :application_name
3 | layout nil
4 |
5 | def index
6 | @exception_names = LoggedException.find_exception_class_names
7 | @controller_actions = LoggedException.find_exception_controllers_and_actions
8 | query
9 | end
10 |
11 | def query
12 | conditions = []
13 | parameters = []
14 | unless params[:id].blank?
15 | conditions << 'id = ?'
16 | parameters << params[:id]
17 | end
18 | unless params[:query].blank?
19 | conditions << 'message LIKE ?'
20 | parameters << "%#{params[:query]}%"
21 | end
22 | unless params[:date_ranges_filter].blank?
23 | conditions << 'created_at >= ?'
24 | parameters << params[:date_ranges_filter].to_f.days.ago.utc
25 | end
26 | unless params[:exception_names_filter].blank?
27 | conditions << 'exception_class = ?'
28 | parameters << params[:exception_names_filter]
29 | end
30 | unless params[:controller_actions_filter].blank?
31 | conditions << 'controller_name = ? AND action_name = ?'
32 | parameters += params[:controller_actions_filter].split('/').collect(&:downcase)
33 | end
34 | if $PAGINATION_TYPE == 'will_paginate' then
35 | @exceptions = LoggedException.paginate :order => 'created_at desc', :per_page => 30,
36 | :conditions => conditions.empty? ? nil : parameters.unshift(conditions * ' and '), :page => params[:page]
37 | elsif $PAGINATION_TYPE == 'paginating_find' then
38 | params[:limit] ||= 25
39 | params[:page] ||= 1
40 | @exceptions = LoggedException.find (:all,:order => 'created_at desc',:page => {:size => params[:limit], :current => params[:page]},
41 | :conditions => conditions.empty? ? nil : parameters.unshift(conditions * ' and '))
42 | else
43 | #we have no pagination so do basic sql pagination
44 | params[:limit] ||= 25
45 | params[:page] ||= 0
46 | page = params[:page]
47 | if params[:page].to_i >= 1 then
48 | page = params[:page].to_i * params[:limit].to_i
49 | end
50 | @exceptions = LoggedException.find(:all, :limit => "#{page},#{params[:limit]}", :conditions => conditions.empty? ? nil : parameters.unshift(conditions * ' and '))
51 | end
52 |
53 | respond_to do |format|
54 | format.html { redirect_to :action => 'index' unless action_name == 'index' }
55 | format.js { render :action => 'query.rjs' }
56 | format.rss { render :action => 'query.rxml' }
57 | end
58 | end
59 |
60 | def show
61 | @exc = LoggedException.find params[:id]
62 | end
63 |
64 | def destroy
65 | LoggedException.destroy params[:id]
66 | end
67 |
68 | def destroy_all
69 | LoggedException.delete_all ['id in (?)', params[:ids]] unless params[:ids].blank?
70 | query
71 | end
72 |
73 | private
74 | def access_denied_with_basic_auth
75 | headers["Status"] = "Unauthorized"
76 | headers["WWW-Authenticate"] = %(Basic realm="Web Password")
77 | render :text => "Could't authenticate you", :status => '401 Unauthorized'
78 | end
79 |
80 | @@http_auth_headers = %w(X-HTTP_AUTHORIZATION HTTP_AUTHORIZATION Authorization)
81 | # gets BASIC auth info
82 | def get_auth_data
83 | auth_key = @@http_auth_headers.detect { |h| request.env.has_key?(h) }
84 | auth_data = request.env[auth_key].to_s.split unless auth_key.blank?
85 | return auth_data && auth_data[0] == 'Basic' ? Base64.decode64(auth_data[1]).split(':')[0..1] : [nil, nil]
86 | end
87 | end
88 |
--------------------------------------------------------------------------------
/assets/style.css:
--------------------------------------------------------------------------------
1 | body
2 | {
3 | margin:0;
4 | padding:0;
5 | background:#000;
6 | font-family:'Lucida Grande', Arial, Helvetica, sans-serif;
7 | font-size:1.0em;
8 | color:white;
9 | }
10 |
11 | #container
12 | {
13 | xwidth:95%;
14 | margin:0 auto;
15 | min-width:800px;
16 | }
17 |
18 | #left
19 | {
20 | xfloat:left;
21 | xwidth:76%;
22 | margin-right:235px;
23 |
24 | }
25 |
26 | #left .page
27 | {
28 | font-size:0.9em;
29 | background:white;
30 | border:2px solid #999;
31 | border-width:0 2px 2px 0;
32 | padding:25px;
33 | xmargin-bottom:1em;
34 | color:black;
35 | }
36 |
37 | #right
38 | {
39 | margin-top:1em;
40 | float:right;
41 | xwidth:22%;
42 | font-size:0.9em;
43 | width:200px;
44 | margin-right:1.25em;
45 | }
46 |
47 | #right hr
48 | {
49 | border:0;
50 | border-top:1px solid #222;
51 | }
52 |
53 | #search
54 | {
55 | margin-top:2em;
56 | background:#111;
57 | padding:10px 5px;
58 | border:1px solid #222;
59 | border-width:1px 0;
60 | }
61 |
62 |
63 | ul.filters
64 | {
65 | list-style-type:none;
66 | padding:0;
67 | margin:0;
68 | font-size:0.9em;
69 | }
70 | ul.filters li { margin-bottom:0.2em;}
71 |
72 |
73 | ul.filters a,
74 | ul.filters a:visited {
75 | display:block;
76 | padding:1px 7px;
77 | color:#fff;
78 | xbackground-color:#fff;
79 | text-decoration:none;
80 | }
81 |
82 | ul.filters a:hover
83 | {
84 | background:#666;
85 | color:white;
86 | }
87 |
88 | ul.filters a.selected,
89 | ul.filters a.selected:visited,
90 | ul.filters a:active {
91 | color:gold;
92 | background-color:#333;
93 | text-decoration:none;
94 | font-weight:bold;
95 | }
96 |
97 |
98 | onclick a:hover
99 | {
100 | color:#fff;
101 | text-decoration:none;
102 | }
103 |
104 | #exceptions table
105 | {
106 | width:99%;
107 | border-collapse:collapse;
108 | }
109 |
110 | td
111 | {
112 | padding:5px 10px;
113 | xborder:1px solid #ddd;
114 | }
115 |
116 | #backtrace
117 | {
118 | overflow:auto;
119 | font-family:"Bitstream Vera Sans Mono", "Monaco", "Courier", monospace;;
120 | font-size:0.85em;
121 | margin-top:0.25em;
122 | }
123 |
124 | h2
125 | {
126 | background-color:#ddd;
127 | font-size:0.8em;
128 | padding:3px 10px;
129 | }
130 |
131 | form {margin:0;}
132 |
133 | h3 {
134 | font-size:0.8em;
135 | color:#ddd;
136 | background:#222;
137 | padding:3px 7px;
138 | }
139 |
140 | div.date
141 | {
142 | color:#666;
143 | font-size:0.8em;
144 | }
145 |
146 | h1
147 | {
148 | margin-top:0.25em;
149 | font-size:1.25em;
150 | padding-bottom:5px;
151 | border-bottom:2px solid #ddd;
152 | }
153 |
154 | h1 span
155 | {
156 | color:#aaa;
157 | font-weight:normal;
158 | }
159 |
160 | a
161 | {
162 | color:#369;
163 | text-decoration:none;
164 | }
165 | a:hover
166 | {
167 | color:blue;
168 | text-decoration:underline;
169 | }
170 |
171 | th
172 | {
173 | text-align:left;
174 | xbackground:#333;
175 | xcolor:gold;
176 | font-size:0.75em;
177 | padding:2px 10px;
178 | }
179 |
180 | tr { xcursor:pointer; }
181 |
182 | tr.eor td
183 | {
184 | background:#e7e7e7;
185 |
186 | }
187 |
188 | /*
189 | tr:hover td,
190 | tr.eor:hover td
191 | {
192 | background:#333;
193 | color:white;
194 | }
195 | tr:hover td a,
196 | tr.eor:hover td a { color:gold; }
197 | */
198 |
199 | .message
200 | {
201 | font-size:0.8em;
202 | }
203 |
204 | a.util
205 | {
206 | color:#c00;
207 | font-size:0.7em;
208 | }
209 |
210 | .pipe
211 | {
212 | font-size:0.75em;
213 | color:#999;
214 | }
215 |
216 | .tools { float:right; }
217 |
218 | .time
219 | {
220 | color:#666;
221 | font-size:0.75em;
222 | xvertical-align:top;
223 | }
224 |
225 |
226 | .expclass
227 | {
228 | xcolor:#999;
229 | }
230 | .expclass a
231 | {
232 | font-size:0.9em;
233 | }
234 |
235 | tr.deleted td {
236 | color:#aaa;
237 | text-decoration: line-through;
238 | }
239 | tr.deleted td a { color:#aaa; }
240 |
241 | .pages { float:right; margin-right:1em; }
242 | .pages a { text-decoration:underline; }
243 | .pages-bottom { border-top:2px solid #ddd; text-align:right; float:none;
244 | padding-top:0.4em;
245 | margin-top:0.4em;
246 | padding-right:1em;
247 | margin-right:0;
248 | }
249 |
250 | /* right */
251 |
252 | #right h4
253 | {
254 | font-size:0.75em;
255 | xbackground:#171717;
256 | xbackground:#333;
257 | color:#999;
258 | padding:3px 5px;
259 | margin-bottom:0.5em;
260 | font-weight:normal;
261 | }
262 |
263 | /* tabs */
264 |
265 | ul.tabs
266 | {
267 | list-style-type:none;
268 | padding:0;
269 | margin:1em 0;
270 | float:left;
271 | }
272 | ul.tabs li { float:left; display:inline; }
273 | ul.tabs li a
274 | {
275 | font-size:0.8em;
276 | padding:3px 7px;
277 | margin-right:1em;
278 | text-decoration:none;
279 | color:black;
280 | }
281 |
282 | ul.tabs li a:hover
283 | {
284 | background:#666;
285 | color:white;
286 | }
287 | ul.tabs li.selected a
288 | {
289 | background:black;
290 | color:white;
291 | }
292 |
293 | #activity {
294 | position:fixed;
295 | top:0; right:18px;
296 | width:200px;
297 | padding:5px 0;
298 | text-align:center;
299 | background-color:#cf1313;
300 | color:#fff;
301 | opacity:.8;
302 | font-size: 11px;
303 | }
304 |
305 | #feed {
306 | margin-top: 15px;
307 | }
308 |
--------------------------------------------------------------------------------