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.

201 lines
5.7 KiB

10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
11 months ago
10 months ago
11 months ago
11 months ago
11 months ago
11 months ago
11 months ago
11 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
11 months ago
11 months ago
11 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
11 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. RENDERED_VIEW_DURATION = 100
  11. ANSI_RESET = "\e[0m".freeze
  12. ANSI_BOLD = "\e[1m".freeze
  13. ANSI_DEBUG = "\e[90m".freeze
  14. ANSI_INFO = SemanticLogger::AnsiColors::GREEN
  15. ANSI_WARN = SemanticLogger::AnsiColors::YELLOW
  16. ANSI_ERROR = "\e[91m".freeze
  17. ANSI_NEUTRAL_INFO = SemanticLogger::AnsiColors::WHITE
  18. ANSI_REVERSED_WARNING = "\e[0;30;43m".freeze
  19. ANSI_REVERSED_ERROR = "\e[1;30;41m".freeze
  20. ANSI_REVERSED_FATAL = "\e[1;30;41m".freeze
  21. CONTENT_COLOR_MAP = ColorMap.new(
  22. debug: ANSI_DEBUG,
  23. info: ANSI_NEUTRAL_INFO,
  24. warn: ANSI_REVERSED_WARNING,
  25. error: ANSI_REVERSED_ERROR,
  26. fatal: ANSI_REVERSED_FATAL
  27. )
  28. # exclude log eagerly!
  29. EXCLUDE_LAMBDA = lambda { |log|
  30. if log.name == 'ActionView::Base'
  31. !log.message.starts_with?(' Rendering')
  32. elsif log.name == 'Rails' && !log.message.nil?
  33. log.message.exclude?('Started GET "/rails/live/reload')
  34. elsif log.name == 'ActiveRecord::Base'
  35. log.message.exclude?('↳ lib/formatters/basic_formatter.rb')
  36. else
  37. true
  38. end
  39. }
  40. def initialize
  41. super(color_map: ColorMap.new(
  42. debug: ANSI_DEBUG,
  43. info: ANSI_INFO,
  44. warn: ANSI_WARN,
  45. error: ANSI_ERROR,
  46. fatal: ANSI_ERROR
  47. ))
  48. end
  49. def message
  50. return unless log.message
  51. message = wrap_message(log.message)
  52. "#{CONTENT_COLOR_MAP[log.level]}#{message}#{color_map.clear}"
  53. end
  54. def payload
  55. return unless log.payload
  56. lines = log.payload.ai(ruby19_syntax: true, indent: 2).split("\n")
  57. first_line = lines.shift
  58. space_prefix = first_line.match(/^\s*/)
  59. lines = lines.map do |l|
  60. "#{before_message(wrapped: true)}#{space_prefix}#{l}"
  61. end
  62. result = lines.unshift(first_line).join("\n")
  63. result.sub!(/\s*/) { |m| '-' * m.length }
  64. result
  65. end
  66. def level
  67. case log.level
  68. when :info, :debug then draw_rails ' '
  69. else draw_rails(log.level.to_s.chr.upcase)
  70. end
  71. end
  72. def name
  73. "#{ANSI_DEBUG}#{log.name.truncate(NAME_MAX_SIZE).center(NAME_MAX_SIZE)}#{color_map.clear}"
  74. end
  75. def exception # rubocop:disable Metrics/AbcSize
  76. return unless log.exception
  77. root_path = Rails.root.to_s
  78. stack = log.exception.backtrace.select { |line| line.starts_with?(root_path) }
  79. stack = stack.map { |line| line.delete_prefix("#{root_path}/") }
  80. "#{ANSI_REVERSED_WARNING}#{log.exception.class}#{color_map.clear} #{ANSI_REVERSED_ERROR}#{log.exception.message}#{color_map.clear}#{backtrace(stack)}" # rubocop:disable Layout/LineLength
  81. end
  82. def call(log, logger)
  83. self.log = transform_log(log)
  84. self.color = color_map[self.log.level]
  85. self.logger = logger
  86. before_message + [message, payload, exception].compact.join(' ')
  87. end
  88. private
  89. def draw_rails(char)
  90. "#{color}#{char}#{color_map.clear}"
  91. end
  92. def continuation
  93. draw_rails('┆')
  94. end
  95. # transform log before display
  96. def transform_log(log)
  97. if log.name == 'ActionView::Base'
  98. log = transform_action_view_base(log)
  99. elsif log.name == 'Rails' && log.message
  100. log.message = transform_rails_message(log.message.rstrip)
  101. end
  102. log
  103. end
  104. def transform_rails_message(message)
  105. case message
  106. when /^Completed [23]/
  107. "#{message}\n"
  108. when /^Started/
  109. two_captures_last_as_bold(message, /(^Started \w* )"(.*?)"/)
  110. when /^Processing/
  111. two_captures_last_as_bold(message, /(^Processing by \w*#\w* as )(.*)/)
  112. else
  113. message
  114. end
  115. end
  116. def two_captures_last_as_bold(message, regex)
  117. m1, m2 = message.match(regex).captures
  118. "#{m1}#{ANSI_BOLD}#{m2}#{ANSI_RESET}"
  119. end
  120. def transform_action_view_base(log)
  121. log.level, message = transform_log_debug_lstrip(log)
  122. message = transform_rendered_message_with_filename(message)
  123. message = transform_duration_strip(message)
  124. log.message = message
  125. log
  126. end
  127. def transform_duration_strip(message)
  128. md2 = message.match(/( \(Duration: \d+\.?\d?ms.*\))/)
  129. md3 = md2.match(1).match(/Duration: (\d+\.?\d?)ms/)
  130. duration = md3.match(1).to_f
  131. duration < RENDERED_VIEW_DURATION ? md2.pre_match : message
  132. end
  133. def transform_rendered_message_with_filename(message)
  134. md = message.match(/^Rendered( layout| collection of|) (.*?\.erb)/)
  135. filename = "app/views/#{md.match(2)}"
  136. log.message = "Rendered#{md.match(1)} #{filename}#{md.post_match}"
  137. end
  138. def transform_log_debug_lstrip(log)
  139. [:debug, log.message.lstrip]
  140. end
  141. def wrap_message(message)
  142. message, space_prefix = split_spaces_in_front(message)
  143. message = Wrapper.wrap("#{CONTENT_COLOR_MAP[log.level]}#{message}",
  144. before_message(wrapped: true) + space_prefix.to_s,
  145. compute_useful_length - space_prefix.length)
  146. "#{space_prefix}#{message}"
  147. end
  148. def split_spaces_in_front(message)
  149. md = message.match(/^\s*/)
  150. [md.post_match, md.match(0)]
  151. end
  152. def compute_useful_length
  153. IO.console.winsize[1] - TERMINAL_PREFIX - before_message.length + CONTENT_PREFIX.length + 12
  154. rescue StandardError
  155. 100 # FIXME: CONSTANTIZE, only useful in DEBUGGER, no IO.console detected!
  156. end
  157. def before_message(wrapped: false)
  158. [name, wrapped ? continuation : level, tags, named_tags, duration].compact.join(' ') + CONTENT_PREFIX
  159. end
  160. def backtrace(stack)
  161. return "\n" unless stack.count.positive?
  162. "\n#{before_message} #{ANSI_ERROR}#{stack.join("\n#{before_message} #{ANSI_ERROR}")}#{color_map.clear}\n"
  163. end
  164. def color_content
  165. color
  166. end
  167. end