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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +280 -0
- data/Rakefile +20 -0
- data/app/assets/javascripts/effective_logging.js +1 -0
- data/app/assets/javascripts/effective_logging/effective_logger.js.coffee.erb +15 -0
- data/app/controllers/admin/logs_controller.rb +31 -0
- data/app/controllers/effective/logs_controller.rb +71 -0
- data/app/helpers/effective_logging_helper.rb +59 -0
- data/app/models/effective/access_denied.rb +17 -0
- data/app/models/effective/datatables/logs.rb +49 -0
- data/app/models/effective/log.rb +59 -0
- data/app/models/effective/user_logger.rb +7 -0
- data/app/models/effective_logger.rb +38 -0
- data/app/views/active_admin/effective_logging/logs/index.html.haml +8 -0
- data/app/views/active_admin/effective_logging/logs/show.html.haml +4 -0
- data/app/views/admin/logs/index.html.haml +7 -0
- data/app/views/admin/logs/show.html.haml +3 -0
- data/app/views/effective/logs/_log.html.haml +59 -0
- data/app/views/effective/logs/index.html.haml +7 -0
- data/app/views/effective/logs/show.html.haml +2 -0
- data/config/routes.rb +18 -0
- data/db/migrate/01_create_effective_logging.rb.erb +29 -0
- data/lib/effective_logging.rb +32 -0
- data/lib/effective_logging/email_logger.rb +28 -0
- data/lib/effective_logging/engine.rb +50 -0
- data/lib/effective_logging/log_page_views.rb +68 -0
- data/lib/effective_logging/version.rb +3 -0
- data/lib/generators/effective_logging/install_generator.rb +33 -0
- data/lib/generators/templates/README +1 -0
- data/lib/generators/templates/effective_logging.rb +46 -0
- data/spec/effective_logging_spec.rb +7 -0
- data/spec/spec_helper.rb +33 -0
- 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 (#{log.logs_count} 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,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,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
|
+
|
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
|
+
|
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
|
+
|
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
|
+
|
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
|