require 'logger' require 'time' require 'json' require 'colorize' # Extending the base Logger with TRACE capabilities. class Logger # Adding the new severity level SEV_LABEL << 'TRACE' # So we can reference it by name TRACE = SEV_LABEL.index('TRACE') # Send a TRACE-level log def trace progname, &block return true unless @trace add TRACE, nil, progname, &block end end module Slog # A colorful structured logger. class Logger < Logger DEFAULT_LEVEL = ::Logger::INFO # Create a new Logger. # # A little different than the canonical Logger. Add options to set the # log level and to disable both colorization and pretty JSON output. In # this form, it's perfect for use with either Logstash or Franz. def initialize \ out:$stdout, # Output handle shift_age:7, # Max seven logs shift_size:1_048_576, # 1024 KiB colorize:true, # Disable for files prettify:true, # Disable for files level:DEFAULT_LEVEL, # INFO level_transform:'downcase', # Any String method level_field:'level', # nil to disable message_field: 'message', # nil to disable timestamp_field:'@timestamp', # nil to disable timestamp_format:'%FT%T.%3N%:z', # ISO-8601 color_map: { 'debug' => [ :blue, :default ], 'info' => [ :green, :default ], 'warn' => [ :yellow, :default ], 'error' => [ :red, :default ], 'fatal' => [ :red, :black ], 'trace' => [ :magenta, :default ] } @prettify = prettify @colorize = colorize @color_map = color_map @level_field = level_field @level_transform = level_transform @message_field = message_field @timestamp_field = timestamp_field @timestamp_format = timestamp_format super out, shift_age, shift_size self.level = level set_formatter end # Set the formatter to work our magic def set_formatter self.formatter = proc do |severity, datetime, _, message| severity.downcase! # If it ain't a structured log, it is now. event = structure_event severity, datetime, message # When debugging, mark the location in Hoss's source unless level == DEFAULT_LEVEL event.merge! marker: File.basename(caller[4]) end # Apply colorization and prettification as required format_json event, severity end end # Turn a call to the formatter into a Hash structure def structure_event severity, datetime, message message = { @message_field => message } unless message.is_a? Hash event = { @level_field => severity.send(@level_transform), @timestamp_field => datetime.strftime(@timestamp_format) } event.merge! message event.delete nil # ignore @field if @field.nil? event end # Convert the structured event into it's JSON representation def format_json event, severity generator = @prettify ? :pretty_generate : :generate event = JSON.send(generator, event) + "\n" return event unless @colorize event.colorize \ color: @color_map[severity][0], background: @color_map[severity][1] end # Override the level setter to allow symbols and TRACE def level= l l = ::Logger.const_get l.upcase if l.is_a? Symbol @trace = l == ::Logger::TRACE # TRACE is really a fancy DEBUG l = ::Logger::DEBUG if @trace # Here's the proof. super l end end end