<commit_before># frozen_string_literal: true
require 'logger'
require 'ltsv'
require 'ltsv_logger_formatter/version'

class LtsvLoggerFormatter < ::Logger::Formatter
  attr_accessor :severity_key, :time_key, :progname_key

  # Initialize formatter.
  #
  # @param [String] datetime_format Optional, datetime format string. Default is '%Y-%m-%dT%H:%M:%S.%6N'.
  # @param [Symbol] severity_key Optional, key for severity. Default is :level.
  # @param [Symbol] time_key Optional, key for time. Default is :time.
  # @param [Symbol] progname_key Optional, key for progname. Default is :progname.
  # @param [Object] filter Optional, object which responds to #filter, e.g. ActionDispatch::Http::ParameterFilter.
  #   This object used to filter parameter in hash such as 'password'.
  def initialize(datetime_format: '%Y-%m-%dT%H:%M:%S.%6N',
                 severity_key: :level, time_key: :time, progname_key: :progname, filter: nil)
    super()
    self.datetime_format = datetime_format
    @severity_key = severity_key
    @time_key = time_key
    @progname_key = progname_key
    @filter = filter
  end

  # Return formatted string using arguments.
  #
  # This method is expected to call by ::Logger
  #
  # @param [String] severity
  # @param [Time] time
  # @param [String] progname
  # @param [Hash, Exception, Object] data Data for logging,
  #   If data is Exception, then #message, #class and #backtrace is logged,
  #   or else if data can be respond to #to_hash, then #to_hash result is logged,
  #   or else #to_s result is logged with :message key.
  #   Hash, String, Exception or Object respond_to :to_hash can be used.
  def call(severity, time, progname, data)
    log_data = { @severity_key => severity, @time_key => format_datetime(time) }
    if progname
      log_data.merge!(@progname_key => progname)
    end
    log_data.merge!(format_data(data))
    ::LTSV.dump(log_data) + "\n"
  end

  private

  def format_data(data)
    return { message: data.message, class: data.class, backtrace: Array(data.backtrace).join('\n') } if data.is_a? Exception
    return { message: data.to_s } unless data.respond_to?(:to_hash)

    @filter ? @filter.filter(data.to_hash) : data.to_hash
  end
end<commit_msg>Use Hash#[]= instead of merge! due to performance<commit_after># frozen_string_literal: true
require 'logger'
require 'ltsv'
require 'ltsv_logger_formatter/version'

class LtsvLoggerFormatter < ::Logger::Formatter
  attr_accessor :severity_key, :time_key, :progname_key

  # Initialize formatter.
  #
  # @param [String] datetime_format Optional, datetime format string. Default is '%Y-%m-%dT%H:%M:%S.%6N'.
  # @param [Symbol] severity_key Optional, key for severity. Default is :level.
  # @param [Symbol] time_key Optional, key for time. Default is :time.
  # @param [Symbol] progname_key Optional, key for progname. Default is :progname.
  # @param [Object] filter Optional, object which responds to #filter, e.g. ActionDispatch::Http::ParameterFilter.
  #   This object used to filter parameter in hash such as 'password'.
  def initialize(datetime_format: '%Y-%m-%dT%H:%M:%S.%6N',
                 severity_key: :level, time_key: :time, progname_key: :progname, filter: nil)
    super()
    self.datetime_format = datetime_format
    @severity_key = severity_key
    @time_key = time_key
    @progname_key = progname_key
    @filter = filter
  end

  # Return formatted string using arguments.
  #
  # This method is expected to call by ::Logger
  #
  # @param [String] severity
  # @param [Time] time
  # @param [String] progname
  # @param [Hash, Exception, Object] data Data for logging,
  #   If data is Exception, then #message, #class and #backtrace is logged,
  #   or else if data can be respond to #to_hash, then #to_hash result is logged,
  #   or else #to_s result is logged with :message key.
  #   Hash, String, Exception or Object respond_to :to_hash can be used.
  def call(severity, time, progname, data)
    log_data = { @severity_key => severity, @time_key => format_datetime(time) }
    if progname
      log_data[@progname_key] = progname
    end
    log_data.merge!(format_data(data))
    ::LTSV.dump(log_data) + "\n"
  end

  private

  def format_data(data)
    return { message: data.message, class: data.class, backtrace: Array(data.backtrace).join('\n') } if data.is_a? Exception
    return { message: data.to_s } unless data.respond_to?(:to_hash)

    @filter ? @filter.filter(data.to_hash) : data.to_hash
  end
end
{'fraction_non_alphanumeric': 0.1141961486789073, 'fraction_numerical': 0.0008956560680698612, 'mean_word_length': 3.957824639289678, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 8, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7122134', 'n_tokens_mistral': 1501, 'n_tokens_neox': 1438, 'n_words': 444}