effective_logging 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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