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.

186 lines
5.6 KiB

6 months ago
5 months ago
6 months ago
6 months ago
5 months ago
5 months ago
6 months ago
5 months ago
6 months ago
5 months ago
6 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
6 months ago
5 months ago
6 months ago
5 months ago
6 months ago
6 months ago
6 months ago
5 months ago
5 months ago
5 months ago
6 months ago
  1. require_relative 'ansi_wrapper'
  2. require_relative 'ansi_colors'
  3. require_relative 'ansi_dimensions'
  4. require_relative 'ansi_common'
  5. require 'io/console'
  6. require 'amazing_print'
  7. require 'json'
  8. # wraps meanwhile takes care of ansi colors
  9. class AnsiFormatter < SemanticLogger::Formatters::Color
  10. include AnsiColors
  11. CENTER_SIZE = 20
  12. FOREMAN_PREFIX_LENGTH = 18
  13. FAILOVER_WRAP = 80
  14. CHAR_FATAL = '⯶'.freeze
  15. TERMINUS_STRING = '╙─╜'.freeze
  16. RENDERED_VIEW_DURATION = 100
  17. def initialize
  18. super(color_map:
  19. ColorMap.new(
  20. debug: CLEAR + TEXT_GRAY_400,
  21. info: CLEAR + TEXT_GRAY_100,
  22. warn: CLEAR + BG_YELLOW + TEXT_BLACK,
  23. error: CLEAR + BG_RED + TEXT_WHITE,
  24. fatal: CLEAR + BG_MAGENTA + BOLD + TEXT_WHITE
  25. ))
  26. @memory = nil
  27. end
  28. def call(log, logger)
  29. log = alter(log)
  30. self.log = log
  31. self.logger = logger
  32. self.color = color_map[log.level]
  33. wrap_level(compute_useful_length, message, payload, exception)
  34. end
  35. def reject(log)
  36. return true if log.name == 'ActionView::Base' && log.message&.starts_with?(' Rendering')
  37. true if log.name == 'Rails' && log.message&.starts_with?('Loaded')
  38. end
  39. private
  40. def two_captures_last_as_bold(message, regex)
  41. match = message.match(regex)
  42. return "unmatched: #{message}" unless match
  43. m1, m2 = match.captures
  44. "#{m1}#{BOLD}#{m2}#{CLEAR}"
  45. end
  46. def alter(log)
  47. if log.name == 'Rails'
  48. if log.message
  49. log.message.lstrip!
  50. log.message.chomp!('')
  51. if log.message.starts_with?('Started')
  52. rails = '╓─╖'
  53. before = 1
  54. if @memory
  55. rails = "#{@memory}"
  56. before = 0
  57. end
  58. log.dimensions = AnsiDimensions.new(rails:, before:)
  59. @memory = nil
  60. log.message = two_captures_last_as_bold(log.message, /(^Started \w* )"(.*?)"/)
  61. elsif log.message.starts_with?('Completed 2')
  62. log.dimensions = AnsiDimensions.new(rails: TERMINUS_STRING, after: 1)
  63. elsif log.message.starts_with?('Completed 3')
  64. @memory = '║'
  65. log.dimensions = AnsiDimensions.new(rails: "#{@memory}")
  66. elsif log.message.starts_with?('Completed 4')
  67. log.dimensions = AnsiDimensions.new(rails: '╙╨╜')
  68. elsif log.message.starts_with?('Completed 5')
  69. log.dimensions = AnsiDimensions.new(rails: "#{draw_fatal}")
  70. elsif log.message =~ /^(Processing|Parameters)/
  71. log.level = :debug
  72. if log.message =~ /^Processing/
  73. log.message = two_captures_last_as_bold(log.message, /(^Processing by \w*#\w* as )(.*)/)
  74. end
  75. elsif log.message =~ /Redirected/
  76. log.level = :debug
  77. log.message = two_captures_last_as_bold(log.message,
  78. /^(Redirected to )#{Rails.application.routes.url_helpers.root_url.chop}(.*)/)
  79. end
  80. elsif log.exception
  81. log.dimensions = AnsiDimensions.new(
  82. rails: "#{draw_fatal(log.level.to_s.chr.upcase)}",
  83. after: 1,
  84. terminus: true
  85. )
  86. end
  87. elsif log.name =~ /^(ActionView|ActiveRecord)::Base/
  88. log.level = :debug
  89. log.message.lstrip!
  90. if log.name == 'ActiveRecord::Base' && log.message.starts_with?('↳ ')
  91. log.message = AnsiCommon.ansi_trace(log.message)
  92. elsif log.name == 'ActionView::Base'
  93. match = log.message.match(/^Rendered( layout| collection of|) (.*?\.erb)(.*)(\(Duration:.*\))/)
  94. if match
  95. m1, m2, m3, m4 = match.captures
  96. duration = m4.match(/Duration: (\d+\.?\d*)ms/).match(1).to_f
  97. duration = duration < RENDERED_VIEW_DURATION ? '' : m4.to_s
  98. log.message = "Rendered#{m1} #{m2}#{m3}#{duration}"
  99. end
  100. end
  101. end
  102. log
  103. end
  104. def draw_fatal(char = CHAR_FATAL)
  105. AnsiColors::BG_MAGENTA + AnsiColors::BOLD + AnsiColors::TEXT_WHITE + char + AnsiColors::CLEAR
  106. end
  107. def origin = colorize(centerize(log.name), TEXT_CYAN)
  108. def build_prefix(char) = "#{origin}#{colorize(char)}"
  109. def build_terminus = "#{origin} #{TERMINUS_STRING} "
  110. def centerize(text) = text.truncate(CENTER_SIZE).center(CENTER_SIZE)
  111. def colorize(text, tint = color) = "#{tint}#{text}#{CLEAR}"
  112. def stackisize(items)
  113. return '' if items.empty?
  114. traces = items.map { |item| AnsiCommon.ansi_trace(item) }
  115. "\n#{traces.join("\n")}"
  116. end
  117. def build_dimensions(dimensions)
  118. "#{origin} #{dimensions.rails} "
  119. end
  120. def compute_useful_length
  121. IO.console.winsize[1] - FOREMAN_PREFIX_LENGTH
  122. rescue StandardError
  123. FAILOVER_WRAP
  124. end
  125. def message
  126. colorize(log.message) if log.message
  127. end
  128. def exception
  129. return unless log.exception
  130. exc = log.exception
  131. clazz = colorize("#{exc.class}\n", color_map[:fatal])
  132. message = colorize(exc.message.chomp(''), color_map[:error])
  133. backtrace = stackisize(Rails.backtrace_cleaner.clean(exc.backtrace))
  134. "#{clazz}#{message}#{backtrace}"
  135. end
  136. def level_char
  137. case log.level
  138. when :info, :debug then ' '
  139. else log.level.to_s.chr.upcase
  140. end
  141. end
  142. def wrap_level(length, *items)
  143. prefix = log.dimensions ? build_dimensions(log.dimensions) : build_prefix(level_char)
  144. continuation = build_prefix('┆')
  145. result = items.map do |item|
  146. AnsiWrapper.wrap(item, length, prefix, continuation)
  147. end
  148. if log.dimensions&.terminus
  149. terminus = AnsiWrapper.wrap(' ', length, build_terminus)
  150. result << terminus
  151. end
  152. log.dimensions&.before&.times { result.unshift('') }
  153. log.dimensions&.after&.times { result << '' }
  154. result.compact.join("\n")
  155. end
  156. end