159 lines
3.4 KiB
Ruby
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
|
|
|