17

我有一个包含 ActiveRecord 和 ActiveResource 模型的大型项目。我需要使用这些模型实现用户活动的日志记录,还需要记录模型属性的更改(保存对象状态或类似的东西)。用户或 cron rake 任务可以进行更改。

我还必须有可能按日期搜索任何数据,任何字段..etc

例如,使用最后一个活动生成可读消息也会很好

  • 用户 Bob在 2011-08-12 08:12将他的密码更改为*并通过电子邮件发送到**
  • 员工 Jeff 添加新合伙人:公司名称 2011-08-12 08:13
  • 管理员杰克删除产品:产品名称在 2011-09-12 11:11
  • 客户 Sam 订购了新服务:服务名称于 2011-09-12 11:12

有人实施这样的日志记录吗?想法?建议?

我应该使用 gems 还是可以用观察者不改变模型来做所有的逻辑?


我喜欢 gem https://github.com/airblade/paper_trail谁能说我怎样才能让它与 activeresource 一起工作?

4

6 回答 6

4

你正在寻找

https://github.com/collectiveidea/acts_as_audited

很少有开源项目使用那个插件,我认为Red MineThe Foreman一样。

编辑:不幸的是它只能做 ActiveRecord,不能做 ActiveResource。

于 2011-05-13T08:45:10.943 回答
4

Fivell,我刚看到这个问题,今晚在赏金到期之前没有时间进行更改,所以我会给你我的审计代码,它可以与 ActiveRecord 一起使用,并且应该可以与 ActiveResource 一起使用,也许还有一些调整(我不要经常使用 ARes 来临时知道)。我知道我们使用的回调在那里,但我不确定 ARes 是否有 ActiveRecord 的脏属性changes跟踪。

此代码记录所有模型上的每个 CREATE/UPDATE/DELETE(审计日志模型上的 CREATE 和您指定的任何其他异常除外),并将更改存储为 JSON。还存储了清理过的回溯,因此您可以确定进行更改的代码(这会捕获 MVC 中的任何点以及 rake 任务和控制台使用情况)。

此代码适用于控制台使用、rake 任务和 http 请求,尽管通常只有最后一个记录当前用户。(如果我没记错的话,这个替换的 ActiveRecord 观察者在 rake 任务或控制台中不起作用。)哦,这段代码来自 Rails 2.3 应用程序 - 我有几个 Rails 3 应用程序,但我不需要这种审计他们呢。

我没有可以很好地显示这些信息的代码(我们只在需要调查问题时才挖掘数据),但由于更改存储为 JSON,它应该相当简单。

首先,我们将当前用户存储在 User.current 中,以便在任何地方都可以访问它,所以在app/models/user.rb

Class User < ActiveRecord::Base
  cattr_accessor :current
  ...
end

当前用户在应用程序控制器中为每个请求设置,如下所示(并且不会导致并发问题):

def current_user
  User.current = session[:user_id] ? User.find_by_id(session[:user_id]) : nil
end

User.current如果有意义的话,你可以设置你的 rake 任务。

接下来,我们定义模型来存储审计信息app/models/audit_log_entry.rb- 您需要自定义IgnoreClassesRegEx以适应您不想审计的任何模型:

# == Schema Information
#
# Table name: audit_log_entries
#
#  id         :integer         not null, primary key
#  class_name :string(255)
#  entity_id  :integer
#  user_id    :integer
#  action     :string(255)
#  data       :text
#  call_chain :text
#  created_at :datetime
#  updated_at :datetime
#

class AuditLogEntry < ActiveRecord::Base
  IgnoreClassesRegEx = /^ActiveRecord::Acts::Versioned|ActiveRecord.*::Session|Session|Sequence|SchemaMigration|CronRun|CronRunMessage|FontMetric$/
  belongs_to :user

  def entity (reload = false)
    @entity = nil if reload
    begin
      @entity ||= Kernel.const_get(class_name).find_by_id(entity_id)
    rescue
      nil
    end
  end

  def call_chain
    return if call_chain_before_type_cast.blank?
    if call_chain_before_type_cast.instance_of?(Array)
      call_chain_before_type_cast
    else
      JSON.parse(call_chain_before_type_cast)
    end
  end
  def data
    return if data_before_type_cast.blank?
    if data_before_type_cast.instance_of?(Hash)
      data_before_type_cast
    else
      JSON.parse(data_before_type_cast)
    end
  end

  def self.debug_entity(class_name, entity_id)
    require 'fastercsv'
    FasterCSV.generate do |csv|
      csv << %w[class_name entity_id date action first_name last_name data]
      find_all_by_class_name_and_entity_id(class_name, entity_id,
                                           :order => 'created_at').each do |a|
        csv << [a.class_name, a.entity_id, a.created_at, a.action, 
          (a.user && a.user.first_name), (a.user && a.user.last_name), a.data]
      end
    end
  end
end

接下来我们添加一些方法来ActiveRecord::Base使审计工作。您需要查看该audit_log_clean_backtrace方法并根据您的需要进行修改。(FWIW,我们将添加到现有类中lib/extensions/*.rb,这些类在初始化程序中加载。)在lib/extensions/active_record.rb

class ActiveRecord::Base
  cattr_accessor :audit_log_backtrace_cleaner
  after_create  :audit_log_on_create
  before_update :save_audit_log_update_diff
  after_update  :audit_log_on_update
  after_destroy :audit_log_on_destroy
  def audit_log_on_create
    return if self.class.name =~ /AuditLogEntry/
    return if self.class.name =~ AuditLogEntry::IgnoreClassesRegEx
    audit_log_create 'CREATE', self, caller
  end
  def save_audit_log_update_diff
    @audit_log_update_diff = changes.reject{ |k,v| 'updated_at' == k }
  end
  def audit_log_on_update
    return if self.class.name =~ AuditLogEntry::IgnoreClassesRegEx
    return if @audit_log_update_diff.empty?
    audit_log_create 'UPDATE', @audit_log_update_diff, caller
  end
  def audit_log_on_destroy
    return if self.class.name =~ AuditLogEntry::IgnoreClassesRegEx
    audit_log_create 'DESTROY', self, caller
  end
  def audit_log_create (action, data, call_chain)
    AuditLogEntry.create :user       => User.current,
                         :action     => action,
                         :class_name => self.class.name,
                         :entity_id  => id,
                         :data       => data.to_json,
                         :call_chain => audit_log_clean_backtrace(call_chain).to_json
  end
  def audit_log_clean_backtrace (backtrace)
    if !ActiveRecord::Base.audit_log_backtrace_cleaner
      ActiveRecord::Base.audit_log_backtrace_cleaner = ActiveSupport::BacktraceCleaner.new
      ActiveRecord::Base.audit_log_backtrace_cleaner.add_silencer { |line| line =~ /\/lib\/rake\.rb/ }
      ActiveRecord::Base.audit_log_backtrace_cleaner.add_silencer { |line| line =~ /\/bin\/rake/ }
      ActiveRecord::Base.audit_log_backtrace_cleaner.add_silencer { |line| line =~ /\/lib\/(action_controller|active_(support|record)|hoptoad_notifier|phusion_passenger|rack|ruby|sass)\// }
      ActiveRecord::Base.audit_log_backtrace_cleaner.add_filter   { |line| line.gsub(RAILS_ROOT, '') }
    end
    ActiveRecord::Base.audit_log_backtrace_cleaner.clean backtrace
  end
end

最后,这是我们对此进行的测试 - 当然,您需要修改实际的测试操作。test/integration/audit_log_test.rb

require File.dirname(__FILE__) + '/../test_helper'

class AuditLogTest < ActionController::IntegrationTest
  def setup
  end

  def test_audit_log
    u = users(:manager)
    log_in u
    a = Alert.first :order => 'id DESC'
    visit 'alerts/new'
    fill_in 'alert_note'
    click_button 'Send Alert'
    a = Alert.first :order => 'id DESC', :conditions => ['id > ?', a ? a.id : 0]
    ale = AuditLogEntry.first :conditions => {:class_name => 'Alert', :entity_id => a.id }
    assert_equal 'Alert',  ale.class_name
    assert_equal 'CREATE', ale.action
  end

private

  def log_in (user, password = 'test', initial_url = home_path)
    visit initial_url
    assert_contain 'I forgot my password'
    fill_in 'email',    :with => user.email
    fill_in 'password', :with => password
    click_button 'Log In'
  end

  def log_out
    visit logout_path
    assert_contain 'I forgot my password'
  end
end

并且test/unit/audit_log_entry_test.rb

# == Schema Information
#
# Table name: audit_log_entries
#
#  id         :integer         not null, primary key
#  class_name :string(255)
#  action     :string(255)
#  data       :text
#  user_id    :integer
#  created_at :datetime
#  updated_at :datetime
#  entity_id  :integer
#  call_chain :text
#

require File.dirname(__FILE__) + '/../test_helper'

class AuditLogEntryTest < ActiveSupport::TestCase
  test 'should handle create update and delete' do
    record = Alert.new :note => 'Test Alert'
    assert_difference 'Alert.count' do
      assert_difference 'AuditLogEntry.count' do
        record.save
        ale = AuditLogEntry.first :order => 'created_at DESC'
        assert ale
        assert_equal 'CREATE', ale.action, 'AuditLogEntry.action should be CREATE'
        assert_equal record.class.name, ale.class_name, 'AuditLogEntry.class_name should match record.class.name'
        assert_equal record.id, ale.entity_id, 'AuditLogEntry.entity_id should match record.id'
      end
    end
    assert_difference 'AuditLogEntry.count' do
      record.update_attribute 'note', 'Test Update'
      ale = AuditLogEntry.first :order => 'created_at DESC'
      expected_data = {'note' => ['Test Alert', 'Test Update']}
      assert ale
      assert_equal 'UPDATE', ale.action, 'AuditLogEntry.action should be UPDATE'
      assert_equal expected_data, ale.data
      assert_equal record.class.name, ale.class_name, 'AuditLogEntry.class_name should match record.class.name'
      assert_equal record.id, ale.entity_id, 'AuditLogEntry.entity_id should match record.id'
    end
    assert_difference 'AuditLogEntry.count' do
      record.destroy
      ale = AuditLogEntry.first :order => 'created_at DESC'
      assert ale
      assert_equal 'DESTROY', ale.action, 'AuditLogEntry.action should be CREATE'
      assert_equal record.class.name, ale.class_name, 'AuditLogEntry.class_name should match record.class.name'
      assert_equal record.id, ale.entity_id, 'AuditLogEntry.entity_id should match record.id'
      assert_nil Alert.find_by_id(record.id), 'Alert should be deleted'
    end
  end

  test 'should not log AuditLogEntry create entry and block on update and delete' do
    record = Alert.new :note => 'Test Alert'
    assert_difference 'Alert.count' do
      assert_difference 'AuditLogEntry.count' do
        record.save
      end
    end
    ale = AuditLogEntry.first :order => 'created_at DESC'
    assert_equal 'CREATE', ale.action, 'AuditLogEntry.action should be CREATE'
    assert_equal record.class.name, ale.class_name, 'AuditLogEntry.class_name should match record.class.name'
    assert_equal record.id, ale.entity_id, 'AuditLogEntry.entity_id should match record.id'
    assert_nil AuditLogEntry.first(:conditions => { :class_name => 'AuditLogEntry', :entity_id => ale.id })

    if ale.user_id.nil?
      u = User.first
    else
      u = User.first :conditions => ['id != ?', ale.user_id]
    end
    ale.user_id = u.id
    assert !ale.save

    assert !ale.destroy
  end
end
于 2011-05-29T03:29:21.823 回答
3

https://github.com/collectiveidea/acts_as_audited

https://github.com/airblade/paper_trail

都是很好的解决方案ActiveRecord,但由于大部分内容ActiveRecord已被提取到ActiveModel,因此扩展其中任何一个以支持ActiveResource也是合理的,至少对于只读支持。我浏览了 Github 网络图并四处搜索,似乎没有任何正在进行的解决方案开发,但我希望在这两个插件之一之上实现比从头开始更容易。 paper_trail似乎处于更积极的开发阶段,并且对 Rails 3.1 有一些提交,因此它可能与 Rails 内部结构更新并更容易扩展,但这只是一种直觉——我对两者的内部结构都不熟悉。

于 2011-05-24T21:46:11.937 回答
1

act_as_audited gem 应该适合您:
https ://github.com/collectiveidea/acts_as_audited

并且就 ActiveResource 而言,它也将是其他一些应用程序中的模型。您可以在服务器端使用 gem,而无需在客户端对其进行审核。所有使用 ActiveResource 的 CRUD 操作最终都会转换为 ActiveRecord(在服务器端)上的 CRUD 操作。

因此,您可能需要从远处看,相同的解决方案将适用于这两种情况,但在不同的地方。

于 2011-05-22T12:24:19.333 回答
1

为了跟踪用户活动(CRUD),我创建了一个继承自 Logger 的类,现在我打算编写一个用于跟踪用户的小插件,我可以将它用于构建的任何 ROR 应用程序。我已经检查过是否有这样的插件,但我没有看到。我想有很多 gem,比如 paper-trail、acts_as_audited 或 itslog,但我更喜欢使用插件。有什么建议么?这是一个可能对您有帮助的链接:http ://robaldred.co.uk/2009/01/custom-log-files-for-your-ruby-on-rails-applications/comment-page-1/#comment- 342

不错的编码

于 2011-06-29T08:02:09.463 回答
0

看看这个 railscast,也许它可以帮助你:通知

于 2011-05-26T10:28:42.083 回答