pvincent
3 months ago
15 changed files with 514 additions and 493 deletions
-
2Procfile.dev
-
5config/application.rb
-
7config/command_detection.rb
-
11config/environments/development.rb
-
2config/initializers/monkey_patcher.rb
-
58lib/formatters/ansi_colors.rb
-
20lib/formatters/ansi_dimensions.rb
-
245lib/formatters/ansi_formatter.rb
-
80lib/formatters/ansi_wrapper.rb
-
82lib/formatters/basic_formatter.rb
-
60lib/semantic/ansi_colors.rb
-
22lib/semantic/ansi_dimensions.rb
-
247lib/semantic/ansi_formatter.rb
-
82lib/semantic/ansi_wrapper.rb
-
84lib/semantic/basic_formatter.rb
@ -1,3 +1,3 @@ |
|||||
|
|
||||
web: RUBY_DEBUG_OPEN=true bundle exec -- rails server --port "${RAILS_PORT:-7500}" |
web: RUBY_DEBUG_OPEN=true bundle exec -- rails server --port "${RAILS_PORT:-7500}" |
||||
css: BROWSERSLIST_IGNORE_OLD_DATA=true bundle exec -- rails tailwindcss:watch |
|
||||
|
css: bundle exec -- rails tailwindcss:watch |
@ -0,0 +1,7 @@ |
|||||
|
# useful methods for figuring out which kind of process is running |
||||
|
module CommandDetection |
||||
|
def server? = Rails.const_defined?('Server') |
||||
|
def console? = !server? && defined?(Rails::Console) == 'constant' |
||||
|
def rake? = !server? && !console? && Rails.const_defined?('Rake') |
||||
|
def tailwind_watcher? = rake? && Rake.application.top_level_tasks.first == 'tailwindcss:watch' |
||||
|
end |
@ -1,58 +0,0 @@ |
|||||
# common definitions and constants |
|
||||
module AnsiColors |
|
||||
ANSI_REGEX = /\e\[[0-9;]*m/ # TODO: support for \x1b and \033 |
|
||||
|
|
||||
# FORMAT |
|
||||
CLEAR = "\e[0m".freeze |
|
||||
BOLD = "\e[1m".freeze |
|
||||
UNDERLINE = "\e[4m".freeze |
|
||||
|
|
||||
# TEXT |
|
||||
TEXT_BLACK = "\e[30m".freeze |
|
||||
TEXT_RED = "\e[31m".freeze |
|
||||
TEXT_GREEN = "\e[32m".freeze |
|
||||
TEXT_YELLOW = "\e[33m".freeze |
|
||||
TEXT_BLUE = "\e[34m".freeze |
|
||||
TEXT_MAGENTA = "\e[35m".freeze |
|
||||
TEXT_CYAN = "\e[36m".freeze |
|
||||
TEXT_WHITE = "\e[37m".freeze |
|
||||
|
|
||||
# TEXT GRAY SHADES |
|
||||
TEXT_GRAY_800 = "\e[38;5;232m".freeze |
|
||||
TEXT_GRAY_700 = "\e[38;5;233m".freeze |
|
||||
TEXT_GRAY_600 = "\e[38;5;235m".freeze |
|
||||
TEXT_GRAY_500 = "\e[38;5;238m".freeze |
|
||||
TEXT_GRAY_400 = "\e[38;5;241m".freeze |
|
||||
TEXT_GRAY_300 = "\e[38;5;244m".freeze |
|
||||
TEXT_GRAY_200 = "\e[38;5;249m".freeze |
|
||||
TEXT_GRAY_100 = "\e[38;5;252m".freeze |
|
||||
|
|
||||
# DARK TEXT |
|
||||
DARK_TEXT_BLACK = "\e[90m".freeze |
|
||||
DARK_TEXT_RED = "\e[91m".freeze |
|
||||
DARK_TEXT_GREEN = "\e[92m".freeze |
|
||||
DARK_TEXT_YELLOW = "\e[93m".freeze |
|
||||
DARK_TEXT_BLUE = "\e[94m".freeze |
|
||||
DARK_TEXT_MAGENTA = "\e[95m".freeze |
|
||||
DARK_TEXT_CYAN = "\e[96m".freeze |
|
||||
DARK_TEXT_WHITE = "\e[97m".freeze |
|
||||
|
|
||||
# BACKGROUND |
|
||||
BG_BLACK = "\e[40m".freeze |
|
||||
BG_WHITE = "\e[47m".freeze |
|
||||
BG_GRAY = "\e[100m".freeze |
|
||||
BG_RED = "\e[41m".freeze |
|
||||
BG_GREEN = "\e[42m".freeze |
|
||||
BG_YELLOW = "\e[43m".freeze |
|
||||
BG_BLUE = "\e[44m".freeze |
|
||||
BG_MAGENTA = "\e[45m".freeze |
|
||||
BG_CYAN = "\e[46m".freeze |
|
||||
|
|
||||
# DARK BACKGROUND |
|
||||
DARK_BG_RED = "\e[101m".freeze |
|
||||
DARK_BG_GREEN = "\e[102m".freeze |
|
||||
DARK_BG_YELLOW = "\e[103m".freeze |
|
||||
DARK_BG_BLUE = "\e[104m".freeze |
|
||||
DARK_BG_MAGENTA = "\e[105m".freeze |
|
||||
DARK_BG_CYAN = "\e[106m".freeze |
|
||||
end |
|
@ -1,20 +0,0 @@ |
|||||
require 'ostruct' |
|
||||
|
|
||||
# extra dimensions for customizing the logging format |
|
||||
module AnsiDimensions |
|
||||
def self.new(rails: '╣x╠', before: 0, after: 0, terminus: false) |
|
||||
OpenStruct.new(rails:, before:, after:, terminus:) # rubocop:disable Style/OpenStructUse |
|
||||
end |
|
||||
|
|
||||
def self.start |
|
||||
OpenStruct.new(rails: '╓─╖') # rubocop:disable Style/OpenStructUse |
|
||||
end |
|
||||
|
|
||||
def self.end |
|
||||
OpenStruct.new(rails: '╣ ╠') # rubocop:disable Style/OpenStructUse |
|
||||
end |
|
||||
|
|
||||
def self.around |
|
||||
OpenStruct.new(rails: '╣ ╠') # rubocop:disable Style/OpenStructUse |
|
||||
end |
|
||||
end |
|
@ -1,245 +0,0 @@ |
|||||
require_relative 'ansi_wrapper' |
|
||||
require_relative 'ansi_colors' |
|
||||
require_relative 'ansi_dimensions' |
|
||||
|
|
||||
require 'io/console' |
|
||||
require 'amazing_print' |
|
||||
require 'json' |
|
||||
|
|
||||
# wraps meanwhile takes care of ansi colors |
|
||||
class AnsiFormatter < SemanticLogger::Formatters::Color |
|
||||
include AnsiColors |
|
||||
|
|
||||
CENTER_SIZE = 20 |
|
||||
FOREMAN_PREFIX_LENGTH = 18 |
|
||||
FAILOVER_WRAP = 80 |
|
||||
CHAR_FATAL = '⯶'.freeze |
|
||||
TERMINUS_STRING = '╙─╜'.freeze |
|
||||
RENDERED_VIEW_DURATION = 100 |
|
||||
TOTAL_RENDERED_VIEW_DURATION = RENDERED_VIEW_DURATION * 5 |
|
||||
|
|
||||
def initialize |
|
||||
super(color_map: |
|
||||
ColorMap.new( |
|
||||
debug: CLEAR + TEXT_GRAY_400, |
|
||||
info: CLEAR + TEXT_GRAY_100, |
|
||||
warn: CLEAR + BG_YELLOW + TEXT_BLACK, |
|
||||
error: CLEAR + BG_RED + TEXT_WHITE, |
|
||||
fatal: CLEAR + BG_MAGENTA + BOLD + TEXT_WHITE |
|
||||
)) |
|
||||
@memory = nil |
|
||||
end |
|
||||
|
|
||||
def call(log, logger) |
|
||||
log = alter(log) |
|
||||
|
|
||||
self.log = log |
|
||||
self.logger = logger |
|
||||
self.color = color_map[log.level] |
|
||||
|
|
||||
wrap_level(compute_useful_length, message, payload, exception) |
|
||||
end |
|
||||
|
|
||||
def reject(log) |
|
||||
return true if log.name == 'ActionView::Base' && log.message&.starts_with?(' Rendering') |
|
||||
|
|
||||
true if log.name == 'Rails' && log.message&.starts_with?('Loaded') |
|
||||
end |
|
||||
|
|
||||
private |
|
||||
|
|
||||
def build_regex_redirected |
|
||||
dev_port = Rails.application.routes.default_url_options[:port] |
|
||||
dev_hosts = ['127.0.0.1', 'localhost', Rails.application.routes.default_url_options[:host]].uniq.join('|') |
|
||||
dev_from = "http://(?:#{dev_hosts}):#{dev_port}" |
|
||||
regex_s = "^(Redirected to )#{dev_from}(.*)" |
|
||||
Regexp.new(regex_s) |
|
||||
end |
|
||||
|
|
||||
def ansi_trace(trace, symbol) |
|
||||
match = trace.match(/(↳ )?(.*:\d+)(:in `)?(.*'?)/) # only m2(=file) and m4(=optional function) are useful |
|
||||
return trace unless match |
|
||||
|
|
||||
_, m2, _, m4 = match.captures |
|
||||
"#{symbol} #{m2} #{BOLD}#{m4.chop}#{CLEAR}" |
|
||||
end |
|
||||
|
|
||||
def two_captures_last_as_bold(message, regex) |
|
||||
match = message.match(regex) |
|
||||
return "unmatched: #{message}" unless match |
|
||||
|
|
||||
m1, m2 = match.captures |
|
||||
"#{m1}#{BOLD}#{m2}#{CLEAR}" |
|
||||
end |
|
||||
|
|
||||
def alter(log) |
|
||||
if log.name == 'Rails' |
|
||||
if log.message |
|
||||
log.message.lstrip! |
|
||||
log.message.chomp!('') |
|
||||
if log.message.starts_with?('Started') |
|
||||
rails = '╓─╖' |
|
||||
before = 1 |
|
||||
if @memory |
|
||||
rails = "╓#{@memory}╖" |
|
||||
before = 0 |
|
||||
end |
|
||||
log.dimensions = AnsiDimensions.new(rails:, before:) |
|
||||
@memory = nil |
|
||||
log.message = two_captures_last_as_bold(log.message, /(^Started \w* )"(.*?)"/) |
|
||||
elsif log.message.starts_with?('Parameters') |
|
||||
parameters = log.message.match(/Parameters: ({.*}$)/).match(1) |
|
||||
parameters = JSON.parse(parameters.gsub('=>', ':'), symbolize_names: true) |
|
||||
parameters = parameters.ai(ruby19_syntax: true, plain: true, multiline: false) |
|
||||
parameters = parameters.gsub(/\w*:/) { |key| "#{TEXT_GRAY_200}#{key.chop}#{color_map[:debug]}:" } |
|
||||
parameters = parameters.gsub(/: ".*?"/) do |value| |
|
||||
": \"#{TEXT_YELLOW}#{value[3..-2]}#{color_map[:debug]}\"" |
|
||||
end |
|
||||
log.message = "Parameters: #{parameters}" |
|
||||
log.level = :debug |
|
||||
elsif log.message.starts_with?('Completed') |
|
||||
m1, m2, m3, m4 = log.message.match(/^Completed (\d+) (.*) in (\d+\.?\d*)ms (.*)$/).captures |
|
||||
http_code = m1.to_i |
|
||||
duration = m3.to_f |
|
||||
duration = duration > TOTAL_RENDERED_VIEW_DURATION ? " in #{m3}ms #{m4}" : '' |
|
||||
log.message = "Completed #{BOLD}#{http_code}#{CLEAR} #{m2}#{duration}" |
|
||||
if http_code / 100 == 2 |
|
||||
log.dimensions = AnsiDimensions.new(rails: TERMINUS_STRING, after: 1) |
|
||||
elsif http_code / 100 == 3 |
|
||||
@memory = '║' |
|
||||
log.dimensions = AnsiDimensions.new(rails: "╙#{@memory}╜") |
|
||||
elsif http_code / 100 == 4 |
|
||||
log.dimensions = AnsiDimensions.new(rails: '╙╨╜') |
|
||||
elsif http_code / 100 == 5 |
|
||||
log.dimensions = AnsiDimensions.new(rails: "╙#{draw_fatal}╜") |
|
||||
end |
|
||||
elsif log.message =~ /^(Processing|Parameters)/ |
|
||||
log.level = :debug |
|
||||
if log.message =~ /^Processing/ |
|
||||
log.message = two_captures_last_as_bold(log.message, /(^Processing by \w*#\w* as )(.*)/) |
|
||||
end |
|
||||
elsif log.message =~ /Redirected/ |
|
||||
log.level = :debug |
|
||||
@regex_redirected ||= build_regex_redirected # lazy building |
|
||||
log.message = two_captures_last_as_bold(log.message, @regex_redirected) |
|
||||
end |
|
||||
elsif log.exception |
|
||||
log.dimensions = AnsiDimensions.new( |
|
||||
rails: "╓#{draw_fatal(log.level.to_s.chr.upcase)}╖", |
|
||||
after: 1, |
|
||||
terminus: true |
|
||||
) |
|
||||
end |
|
||||
elsif log.name =~ /^(ActionView|ActiveRecord)::Base/ |
|
||||
log.level = :debug |
|
||||
log.message.lstrip! |
|
||||
if log.name == 'ActiveRecord::Base' |
|
||||
unbold!(log.message) |
|
||||
|
|
||||
if log.message.starts_with?('↳ ') |
|
||||
log.message = ansi_trace(log.message, '⇄') |
|
||||
else |
|
||||
sql_match = log.message.match(/\s+\[\[.*\]\]$/) |
|
||||
if sql_match |
|
||||
sql_command = sql_match.pre_match |
|
||||
sql_type, sql_entry = sql_command.split("\e[0m") |
|
||||
sql_color_entry = sql_entry.match(ANSI_REGEX).to_s |
|
||||
sql_args = JSON.parse(sql_match.to_s).map(&:last) |
|
||||
sql_args.each_with_index do |val, index| |
|
||||
sql_arg = val.inspect |
|
||||
sql_arg = "'#{val.gsub("'", "''")}'" if val.is_a?(String) |
|
||||
sql_entry.gsub!("$#{index + 1}", colorize(sql_arg, BOLD) + sql_color_entry) |
|
||||
end |
|
||||
log.message = [sql_type, sql_entry].join(CLEAR) |
|
||||
end |
|
||||
end |
|
||||
elsif log.name == 'ActionView::Base' |
|
||||
match = log.message.match(/^Rendered( layout| collection of|) (.*?\.erb)(.*)(\(Duration:.*\))/) |
|
||||
if match |
|
||||
m1, m2, m3, m4 = match.captures |
|
||||
duration = m4.match(/Duration: (\d+\.?\d*)ms/).match(1).to_f |
|
||||
duration = duration < RENDERED_VIEW_DURATION ? '' : m4.to_s |
|
||||
log.message = "⇤ Rendered#{m1} app/views/#{m2}#{m3}#{duration}" |
|
||||
end |
|
||||
end |
|
||||
|
|
||||
end |
|
||||
|
|
||||
log |
|
||||
end |
|
||||
|
|
||||
def draw_fatal(char = CHAR_FATAL) |
|
||||
BG_MAGENTA + BOLD + TEXT_WHITE + char + CLEAR |
|
||||
end |
|
||||
|
|
||||
def origin = colorize(centerize(log.name), TEXT_CYAN) |
|
||||
def build_prefix(char) = "#{origin} ╣#{colorize(char)}╠ " |
|
||||
def build_terminus = "#{origin} #{TERMINUS_STRING} " |
|
||||
def centerize(text) = text.truncate(CENTER_SIZE).center(CENTER_SIZE) |
|
||||
def colorize(text, tint = color) = "#{tint}#{text}#{CLEAR}" |
|
||||
|
|
||||
def stackisize(items) |
|
||||
return '' if items.empty? |
|
||||
|
|
||||
traces = items.map { |item| ansi_trace(item, '➟') } |
|
||||
"\n#{traces.join("\n")}" |
|
||||
end |
|
||||
|
|
||||
def build_dimensions(dimensions) |
|
||||
"#{origin} #{dimensions.rails} " |
|
||||
end |
|
||||
|
|
||||
def compute_useful_length |
|
||||
IO.console.winsize[1] - FOREMAN_PREFIX_LENGTH |
|
||||
rescue StandardError |
|
||||
FAILOVER_WRAP |
|
||||
end |
|
||||
|
|
||||
def unbold!(text) = text.gsub!(BOLD, '') |
|
||||
|
|
||||
def message |
|
||||
colorize(log.message) if log.message |
|
||||
end |
|
||||
|
|
||||
def payload |
|
||||
return unless log.payload |
|
||||
|
|
||||
log.payload.ai(indent: 2, object_id: false) |
|
||||
end |
|
||||
|
|
||||
def exception |
|
||||
return unless log.exception |
|
||||
|
|
||||
exc = log.exception |
|
||||
clazz = colorize("#{exc.class}\n", color_map[:fatal]) |
|
||||
message = colorize(exc.message.chomp(''), color_map[:error]) |
|
||||
backtrace = stackisize(Rails.backtrace_cleaner.clean(exc.backtrace)) |
|
||||
|
|
||||
"#{clazz}#{message}#{backtrace}" |
|
||||
end |
|
||||
|
|
||||
def level_char |
|
||||
case log.level |
|
||||
when :info, :debug then ' ' |
|
||||
else log.level.to_s.chr.upcase |
|
||||
end |
|
||||
end |
|
||||
|
|
||||
def wrap_level(length, *items) |
|
||||
prefix = log.dimensions ? build_dimensions(log.dimensions) : build_prefix(level_char) |
|
||||
continuation = build_prefix('┆') |
|
||||
|
|
||||
result = items.map do |item| |
|
||||
AnsiWrapper.wrap(item, length, prefix, continuation) |
|
||||
end |
|
||||
|
|
||||
if log.dimensions&.terminus |
|
||||
terminus = AnsiWrapper.wrap(' ', length, build_terminus) |
|
||||
result << terminus |
|
||||
end |
|
||||
|
|
||||
log.dimensions&.before&.times { result.unshift('') } |
|
||||
log.dimensions&.after&.times { result << '' } |
|
||||
result.compact.join("\n") |
|
||||
end |
|
||||
end |
|
@ -1,80 +0,0 @@ |
|||||
require_relative 'ansi_colors' |
|
||||
|
|
||||
# AnsiWrapper cares about Ansi Colour Code \e[... |
|
||||
class AnsiWrapper |
|
||||
include AnsiColors |
|
||||
|
|
||||
TAB_TO_SPACES = 2 |
|
||||
|
|
||||
def self.wrap(text, length, prefix = '', continuation = prefix) |
|
||||
if visible_length(prefix) != visible_length(continuation) |
|
||||
raise "continuation <#{continuation.inspect}> should have the same length as prefix <#{prefix.inspect}>" |
|
||||
end |
|
||||
return unless text |
|
||||
|
|
||||
text = text.gsub("\t", ' ' * TAB_TO_SPACES) |
|
||||
|
|
||||
lines = split_text_to_lines(text, length - visible_length(prefix)) |
|
||||
lines = inject_continuation_and_ansi_colors_to_lines(lines, prefix, continuation) |
|
||||
lines.join("\n") |
|
||||
end |
|
||||
|
|
||||
private_class_method def self.inject_continuation_and_ansi_colors_to_lines(lines, prefix, continuation) |
|
||||
last_ansi = '' |
|
||||
lines.each_with_index.map do |line, index| |
|
||||
current = index.zero? ? prefix : continuation |
|
||||
current += last_ansi unless last_ansi.empty? || last_ansi == CLEAR |
|
||||
current += line |
|
||||
|
|
||||
last_ansi = scan_for_actual_ansi(line, last_ansi) |
|
||||
|
|
||||
current += CLEAR if last_ansi.empty? || last_ansi != CLEAR |
|
||||
current |
|
||||
end |
|
||||
end |
|
||||
|
|
||||
private_class_method def self.scan_for_actual_ansi(line, last_ansi) |
|
||||
line.scan(ANSI_REGEX).each do |match| |
|
||||
ansi_code = match.to_s |
|
||||
if ansi_code == CLEAR |
|
||||
last_ansi = CLEAR |
|
||||
else |
|
||||
last_ansi += ansi_code |
|
||||
end |
|
||||
end |
|
||||
last_ansi |
|
||||
end |
|
||||
|
|
||||
private_class_method def self.split_text_to_lines(text, length) |
|
||||
lines = text.split("\n") |
|
||||
sublines = lines.map do |line| |
|
||||
visible_length(line) > length ? visible_split(line, length) : [line] |
|
||||
end |
|
||||
sublines.flatten |
|
||||
end |
|
||||
|
|
||||
private_class_method def self.visible_length(line) |
|
||||
raise 'line should not contain carriage return character!' if line.match "\n" |
|
||||
|
|
||||
ansi_code_length = line.scan(ANSI_REGEX).map(&:length).sum |
|
||||
line.length - ansi_code_length |
|
||||
end |
|
||||
|
|
||||
# TODO: might be refactored with less complexity |
|
||||
private_class_method def self.visible_split(line, length, stack = '') # rubocop:disable Metrics/AbcSize,Metrics/MethodLength |
|
||||
before, ansi_code, after = line.partition(ANSI_REGEX) |
|
||||
stack_length = visible_length(stack) |
|
||||
visible_length = before.length + stack_length |
|
||||
if visible_length == length |
|
||||
["#{stack}#{before}#{ansi_code}"] + visible_split(after, length) |
|
||||
elsif visible_length > length |
|
||||
first_line = stack + before[0...length - stack_length] |
|
||||
tail = before[length - stack_length..] + ansi_code + after |
|
||||
[first_line] + visible_split(tail, length) |
|
||||
elsif ansi_code.length.positive? |
|
||||
visible_split(after, length, "#{stack}#{before}#{ansi_code}") |
|
||||
else |
|
||||
["#{stack}#{before}#{ansi_code}"] |
|
||||
end |
|
||||
end |
|
||||
end |
|
@ -1,82 +0,0 @@ |
|||||
# My Custom colorized formatter |
|
||||
class BasicFormatter < SemanticLogger::Formatters::Color |
|
||||
ANSI_REVERSED_WARNING = "\e[0;30;43m".freeze |
|
||||
ANSI_REVERSED_ERROR = "\e[1;30;41m".freeze |
|
||||
ANSI_GRAY = "\e[90m".freeze |
|
||||
# Return the complete log level name in uppercase |
|
||||
|
|
||||
def initialize |
|
||||
super(time_format: '%H:%M:%S', |
|
||||
color_map: ColorMap.new( |
|
||||
debug: ANSI_GRAY, |
|
||||
info: SemanticLogger::AnsiColors::GREEN, |
|
||||
warn: SemanticLogger::AnsiColors::YELLOW |
|
||||
)) |
|
||||
@time_format = nil if File.exist?(File.join(Rails.root, 'Procfile.dev')) |
|
||||
end |
|
||||
|
|
||||
def time |
|
||||
"#{color}#{format_time(log.time)}#{color_map.clear}" if time_format |
|
||||
end |
|
||||
|
|
||||
def message |
|
||||
return unless log.message |
|
||||
|
|
||||
prefix = "#{color}--#{color_map.clear}" |
|
||||
|
|
||||
case log.level |
|
||||
when :info |
|
||||
message = log.message |
|
||||
message = message&.rstrip if log.name == 'Rails' && message.starts_with?('Completed') |
|
||||
if log.name == 'Rails' && message.starts_with?('Started') |
|
||||
message = message.split('for')[0] |
|
||||
puts '' if Rails.env.development? |
|
||||
end |
|
||||
if log.name == 'Rails' || log.name == 'ActionView::Base' |
|
||||
"#{prefix} #{ANSI_GRAY}#{message}#{color_map.clear}" |
|
||||
else |
|
||||
"#{prefix} #{SemanticLogger::AnsiColors::WHITE}#{message}#{color_map.clear}" |
|
||||
end |
|
||||
when :warn |
|
||||
"#{prefix} #{ANSI_REVERSED_WARNING}#{log.message}#{color_map.clear}" |
|
||||
when :error, :fatal |
|
||||
"#{prefix} #{ANSI_REVERSED_ERROR}#{log.message}#{color_map.clear}" |
|
||||
else |
|
||||
"#{prefix} #{color}#{log.message}#{color_map.clear}" |
|
||||
end |
|
||||
end |
|
||||
|
|
||||
def tags; end |
|
||||
|
|
||||
def process_info |
|
||||
fname = file_name_and_line |
|
||||
"#{color}[#{fname}]#{color_map.clear}" if fname |
|
||||
end |
|
||||
|
|
||||
def exception |
|
||||
return unless log.exception |
|
||||
|
|
||||
root_path = Rails.root.to_s |
|
||||
backtrace = log.exception.backtrace.select do |line| |
|
||||
line.starts_with?(root_path) |
|
||||
end |
|
||||
|
|
||||
if backtrace.count.positive? |
|
||||
"-- #{ANSI_REVERSED_ERROR}#{log.exception.class}#{color_map.clear} #{color}#{log.exception.message}#{color_map.clear}#{SemanticLogger::AnsiColors::WHITE}\n\t#{backtrace.join("\n\t")}#{color_map.clear}\n\n" |
|
||||
else |
|
||||
"-- #{ANSI_REVERSED_ERROR}#{log.exception.class}: #{log.exception.message}#{color_map.clear}\n\n" |
|
||||
end |
|
||||
end |
|
||||
|
|
||||
def call(log, logger) |
|
||||
self.color = color_map[log.level] |
|
||||
self.log = log |
|
||||
self.logger = logger |
|
||||
|
|
||||
if @time_format |
|
||||
[time, level, process_info, tags, named_tags, duration, name, message, payload, exception].compact.join(' ') |
|
||||
else |
|
||||
[tags, named_tags, duration, name, message, payload, exception].compact.join(' ') |
|
||||
end |
|
||||
end |
|
||||
end |
|
@ -0,0 +1,60 @@ |
|||||
|
module Semantic |
||||
|
# common definitions and constants |
||||
|
module AnsiColors |
||||
|
ANSI_REGEX = /\e\[[0-9;]*m/ # TODO: support for \x1b and \033 |
||||
|
|
||||
|
# FORMAT |
||||
|
CLEAR = "\e[0m".freeze |
||||
|
BOLD = "\e[1m".freeze |
||||
|
UNDERLINE = "\e[4m".freeze |
||||
|
|
||||
|
# TEXT |
||||
|
TEXT_BLACK = "\e[30m".freeze |
||||
|
TEXT_RED = "\e[31m".freeze |
||||
|
TEXT_GREEN = "\e[32m".freeze |
||||
|
TEXT_YELLOW = "\e[33m".freeze |
||||
|
TEXT_BLUE = "\e[34m".freeze |
||||
|
TEXT_MAGENTA = "\e[35m".freeze |
||||
|
TEXT_CYAN = "\e[36m".freeze |
||||
|
TEXT_WHITE = "\e[37m".freeze |
||||
|
|
||||
|
# TEXT GRAY SHADES |
||||
|
TEXT_GRAY_800 = "\e[38;5;232m".freeze |
||||
|
TEXT_GRAY_700 = "\e[38;5;233m".freeze |
||||
|
TEXT_GRAY_600 = "\e[38;5;235m".freeze |
||||
|
TEXT_GRAY_500 = "\e[38;5;238m".freeze |
||||
|
TEXT_GRAY_400 = "\e[38;5;241m".freeze |
||||
|
TEXT_GRAY_300 = "\e[38;5;244m".freeze |
||||
|
TEXT_GRAY_200 = "\e[38;5;249m".freeze |
||||
|
TEXT_GRAY_100 = "\e[38;5;252m".freeze |
||||
|
|
||||
|
# DARK TEXT |
||||
|
DARK_TEXT_BLACK = "\e[90m".freeze |
||||
|
DARK_TEXT_RED = "\e[91m".freeze |
||||
|
DARK_TEXT_GREEN = "\e[92m".freeze |
||||
|
DARK_TEXT_YELLOW = "\e[93m".freeze |
||||
|
DARK_TEXT_BLUE = "\e[94m".freeze |
||||
|
DARK_TEXT_MAGENTA = "\e[95m".freeze |
||||
|
DARK_TEXT_CYAN = "\e[96m".freeze |
||||
|
DARK_TEXT_WHITE = "\e[97m".freeze |
||||
|
|
||||
|
# BACKGROUND |
||||
|
BG_BLACK = "\e[40m".freeze |
||||
|
BG_WHITE = "\e[47m".freeze |
||||
|
BG_GRAY = "\e[100m".freeze |
||||
|
BG_RED = "\e[41m".freeze |
||||
|
BG_GREEN = "\e[42m".freeze |
||||
|
BG_YELLOW = "\e[43m".freeze |
||||
|
BG_BLUE = "\e[44m".freeze |
||||
|
BG_MAGENTA = "\e[45m".freeze |
||||
|
BG_CYAN = "\e[46m".freeze |
||||
|
|
||||
|
# DARK BACKGROUND |
||||
|
DARK_BG_RED = "\e[101m".freeze |
||||
|
DARK_BG_GREEN = "\e[102m".freeze |
||||
|
DARK_BG_YELLOW = "\e[103m".freeze |
||||
|
DARK_BG_BLUE = "\e[104m".freeze |
||||
|
DARK_BG_MAGENTA = "\e[105m".freeze |
||||
|
DARK_BG_CYAN = "\e[106m".freeze |
||||
|
end |
||||
|
end |
@ -0,0 +1,22 @@ |
|||||
|
require 'ostruct' |
||||
|
|
||||
|
module Semantic |
||||
|
# extra dimensions for customizing the logging format |
||||
|
module AnsiDimensions |
||||
|
def self.new(rails: '╣x╠', before: 0, after: 0, terminus: false) |
||||
|
OpenStruct.new(rails:, before:, after:, terminus:) # rubocop:disable Style/OpenStructUse |
||||
|
end |
||||
|
|
||||
|
def self.start |
||||
|
OpenStruct.new(rails: '╓─╖') # rubocop:disable Style/OpenStructUse |
||||
|
end |
||||
|
|
||||
|
def self.end |
||||
|
OpenStruct.new(rails: '╣ ╠') # rubocop:disable Style/OpenStructUse |
||||
|
end |
||||
|
|
||||
|
def self.around |
||||
|
OpenStruct.new(rails: '╣ ╠') # rubocop:disable Style/OpenStructUse |
||||
|
end |
||||
|
end |
||||
|
end |
@ -0,0 +1,247 @@ |
|||||
|
require_relative 'ansi_wrapper' |
||||
|
require_relative 'ansi_colors' |
||||
|
require_relative 'ansi_dimensions' |
||||
|
|
||||
|
require 'io/console' |
||||
|
require 'amazing_print' |
||||
|
require 'json' |
||||
|
|
||||
|
module Semantic |
||||
|
# wraps meanwhile takes care of ansi colors |
||||
|
class AnsiFormatter < SemanticLogger::Formatters::Color |
||||
|
include AnsiColors |
||||
|
|
||||
|
CENTER_SIZE = 20 |
||||
|
FOREMAN_PREFIX_LENGTH = 18 |
||||
|
FAILOVER_WRAP = 80 |
||||
|
CHAR_FATAL = '⯶'.freeze |
||||
|
TERMINUS_STRING = '╙─╜'.freeze |
||||
|
RENDERED_VIEW_DURATION = 100 |
||||
|
TOTAL_RENDERED_VIEW_DURATION = RENDERED_VIEW_DURATION * 5 |
||||
|
|
||||
|
def initialize |
||||
|
super(color_map: |
||||
|
ColorMap.new( |
||||
|
debug: CLEAR + TEXT_GRAY_400, |
||||
|
info: CLEAR + TEXT_GRAY_100, |
||||
|
warn: CLEAR + BG_YELLOW + TEXT_BLACK, |
||||
|
error: CLEAR + BG_RED + TEXT_WHITE, |
||||
|
fatal: CLEAR + BG_MAGENTA + BOLD + TEXT_WHITE |
||||
|
)) |
||||
|
@memory = nil |
||||
|
end |
||||
|
|
||||
|
def call(log, logger) |
||||
|
log = alter(log) |
||||
|
|
||||
|
self.log = log |
||||
|
self.logger = logger |
||||
|
self.color = color_map[log.level] |
||||
|
|
||||
|
wrap_level(compute_useful_length, message, payload, exception) |
||||
|
end |
||||
|
|
||||
|
def reject(log) |
||||
|
return true if log.name == 'ActionView::Base' && log.message&.starts_with?(' Rendering') |
||||
|
|
||||
|
true if log.name == 'Rails' && log.message&.starts_with?('Loaded') |
||||
|
end |
||||
|
|
||||
|
private |
||||
|
|
||||
|
def build_regex_redirected |
||||
|
dev_port = Rails.application.routes.default_url_options[:port] |
||||
|
dev_hosts = ['127.0.0.1', 'localhost', Rails.application.routes.default_url_options[:host]].uniq.join('|') |
||||
|
dev_from = "http://(?:#{dev_hosts}):#{dev_port}" |
||||
|
regex_s = "^(Redirected to )#{dev_from}(.*)" |
||||
|
Regexp.new(regex_s) |
||||
|
end |
||||
|
|
||||
|
def ansi_trace(trace, symbol) |
||||
|
match = trace.match(/(↳ )?(.*:\d+)(:in `)?(.*'?)/) # only m2(=file) and m4(=optional function) are useful |
||||
|
return trace unless match |
||||
|
|
||||
|
_, m2, _, m4 = match.captures |
||||
|
"#{symbol} #{m2} #{BOLD}#{m4.chop}#{CLEAR}" |
||||
|
end |
||||
|
|
||||
|
def two_captures_last_as_bold(message, regex) |
||||
|
match = message.match(regex) |
||||
|
return "unmatched: #{message}" unless match |
||||
|
|
||||
|
m1, m2 = match.captures |
||||
|
"#{m1}#{BOLD}#{m2}#{CLEAR}" |
||||
|
end |
||||
|
|
||||
|
def alter(log) |
||||
|
if log.name == 'Rails' |
||||
|
if log.message |
||||
|
log.message.lstrip! |
||||
|
log.message.chomp!('') |
||||
|
if log.message.starts_with?('Started') |
||||
|
rails = '╓─╖' |
||||
|
before = 1 |
||||
|
if @memory |
||||
|
rails = "╓#{@memory}╖" |
||||
|
before = 0 |
||||
|
end |
||||
|
log.dimensions = AnsiDimensions.new(rails:, before:) |
||||
|
@memory = nil |
||||
|
log.message = two_captures_last_as_bold(log.message, /(^Started \w* )"(.*?)"/) |
||||
|
elsif log.message.starts_with?('Parameters') |
||||
|
parameters = log.message.match(/Parameters: ({.*}$)/).match(1) |
||||
|
parameters = JSON.parse(parameters.gsub('=>', ':'), symbolize_names: true) |
||||
|
parameters = parameters.ai(ruby19_syntax: true, plain: true, multiline: false) |
||||
|
parameters = parameters.gsub(/\w*:/) { |key| "#{TEXT_GRAY_200}#{key.chop}#{color_map[:debug]}:" } |
||||
|
parameters = parameters.gsub(/: ".*?"/) do |value| |
||||
|
": \"#{TEXT_YELLOW}#{value[3..-2]}#{color_map[:debug]}\"" |
||||
|
end |
||||
|
log.message = "Parameters: #{parameters}" |
||||
|
log.level = :debug |
||||
|
elsif log.message.starts_with?('Completed') |
||||
|
m1, m2, m3, m4 = log.message.match(/^Completed (\d+) (.*) in (\d+\.?\d*)ms (.*)$/).captures |
||||
|
http_code = m1.to_i |
||||
|
duration = m3.to_f |
||||
|
duration = duration > TOTAL_RENDERED_VIEW_DURATION ? " in #{m3}ms #{m4}" : '' |
||||
|
log.message = "Completed #{BOLD}#{http_code}#{CLEAR} #{m2}#{duration}" |
||||
|
if http_code / 100 == 2 |
||||
|
log.dimensions = AnsiDimensions.new(rails: TERMINUS_STRING, after: 1) |
||||
|
elsif http_code / 100 == 3 |
||||
|
@memory = '║' |
||||
|
log.dimensions = AnsiDimensions.new(rails: "╙#{@memory}╜") |
||||
|
elsif http_code / 100 == 4 |
||||
|
log.dimensions = AnsiDimensions.new(rails: '╙╨╜') |
||||
|
elsif http_code / 100 == 5 |
||||
|
log.dimensions = AnsiDimensions.new(rails: "╙#{draw_fatal}╜") |
||||
|
end |
||||
|
elsif log.message =~ /^(Processing|Parameters)/ |
||||
|
log.level = :debug |
||||
|
if log.message =~ /^Processing/ |
||||
|
log.message = two_captures_last_as_bold(log.message, /(^Processing by \w*#\w* as )(.*)/) |
||||
|
end |
||||
|
elsif log.message =~ /Redirected/ |
||||
|
log.level = :debug |
||||
|
@regex_redirected ||= build_regex_redirected # lazy building |
||||
|
log.message = two_captures_last_as_bold(log.message, @regex_redirected) |
||||
|
end |
||||
|
elsif log.exception |
||||
|
log.dimensions = AnsiDimensions.new( |
||||
|
rails: "╓#{draw_fatal(log.level.to_s.chr.upcase)}╖", |
||||
|
after: 1, |
||||
|
terminus: true |
||||
|
) |
||||
|
end |
||||
|
elsif log.name =~ /^(ActionView|ActiveRecord)::Base/ |
||||
|
log.level = :debug |
||||
|
log.message.lstrip! |
||||
|
if log.name == 'ActiveRecord::Base' |
||||
|
unbold!(log.message) |
||||
|
|
||||
|
if log.message.starts_with?('↳ ') |
||||
|
log.message = ansi_trace(log.message, '⇄') |
||||
|
else |
||||
|
sql_match = log.message.match(/\s+\[\[.*\]\]$/) |
||||
|
if sql_match |
||||
|
sql_command = sql_match.pre_match |
||||
|
sql_type, sql_entry = sql_command.split("\e[0m") |
||||
|
sql_color_entry = sql_entry.match(ANSI_REGEX).to_s |
||||
|
sql_args = JSON.parse(sql_match.to_s).map(&:last) |
||||
|
sql_args.each_with_index do |val, index| |
||||
|
sql_arg = val.inspect |
||||
|
sql_arg = "'#{val.gsub("'", "''")}'" if val.is_a?(String) |
||||
|
sql_entry.gsub!("$#{index + 1}", colorize(sql_arg, BOLD) + sql_color_entry) |
||||
|
end |
||||
|
log.message = [sql_type, sql_entry].join(CLEAR) |
||||
|
end |
||||
|
end |
||||
|
elsif log.name == 'ActionView::Base' |
||||
|
match = log.message.match(/^Rendered( layout| collection of|) (.*?\.erb)(.*)(\(Duration:.*\))/) |
||||
|
if match |
||||
|
m1, m2, m3, m4 = match.captures |
||||
|
duration = m4.match(/Duration: (\d+\.?\d*)ms/).match(1).to_f |
||||
|
duration = duration < RENDERED_VIEW_DURATION ? '' : m4.to_s |
||||
|
log.message = "⇤ Rendered#{m1} app/views/#{m2}#{m3}#{duration}" |
||||
|
end |
||||
|
end |
||||
|
|
||||
|
end |
||||
|
|
||||
|
log |
||||
|
end |
||||
|
|
||||
|
def draw_fatal(char = CHAR_FATAL) |
||||
|
BG_MAGENTA + BOLD + TEXT_WHITE + char + CLEAR |
||||
|
end |
||||
|
|
||||
|
def origin = colorize(centerize(log.name), TEXT_CYAN) |
||||
|
def build_prefix(char) = "#{origin} ╣#{colorize(char)}╠ " |
||||
|
def build_terminus = "#{origin} #{TERMINUS_STRING} " |
||||
|
def centerize(text) = text.truncate(CENTER_SIZE).center(CENTER_SIZE) |
||||
|
def colorize(text, tint = color) = "#{tint}#{text}#{CLEAR}" |
||||
|
|
||||
|
def stackisize(items) |
||||
|
return '' if items.empty? |
||||
|
|
||||
|
traces = items.map { |item| ansi_trace(item, '➟') } |
||||
|
"\n#{traces.join("\n")}" |
||||
|
end |
||||
|
|
||||
|
def build_dimensions(dimensions) |
||||
|
"#{origin} #{dimensions.rails} " |
||||
|
end |
||||
|
|
||||
|
def compute_useful_length |
||||
|
IO.console.winsize[1] - FOREMAN_PREFIX_LENGTH |
||||
|
rescue StandardError |
||||
|
FAILOVER_WRAP |
||||
|
end |
||||
|
|
||||
|
def unbold!(text) = text.gsub!(BOLD, '') |
||||
|
|
||||
|
def message |
||||
|
colorize(log.message) if log.message |
||||
|
end |
||||
|
|
||||
|
def payload |
||||
|
return unless log.payload |
||||
|
|
||||
|
log.payload.ai(indent: 2, object_id: false) |
||||
|
end |
||||
|
|
||||
|
def exception |
||||
|
return unless log.exception |
||||
|
|
||||
|
exc = log.exception |
||||
|
clazz = colorize("#{exc.class}\n", color_map[:fatal]) |
||||
|
message = colorize(exc.message.chomp(''), color_map[:error]) |
||||
|
backtrace = stackisize(Rails.backtrace_cleaner.clean(exc.backtrace)) |
||||
|
|
||||
|
"#{clazz}#{message}#{backtrace}" |
||||
|
end |
||||
|
|
||||
|
def level_char |
||||
|
case log.level |
||||
|
when :info, :debug then ' ' |
||||
|
else log.level.to_s.chr.upcase |
||||
|
end |
||||
|
end |
||||
|
|
||||
|
def wrap_level(length, *items) |
||||
|
prefix = log.dimensions ? build_dimensions(log.dimensions) : build_prefix(level_char) |
||||
|
continuation = build_prefix('┆') |
||||
|
|
||||
|
result = items.map do |item| |
||||
|
AnsiWrapper.wrap(item, length, prefix, continuation) |
||||
|
end |
||||
|
|
||||
|
if log.dimensions&.terminus |
||||
|
terminus = AnsiWrapper.wrap(' ', length, build_terminus) |
||||
|
result << terminus |
||||
|
end |
||||
|
|
||||
|
log.dimensions&.before&.times { result.unshift('') } |
||||
|
log.dimensions&.after&.times { result << '' } |
||||
|
result.compact.join("\n") |
||||
|
end |
||||
|
end |
||||
|
end |
@ -0,0 +1,82 @@ |
|||||
|
require_relative 'ansi_colors' |
||||
|
|
||||
|
module Semantic |
||||
|
# AnsiWrapper cares about Ansi Colour Code \e[... |
||||
|
class AnsiWrapper |
||||
|
include AnsiColors |
||||
|
|
||||
|
TAB_TO_SPACES = 2 |
||||
|
|
||||
|
def self.wrap(text, length, prefix = '', continuation = prefix) |
||||
|
if visible_length(prefix) != visible_length(continuation) |
||||
|
raise "continuation <#{continuation.inspect}> should have the same length as prefix <#{prefix.inspect}>" |
||||
|
end |
||||
|
return unless text |
||||
|
|
||||
|
text = text.gsub("\t", ' ' * TAB_TO_SPACES) |
||||
|
|
||||
|
lines = split_text_to_lines(text, length - visible_length(prefix)) |
||||
|
lines = inject_continuation_and_ansi_colors_to_lines(lines, prefix, continuation) |
||||
|
lines.join("\n") |
||||
|
end |
||||
|
|
||||
|
private_class_method def self.inject_continuation_and_ansi_colors_to_lines(lines, prefix, continuation) |
||||
|
last_ansi = '' |
||||
|
lines.each_with_index.map do |line, index| |
||||
|
current = index.zero? ? prefix : continuation |
||||
|
current += last_ansi unless last_ansi.empty? || last_ansi == CLEAR |
||||
|
current += line |
||||
|
|
||||
|
last_ansi = scan_for_actual_ansi(line, last_ansi) |
||||
|
|
||||
|
current += CLEAR if last_ansi.empty? || last_ansi != CLEAR |
||||
|
current |
||||
|
end |
||||
|
end |
||||
|
|
||||
|
private_class_method def self.scan_for_actual_ansi(line, last_ansi) |
||||
|
line.scan(ANSI_REGEX).each do |match| |
||||
|
ansi_code = match.to_s |
||||
|
if ansi_code == CLEAR |
||||
|
last_ansi = CLEAR |
||||
|
else |
||||
|
last_ansi += ansi_code |
||||
|
end |
||||
|
end |
||||
|
last_ansi |
||||
|
end |
||||
|
|
||||
|
private_class_method def self.split_text_to_lines(text, length) |
||||
|
lines = text.split("\n") |
||||
|
sublines = lines.map do |line| |
||||
|
visible_length(line) > length ? visible_split(line, length) : [line] |
||||
|
end |
||||
|
sublines.flatten |
||||
|
end |
||||
|
|
||||
|
private_class_method def self.visible_length(line) |
||||
|
raise 'line should not contain carriage return character!' if line.match "\n" |
||||
|
|
||||
|
ansi_code_length = line.scan(ANSI_REGEX).map(&:length).sum |
||||
|
line.length - ansi_code_length |
||||
|
end |
||||
|
|
||||
|
# TODO: might be refactored with less complexity |
||||
|
private_class_method def self.visible_split(line, length, stack = '') # rubocop:disable Metrics/AbcSize,Metrics/MethodLength |
||||
|
before, ansi_code, after = line.partition(ANSI_REGEX) |
||||
|
stack_length = visible_length(stack) |
||||
|
visible_length = before.length + stack_length |
||||
|
if visible_length == length |
||||
|
["#{stack}#{before}#{ansi_code}"] + visible_split(after, length) |
||||
|
elsif visible_length > length |
||||
|
first_line = stack + before[0...length - stack_length] |
||||
|
tail = before[length - stack_length..] + ansi_code + after |
||||
|
[first_line] + visible_split(tail, length) |
||||
|
elsif ansi_code.length.positive? |
||||
|
visible_split(after, length, "#{stack}#{before}#{ansi_code}") |
||||
|
else |
||||
|
["#{stack}#{before}#{ansi_code}"] |
||||
|
end |
||||
|
end |
||||
|
end |
||||
|
end |
@ -0,0 +1,84 @@ |
|||||
|
module Semantic |
||||
|
# My Custom colorized formatter |
||||
|
class BasicFormatter < SemanticLogger::Formatters::Color |
||||
|
ANSI_REVERSED_WARNING = "\e[0;30;43m".freeze |
||||
|
ANSI_REVERSED_ERROR = "\e[1;30;41m".freeze |
||||
|
ANSI_GRAY = "\e[90m".freeze |
||||
|
# Return the complete log level name in uppercase |
||||
|
|
||||
|
def initialize |
||||
|
super(time_format: '%H:%M:%S', |
||||
|
color_map: ColorMap.new( |
||||
|
debug: ANSI_GRAY, |
||||
|
info: SemanticLogger::AnsiColors::GREEN, |
||||
|
warn: SemanticLogger::AnsiColors::YELLOW |
||||
|
)) |
||||
|
@time_format = nil if File.exist?(File.join(Rails.root, 'Procfile.dev')) |
||||
|
end |
||||
|
|
||||
|
def time |
||||
|
"#{color}#{format_time(log.time)}#{color_map.clear}" if time_format |
||||
|
end |
||||
|
|
||||
|
def message |
||||
|
return unless log.message |
||||
|
|
||||
|
prefix = "#{color}--#{color_map.clear}" |
||||
|
|
||||
|
case log.level |
||||
|
when :info |
||||
|
message = log.message |
||||
|
message = message&.rstrip if log.name == 'Rails' && message.starts_with?('Completed') |
||||
|
if log.name == 'Rails' && message.starts_with?('Started') |
||||
|
message = message.split('for')[0] |
||||
|
puts '' if Rails.env.development? |
||||
|
end |
||||
|
if log.name == 'Rails' || log.name == 'ActionView::Base' |
||||
|
"#{prefix} #{ANSI_GRAY}#{message}#{color_map.clear}" |
||||
|
else |
||||
|
"#{prefix} #{SemanticLogger::AnsiColors::WHITE}#{message}#{color_map.clear}" |
||||
|
end |
||||
|
when :warn |
||||
|
"#{prefix} #{ANSI_REVERSED_WARNING}#{log.message}#{color_map.clear}" |
||||
|
when :error, :fatal |
||||
|
"#{prefix} #{ANSI_REVERSED_ERROR}#{log.message}#{color_map.clear}" |
||||
|
else |
||||
|
"#{prefix} #{color}#{log.message}#{color_map.clear}" |
||||
|
end |
||||
|
end |
||||
|
|
||||
|
def tags; end |
||||
|
|
||||
|
def process_info |
||||
|
fname = file_name_and_line |
||||
|
"#{color}[#{fname}]#{color_map.clear}" if fname |
||||
|
end |
||||
|
|
||||
|
def exception |
||||
|
return unless log.exception |
||||
|
|
||||
|
root_path = Rails.root.to_s |
||||
|
backtrace = log.exception.backtrace.select do |line| |
||||
|
line.starts_with?(root_path) |
||||
|
end |
||||
|
|
||||
|
if backtrace.count.positive? |
||||
|
"-- #{ANSI_REVERSED_ERROR}#{log.exception.class}#{color_map.clear} #{color}#{log.exception.message}#{color_map.clear}#{SemanticLogger::AnsiColors::WHITE}\n\t#{backtrace.join("\n\t")}#{color_map.clear}\n\n" |
||||
|
else |
||||
|
"-- #{ANSI_REVERSED_ERROR}#{log.exception.class}: #{log.exception.message}#{color_map.clear}\n\n" |
||||
|
end |
||||
|
end |
||||
|
|
||||
|
def call(log, logger) |
||||
|
self.color = color_map[log.level] |
||||
|
self.log = log |
||||
|
self.logger = logger |
||||
|
|
||||
|
if @time_format |
||||
|
[time, level, process_info, tags, named_tags, duration, name, message, payload, exception].compact.join(' ') |
||||
|
else |
||||
|
[tags, named_tags, duration, name, message, payload, exception].compact.join(' ') |
||||
|
end |
||||
|
end |
||||
|
end |
||||
|
end |
Write
Preview
Loading…
Cancel
Save
Reference in new issue