effective_logging 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +280 -0
  4. data/Rakefile +20 -0
  5. data/app/assets/javascripts/effective_logging.js +1 -0
  6. data/app/assets/javascripts/effective_logging/effective_logger.js.coffee.erb +15 -0
  7. data/app/controllers/admin/logs_controller.rb +31 -0
  8. data/app/controllers/effective/logs_controller.rb +71 -0
  9. data/app/helpers/effective_logging_helper.rb +59 -0
  10. data/app/models/effective/access_denied.rb +17 -0
  11. data/app/models/effective/datatables/logs.rb +49 -0
  12. data/app/models/effective/log.rb +59 -0
  13. data/app/models/effective/user_logger.rb +7 -0
  14. data/app/models/effective_logger.rb +38 -0
  15. data/app/views/active_admin/effective_logging/logs/index.html.haml +8 -0
  16. data/app/views/active_admin/effective_logging/logs/show.html.haml +4 -0
  17. data/app/views/admin/logs/index.html.haml +7 -0
  18. data/app/views/admin/logs/show.html.haml +3 -0
  19. data/app/views/effective/logs/_log.html.haml +59 -0
  20. data/app/views/effective/logs/index.html.haml +7 -0
  21. data/app/views/effective/logs/show.html.haml +2 -0
  22. data/config/routes.rb +18 -0
  23. data/db/migrate/01_create_effective_logging.rb.erb +29 -0
  24. data/lib/effective_logging.rb +32 -0
  25. data/lib/effective_logging/email_logger.rb +28 -0
  26. data/lib/effective_logging/engine.rb +50 -0
  27. data/lib/effective_logging/log_page_views.rb +68 -0
  28. data/lib/effective_logging/version.rb +3 -0
  29. data/lib/generators/effective_logging/install_generator.rb +33 -0
  30. data/lib/generators/templates/README +1 -0
  31. data/lib/generators/templates/effective_logging.rb +46 -0
  32. data/spec/effective_logging_spec.rb +7 -0
  33. data/spec/spec_helper.rb +33 -0
  34. metadata +150 -0
@@ -0,0 +1,59 @@
1
+ module EffectiveLoggingHelper
2
+ def bootstrap_class_for_status(status)
3
+ case status
4
+ when 'success' ; 'success'
5
+ when 'info' ; 'info'
6
+ when 'warning' ; 'warning'
7
+ when 'error' ; 'danger'
8
+ else 'primary'
9
+ end
10
+ end
11
+
12
+ def render_log(log)
13
+ render(:partial => 'effective/logs/log', :locals => {:log => log})
14
+ end
15
+
16
+ def parents_of_log(log)
17
+ parents = [log.parent]
18
+ parents << parents.last.parent while(parents.last.try(:parent_id).present?)
19
+ parents.compact.reverse
20
+ end
21
+
22
+ # Call me with :th => true, :sub_th => false
23
+ # Any other options are sent to the table tag
24
+ def tableize_hash(hash, options = {})
25
+ if hash.present?
26
+ content_tag(:table, options) do
27
+ hash.map do |k, v|
28
+ content_tag(:tr) do
29
+ content_tag((options[:th] ? :th : :td), k) +
30
+ content_tag(:td) do
31
+ if v.kind_of?(Hash)
32
+ tableize_hash(v, options.merge({:class => 'table table-bordered', :th => (options.key?(:sub_th) ? options[:sub_th] : options[:th])}))
33
+ elsif v.kind_of?(Array)
34
+ '[' + v.join(', ') + ']'
35
+ else
36
+ v
37
+ end
38
+ end
39
+ end
40
+ end.join('').html_safe
41
+ end.html_safe
42
+ end
43
+ end
44
+
45
+ # This is called on the Logs#show Admin page, and is intended for override by the application
46
+ def effective_logging_object_link_to(obj, action = :show)
47
+ if obj.kind_of?(User)
48
+ return (
49
+ if action == :show && defined?(admin_user_path)
50
+ link_to('View', admin_user_path(obj))
51
+ elsif action == :edit && defined?(edit_admin_user_path)
52
+ link_to('Edit', edit_admin_user_path(obj))
53
+ end
54
+ )
55
+ end
56
+ end
57
+
58
+
59
+ end
@@ -0,0 +1,17 @@
1
+ unless defined?(Effective::AccessDenied)
2
+ module Effective
3
+ class AccessDenied < StandardError
4
+ attr_reader :action, :subject
5
+
6
+ def initialize(message = nil, action = nil, subject = nil)
7
+ @message = message
8
+ @action = action
9
+ @subject = subject
10
+ end
11
+
12
+ def to_s
13
+ @message || I18n.t(:'unauthorized.default', :default => 'Access Denied')
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,49 @@
1
+ if defined?(EffectiveDatatables)
2
+ module Effective
3
+ module Datatables
4
+ class Logs < Effective::Datatable
5
+ include EffectiveLoggingHelper
6
+
7
+ default_order :created_at, :desc
8
+
9
+ table_column :created_at
10
+ table_column :user
11
+
12
+ table_column :status, :filter => {:type => :select, :values => EffectiveLogging.statuses }
13
+ table_column :message, :width => '50%'
14
+
15
+ table_column :details, :visible => false do |log|
16
+ log.details.delete(:email)
17
+ tableize_hash(log.details, :th => true, :sub_th => false, :width => '100%')
18
+ end
19
+
20
+ table_column :actions, :sortable => false, :filter => false do |log|
21
+ show_path =
22
+ if datatables_active_admin_path?
23
+ admin_effective_log_path(log)
24
+ elsif datatables_admin_path?
25
+ effective_logging.admin_log_path(log)
26
+ else
27
+ effective_logging.log_path(log)
28
+ end
29
+
30
+ if log.logs_count.to_i > 0
31
+ link_to "View&nbsp;(#{log.logs_count}&nbsp;more)".html_safe, show_path
32
+ else
33
+ link_to 'View', show_path
34
+ end
35
+ end
36
+
37
+ # A nil attributes[:log_id] means give me all the top level log entries
38
+ # If we set a log_id then it's for sub logs
39
+ def collection
40
+ if attributes[:user_id].present?
41
+ Effective::Log.unscoped.where(:parent_id => attributes[:log_id]).where(:user_id => attributes[:user_id]).includes(:user)
42
+ else
43
+ Effective::Log.unscoped.where(:parent_id => attributes[:log_id]).includes(:user)
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,59 @@
1
+ module Effective
2
+ class Log < ActiveRecord::Base
3
+ self.table_name = EffectiveLogging.logs_table_name.to_s
4
+
5
+ # These 3 attr_accessors are set on the controller #show actions
6
+ attr_accessor :datatable
7
+ attr_accessor :next_log
8
+ attr_accessor :prev_log
9
+
10
+ # Self-Referencing relationship
11
+ belongs_to :parent, :class_name => 'Effective::Log', :counter_cache => true
12
+ has_many :logs, :dependent => :destroy, :class_name => 'Effective::Log', :foreign_key => :parent_id
13
+
14
+ # The user this log item is referring to
15
+ # An associated object, if we wanna add anything extra
16
+ belongs_to :user
17
+ belongs_to :associated, :polymorphic => true
18
+
19
+ serialize :details, Hash
20
+
21
+ structure do
22
+ logs_count :integer # Rails Counter Cache
23
+
24
+ message :string, :validates => [:presence]
25
+ details :text
26
+
27
+ status :string, :validates => [:presence, :inclusion => {:in => EffectiveLogging.statuses }]
28
+
29
+ timestamps
30
+ end
31
+
32
+ default_scope -> { order("#{EffectiveLogging.logs_table_name.to_s}.updated_at DESC") }
33
+
34
+ def log(message, status = EffectiveLogging.statuses.first, options = {})
35
+ EffectiveLogger.log(message, status, (options || {}).merge({:parent => self}))
36
+ end
37
+
38
+ def details
39
+ self[:details] ||= {}
40
+ end
41
+
42
+ # def next_log
43
+ # @next_log ||= Log.unscoped.order(:id).where(:parent_id => self.parent_id).where('id > ?', self.id).first
44
+ # end
45
+
46
+ # def prev_log
47
+ # @prev_log ||= Log.unscoped.order(:id).where(:parent_id => self.parent_id).where('id < ?', self.id).last
48
+ # end
49
+
50
+ # Dynamically add logging methods based on the defined statuses
51
+ # EffectiveLogging.info 'my message'
52
+ (EffectiveLogging.statuses || []).each do |status|
53
+ send(:define_method, status) { |message, options={}| log(message, status, options) }
54
+ end
55
+
56
+ end
57
+ end
58
+
59
+
@@ -0,0 +1,7 @@
1
+ module Effective
2
+ class UserLogger
3
+ def self.successful_login(user)
4
+ EffectiveLogger.success("user login from #{user.try(:current_sign_in_ip) || 'unknown IP'}", :user => user)
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,38 @@
1
+ # Call EffectiveLog.info or EffectiveLog.success EffectiveLog.error
2
+
3
+ class EffectiveLogger
4
+ def self.log(message, status = EffectiveLogging.statuses.first, options = {})
5
+ if options[:user].present? && !options[:user].kind_of?(User)
6
+ raise ArgumentError.new("Log.log :user => ... argument must be a User object")
7
+ end
8
+
9
+ if options[:parent].present? && !options[:parent].kind_of?(Effective::Log)
10
+ raise ArgumentError.new("Log.log :parent => ... argument must be an Effective::Log object")
11
+ end
12
+
13
+ if options[:associated].present? && !options[:associated].kind_of?(ActiveRecord::Base)
14
+ raise ArgumentError.new("Log.log :associated => ... argument must be an ActiveRecord::Base object")
15
+ end
16
+
17
+ Effective::Log.new().tap do |log|
18
+ log.message = message
19
+ log.status = status
20
+
21
+ log.user_id = options.delete(:user_id)
22
+ log.user = options.delete(:user)
23
+ log.parent = options.delete(:parent)
24
+ log.associated = options.delete(:associated)
25
+
26
+ log.details = options.delete_if { |k, v| v.blank? } if options.kind_of?(Hash)
27
+
28
+ log.save
29
+ end
30
+ end
31
+
32
+ # Dynamically add logging methods based on the defined statuses
33
+ # EffectiveLogging.info 'my message'
34
+ (EffectiveLogging.statuses || []).each do |status|
35
+ self.singleton_class.send(:define_method, status) { |message, options={}| log(message, status, options) }
36
+ end
37
+
38
+ end
@@ -0,0 +1,8 @@
1
+ .fluid-container
2
+ .row
3
+ .col-sm-12
4
+ - unless (@datatable.collection.length rescue 0) > 0
5
+ %p
6
+ There are no logs
7
+ - else
8
+ = render_datatable @datatable
@@ -0,0 +1,4 @@
1
+ .fluid-container
2
+ .row
3
+ .col-sm-12
4
+ = render_log(@log)
@@ -0,0 +1,7 @@
1
+ %h2= @page_title
2
+
3
+ - unless (@datatable.collection.length rescue 0) > 0
4
+ %p
5
+ There are no logs
6
+ - else
7
+ = render_datatable @datatable
@@ -0,0 +1,3 @@
1
+ %h2= @page_title
2
+ = render_log(@log)
3
+
@@ -0,0 +1,59 @@
1
+ .panel{:class => "panel-#{bootstrap_class_for_status(log.status)}"}
2
+ .panel-heading
3
+ - parents_of_log(log).each do |parent|
4
+ = link_to(log.message, (request.fullpath.gsub(log.to_param, parent.to_param) rescue '#'))
5
+ = ' > '
6
+ %strong= log.message
7
+ .pull-right
8
+ = link_to_if(log.prev_log.present?, '< Prev', (request.fullpath.gsub(log.to_param, log.prev_log.try(:to_param).to_s))) { 'Prev' }
9
+ = ' - '
10
+ = link_to_if(log.next_log.present?, 'Next >', (request.fullpath.gsub(log.to_param, log.next_log.try(:to_param).to_s))) { 'Next' }
11
+
12
+ .panel-body
13
+ .row
14
+ .col-lg-6
15
+ %span.label{:class => "label-#{bootstrap_class_for_status(log.status)}"}= log.status
16
+ &nbsp;
17
+ = log.created_at.strftime("%Y-%m-%d %H:%M:%S")
18
+ = '(' + time_ago_in_words(log.created_at) + ' ago)'
19
+
20
+ .col-lg-6
21
+ - if log.user.present?
22
+ %strong User:
23
+ &nbsp;
24
+ = (log.user.to_s.starts_with?('#<User:0x') ? (log.user.email rescue log.user) : log.user)
25
+ - if log.associated.present?
26
+ %br
27
+ %strong Regarding:
28
+ &nbsp;
29
+ - if log.associated.to_s.starts_with?('#<')
30
+ = "#{log.associated.class.name} ##{log.associated.to_param}"
31
+ - else
32
+ = log.associated
33
+
34
+ - log.details.each do |key, value|
35
+ - next unless value.present?
36
+ .row
37
+ .col-lg-12
38
+ %br
39
+ %p
40
+ %strong= "#{key.to_s.titleize}:"
41
+ - if value.kind_of?(Hash)
42
+ = tableize_hash(value, :class => 'table', :style => 'width: 50%', :th => true)
43
+ - else
44
+ - allowed_tags = ActionView::Base.sanitized_allowed_tags.to_a + ['table', 'thead', 'tbody', 'tfoot', 'tr', 'td', 'th']
45
+ - allowed_attributes = ActionView::Base.sanitized_allowed_attributes.to_a + ['colspan', 'rowspan']
46
+ = simple_format(sanitize(value.to_s, :tags => allowed_tags, :attributes => allowed_attributes), {}, :sanitize => false)
47
+
48
+ - if log.logs.present?
49
+ .row
50
+ .col-lg-12
51
+ %br
52
+ %p
53
+ %strong Additional Entries:
54
+ = "this log contains #{log.logs_count} additional sub entries"
55
+ - if log.datatable.present?
56
+ %hr
57
+ .row
58
+ .col-lg-12= render_datatable log.datatable
59
+
@@ -0,0 +1,7 @@
1
+ %h2= @page_title
2
+
3
+ - unless (@datatable.collection.length rescue 0) > 0
4
+ %p
5
+ You have no previous recorded activity
6
+ - else
7
+ = render_datatable @datatable
@@ -0,0 +1,2 @@
1
+ %h2= @page_title
2
+ = render_log(@log)
data/config/routes.rb ADDED
@@ -0,0 +1,18 @@
1
+ EffectiveLogging::Engine.routes.draw do
2
+ scope :module => 'effective' do
3
+ # Create is our javascript POST event for EffectiveLogging from JS side
4
+ # The show and index routes are for user specific logs
5
+ resources :logs, :only => [:create, :show, :index]
6
+ end
7
+
8
+ if defined?(EffectiveDatatables)
9
+ namespace :admin do
10
+ resources :logs, :only => [:index, :show]
11
+ end
12
+ end
13
+
14
+ end
15
+
16
+ Rails.application.routes.draw do
17
+ mount EffectiveLogging::Engine => '/', :as => 'effective_logging'
18
+ end
@@ -0,0 +1,29 @@
1
+ class CreateEffectiveLogging < ActiveRecord::Migration
2
+ def self.up
3
+ create_table <%= @logs_table_name %> do |t|
4
+ t.integer :parent_id
5
+ t.integer :user_id
6
+
7
+ t.string :associated_type
8
+ t.integer :associated_id
9
+
10
+ t.integer :logs_count
11
+
12
+ t.string :message
13
+ t.text :details
14
+
15
+ t.string :status
16
+
17
+ t.timestamps
18
+ end
19
+
20
+ add_index <%= @logs_table_name %>, :user_id
21
+ add_index <%= @logs_table_name %>, :parent_id
22
+ add_index <%= @logs_table_name %>, [:associated_type, :associated_id]
23
+ add_index <%= @logs_table_name %>, :associated_id
24
+ end
25
+
26
+ def self.down
27
+ drop_table <%= @logs_table_name %>
28
+ end
29
+ end
@@ -0,0 +1,32 @@
1
+ require "effective_logging/engine"
2
+ require "effective_logging/version"
3
+ require "effective_logging/log_page_views"
4
+
5
+ module EffectiveLogging
6
+ # The following are all valid config keys
7
+ mattr_accessor :logs_table_name
8
+ mattr_accessor :use_active_admin
9
+
10
+ mattr_accessor :authorization_method
11
+ mattr_accessor :layout
12
+ mattr_accessor :additional_statuses
13
+
14
+ mattr_accessor :emails_enabled
15
+ mattr_accessor :user_logins_enabled
16
+
17
+ def self.setup
18
+ yield self
19
+ end
20
+
21
+ def self.authorized?(controller, action, resource)
22
+ if authorization_method.respond_to?(:call) || authorization_method.kind_of?(Symbol)
23
+ raise Effective::AccessDenied.new() unless (controller || self).instance_exec(controller, action, resource, &authorization_method)
24
+ end
25
+ true
26
+ end
27
+
28
+ def self.statuses
29
+ @statuses ||= (Array(@@additional_statuses).map { |status| status.to_s.downcase } | ['info', 'success', 'error'])
30
+ end
31
+
32
+ end
@@ -0,0 +1,28 @@
1
+ module EffectiveLogging
2
+ class EmailLogger
3
+ def self.delivered_email(message)
4
+ return unless message.present?
5
+
6
+ # Cleanup the Header
7
+ message_header = message.header.to_s
8
+ message_header.gsub!(";\r\n charset", '; charset')
9
+
10
+ # Cleanup the Body
11
+ if (message_body = message.body.to_s).include?('<html>')
12
+ message_body.gsub!(/(\r)*\n\s*/, '')
13
+ message_body.gsub!("<!DOCTYPE html>", '')
14
+ end
15
+
16
+ (message.to || []).each do |email|
17
+ user = User.where(:email => email).first
18
+
19
+ if user.present?
20
+ EffectiveLogger.success("email sent: #{message.subject}", :user => user, :recipient => email, :subject => message.subject, :email => message_header + '<hr>' + message_body)
21
+ else
22
+ EffectiveLogger.success("email sent to #{email}: #{message.subject}", :recipient => email, :subject => message.subject, :email => message_header + '<hr>' + message_body)
23
+ end
24
+ end
25
+ end
26
+
27
+ end
28
+ end