You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

156 lines
4.4 KiB

10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
  1. require_relative 'wrapper'
  2. require_relative 'base'
  3. require 'io/console'
  4. require 'amazing_print'
  5. # Opinioned Rails custom formatter
  6. class BasicFormatter < SemanticLogger::Formatters::Color # rubocop:disable Metrics/ClassLength
  7. NAME_MAX_SIZE = 25
  8. TERMINAL_PREFIX = ENV['TERMINAL_PREFIX'].to_i || 0
  9. CONTENT_PREFIX = ' '.freeze
  10. ANSI_DEBUG = "\e[90m".freeze
  11. ANSI_INFO = SemanticLogger::AnsiColors::GREEN
  12. ANSI_WARN = SemanticLogger::AnsiColors::YELLOW
  13. ANSI_ERROR = "\e[91m".freeze
  14. ANSI_NEUTRAL_INFO = SemanticLogger::AnsiColors::WHITE
  15. ANSI_REVERSED_WARNING = "\e[0;30;43m".freeze
  16. ANSI_REVERSED_ERROR = "\e[1;30;41m".freeze
  17. ANSI_REVERSED_FATAL = "\e[1;30;41m".freeze
  18. CONTENT_COLOR_MAP = ColorMap.new(
  19. debug: ANSI_DEBUG,
  20. info: ANSI_NEUTRAL_INFO,
  21. warn: ANSI_REVERSED_WARNING,
  22. error: ANSI_REVERSED_ERROR,
  23. fatal: ANSI_REVERSED_FATAL
  24. )
  25. EXCLUDE_LAMBDA = lambda { |log|
  26. if log.name == 'ActionView::Base'
  27. !log.message.starts_with?(' Rendering')
  28. elsif log.name == 'Rails' && !log.message.nil?
  29. log.message.exclude?('Started GET "/rails/live/reload')
  30. elsif log.name == 'ActiveRecord::Base'
  31. log.message.exclude?('↳ lib/formatters/basic_formatter.rb')
  32. else
  33. true
  34. end
  35. }
  36. def initialize
  37. super(color_map: ColorMap.new(
  38. debug: ANSI_DEBUG,
  39. info: ANSI_INFO,
  40. warn: ANSI_WARN,
  41. error: ANSI_ERROR,
  42. fatal: ANSI_ERROR
  43. ))
  44. @caller = nil
  45. end
  46. def message
  47. return unless log.message
  48. message = wrap_message(log.message)
  49. "#{CONTENT_COLOR_MAP[log.level]}#{message}#{color_map.clear}"
  50. end
  51. def payload
  52. return unless log.payload
  53. lines = log.payload.ai(ruby19_syntax: true, indent: 2).split("\n")
  54. first_line = lines.shift
  55. space_prefix = first_line.match(/^\s*/)
  56. lines = lines.map do |l|
  57. "#{before_message(wrapped: true)}#{space_prefix}#{l}"
  58. end
  59. result = lines.unshift(first_line).join("\n")
  60. result.sub!(/\s*/) { |m| '-' * m.length }
  61. result
  62. end
  63. def level
  64. case log.level
  65. when :info, :debug then draw_rails ' '
  66. else draw_rails(log.level.to_s.chr.upcase)
  67. end
  68. end
  69. def draw_rails(char)
  70. "#{color}#{char}#{color_map.clear}"
  71. end
  72. def continuation
  73. draw_rails('┆')
  74. end
  75. def name
  76. "#{ANSI_DEBUG}#{log.name.truncate(NAME_MAX_SIZE).center(NAME_MAX_SIZE)}#{color_map.clear}"
  77. end
  78. def exception # rubocop:disable Metrics/AbcSize
  79. return unless log.exception
  80. root_path = Rails.root.to_s
  81. stack = log.exception.backtrace.select { |line| line.starts_with?(root_path) }
  82. stack = stack.map { |line| line.delete_prefix("#{root_path}/") }
  83. "#{ANSI_REVERSED_WARNING}#{log.exception.class}#{color_map.clear} #{ANSI_REVERSED_ERROR}#{log.exception.message}#{color_map.clear}#{backtrace(stack)}" # rubocop:disable Layout/LineLength
  84. end
  85. def call(log, logger)
  86. self.log = transform_log(log)
  87. self.color = color_map[self.log.level]
  88. self.logger = logger
  89. before_message + [message, payload, exception].compact.join(' ')
  90. end
  91. private
  92. def transform_log(log)
  93. if log.name == 'ActionView::Base'
  94. log.level = :debug
  95. log.message = log.message.lstrip
  96. elsif log.name == 'Rails' && log.message
  97. message = log.message.rstrip
  98. message += "\n" if message.starts_with?('Completed 2') || message.starts_with?('Completed 3')
  99. log.message = message
  100. end
  101. log
  102. end
  103. def wrap_message(message)
  104. message, space_prefix = split_spaces_in_front(message)
  105. message = Wrapper.wrap("#{CONTENT_COLOR_MAP[log.level]}#{message}",
  106. before_message(wrapped: true) + space_prefix.to_s,
  107. compute_useful_length - space_prefix.length)
  108. "#{space_prefix}#{message}"
  109. end
  110. def split_spaces_in_front(message)
  111. md = message.match(/^\s*/)
  112. [md.post_match, md.match(0)]
  113. end
  114. def compute_useful_length
  115. IO.console.winsize[1] - TERMINAL_PREFIX - before_message.length + CONTENT_PREFIX.length + 12
  116. rescue StandardError
  117. 100 # FIXME: CONSTANTIZE, only useful in DEBUGGER, no IO.console detected!
  118. end
  119. def before_message(wrapped: false)
  120. [name, wrapped ? continuation : level, tags, named_tags, duration].compact.join(' ') + CONTENT_PREFIX
  121. end
  122. def backtrace(stack)
  123. nil unless stack.count.positive?
  124. "\n#{before_message} #{ANSI_ERROR}#{stack.join("\n#{before_message} #{ANSI_ERROR}")}#{color_map.clear}"
  125. end
  126. def color_content
  127. color
  128. end
  129. end