Как отключить ведение журнала ActiveRecord для определенного столбца?

У меня возникла проблема, которая, на мой взгляд, должна быть проблемой для большинства пользователей rails, но я пока не нашел для нее никакого решения.

Когда, например, выполняя загрузку файла потенциально большого двоичного файла и сохраняя его в базе данных, вы, разумеется, не хотите, чтобы рельсы или ActiveRecord регистрировали это конкретное поле в режиме разработки (файл журнала, stdout), В случае довольно большого файла это приводит к тому, что выполнение запроса прерывается и почти убивает мой терминал.

Есть ли какой-либо надежный и не хаккий способ отключения ведения журнала для определенных полей? Помните, я не говорю об отключении ведения журнала для параметров запроса - это было решено довольно хорошо.

Спасибо за любую информацию об этом!

Ответ 1

Если это кому-то помогает, вот версия вышеописанной версии Rails 4.1, которая также включает в себя редактирование не двоичных параметров привязки (например, текстового или json-столбца) и увеличивает регистрацию до 100 char перед редактированием. Спасибо всем за помощь!

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

Ответ 2

Создать файл в config/initializers 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

Это будет транслировать все поля длиной более 20 символов в журнале вывода.

Ответ 3

ПРИМЕЧАНИЕ: Работает с рельсами 3, но, по-видимому, не 4 (который не был выпущен, когда был дан ответ на этот вопрос)

В файле application.rb:

config.filter_parameters << :parameter_name

Это приведет к удалению этого атрибута из отображения в ваших журналах, заменив его на [FILTERED] Обычный вариант использования фильтрующих параметров - это, конечно, пароли, но я не вижу причин, по которым он не должен работать с вашим двоичным файлом.

Ответ 4

Здесь реализуется подход, предложенный @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 символов, тогда "[REDACTED xx chars]" будет длиннее, чем замененный текст, поэтому нет смысла), Я не проводил тестирование производительности, чтобы определить, было ли более медленным использование жадного или ленивого повторения для отредактированного фрагмента. Мой инстинкт должен был лениться, поэтому я сделал это, но возможно, что жадный будет быстрее, особенно если в SQL есть только одно двоичное поле.

Ответ 5

Я тоже не нашел этого, хотя одна вещь, которую вы могли бы сделать, это

ActiveRecord::Base.logger = nil

полностью отключить ведение журнала, хотя вы, вероятно, не захотите этого делать. Лучшее решение может заключаться в том, чтобы установить регистратор ActiveRecord на какой-либо пользовательский подкласс, который не регистрирует сообщения по определенному размеру, или делает что-то более умное, чтобы анализировать отдельные разделы слишком большого сообщения.

Это не кажется идеальным, но это похоже на работоспособное решение, хотя я не рассматривал конкретные детали реализации. Мне было бы очень интересно услышать лучшие решения.

Ответ 6

Я столкнулся с одной и той же проблемой, но я не мог понять, как решить проблему. Я закончил писать пользовательский форматировщик для журнала Rails, который отфильтровывает blob.

Вышеупомянутый код должен быть помещен в config/initializers и заменить file_dat a на столбец, который вы хотите удалить, и file_name на столбец, который появляется после в регулярном выражении.

Ответ 7

Вот версия 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

Ответ 8

В рельсах 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_data, value и timeline например.