class SlackLogDevice::Formatter

Constants

DEFAULT_ICON_EMOJIS
MAX_MESSAGE_LENGTH

Attributes

extra_metadata[R]
max_backtrace_lines[R]

Public Class Methods

new(options = {}, &block) click to toggle source
# File lib/slack_log_device/formatter.rb, line 17
def initialize(options = {}, &block)
  options.assert_valid_keys(:disable_default_metadata, :extra_metadata, :icon_emoji, :icon_emojis, :max_backtrace_lines)
  self.disable_default_metadata = options[:disable_default_metadata]
  self.extra_metadata = options.key?(:extra_metadata) ? options[:extra_metadata] : {}
  self.max_backtrace_lines = options.key?(:max_backtrace_lines) ? options[:max_backtrace_lines] : 10

  @icon_emojis = DEFAULT_ICON_EMOJIS.dup
  self.icon_emojis = options[:icon_emojis] if options.key?(:icon_emojis)
  self.icon_emoji = options[:icon_emoji] if options.key?(:icon_emoji)

  @message_converter = block_given? ? Proc.new(&block) : -> (message) { message }
end

Public Instance Methods

call(severity, datetime, progname, message) click to toggle source
# File lib/slack_log_device/formatter.rb, line 30
def call(severity, datetime, progname, message)
  text = "*`#{severity}`*"
  text << " (*#{to_utf8(progname)}*)" if progname.present?
  text << ':'
  if message.is_a?(Exception)
    exception = message
    text << " A `#{exception.class}` occurred: #{convert_message(exception.message)}".rstrip
    text = truncate(text)
    text = append_metadata(text, exception)
    text = append_exception_backtrace(text, exception)
    text = append_exception_cause(text, exception)
  else
    text << " #{convert_message(message)}".rstrip
    text = append_metadata(text, message)
  end
  Message.new(truncate(text), icon_emoji: icon_emoji(severity))
end
disable_default_metadata=(value) click to toggle source
# File lib/slack_log_device/formatter.rb, line 48
def disable_default_metadata=(value)
  @disable_default_metadata = value.present?
end
disable_default_metadata?() click to toggle source
# File lib/slack_log_device/formatter.rb, line 52
def disable_default_metadata?
  @disable_default_metadata
end
extra_metadata=(value) click to toggle source
# File lib/slack_log_device/formatter.rb, line 56
def extra_metadata=(value)
  @extra_metadata = (value.presence || {})
end
icon_emoji(severity) click to toggle source
# File lib/slack_log_device/formatter.rb, line 60
def icon_emoji(severity)
  @icon_emojis[parse_severity(severity)]
end
icon_emoji=(value) click to toggle source
# File lib/slack_log_device/formatter.rb, line 64
def icon_emoji=(value)
  value = value.to_s.strip.presence
  @icon_emojis.keys.each do |severity|
    @icon_emojis[severity] = value
  end
end
icon_emojis() click to toggle source
# File lib/slack_log_device/formatter.rb, line 71
def icon_emojis
  @icon_emojis.freeze
end
icon_emojis=(values = {}) click to toggle source
# File lib/slack_log_device/formatter.rb, line 75
def icon_emojis=(values = {})
  values.each do |severity, emoji|
    @icon_emojis[parse_severity(severity)] = emoji.to_s.strip.presence
  end
  @icon_emojis
end
max_backtrace_lines=(value) click to toggle source
# File lib/slack_log_device/formatter.rb, line 82
def max_backtrace_lines=(value)
  length = Integer(value) rescue nil
  raise ArgumentError.new("Invalid max backtrace lines: #{value.inspect}") if length.nil? || length < -1
  @max_backtrace_lines = length
end

Private Instance Methods

append_exception_backtrace(text, exception) click to toggle source
# File lib/slack_log_device/formatter.rb, line 90
def append_exception_backtrace(text, exception)
  backtrace = format_backtrace(exception, MAX_MESSAGE_LENGTH - text.size - 2)
  backtrace.present? ? "#{to_utf8(text)}\n\n#{backtrace}" : text
end
append_exception_cause(text, exception) click to toggle source
# File lib/slack_log_device/formatter.rb, line 95
def append_exception_cause(text, exception)
  cause = exception.cause
  text = to_utf8(text)
  return text if cause.nil?
  message = "\n\nCaused by `#{cause.class}`"
  return text if (text + message).size > MAX_MESSAGE_LENGTH
  text = truncate("#{text}#{message}: #{to_utf8(cause.message)}")
  text = append_exception_backtrace(text, cause)
  append_exception_cause(text, cause)
end
append_metadata(text, message) click to toggle source
# File lib/slack_log_device/formatter.rb, line 106
def append_metadata(text, message)
  metadata = format_metadata(message, MAX_MESSAGE_LENGTH - text.size - 2)
  metadata.present? ? "#{to_utf8(text)}\n\n#{metadata}" : text
end
convert_message(message) click to toggle source
# File lib/slack_log_device/formatter.rb, line 111
def convert_message(message)
  to_utf8(@message_converter.call(to_utf8(message.to_s.strip)).to_s.strip)
end
default_metadata(request) click to toggle source
# File lib/slack_log_device/formatter.rb, line 115
def default_metadata(request)
  metadata = {}
  return metadata if disable_default_metadata?
  metadata.merge!({
    'Method' => request.method,
    'URL' => request.url,
    'Remote address' => request.remote_addr,
    'User-Agent' => request.user_agent,
  }) if request.present?
  metadata.merge!({
    'User' => ENV['USER'],
    'Machine' => Socket.gethostname,
    'PID' => Process.pid,
  })
  metadata.keys.each do |key|
    value = metadata[key]
    metadata[key] = "`#{to_utf8(value.to_s.strip)}`" if value.present?
  end
  metadata
end
format_backtrace(exception, size_available) click to toggle source
# File lib/slack_log_device/formatter.rb, line 136
def format_backtrace(exception, size_available)
  return nil if max_backtrace_lines == 0 || size_available < 7
  backtrace = (exception.backtrace || []).select(&:present?).compact.map { |line| to_utf8(line) }
  return nil if backtrace.empty?
  if max_backtrace_lines < 0
    text = backtrace.join("\n")
  else
    text = backtrace[0, max_backtrace_lines].join("\n")
    text << "\n..." if backtrace.size > max_backtrace_lines
  end
  "```#{truncate(text, size_available - 6)}```"
end
format_metadata(message, size_available) click to toggle source
# File lib/slack_log_device/formatter.rb, line 149
def format_metadata(message, size_available)
  return nil if size_available < 11
  options = {}
  options[:exception] = message if message.is_a?(Exception)
  request = Thread.current[:slack_log_device_request]
  options[:request] = request if request.present?
  text = default_metadata(request).merge(extra_metadata).map do |name, value|
    value = value.call(options) if value.respond_to?(:call)
    value.present? ? "• *#{to_utf8(name).strip}*: #{to_utf8(value).strip}" : nil
  end.compact.join("\n")
  return nil if text.blank?
  truncate(text, size_available)
end
parse_severity(value) click to toggle source
# File lib/slack_log_device/formatter.rb, line 163
def parse_severity(value)
  severity = value.to_s.strip.upcase
  return severity if DEFAULT_ICON_EMOJIS.key?(severity)
  raise("Invalid log severity: #{value.inspect}")
end
to_utf8(text) click to toggle source
# File lib/slack_log_device/formatter.rb, line 176
def to_utf8(text)
  return text if text.nil? || text.encoding == Encoding::UTF_8
  text.encode(Encoding::UTF_8) rescue text.dup.force_encoding(Encoding::UTF_8)
end
truncate(message, max_length = MAX_MESSAGE_LENGTH) click to toggle source
# File lib/slack_log_device/formatter.rb, line 169
def truncate(message, max_length = MAX_MESSAGE_LENGTH)
  message = message.strip
  return message if message.size <= max_length
  return message[0, max_length] if max_length < 3
  to_utf8("#{message[0, max_length - 3]}...")
end