ixtlan-audit 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.textile ADDED
@@ -0,0 +1,4 @@
1
+ h1. Rails Audit Log
2
+
3
+ p. once you have a logged in user in your system, the usual request log can be improved by added the login name of the user to log output. but since you associate data with a "real" person privacy comes into play. the best privacy protection is not to store data, but in our case the compromise is to just define how long the data shall be stored (define an expiration date for the data).
4
+
@@ -2,16 +2,14 @@ module Ixtlan
2
2
  module Audit
3
3
  class AuditRack
4
4
 
5
- def initialize(app)
5
+ def initialize(app, audit_manager)
6
6
  @app = app
7
- self.class_eval do
8
- include Rails.application.routes.url_helpers
9
- end
7
+ @audit_manager = audit_manager
10
8
  end
11
9
 
12
10
  def call(env)
13
11
  result = @app.call(env)
14
- ::Rails.application.config.audit_manager.save_all
12
+ @audit_manager.save_all
15
13
  result
16
14
  end
17
15
 
@@ -0,0 +1,36 @@
1
+ module Ixtlan
2
+ module Audit
3
+ class LoggingConfigurator
4
+
5
+ @logger = Logging::Logger[self]
6
+
7
+ def initialize(filename, options = {}, categories = [])
8
+ @categories = categories
9
+ @options = options
10
+ @options[:filename] =
11
+ if filename.is_a? File
12
+ filename
13
+ else
14
+ File.join(Rails.root, "log", filename.to_s)
15
+ end.expand_path
16
+ @options[:age] = 'daily'
17
+ @options[:layout] = Logging.layouts.pattern(:pattern => '%d %m\n') unless @options[:layout]
18
+ end
19
+
20
+ def call(manager)
21
+ @options[:keep] = manager.keep_log
22
+ appender = Logging.appenders.rolling_file("audit", @options)
23
+ @categories.each do |category|
24
+ logger = Logging::Logger[category]
25
+ logger.remove_appenders('audit')
26
+ logger.add_appenders(audit_appender)
27
+ @logger.debug("setup logger for #{category}")
28
+ end
29
+ Dir["@options[:filename].*.log"][manager.keep_log, 100000].sort.each do |f|
30
+ FileUtils.rm_f(f)
31
+ end
32
+ @logger.info("initialized audit log . . .")
33
+ end
34
+ end
35
+ end
36
+ end
@@ -20,9 +20,9 @@ module Ixtlan
20
20
 
21
21
  def initialize
22
22
  @username_method = :login
23
- @keep_log = 90
23
+ @keep_logs = 90
24
24
  end
25
-
25
+
26
26
  def username_method=(method)
27
27
  @username_method = method.to_sym if method
28
28
  end
@@ -31,8 +31,10 @@ module Ixtlan
31
31
  @model = m if m
32
32
  end
33
33
 
34
- def keep_log=(days)
35
- @keep_log = days.to_i
34
+ def keep_logs=(days)
35
+ old = @keep_logs
36
+ @keep_logs = days.to_i
37
+ daily_cleanup if old != @keep_logs
36
38
  end
37
39
 
38
40
  def push(message, username)
@@ -41,6 +43,7 @@ module Ixtlan
41
43
  end
42
44
 
43
45
  def save_all
46
+ daily_cleanup
44
47
  list.each do |audit|
45
48
  begin
46
49
  audit.save
@@ -58,22 +61,30 @@ module Ixtlan
58
61
  end
59
62
 
60
63
  def daily_cleanup
61
- if @model
62
- if(!@last_cleanup.nil? && @last_cleanup < 1.days.ago)
63
- @last_cleanup = Date.today
64
+ if model
65
+ if(@last_cleanup.nil? || @last_cleanup < 1.days.ago)
66
+ @last_cleanup = 0.days.ago # to have the right type
64
67
  begin
65
- if defined? ::DataMapper
66
- @model.all(:date.lt => @keep_log.days.ago).destroy!
67
- else # ActiveRecord
68
- @model.all(:conditions => ["date < ?", @keep_log.days.ago]).each(&:delete)
69
- end
70
- @logger.info("cleaned audit logs")
71
- rescue Error
72
- # TODO log this !!
68
+ delete_all
69
+ logger.info("cleaned audit logs")
70
+ rescue Exception => e
71
+ logger.error("cleanup audit logs", e)
73
72
  end
74
73
  end
75
74
  end
76
75
  end
76
+
77
+ private
78
+
79
+ if defined? ::DataMapper
80
+ def delete_all
81
+ model.all(:created_at.lte => @keep_logs.days.ago).destroy!
82
+ end
83
+ else # ActiveRecord
84
+ def delete_all
85
+ model.all(:conditions => ["created_at <= ?", @keep_logs.days.ago]).each(&:delete)
86
+ end
87
+ end
77
88
  end
78
89
  end
79
90
  end
@@ -0,0 +1,69 @@
1
+ require 'slf4r/logger'
2
+
3
+ module Ixtlan
4
+ module Audit
5
+ class Manager
6
+
7
+ private
8
+
9
+ include ::Slf4r::Logger
10
+
11
+ def list
12
+ Thread.current[:audit] ||= []
13
+ end
14
+
15
+ public
16
+
17
+ def initialize
18
+ @model = ::Audit if defined? ::Audit
19
+ @username_method = :login
20
+ @keep_log = 90
21
+ end
22
+
23
+ def username_method=(method)
24
+ @username_method = method.to_sym if method
25
+ end
26
+
27
+ def model=(model)
28
+ @model = model if model
29
+ end
30
+
31
+ def keep_log=(days)
32
+ @keep_log = days.to_i
33
+ end
34
+
35
+ def push(message, username)
36
+ list << @model.new(:date => DateTime.now, :message => message, :login => username) if @model
37
+ end
38
+
39
+ def save_all
40
+ list.each do |audit|
41
+ audit.save
42
+ end
43
+ Thread.current[:audit] = nil
44
+ end
45
+
46
+ def username_method
47
+ @username_method
48
+ end
49
+
50
+ def daily_cleanup
51
+ if @model
52
+ if(!@last_cleanup.nil? && @last_cleanup < 1.days.ago)
53
+ @last_cleanup = Date.today
54
+ begin
55
+ if defined? ::DataMapper
56
+ @model.all(:date.lt => @keep_log.days.ago).destroy!
57
+ else # ActiveRecord
58
+ @model.all(:conditions => ["date < ?", @keep_log.days.ago]).each(&:delete)
59
+ end
60
+ @logger.info("cleaned audit logs")
61
+ rescue Error
62
+ # TODO log this !!
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -8,32 +8,18 @@ module Ixtlan
8
8
  class Railtie < ::Rails::Railtie
9
9
 
10
10
  config.before_configuration do |app|
11
- app.config.class.class_eval do
12
- attr_accessor :audit_manager
13
- end
14
11
  app.config.audit_manager = Manager.new
15
- ::ActionController::Base.append_after_filter(Ixtlan::Audit::AuditFilter)
16
- ::ActionController::Base.append_before_filter(Ixtlan::Audit::AuditCleanupFilter)
17
- app.config.middleware.use Ixtlan::Audit::AuditRack
12
+ ::ActionController::Base.send(:include, Module)
13
+ ::ActionController::Base.send(:after_filter, :audit)
14
+ app.config.middleware.use(AuditRack, app.config.audit_manager)
18
15
  end
19
16
  end
20
-
21
- class AuditFilter
22
-
23
- def self.logger
24
- @logger ||= UserLogger.new(Rails.configuration.audit_manager)
25
- end
26
-
27
- def self.filter(controller)
28
- logger.log_action(controller)
29
- end
30
- end
31
-
32
17
 
33
- class AuditCleanupFilter
18
+ module Module
34
19
 
35
- def self.filter(controller)
36
- Rails.application.config.audit_manager.daily_cleanup
20
+ def audit
21
+ @audit_logger ||= UserLogger.new(Rails.application.config.audit_manager)
22
+ @audit_logger.log_action(self)
37
23
  end
38
24
  end
39
25
  end
@@ -27,11 +27,11 @@ module Ixtlan
27
27
  log_user(login_from(controller)) do
28
28
  as_xml = controller.response.content_type == 'application/xml' ? " - xml" : ""
29
29
  if controller.params[:controller]
30
- audits = controller.instance_variable_get("@#{controller.params[:controller].to_sym}")
30
+ audits = controller.instance_variable_get("@#{controller.params[:controller]}")
31
31
  if(audits)
32
- "#{controller.params[:controller]}##{controller.params[:action]} #{audits.class.name.to_s.pluralize}[#{audits.size}]#{as_xml}#{message}"
32
+ "#{controller.params[:controller]}##{controller.params[:action]} #{controller.params[:controller].classify}[#{audits.size}]#{as_xml}#{message}"
33
33
  else
34
- audit = controller.instance_variable_get("@#{controller.params[:controller].singularize.to_sym}")
34
+ audit = controller.instance_variable_get("@#{controller.params[:controller].singularize}")
35
35
  if(audit)
36
36
  errors = if(audit.respond_to?(:errors) && !audit.errors.empty?)
37
37
  " - errors: " + audit.errors.full_messages.join(", ")
@@ -0,0 +1,59 @@
1
+ require 'slf4r/logger'
2
+
3
+ module Ixtlan
4
+ module Audit
5
+ class UserLogger
6
+
7
+ include ::Slf4r::Logger
8
+
9
+ def initialize(audit_manager)
10
+ @manager = audit_manager
11
+ end
12
+
13
+ private
14
+
15
+ def login_from(controller)
16
+ user = controller.respond_to?(:current_user) ? controller.send(:current_user) : nil
17
+ user.nil? ? nil: user.send(@manager.username_method)
18
+ end
19
+
20
+ public
21
+
22
+ def log(controller, message = nil, &block)
23
+ log_user(login_from(controller), message, &block)
24
+ end
25
+
26
+ def log_action(controller, message = nil)
27
+ log_user(login_from(controller)) do
28
+ as_xml = controller.response.content_type == 'application/xml' ? " - xml" : ""
29
+ if controller.params[:controller]
30
+ audits = controller.instance_variable_get("@#{controller.params[:controller].to_sym}")
31
+ if(audits)
32
+ "#{controller.params[:controller]}##{controller.params[:action]} #{audits.model.to_s.plural}[#{audits.size}]#{as_xml}#{message}"
33
+ else
34
+ audit = controller.instance_variable_get("@#{controller.params[:controller].singular.to_sym}")
35
+ if(audit)
36
+ errors = if(audit.respond_to?(:errors) && !audit.errors.empty?)
37
+ " - errors: " + audit.errors.full_messages.join(", ")
38
+ end
39
+ audit_log = audit.respond_to?(:to_log) ? audit.to_log : "#{audit.model}(#{audit.key})"
40
+ "#{controller.params[:controller]}##{controller.params[:action]} #{audit_log}#{as_xml}#{message}#{errors}"
41
+ else
42
+ "#{controller.params[:controller]}##{controller.params[:action]}#{as_xml}#{message}"
43
+ end
44
+ end
45
+ else
46
+ "params=#{controller.params.inspect}#{message}"
47
+ end
48
+ end
49
+ end
50
+
51
+ def log_user(user, message = nil, &block)
52
+ user ||= "???"
53
+ msg = "#{message}#{block.call if block}"
54
+ @manager.push( msg, user)
55
+ @logger.debug {"[#{user}] #{msg}" }
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,3 @@
1
+ if defined?(Rails)
2
+ require 'ixtlan/audit/railtie'
3
+ end
@@ -0,0 +1,55 @@
1
+ require 'dm-core'
2
+ require 'dm-migrations'
3
+ require 'slf4r/ruby_logger'
4
+ require 'ixtlan/audit/manager'
5
+
6
+ class Audit
7
+ include DataMapper::Resource
8
+
9
+ property :id, Serial
10
+
11
+ property :login, String
12
+ property :message, String
13
+
14
+ property :created_at, DateTime
15
+
16
+ before :save do
17
+ self.created_at = DateTime.now
18
+ end
19
+ end
20
+
21
+ class Fixnum
22
+ def days
23
+ self
24
+ end
25
+ def ago
26
+ DateTime.now - 86000 * self
27
+ end
28
+ end
29
+ DataMapper.setup(:default, "sqlite3::memory:")
30
+ DataMapper.finalize
31
+ DataMapper.repository.auto_migrate!
32
+
33
+ describe Ixtlan::Audit::Manager do
34
+
35
+ it 'should collect log events and the save them all in one go' do
36
+ size = Audit.all.size
37
+ subject.push("msg1", "login1")
38
+ subject.push("msg2", "login2")
39
+ subject.push("msg3", "login3")
40
+ subject.save_all
41
+
42
+ Audit.all.size.should == size + 3
43
+ end
44
+
45
+ it "should clean up audit logs" do
46
+ Audit.create(:message => "msg", :login => "login")
47
+ Audit.all.size.should > 0
48
+ subject.keep_logs = 0
49
+ Audit.all.size.should == 0
50
+ subject.push("msg", "login")
51
+ subject.push("msg", "login")
52
+ subject.save_all
53
+ Audit.all.size.should == 2
54
+ end
55
+ end
@@ -0,0 +1,18 @@
1
+ require 'dm-core'
2
+ require 'dm-migrations'
3
+ require 'slf4r/ruby_logger'
4
+
5
+ class Audit
6
+ include DataMapper::Resource
7
+
8
+ property :id, Serial
9
+
10
+ property :login, String
11
+ property :message, String
12
+
13
+ property :created_at, DateTime
14
+
15
+ before :save do
16
+ self.created_at = DateTime.now
17
+ end
18
+ end
metadata CHANGED
@@ -1,12 +1,8 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ixtlan-audit
3
3
  version: !ruby/object:Gem::Version
4
- prerelease: false
5
- segments:
6
- - 0
7
- - 1
8
- - 1
9
- version: 0.1.1
4
+ prerelease:
5
+ version: 0.2.0
10
6
  platform: ruby
11
7
  authors:
12
8
  - mkristian
@@ -14,65 +10,75 @@ autorequire:
14
10
  bindir: bin
15
11
  cert_chain: []
16
12
 
17
- date: 2011-04-30 00:00:00 +05:30
13
+ date: 2011-11-04 00:00:00 +05:30
18
14
  default_executable:
19
15
  dependencies:
20
16
  - !ruby/object:Gem::Dependency
21
- name: rails
17
+ name: slf4r
22
18
  prerelease: false
23
19
  requirement: &id001 !ruby/object:Gem::Requirement
20
+ none: false
24
21
  requirements:
25
- - - "="
22
+ - - ~>
26
23
  - !ruby/object:Gem::Version
27
- segments:
28
- - 3
29
- - 0
30
- - 1
31
- version: 3.0.1
32
- type: :development
24
+ version: 0.4.2
25
+ type: :runtime
33
26
  version_requirements: *id001
34
27
  - !ruby/object:Gem::Dependency
35
28
  name: rspec
36
29
  prerelease: false
37
30
  requirement: &id002 !ruby/object:Gem::Requirement
31
+ none: false
38
32
  requirements:
39
33
  - - "="
40
34
  - !ruby/object:Gem::Version
41
- segments:
42
- - 2
43
- - 0
44
- - 1
45
- version: 2.0.1
35
+ version: 2.6.0
46
36
  type: :development
47
37
  version_requirements: *id002
48
38
  - !ruby/object:Gem::Dependency
49
- name: cucumber
39
+ name: rake
50
40
  prerelease: false
51
41
  requirement: &id003 !ruby/object:Gem::Requirement
42
+ none: false
52
43
  requirements:
53
44
  - - "="
54
45
  - !ruby/object:Gem::Version
55
- segments:
56
- - 0
57
- - 9
58
- - 4
59
- version: 0.9.4
46
+ version: 0.8.7
60
47
  type: :development
61
48
  version_requirements: *id003
62
49
  - !ruby/object:Gem::Dependency
63
- name: rake
50
+ name: dm-core
64
51
  prerelease: false
65
52
  requirement: &id004 !ruby/object:Gem::Requirement
53
+ none: false
66
54
  requirements:
67
55
  - - "="
68
56
  - !ruby/object:Gem::Version
69
- segments:
70
- - 0
71
- - 8
72
- - 7
73
- version: 0.8.7
57
+ version: 1.2.0
74
58
  type: :development
75
59
  version_requirements: *id004
60
+ - !ruby/object:Gem::Dependency
61
+ name: dm-migrations
62
+ prerelease: false
63
+ requirement: &id005 !ruby/object:Gem::Requirement
64
+ none: false
65
+ requirements:
66
+ - - "="
67
+ - !ruby/object:Gem::Version
68
+ version: 1.2.0
69
+ type: :development
70
+ version_requirements: *id005
71
+ - !ruby/object:Gem::Dependency
72
+ name: dm-sqlite-adapter
73
+ prerelease: false
74
+ requirement: &id006 !ruby/object:Gem::Requirement
75
+ none: false
76
+ requirements:
77
+ - - "="
78
+ - !ruby/object:Gem::Version
79
+ version: 1.2.0
80
+ type: :development
81
+ version_requirements: *id006
76
82
  description: audit the controller actions for the current user. log that data into the database and allow to expire this log files (privacy protection) and be able to browse it from the UI
77
83
  email:
78
84
  - m.kristian@web.de
@@ -84,14 +90,18 @@ extra_rdoc_files: []
84
90
 
85
91
  files:
86
92
  - MIT-LICENSE
93
+ - README.textile
94
+ - lib/ixtlan-audit.rb~
87
95
  - lib/ixtlan-audit.rb
88
- - lib/generators/ixtlan/audit_base.rb
89
- - lib/generators/ixtlan/audit_scaffold/audit_scaffold_generator.rb
90
- - lib/generators/ixtlan/audit_model/audit_model_generator.rb
91
- - lib/ixtlan/audit/railtie.rb
96
+ - lib/ixtlan/audit/logging_configurator.rb~
97
+ - lib/ixtlan/audit/user_logger.rb
92
98
  - lib/ixtlan/audit/manager.rb
93
99
  - lib/ixtlan/audit/audit_rack.rb
94
- - lib/ixtlan/audit/user_logger.rb
100
+ - lib/ixtlan/audit/manager.rb~
101
+ - lib/ixtlan/audit/railtie.rb
102
+ - lib/ixtlan/audit/user_logger.rb~
103
+ - spec/audit_manager_spec.rb
104
+ - spec/audit_manager_spec.rb~
95
105
  has_rdoc: true
96
106
  homepage: http://github.com/mkristian/ixtlan-audit
97
107
  licenses:
@@ -103,25 +113,23 @@ rdoc_options:
103
113
  require_paths:
104
114
  - lib
105
115
  required_ruby_version: !ruby/object:Gem::Requirement
116
+ none: false
106
117
  requirements:
107
118
  - - ">="
108
119
  - !ruby/object:Gem::Version
109
- segments:
110
- - 0
111
120
  version: "0"
112
121
  required_rubygems_version: !ruby/object:Gem::Requirement
122
+ none: false
113
123
  requirements:
114
124
  - - ">="
115
125
  - !ruby/object:Gem::Version
116
- segments:
117
- - 0
118
126
  version: "0"
119
127
  requirements: []
120
128
 
121
129
  rubyforge_project:
122
- rubygems_version: 1.3.6
130
+ rubygems_version: 1.5.1
123
131
  signing_key:
124
132
  specification_version: 3
125
133
  summary: audit the controller actions for the current user
126
- test_files: []
127
-
134
+ test_files:
135
+ - spec/audit_manager_spec.rb
@@ -1,31 +0,0 @@
1
- require 'rails/generators/base'
2
- module Ixtlan
3
- module Generators
4
- class AuditBase < Rails::Generators::Base
5
-
6
- argument :name, :type => :string, :required => false
7
-
8
- protected
9
- def generator_name
10
- raise "please overwrite generator_name"
11
- end
12
-
13
- public
14
- def create
15
- args = []
16
- if name
17
- args << ARGV.shift
18
- else
19
- args << "audit"
20
- end
21
-
22
- args << "created_at:datetime"
23
- args << "login:string"
24
- args << "message:string"
25
- args += ARGV[0, 10000] || []
26
-
27
- generate generator_name, *args
28
- end
29
- end
30
- end
31
- end
@@ -1,12 +0,0 @@
1
- require 'generators/ixtlan/audit_base'
2
- module Ixtlan
3
- module Generators
4
- class AuditModelGenerator < AuditBase
5
-
6
- protected
7
- def generator_name
8
- "model"
9
- end
10
- end
11
- end
12
- end
@@ -1,12 +0,0 @@
1
- require 'generators/ixtlan/audit_base'
2
- module Ixtlan
3
- module Generators
4
- class AuditScaffoldGenerator < AuditBase
5
-
6
- protected
7
- def generator_name
8
- "scaffold"
9
- end
10
- end
11
- end
12
- end