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.

181 lines
5.2 KiB

11 months ago
10 months ago
11 months ago
10 months ago
11 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
11 months ago
11 months ago
11 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_DEBUG = "\e[90m".freeze
  12. ANSI_INFO = SemanticLogger::AnsiColors::GREEN
  13. ANSI_WARN = SemanticLogger::AnsiColors::YELLOW
  14. ANSI_ERROR = "\e[91m".freeze
  15. ANSI_NEUTRAL_INFO = SemanticLogger::AnsiColors::WHITE
  16. ANSI_REVERSED_WARNING = "\e[0;30;43m".freeze
  17. ANSI_REVERSED_ERROR = "\e[1;30;41m".freeze
  18. ANSI_REVERSED_FATAL = "\e[1;30;41m".freeze
  19. CONTENT_COLOR_MAP = ColorMap.new(
  20. debug: ANSI_DEBUG,
  21. info: ANSI_NEUTRAL_INFO,
  22. warn: ANSI_REVERSED_WARNING,
  23. error: ANSI_REVERSED_ERROR,
  24. fatal: ANSI_REVERSED_FATAL
  25. )
  26. EXCLUDE_LAMBDA = lambda { |log|
  27. if log.name == 'ActionView::Base'
  28. !log.message.starts_with?(' Rendering')
  29. elsif log.name == 'Rails' && !log.message.nil?
  30. log.message.exclude?('Started GET "/rails/live/reload')
  31. elsif log.name == 'ActiveRecord::Base'
  32. log.message.exclude?('↳ lib/formatters/basic_formatter.rb')
  33. else
  34. true
  35. end
  36. }
  37. def initialize
  38. super(color_map: ColorMap.new(
  39. debug: ANSI_DEBUG,
  40. info: ANSI_INFO,
  41. warn: ANSI_WARN,
  42. error: ANSI_ERROR,
  43. fatal: ANSI_ERROR
  44. ))
  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 name
  70. "#{ANSI_DEBUG}#{log.name.truncate(NAME_MAX_SIZE).center(NAME_MAX_SIZE)}#{color_map.clear}"
  71. end
  72. def exception # rubocop:disable Metrics/AbcSize
  73. return unless log.exception
  74. root_path = Rails.root.to_s
  75. stack = log.exception.backtrace.select { |line| line.starts_with?(root_path) }
  76. stack = stack.map { |line| line.delete_prefix("#{root_path}/") }
  77. "#{ANSI_REVERSED_WARNING}#{log.exception.class}#{color_map.clear} #{ANSI_REVERSED_ERROR}#{log.exception.message}#{color_map.clear}#{backtrace(stack)}" # rubocop:disable Layout/LineLength
  78. end
  79. def call(log, logger)
  80. self.log = transform_log(log)
  81. self.color = color_map[self.log.level]
  82. self.logger = logger
  83. before_message + [message, payload, exception].compact.join(' ')
  84. end
  85. private
  86. def draw_rails(char)
  87. "#{color}#{char}#{color_map.clear}"
  88. end
  89. def continuation
  90. draw_rails('┆')
  91. end
  92. def transform_log(log)
  93. if log.name == 'ActionView::Base'
  94. log = transform_action_view_base(log)
  95. elsif log.name == 'Rails' && log.message
  96. message = log.message.rstrip
  97. message += "\n" if message.starts_with?('Completed 2') || message.starts_with?('Completed 3')
  98. log.message = message
  99. end
  100. log
  101. end
  102. def transform_action_view_base(log)
  103. log.level, message = transform_log_debug_lstrip(log)
  104. message = transform_rendered_message_with_filename(message)
  105. message = transform_duration_strip(message)
  106. log.message = message
  107. log
  108. end
  109. def transform_duration_strip(message)
  110. md2 = message.match(/( \(Duration: \d+\.?\d?ms.*\))/)
  111. md3 = md2.match(1).match(/Duration: (\d+\.?\d?)ms/)
  112. duration = md3.match(1).to_f
  113. duration < RENDERED_VIEW_DURATION ? md2.pre_match : message
  114. end
  115. def transform_rendered_message_with_filename(message)
  116. md = message.match(/^Rendered( layout| collection of|) (.*?\.erb)/)
  117. filename = "app/views/#{md.match(2)}"
  118. log.message = "Rendered#{md.match(1)} #{filename}#{md.post_match}"
  119. end
  120. def transform_log_debug_lstrip(log)
  121. [:debug, log.message.lstrip]
  122. end
  123. def wrap_message(message)
  124. message, space_prefix = split_spaces_in_front(message)
  125. message = Wrapper.wrap("#{CONTENT_COLOR_MAP[log.level]}#{message}",
  126. before_message(wrapped: true) + space_prefix.to_s,
  127. compute_useful_length - space_prefix.length)
  128. "#{space_prefix}#{message}"
  129. end
  130. def split_spaces_in_front(message)
  131. md = message.match(/^\s*/)
  132. [md.post_match, md.match(0)]
  133. end
  134. def compute_useful_length
  135. IO.console.winsize[1] - TERMINAL_PREFIX - before_message.length + CONTENT_PREFIX.length + 12
  136. rescue StandardError
  137. 100 # FIXME: CONSTANTIZE, only useful in DEBUGGER, no IO.console detected!
  138. end
  139. def before_message(wrapped: false)
  140. [name, wrapped ? continuation : level, tags, named_tags, duration].compact.join(' ') + CONTENT_PREFIX
  141. end
  142. def backtrace(stack)
  143. return unless stack.count.positive?
  144. "\n#{before_message} #{ANSI_ERROR}#{stack.join("\n#{before_message} #{ANSI_ERROR}")}#{color_map.clear}"
  145. end
  146. def color_content
  147. color
  148. end
  149. end