pvincent
5 months ago
5 changed files with 11 additions and 512 deletions
-
8config/environments/development.rb
-
12lib/formatters/ansi_formatter.rb
-
243lib/old/basic_formatter.rb
-
171lib/old/symbols.txt
-
89lib/old/wrapper.rb
@ -1,243 +0,0 @@ |
|||
require_relative 'wrapper' |
|||
require_relative 'base' |
|||
require 'io/console' |
|||
require 'amazing_print' |
|||
require 'json' |
|||
|
|||
# Opinioned Rails custom formatter |
|||
class BasicFormatter < SemanticLogger::Formatters::Color # rubocop:disable Metrics/ClassLength #FIXME: remove rubocop disable here! |
|||
NAME_MAX_SIZE = 25 |
|||
TERMINAL_PREFIX = ENV['TERMINAL_PREFIX'].to_i || 0 |
|||
CONTENT_PREFIX = ' '.freeze |
|||
PREFIX_RAILS_INTERNAL = '⬂ '.freeze |
|||
PREFIX_RECORD_INTERNAL = '⬄ '.freeze |
|||
PREFIX_ACTION_INTERNAL = '⬃ '.freeze |
|||
PREFIX_BUG_INTERNAL = '➟ '.freeze |
|||
|
|||
RENDERED_VIEW_DURATION = 100 |
|||
COMPLETED_DURATION = RENDERED_VIEW_DURATION * 5 |
|||
|
|||
ANSI_RESET = "\e[0m".freeze |
|||
ANSI_BOLD = "\e[1m".freeze |
|||
ANSI_DEBUG = "\e[90m".freeze |
|||
ANSI_INFO = SemanticLogger::AnsiColors::GREEN |
|||
ANSI_WARN = SemanticLogger::AnsiColors::YELLOW |
|||
ANSI_ERROR = "\e[91m".freeze |
|||
ANSI_NEUTRAL_INFO = SemanticLogger::AnsiColors::WHITE |
|||
ANSI_REVERSED_WARNING = "\e[0;30;43m".freeze |
|||
ANSI_REVERSED_ERROR = "\e[1;30;41m".freeze |
|||
ANSI_REVERSED_FATAL = "\e[1;30;41m".freeze |
|||
|
|||
CONTENT_COLOR_MAP = ColorMap.new( |
|||
debug: ANSI_DEBUG, |
|||
info: ANSI_NEUTRAL_INFO, |
|||
warn: ANSI_REVERSED_WARNING, |
|||
error: ANSI_REVERSED_ERROR, |
|||
fatal: ANSI_REVERSED_FATAL |
|||
) |
|||
|
|||
# exclude log eagerly! |
|||
EXCLUDE_LAMBDA = lambda { |log| |
|||
if log.name == 'ActionView::Base' |
|||
!log.message.starts_with?(' Rendering') |
|||
elsif log.name == 'Rails' && !log.message.nil? |
|||
log.message.exclude?('Started GET "/rails/live/reload') |
|||
elsif log.name == 'ActiveRecord::Base' |
|||
log.message.exclude?('↳ lib/formatters/basic_formatter.rb') |
|||
else |
|||
true |
|||
end |
|||
} |
|||
|
|||
def initialize |
|||
super(color_map: ColorMap.new( |
|||
debug: ANSI_DEBUG, |
|||
info: ANSI_INFO, |
|||
warn: ANSI_WARN, |
|||
error: ANSI_ERROR, |
|||
fatal: ANSI_ERROR |
|||
)) |
|||
end |
|||
|
|||
def message |
|||
return unless log.message |
|||
|
|||
message = wrap_message(log.message) |
|||
ansi_wrap(message, CONTENT_COLOR_MAP[log.level]) |
|||
end |
|||
|
|||
def payload |
|||
return unless log.payload |
|||
|
|||
lines = log.payload.ai(ruby19_syntax: true, indent: 2, object_id: false).split("\n") |
|||
first_line = lines.shift |
|||
space_prefix = first_line.match(/^\s*/) |
|||
lines = lines.map do |l| |
|||
"#{before_message(wrapped: true)}#{space_prefix}#{l}" |
|||
end |
|||
result = lines.unshift(first_line).join("\n") |
|||
result.sub!(/\s*/) { |m| '-' * m.length } |
|||
result |
|||
end |
|||
|
|||
def level |
|||
case log.level |
|||
when :info, :debug then draw_rails ' ' |
|||
else draw_rails(log.level.to_s.chr.upcase) |
|||
end |
|||
end |
|||
|
|||
def name |
|||
ansi_wrap(log.name.truncate(NAME_MAX_SIZE).center(NAME_MAX_SIZE), ANSI_DEBUG) |
|||
end |
|||
|
|||
def exception |
|||
return unless log.exception |
|||
|
|||
clazz = log.exception.class |
|||
message = log.exception.message |
|||
stack = backtrace(log.exception) |
|||
"#{ansi_wrap(clazz, ANSI_REVERSED_WARNING)} #{ansi_wrap(message, ANSI_REVERSED_ERROR)}#{stack}" |
|||
end |
|||
|
|||
def call(log, logger) |
|||
self.log = transform_log(log) |
|||
self.color = color_map[self.log.level] |
|||
self.logger = logger |
|||
|
|||
before_message + [message, payload, exception].compact.join(' ') |
|||
end |
|||
|
|||
private |
|||
|
|||
def ansi_wrap(text, ansi_code) |
|||
"#{ansi_code}#{text}#{color_map.clear}" |
|||
end |
|||
|
|||
def draw_rails(char) |
|||
ansi_wrap("╣#{char}╠", color) |
|||
end |
|||
|
|||
def continuation |
|||
draw_rails('┆') |
|||
end |
|||
|
|||
# transform log before display |
|||
def transform_log(log) |
|||
case log.name |
|||
when 'ActionView::Base' then transform_action_view_base(log) |
|||
when 'Rails' then transform_rails_log(log) |
|||
when 'ActiveRecord::Base' |
|||
log.message = transform_active_record_message(log.message) |
|||
log |
|||
else log end |
|||
end |
|||
|
|||
def transform_rails_log(log) |
|||
return log unless log.message |
|||
|
|||
log.message = transform_rails_message(log.message.rstrip) |
|||
log.level = :debug if log.message =~ /^#{PREFIX_RAILS_INTERNAL}(Processing|Parameters)/ |
|||
log |
|||
end |
|||
|
|||
def transform_active_record_message(message) |
|||
message = message.lstrip.sub(/^↳ /, 'Processed by ') |
|||
"#{PREFIX_RECORD_INTERNAL}#{message.lstrip}" |
|||
end |
|||
|
|||
def transform_rails_message(message) |
|||
case message |
|||
when /^Completed/ then transform_rails_completed(message) |
|||
when /^Started/ then two_captures_last_as_bold(message, /(^Started \w* )"(.*?)"/) |
|||
when /^ Parameters/ then transform_rails_parameters(message) |
|||
when /^Processing/ then transform_rails_processing(message) |
|||
else message end |
|||
end |
|||
|
|||
def transform_rails_processing(message) |
|||
message = two_captures_last_as_bold(message, /(^Processing by \w*#\w* as )(.*)/) |
|||
"#{PREFIX_RAILS_INTERNAL}#{message}" |
|||
end |
|||
|
|||
def transform_rails_parameters(message) |
|||
parameters = message.lstrip.match(/Parameters: ({.*}$)/).match(1) |
|||
parameters = JSON.parse(parameters.gsub('=>', ':'), symbolize_names: true) |
|||
"#{PREFIX_RAILS_INTERNAL}Parameters: #{parameters.ai(ruby19_syntax: true, plain: true, multiline: false)}" |
|||
end |
|||
|
|||
def transform_rails_completed(message) |
|||
m1, m2, m3, m4 = message.match(/^Completed (\d*) (.*) in (\d*)ms(.*)$/).captures |
|||
http_code = ansi_wrap("#{m1} #{m2}", ANSI_BOLD) |
|||
message = "Completed #{http_code} in #{m3}ms" |
|||
message += m4 if m3.to_i > COMPLETED_DURATION |
|||
message += "\n" if m1 =~ /^[23]/ |
|||
message |
|||
end |
|||
|
|||
def two_captures_last_as_bold(message, regex) |
|||
m1, m2 = message.match(regex).captures |
|||
"#{m1}#{ansi_wrap(m2, ANSI_BOLD)}" |
|||
end |
|||
|
|||
def transform_action_view_base(log) |
|||
log.level, message = transform_log_debug_lstrip(log) |
|||
message = transform_rendered_message_with_filename(message) |
|||
message = transform_duration_strip(message) |
|||
log.message = "#{PREFIX_ACTION_INTERNAL}#{message}" |
|||
log |
|||
end |
|||
|
|||
def transform_duration_strip(message) |
|||
md2 = message.match(/( \(Duration: \d+\.?\d?ms.*\))/) |
|||
md3 = md2.match(1).match(/Duration: (\d+\.?\d?)ms/) |
|||
duration = md3.match(1).to_f |
|||
duration < RENDERED_VIEW_DURATION ? md2.pre_match : message |
|||
end |
|||
|
|||
def transform_rendered_message_with_filename(message) |
|||
md = message.match(/^Rendered( layout| collection of|) (.*?\.erb)/) |
|||
return message unless md |
|||
|
|||
filename = "app/views/#{md.match(2)}" |
|||
"Rendered#{md.match(1)} #{filename}#{md.post_match}" |
|||
end |
|||
|
|||
def transform_log_debug_lstrip(log) |
|||
[:debug, log.message.lstrip] |
|||
end |
|||
|
|||
def wrap_message(message) |
|||
message, space_prefix = split_spaces_in_front(message) |
|||
message = Wrapper.wrap("#{CONTENT_COLOR_MAP[log.level]}#{message}", |
|||
before_message(true) + space_prefix.to_s, |
|||
compute_useful_length - space_prefix.length) |
|||
"#{space_prefix}#{message}" |
|||
end |
|||
|
|||
def split_spaces_in_front(message) |
|||
md = message.match(/^\s*/) |
|||
[md.post_match, md.match(0)] |
|||
end |
|||
|
|||
def compute_useful_length |
|||
IO.console.winsize[1] - TERMINAL_PREFIX - before_message.length + CONTENT_PREFIX.length + 12 |
|||
rescue StandardError |
|||
100 # FIXME: CONSTANTIZE, only useful in DEBUGGER, no IO.console detected! |
|||
end |
|||
|
|||
def before_message(wrapped = false) |
|||
[name, wrapped ? continuation : level, tags, named_tags, duration].compact.join(' ') + CONTENT_PREFIX |
|||
end |
|||
|
|||
def backtrace(exception) |
|||
root_path = Rails.root.to_s |
|||
stack = exception.backtrace.select { |line| line.starts_with?(root_path) } |
|||
stack = stack.map { |line| line.delete_prefix("#{root_path}/") } |
|||
return "\n" unless stack.count.positive? |
|||
|
|||
stack_message = PREFIX_BUG_INTERNAL |
|||
stack_message += stack.join("\n#{before_message}#{ANSI_ERROR}#{PREFIX_BUG_INTERNAL}") |
|||
"\n#{before_message}#{ansi_wrap(stack_message, ANSI_ERROR)}\n" |
|||
end |
|||
end |
@ -1,171 +0,0 @@ |
|||
╣ ╠ |
|||
╣ǁ╠ |
|||
╣▹╠ |
|||
╣◉╠ |
|||
╣◼╠ |
|||
╙╨╜ |
|||
╙─╜ |
|||
╙─╜ |
|||
|
|||
╣▥╠ |
|||
╣☉╠ |
|||
╙─╜ |
|||
|
|||
╣☀╠ |
|||
╙─╜ |
|||
🛑 |
|||
🚨 |
|||
🛟 |
|||
🚧 |
|||
🔥 |
|||
💣 |
|||
⛔ |
|||
↘ |
|||
↪ |
|||
☑ |
|||
✅ |
|||
❌ |
|||
✔ |
|||
⚪ |
|||
🏁 |
|||
☠️ |
|||
😵💫 |
|||
👁 |
|||
👣 |
|||
👉 |
|||
💫 |
|||
🎗 |
|||
🚆 |
|||
⚒ |
|||
📌 |
|||
📎 |
|||
✹♾🔥🚧 |
|||
➾✮✵✹ |
|||
☇ |
|||
|
|||
|
|||
╙─╜ 200 OK |
|||
|
|||
╙ ╜ 300 redirect |
|||
╙⸾╜ 300 redirect |
|||
|
|||
╙ ╜ |
|||
╓ ╖ |
|||
╣⸾╠ |
|||
╙─╜ |
|||
|
|||
|
|||
╙⍑╜ |
|||
╓─╖ |
|||
╣⸾╠ |
|||
╙─╜ |
|||
|
|||
╙─╜ |
|||
|
|||
╓─╖ |
|||
╣⸾╠ |
|||
╙─╜ |
|||
╣─╠ |
|||
╙┻╜ |
|||
╓─╖ |
|||
╙─╜ |
|||
|
|||
|
|||
|
|||
╙╨╜ 400 client error |
|||
╙ǁ╜ 400 client error |
|||
|
|||
╙⍑╜ 500 servererror |
|||
╙⯶╜ 500 servererror |
|||
╙ ╜ 500 servererror |
|||
|
|||
00:20:45 web.1 | Rails ╣ ╠ Redirected to http://127.0.0.1:7500/scores/1 |
|||
00:20:45 web.1 | Rails ╙║╜ Completed 303 See Other in 10ms (ActiveRecord: 0.7ms | Allocations: 2390) |
|||
00:20:45 web.1 | Rails ╓║╖ Started GET "/scores/1" for 127.0.0.1 at 2024-06-07 00:20:45 +0400 |
|||
00:20:45 web.1 | Rails ╣ ╠ Processing by ScoresController#show as TURBO_STREAM |
|||
00:20:45 web.1 | Rails ╣ ╠ Parameters: {"id"=>"1"} |
|||
|
|||
00:37:27 web.1 | Rails ╙║╜ Completed 303 See Other in 16ms (ActiveRecord: 0.8ms | Allocations: 2395) |
|||
00:37:27 web.1 | Rails ╓║╖ Started GET "/scores/1" for 127.0.0.1 at 2024-06-07 00:37:27 +0400 |
|||
00:37:27 web.1 | Rails ╣ ╠ Processing by ScoresController#show as TURBO_STREAM |
|||
|
|||
╣ ╠ |
|||
╣⍑╠ |
|||
╣⍑╠ |
|||
╣⯶╠ |
|||
╣⸾╠ |
|||
╙─╜ |
|||
|
|||
╣┻╠ |
|||
╓─╖ |
|||
╣║╠ |
|||
╣║╠ |
|||
╣╎╠ |
|||
╣┊╠ |
|||
╣┆╠ |
|||
╣│╠ |
|||
|
|||
╣⇰╠ |
|||
╣⭪╠ |
|||
╣⥱╠ |
|||
╣⇴╠ |
|||
╣⇲╠ |
|||
╣⇥╠ |
|||
╣⇄╠ |
|||
╣⇣╠ |
|||
╙╨╜ |
|||
╙─╜ |
|||
╙─╜ |
|||
╣⍾╠ |
|||
╣⬸╠ |
|||
╣☐╠ |
|||
╣☑╠ |
|||
╙─╜ |
|||
╙─╜ |
|||
|
|||
|
|||
╣☒╠ |
|||
╙─╜ |
|||
|
|||
╣⚐╠ |
|||
╙─╜ |
|||
|
|||
╣⛭╠ |
|||
╙─╜ |
|||
|
|||
╣➾╠ |
|||
╙─╜ |
|||
|
|||
╣⤫╠ |
|||
╙─╜ |
|||
⦹ |
|||
|
|||
╣⦾╠ |
|||
⨈ |
|||
⦾ |
|||
⦿ |
|||
⮾ |
|||
|
|||
ⵙⵞ |
|||
⸾ |
|||
╣⫨╠ |
|||
╣⤫╠ |
|||
╣⫫╠ |
|||
|
|||
⫫ |
|||
⫨ |
|||
⮓ |
|||
⮱ |
|||
⮾ |
|||
⇤ |
|||
|
|||
⛭ |
|||
➾ |
|||
➟ |
|||
⬎ |
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
@ -1,89 +0,0 @@ |
|||
class Wrapper |
|||
def self.wrap(text, prefix = ' > ', length = 80) |
|||
text = carriage_return_filler(text.chomp(''), length) |
|||
|
|||
pure = '' |
|||
ansi_code = {} |
|||
while (md = text.match(/\e\[\d+;?\d*m/)) |
|||
pos = md.begin(0) + pure.length |
|||
pure += md.pre_match |
|||
text = md.post_match |
|||
append_in_hash(ansi_code, pos, md.match(0)) |
|||
end |
|||
pure += text |
|||
|
|||
offset = 0 |
|||
ansi_extra = {} |
|||
rows = pure.length / length |
|||
last_code = nil |
|||
rows.times do |i| |
|||
pos = (i + 1) * length |
|||
last_code = last_ansi_by_range(ansi_code, last_code, offset, pos) |
|||
if last_code |
|||
append_in_hash(ansi_extra, pos, "\e[0m\n#{prefix}#{last_code}") |
|||
elsif pos < pure.length |
|||
append_in_hash(ansi_extra, pos, "\n#{prefix}") |
|||
end |
|||
offset = pos + 1 |
|||
end |
|||
ansi_extra.each_pair do |k, v| |
|||
append_in_hash(ansi_code, k, v.first) |
|||
end |
|||
|
|||
ansi_code = ansi_code.sort |
|||
|
|||
final = pure |
|||
offset = 0 |
|||
ansi_code.each do |k, v| |
|||
insert_text = v.join |
|||
final = final.insert(k + offset, insert_text) |
|||
offset += insert_text.length |
|||
end |
|||
|
|||
final |
|||
end |
|||
|
|||
private |
|||
|
|||
def self.carriage_return_filler(text, length) |
|||
lines = text.split("\n") |
|||
return text if lines.count < 2 |
|||
|
|||
last_ansi = nil |
|||
final = lines.map do |line| |
|||
fill_count = length - line.length |
|||
current = line |
|||
result = last_ansi ? "#{last_ansi}#{line}" : line |
|||
last_ansi = nil if last_ansi == "\e[0m" |
|||
while (md = current.match(/\e\[\d+;?\d*m/)) |
|||
last_ansi = md.to_s |
|||
fill_count += last_ansi.length |
|||
current = md.post_match |
|||
end |
|||
result = "#{result}\e[0m" if last_ansi |
|||
next result if fill_count < 1 |
|||
|
|||
"#{result}#{' ' * fill_count}" |
|||
end.join |
|||
last_ansi ? "#{final}\e[0m" : final |
|||
end |
|||
|
|||
def self.last_ansi_by_range(ansi_code, last_code, offset, pos) |
|||
pos.downto(offset) do |i| |
|||
next unless ansi_code.has_key?(i) |
|||
|
|||
result = ansi_code[i].last |
|||
result = nil if result == "\e[0m" |
|||
return result |
|||
end |
|||
last_code |
|||
end |
|||
|
|||
def self.append_in_hash(hash, key, value) |
|||
if hash.has_key?(key) |
|||
hash[key] << value |
|||
else |
|||
hash[key] = [value] |
|||
end |
|||
end |
|||
end |
Write
Preview
Loading…
Cancel
Save
Reference in new issue