2017-03-13 1 views
15

異なるデータベースへの複数の接続を持つActiveRecordアプリケーションでは、ログにはどのクエリがどのデータベースに送られたかが示されません。データベースを分離するために、これらのクエリ:どのデータベースにどのクエリが送られたかを示すActiveRecordログ

Base1.connection.select_value("select * from foo") 
Base2.connection.select_value("select * from foo") 

は、これらのログエントリを放ち:

D, [2017-03-13T09:27:11.844395 #22112] DEBUG -- : (0.6ms) select * from foo 
D, [2017-03-13T09:27:11.844539 #22112] DEBUG -- : (0.1ms) select * from foo 

は、どのように私はActiveRecordのデータベースのログは、クエリはに対して行われたデータベースを示すために、原因となることができますか?

スタンドアロン例

begin 
    require "bundler/inline" 
rescue LoadError => e 
    $stderr.puts "Bundler version 1.10 or later is required." 
    raise e 
end 

gemfile(true) do 
    source "https://rubygems.org" 
    # Activate the gem you are reporting the issue against. 
    gem "activerecord", "4.2.8" 
    gem "sqlite3" 
end 

require "active_record" 
require "logger" 

class Base1 < ActiveRecord::Base 
    self.abstract_class = true 
end 

class Base2 < ActiveRecord::Base 
    self.abstract_class = true 
end 

Base1.establish_connection(adapter: "sqlite3", database: ":memory:") 
Base2.establish_connection(adapter: "sqlite3", database: ":memory:") 

ActiveRecord::Base.logger = Logger.new(STDOUT) 

Base1.connection.execute("create table foo(i int)") 
Base2.connection.execute("create table foo(i int)") 
Base1.connection.execute("insert into foo(i) values (1)") 
Base2.connection.execute("insert into foo(i) values (2)") 
raise unless Base1.connection.select_value("select * from foo") == 1 
raise unless Base2.connection.select_value("select * from foo") == 2 

出力:

D, [2017-03-13T09:27:11.842939 #22112] DEBUG -- : (0.2ms) create table foo(i int) 
D, [2017-03-13T09:27:11.843478 #22112] DEBUG -- : (0.2ms) create table foo(i int) 
D, [2017-03-13T09:27:11.843612 #22112] DEBUG -- : (0.1ms) insert into foo(i) values (1) 
D, [2017-03-13T09:27:11.843720 #22112] DEBUG -- : (0.0ms) insert into foo(i) values (2) 
D, [2017-03-13T09:27:11.844395 #22112] DEBUG -- : (0.6ms) select * from foo 
D, [2017-03-13T09:27:11.844539 #22112] DEBUG -- : (0.1ms) select * from foo 

私が変更できるように、私は、各接続に独自のロガーを与える試み各接続

ための別個のロガーを作成しようとしました各ログのフォーマット:

Base1.logger = Logger.new(STDOUT) 
Base2.logger = Logger.new(STDOUT) 

しかし、残念ながら、例外発生しません。この線で示すように、ActiveRecordので唯一のロガーがあるように表示されます。

  • ルビー2.3.3
  • raise unless Base1.logger.object_id == Base2.logger.object_id 
    

    バージョンこの例ではActiveRecord 4.2.8
  • 、sqlite3の1.13.3(1)の製造において
  • 、mysql2 0.4.5(1)とのActiveRecord-SQLServerのアダプタ4.2.15(1)

(1)この問題は、特定のデータベースアダプタに固有の問題ではありません。私は完全版のためにアダプターのバージョンを記載しました。

答えて

5

ActiveRecordをネイティブで使用することはできません。

class ActiveRecord::ConnectionAdapters::AbstractAdapter 
    alias :original_log :log 
    def log(sql, name = "SQL", binds = [], statement_name = nil) 
    #add info that you want to display to name 
    name = "#{name} #{@connection.hash}" 
    original_log(sql, name, binds, statement_name) { yield } 
    end 
end 

出力:あなたは本当にそれが必要な場合しかし、あなたはAbstractAdapterlogメソッドをオーバーライドすることができ、より人間に優しい

D, [2017-03-15T20:55:59.200533 #73440] DEBUG -- : -4111614587995646180 (0.5ms) create table foo(i int) 
D, [2017-03-15T20:55:59.201178 #73440] DEBUG -- : -4097137311320758185 (0.1ms) create table foo(i int) 
D, [2017-03-15T20:55:59.201298 #73440] DEBUG -- : -4111614587995646180 (0.0ms) insert into foo(i) values (1) 
D, [2017-03-15T20:55:59.201426 #73440] DEBUG -- : -4097137311320758185 (0.1ms) insert into foo(i) values (2) 
D, [2017-03-15T20:55:59.202229 #73440] DEBUG -- : -4111614587995646180 (0.7ms) select * from foo 
D, [2017-03-15T20:55:59.202360 #73440] DEBUG -- : -4097137311320758185 (0.0ms) select * from foo 

上記のコードは、接続ハッシュを記録し、それはですある接続を他の接続と区別するのに十分です。それより人間に優しいものをログに記録する場合は、ややこしいことをする必要があります。

私たちができることは、人にやさしい接続名を返すメソッドで抽象アダプタを飾ることです。あなたのプログラムの初期化中に、各接続アダプタに#log_nameメソッドを追加します。

class ActiveRecord::ConnectionAdapters::AbstractAdapter 
    alias :original_log :log 
    def log(sql, name = "SQL", binds = [], statement_name = nil) 
    connection_name = respond_to?(:log_name) ? log_name : nil 
    name = [connection_name, name].compact.join(" ") 
    original_log(sql, name, binds, statement_name) { yield } 
    end 
end 

出力:

D, [2017-03-21T10:10:53.330021 #22147] DEBUG -- : one (0.3ms) create table foo(i int) 
D, [2017-03-21T10:10:53.330380 #22147] DEBUG -- : two (0.2ms) create table foo(i int) 
D, [2017-03-21T10:10:53.330464 #22147] DEBUG -- : one (0.0ms) insert into foo(i) values (1) 
D, [2017-03-21T10:10:53.330536 #22147] DEBUG -- : two (0.0ms) insert into foo(i) values (2) 
D, [2017-03-21T10:10:53.331002 #22147] DEBUG -- : one (0.4ms) select * from foo 
D, [2017-03-21T10:10:53.331104 #22147] DEBUG -- : two (0.0ms) select * from foo 
1

Base1.connection.define_singleton_method(:log_name) { "one" } 
Base2.connection.define_singleton_method(:log_name) { "two" } 

モンキーパッチは現在、#log_nameメソッドを使用することができます

あなたは(サル)パッチActiveRecord::ConnectionAdapters::AbstractAdapterを持っていなければなりません。

# config/initializers/adapter_monkeypatch.rb 
class ActiveRecord::ConnectionAdapters::AbstractAdapter 
    alias :original_log :log 
    def log(sql, *args, &block) 
    prefix = self.class.to_s.demodulize 

    original_log("#{prefix}: #{sql}", *args, &block) 
    end 
end 

あなたはあなただけの接頭辞としてアダプタのCLASS_NAMEを使用することができ、異なるデータベースを使用しているとして:

patched rails logger をしかし、それはあなたの要件に、より関連するものにプレフィックスを変更するのは簡単です。

+0

ログメッセージでアダプタのクラス名を使用することをお勧めします。私の場合、私は同じデータベースエンジンに複数の接続を持っているので、私にとってはうまくいかないでしょうが、@ idejの答えに加えたトリッキーよりも簡単で多くのことができます。 –

+0

ああ、私は間違ってあなたが異なるDBエンジンを持っていると思った。あなたはデバッガに落として、 'self'を調べることができます。これは、接続に関する多くのデータを持つ接続オブジェクトです。識別子として有用なものを使用できます。 –

+0

あなたは間違ったことはありません:私は異なるDBエンジンを持っています。私はまた、これらのエンジンの1つに複数の接続を持っています。私のアプリケーションは面倒です! –

2

@idej答えは良いですが、別のアプローチを追加したいと思います。 ActiveSupportも使用している場合は、sql.active_recordイベントに登録することができます。

ActiveSupport::Notifications.subscribe("sql.active_record") do |*args| 
    event = ActiveSupport::Notifications::Event.new(*args) 

    puts event.inspect 
end 
あなたは sqlのようないくつかの有用な属性へのアクセス権を持っている event.payload

connection_idbindsなどの問題は、あなたがそれらの属性を自分でログインし、任意のSQLクエリのためのメッセージがログに2回表示される必要があるということです。しかし、クラスを変更する必要はありません。

詳細はdocsにあります。