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.

147 lines
4.1 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
  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
  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. end
  45. def message
  46. return unless log.message
  47. message = log.message
  48. if log.name == 'Rails' && message.starts_with?('Completed')
  49. message.rstrip!
  50. message += "\n" unless message.starts_with?('Completed 5')
  51. end
  52. wrap_length = compute_useful_length
  53. md = message.match(/^\s*/)
  54. space_prefix = md.match(0)
  55. message = md.post_match
  56. message = Wrapper.wrap("#{CONTENT_COLOR_MAP[log.level]}#{message}", before_message(true) + space_prefix.to_s,
  57. wrap_length - md.end(0))
  58. "#{CONTENT_COLOR_MAP[log.level]}#{space_prefix}#{message}#{color_map.clear}"
  59. end
  60. def payload
  61. return unless log.payload
  62. lines = log.payload.ai(ruby19_syntax: true, indent: 2).split("\n")
  63. first_line = lines.shift
  64. space_prefix = first_line.match(/^\s*/)
  65. lines = lines.map do |l|
  66. "#{before_message(true)}#{space_prefix}#{l}"
  67. end
  68. result = lines.unshift(first_line).join("\n")
  69. result.sub!(/\s*/) { |m| '-' * m.length }
  70. result
  71. end
  72. def level
  73. case log.level
  74. when :info, :debug then draw_rails ' '
  75. else draw_rails(log.level.to_s.chr.upcase)
  76. end
  77. end
  78. def draw_rails(char)
  79. "#{color}#{char}#{color_map.clear}"
  80. end
  81. def continuation
  82. draw_rails('┆')
  83. end
  84. def name
  85. "#{ANSI_DEBUG}#{log.name.truncate(NAME_MAX_SIZE).center(NAME_MAX_SIZE)}#{color_map.clear}"
  86. end
  87. def exception # rubocop:disable Metrics/AbcSize
  88. return unless log.exception
  89. root_path = Rails.root.to_s
  90. stack = log.exception.backtrace.select { |line| line.starts_with?(root_path) }
  91. stack = stack.map { |line| line.delete_prefix("#{root_path}/") }
  92. "#{ANSI_REVERSED_WARNING}#{log.exception.class}#{color_map.clear} #{ANSI_REVERSED_ERROR}#{log.exception.message}#{color_map.clear}#{backtrace(stack)}" # rubocop:disable Layout/LineLength
  93. end
  94. def call(log, logger)
  95. unless log.message.is_a?(String)
  96. log.payload = log.message
  97. log.message = nil
  98. end
  99. self.color = color_map[log.level]
  100. self.log = log
  101. self.logger = logger
  102. before_message + [message, payload, exception].compact.join(' ')
  103. end
  104. private
  105. def compute_useful_length
  106. wrap_length = IO.console.winsize[1] - TERMINAL_PREFIX - before_message.length + CONTENT_PREFIX.length + 12
  107. rescue StandardError
  108. wrap_length = 100 # FIXME: CONSTANTIZE, only useful in DEBUGGER, no IO.console detected!
  109. end
  110. def before_message(wrapped = false)
  111. [name, wrapped ? continuation : level, tags, named_tags, duration].compact.join(' ') + CONTENT_PREFIX
  112. end
  113. def backtrace(stack)
  114. nil unless stack.count.positive?
  115. "\n#{before_message} #{ANSI_ERROR}#{stack.join("\n#{before_message} #{ANSI_ERROR}")}#{color_map.clear}"
  116. end
  117. def color_content
  118. color
  119. end
  120. end