Files
dunlop-core/app/services/dunlop/nested_logger.rb
T

159 lines
3.4 KiB
Ruby

# thread safe logger
module Dunlop::NestedLogger
class << self
def formatter=(formatter)
Thread.current[:nested_logger_formatter] = formatter
end
def options
@options ||= {
yaml_options: {}
}
end
def formatter
Thread.current[:nested_logger_formatter] ||= begin
proc do |severity, datetime, progname, msg|
"#{severity == :warn ? 'WARNING' : severity.upcase} - #{msg}"
end
end
end
def reset_formatter
Thread.current[:nested_logger_formatter] = nil
end
def benchmark
Thread.current[:nested_logger_benchmark] = true
yield
ensure
Thread.current[:nested_logger_benchmark] = false
end
def benchmark?
Thread.current[:nested_logger_benchmark] == true
end
def scope(label, options={})
scope_stack << Scope.new(label)
r0 = Time.now
return_value = yield
duration = Time.now - r0
info("duration = #{duration}s") if options.fetch(:benchmark, benchmark?)
if parent_scope?
closing_scope = scope_stack.pop
current_scope.add_scope(closing_scope) unless closing_scope.empty?
end
return_value
end
def scope_stack
Thread.current[:nested_logger_scope_stack] ||= [BaseScope.new]
end
def empty_scope_stack
Thread.current[:nested_logger_scope_stack] = nil
end
alias_method :reset, :empty_scope_stack
def current_scope
scope_stack.last
end
def parent_scope?
scope_stack.size > 1
end
def merge_threads(threads)
threads.map do |thread|
if top_thread_scope = Array.wrap(thread[:nested_logger_scope_stack]).pop
current_scope.entries += top_thread_scope.entries
end
end
end
def copy_scope_stack_for_merge
Marshal.load(Marshal.dump(scope_stack))
end
def merge_scope_stack(stack)
current_scope.entries += stack.pop.entries
end
def flush
current_scope.pop_entries.map(&:to_entry) || []
end
def flush_as_yaml
(f = flush.presence) ? YAML.dump(f, ::Dunlop::NestedLogger.options[:yaml_options]).sub("---\n", '') : ''
end
def log(options={})
current_scope.log(options) if current_scope
end
[:debug, :info, :warn, :error].each do |severity|
define_method(severity) do |msg|
current_scope.log(msg: msg, severity: severity) if current_scope
end
end
end
class Scope
attr_accessor :label, :entries
def initialize(label, entries=[])
@label = label.to_s
@entries = entries
end
def empty?
entries.empty?
end
def log(options={})
entries << Entry.new(options)
end
def add_scope(scope)
entries << scope
end
def to_entry
{ label => entries.map(&:to_entry).compact } unless empty?
end
def pop_entries
entries.tap { self.entries = [] }
end
end
class BaseScope < Scope
def initialize
@entries = []
end
def to_entry
entries.map(&:to_entry).compact unless empty?
end
end
class Entry
attr_reader :severity, :datetime, :progname, :msg
def initialize(options)
@severity = options[:severity]
@datetime = options[:datetime] || Time.current
@progname = options[:progname]
@msg = options[:msg]
end
def to_entry
::Dunlop::NestedLogger.formatter.call(severity, datetime, progname, msg)
end
end
end