class Lorekeeper::JSONLogger

The JSONLogger provides a logger which will output messages in JSON format

Constants

BLACKLISTED_FINGERPRINT
DATA
DATE_FORMAT
EXCEPTION
LEVEL
MESSAGE
STACK
THREAD_KEY
TIMESTAMP

Public Class Methods

new(file) click to toggle source
Calls superclass method Lorekeeper::FastLogger::new
# File lib/lorekeeper/json_logger.rb, line 9
def initialize(file)
  reset_state
  @base_fields = { MESSAGE => '', TIMESTAMP => '', LEVEL => '' }
  @backtrace_cleaner = set_backtrace_cleaner
  super(file)
end

Public Instance Methods

add_fields(fields) click to toggle source
# File lib/lorekeeper/json_logger.rb, line 53
def add_fields(fields)
  remove_invalid_fields(fields)
  state.fetch(:base_fields).merge!(fields)
end
add_thread_unsafe_fields(fields) click to toggle source
# File lib/lorekeeper/json_logger.rb, line 40
def add_thread_unsafe_fields(fields)
  remove_invalid_fields(fields)
  @base_fields.merge!(fields)
  reset_state # Forcing to recreate the thread safe information
end
current_fields() click to toggle source
# File lib/lorekeeper/json_logger.rb, line 16
def current_fields
  state[:base_fields]
end
exception(exception, custom_message = nil, custom_data = nil, custom_level = :error, message: nil, data: nil, level: nil) click to toggle source

@param exception: instance of a class inheriting from Exception By default message comes from exception.message Optional and named parameters to overwrite message, level and add data

# File lib/lorekeeper/json_logger.rb, line 67
def exception(exception, custom_message = nil, custom_data = nil, custom_level = :error,
                         message: nil, data: nil, level: nil) # Backwards compatible named params

  param_level = level || custom_level
  param_data = data || custom_data
  param_message = message || custom_message

  log_level = METHOD_SEVERITY_MAP[param_level] || ERROR

  if exception.is_a?(Exception)
    backtrace = clean_backtrace(exception.backtrace || [])
    exception_fields = {
      EXCEPTION => "#{exception.class}: #{exception.message}",
      STACK => backtrace
    }
    exception_fields[DATA] = param_data if param_data

    message = param_message || exception.message
    with_extra_fields(exception_fields) { log_data(log_level, message) }
  else
    log_data(METHOD_SEVERITY_MAP[:warn], 'Logger exception called without exception class.')
    message = "#{exception.class}: #{exception.inspect} #{param_message}"
    with_extra_fields(DATA => (param_data || {})) { log_data(log_level, message) }
  end
end
inspect() click to toggle source
# File lib/lorekeeper/json_logger.rb, line 93
def inspect
  "Lorekeeper JSON logger. IO: #{@file.inspect}"
end
remove_fields(fields) click to toggle source
# File lib/lorekeeper/json_logger.rb, line 58
def remove_fields(fields)
  [*fields].each do |field|
    state.fetch(:base_fields).delete(field)
  end
end
remove_thread_unsafe_fields(fields) click to toggle source
# File lib/lorekeeper/json_logger.rb, line 46
def remove_thread_unsafe_fields(fields)
  [*fields].each do |field|
    @base_fields.delete(field)
  end
  reset_state
end
reset_state() click to toggle source
# File lib/lorekeeper/json_logger.rb, line 24
def reset_state
  Thread.current[THREAD_KEY] = nil
end
state() click to toggle source
# File lib/lorekeeper/json_logger.rb, line 20
def state
  Thread.current[THREAD_KEY] ||= { base_fields: @base_fields.dup, extra_fields: {} }
end

Private Instance Methods

clean_backtrace(backtrace) click to toggle source

Some instrumentation libraries pollute the stacktrace and create a large output which may cause problems with certain logging backends. Hardcording newrelic and active_support/callbacks now here. In the future if this list grows, we may make it configurable.

# File lib/lorekeeper/json_logger.rb, line 103
def clean_backtrace(backtrace)
  @backtrace_cleaner&.clean(backtrace) || backtrace
end
log_data(severity, message) click to toggle source
# File lib/lorekeeper/json_logger.rb, line 138
def log_data(severity, message)
  current_state = state # Accessing state is slow. Do it only once per call.
  # merging is slow, we do not want to merge with empty hash if possible
  fields_to_log = if current_state[:extra_fields].empty?
    current_state[:base_fields]
  else
    current_state[:base_fields].merge(current_state[:extra_fields])
  end

  fields_to_log[MESSAGE] = message
  fields_to_log[TIMESTAMP] = Time.now.utc.strftime(DATE_FORMAT)
  fields_to_log[LEVEL] = SEVERITY_NAMES_MAP[severity]

  @iodevice.write(Oj.dump(fields_to_log) << "\n")
end
remove_invalid_fields(fields) click to toggle source
# File lib/lorekeeper/json_logger.rb, line 132
def remove_invalid_fields(fields)
  fields.delete_if do |_, v|
    v.nil? || v.respond_to?(:empty?) && v.empty?
  end
end
set_backtrace_cleaner() click to toggle source
# File lib/lorekeeper/json_logger.rb, line 107
def set_backtrace_cleaner
  return nil unless defined?(ActiveSupport::BacktraceCleaner)

  cleaner = ActiveSupport::BacktraceCleaner.new
  cleaner.remove_silencers!
  cleaner.add_silencer { |line| line.match?(BLACKLISTED_FINGERPRINT) }
  cleaner
end
with_extra_fields(fields) { || ... } click to toggle source
# File lib/lorekeeper/json_logger.rb, line 126
def with_extra_fields(fields)
  state[:extra_fields] = fields
  yield
  state[:extra_fields] = {}
end