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.

145 lines
4.0 KiB

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