2012-10-24 56 views
10

我遇到了一個問題,在我看來,這對大部分的用戶來說都是一個問題,但我還找不到任何解決方案。如何禁用某個列的ActiveRecord日誌記錄?

例如,當執行可能較大的二進制文件的文件上傳並將其存儲在數據庫中時,您絕對不希望rails或ActiveRecord以開發模式記錄此特定字段(日誌文件,標準輸出)。如果文件相當大,這會導致查詢執行中斷並幾乎殺死我的終端。

是否有任何可靠和非hacky方法禁用特定字段的日誌記錄?記住,我是而不是談論禁用請求參數的日誌記錄 - 這已經很好地解決了。

感謝您的任何信息!

+0

有一個選項可以像密碼一樣過濾,不知道這是否有幫助。你會考慮重寫ActiveRecord執行hacky嗎?如果不是那麼你的提示。 –

回答

0

我沒有找到太多有關這兩種,但有一兩件事你可以做的是

ActiveRecord::Base.logger = nil 

禁用完全記錄,但你可能不希望這樣做。一個更好的解決方案可能是將ActiveRecord記錄器設置爲某個自定義子類,該子類不會記錄超過特定大小的消息,或者做一些更智能的解析出的消息的特定部分太大。

這似乎並不理想,但它確實看起來像一個可行的解決方案,但我沒有看具體的實施細節。我會很樂意聽到任何更好的解決方案。

+0

是的,這是我想到的唯一可能性,但我覺得這個解決方案相當醜陋(正如您所提到的),因爲您肯定希望呼叫被記錄,但不是該字段。如果沒有「完美」的答案,我可能會繼續實施這樣的自定義記錄器,它可以解析日誌語句......也很難。 – Remo

+0

看起來你想要重寫Logger上的「add」方法,subclassed版本可以進行解析,然後調用超類add方法,傳遞解析的消息和其他參數。 –

4

注:工程與軌道3,但顯然不是4時(這個問題得到回答這是不公佈)

在你的application.rb中的文件:

config.filter_parameters << :parameter_name 

這將刪除屬性顯示在日誌中,用[FILTERED]替換它 過濾參數的常見用例當然是密碼,但我沒有理由認爲它不適用於二進制文件字段。

+1

這不僅僅是過濾HTTP請求參數而不是SQL日誌中的參數? – Remo

+0

不,它過濾軌道日誌中的所有內容。傳入請求和記錄的SQL語句都會顯示[過濾] – agmin

+0

謝謝!我會嘗試一下並儘快報告。 – Remo

5

創建配置/初始化文件whitch修改ActiveRecord::ConnectionAdapters::AbstractAdapter像這樣:

class ActiveRecord::ConnectionAdapters::AbstractAdapter 
    protected 

    def log_with_trunkate(sql, name="SQL", binds=[], &block) 
    b = binds.map {|k,v| 
     v = v.truncate(20) if v.is_a? String and v.size > 20 
     [k,v] 
    } 
    log_without_trunkate(sql, name, b, &block) 
    end 

    alias_method_chain :log, :trunkate 
end 

這將trunkate是在輸出日誌中超過20個字符的所有領域。

+0

還應該注意,這隻會截斷插入請求。在更新的時候,所有的東西都會被引入到sql param中,因此也需要被截斷。 – Patrik

+0

在Rails 3.0.9的'log_without_trunkate'調用中參數的錯誤數目(3代表2)。 – CHsurfer

+0

3.0.x rails的日誌方法使用只有兩個參數'sql'和'name'的日誌方法。所以從參數中刪除綁定和塊,並從sql參數中篩選出來。 – Patrik

0

我遇到了同樣的問題,但我找不出一個乾淨的解決方案來解決問題。我最終爲寫出a custom formatter的Rails記錄器過濾掉了blob。

上面的代碼需要放在config/initializers中,並將file_data替換爲您想要刪除的列,並將file_name替換爲正則表達式後顯示的列。

3

下面是@Patrik建議的方法的實現,該方法可用於針對PostgreSQL的插入和更新。正則表達式可能需要根據其他數據庫的SQL格式進行調整。

class ActiveRecord::ConnectionAdapters::AbstractAdapter 
    protected 

    def log_with_binary_truncate(sql, name="SQL", binds=[], &block) 
    binds = binds.map do |col, data| 
     if col.type == :binary && data.is_a?(String) && data.size > 27 
     data = "#{data[0,10]}[REDACTED #{data.size - 20} bytes]#{data[-10,10]}" 
     end 
     [col, data] 
    end 

    sql = sql.gsub(/(?<='\\x[0-9a-f]{20})[0-9a-f]{20,}?(?=[0-9a-f]{20}')/) do |match| 
     "[REDACTED #{match.size} chars]" 
    end 

    log_without_binary_truncate(sql, name, binds, &block) 
    end 

    alias_method_chain :log, :binary_truncate 
end 

我對此並不滿意,但現在已經足夠了。它保留二進制字符串的第一個和最後10個字節,並指示從中間移除了多少個字節/字符。它不會編輯,除非編輯的文本比替換的文本長(即如果沒有至少20個字符要刪除,那麼「[編輯xx字符]」會比替換的文本更長,所以沒有意義) 。我沒有進行性能測試,以確定對編輯塊使用貪婪還是懶惰重複的速度更快。我的直覺是懶惰,所以我做了,但是貪婪可能會更快,特別是如果SQL中只有一個二進制字段。

+0

恕我直言。這是這個問題的最佳解決方案。 – reto

+0

這是mysql的正則表達式。你想把它添加到你的解決方案? /(?<= X'[0-9A-F] {20})[0-9A-F] {20,}?(?= [0-9a-f] {20}')/ – reto

6

如果這有助於任何人,下面是上面代碼片段的Rails 4.1兼容版本,其中還包括對非二進制綁定參數(例如文本或json列)的編輯,並在編輯之前將日誌記錄增加到100個字符。感謝大家的幫助!

class ActiveRecord::ConnectionAdapters::AbstractAdapter 
    protected 

    def log_with_binary_truncate(sql, name="SQL", binds=[], statement_name = nil, &block) 
    binds = binds.map do |col, data| 
     if data.is_a?(String) && data.size > 100 
     data = "#{data[0,10]} [REDACTED #{data.size - 20} bytes] #{data[-10,10]}" 
     end 
     [col, data] 
    end 

    sql = sql.gsub(/(?<='\\x[0-9a-f]{100})[0-9a-f]{100,}?(?=[0-9a-f]{100}')/) do |match| 
     "[REDACTED #{match.size} chars]" 
    end 

    log_without_binary_truncate(sql, name, binds, statement_name, &block) 
    end 

    alias_method_chain :log, :binary_truncate 
end 
+1

這絕對適用於Rails 4.2,但不適用於5.0+。盡我所能,在Rails 5中不需要它,因爲Rails似乎內置了類似的功能。 – Ritchie

+1

@Ritchie內置的功能是什麼? – abonec

0

這是一個Rails 5版本。開箱即用Rails 5截取二進制數據,但不是長文本列。

module LogTruncater 
    def render_bind(attribute) 
    num_chars = Integer(ENV['ACTIVERECORD_SQL_LOG_MAX_VALUE']) rescue 120 
    half_num_chars = num_chars/2 
    value = if attribute.type.binary? && attribute.value 
     if attribute.value.is_a?(Hash) 
     "<#{attribute.value_for_database.to_s.bytesize} bytes of binary data>" 
     else 
     "<#{attribute.value.bytesize} bytes of binary data>" 
     end 
    else 
     attribute.value_for_database 
    end 

    if value.is_a?(String) && value.size > num_chars 
     value = "#{value[0,half_num_chars]} [REDACTED #{value.size - num_chars} chars] #{value[-half_num_chars,half_num_chars]}" 
    end 

    [attribute.name, value] 
    end 

end 

class ActiveRecord::LogSubscriber 
    prepend LogTruncater 
end 
0

在軌5,你可以把它初始化:

module SqlLogFilter 

    FILTERS = Set.new(%w(geo_data value timeline)) 
    def render_bind(attribute) 
    return [attribute.name, '<filtered>'] if FILTERS.include?(attribute.name) 
    super 
    end 

end 
ActiveRecord::LogSubscriber.prepend SqlLogFilter 

對於過濾器屬性geo_datavaluetimeline例如。