effective_logging 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|