lib/support/rake/task_logger.rb (89 lines of code) (raw):

# frozen_string_literal: true require 'fileutils' module Support module Rake class TaskLogger def self.set_current!(logger) Thread.current[:gdk_task_logger] = logger logger&.create_latest_symlink!(@symlink_mutex ||= Mutex.new) end def self.current Thread.current[:gdk_task_logger] end def self.start_time @start_time ||= Time.now end def self.from_task(task) new("#{TaskLogger.logs_dir}/#{task.name.gsub(%r{[:\s/.]+}, '-')}.log") end def self.logs_dir "#{GDK.root}/log/gdk/rake-#{TaskLogger.start_time.strftime('%Y-%m-%d_%H-%M-%S_%L')}" end attr_reader :file_path, :recent_line def initialize(file_path) @file_path = Pathname(file_path) create_logs_dir! end def file @file ||= File.open(@file_path, 'w').tap { |file| file.sync = true } end # Input must have at least one valid char. This excludes newlines and separators-only lines. INPUT_REGEXP = /\w/ # Rails noise. IGNORE_INPUT_NOISE = 'DEPRECATION WARNING' private_constant :INPUT_REGEXP, :IGNORE_INPUT_NOISE def record_input(string) return unless string recent_line = string .split("\n") .reverse_each .find { |line| !line.include?(IGNORE_INPUT_NOISE) && INPUT_REGEXP.match?(line) } @recent_line = recent_line if recent_line end def cleanup!(delete: true) return if @file&.closed? File.delete(@file_path) if @file&.size === 0 && delete @file&.close end def tail(max_lines: 25, exclude_gems: true) f = File.read(@file_path) original_lines = f.split("\n") lines = original_lines.reject { |l| exclude_gems && l.include?('/ruby/gems/') } needs_log_link = lines.length > max_lines || original_lines.length > lines.length lines = lines.last(max_lines) lines.push("", "See #{@file_path} for the full log.") if needs_log_link lines.join("\n") end def create_latest_symlink!(mutex) mutex.synchronize do link_name = "#{GDK.root}/log/gdk/rake-latest" next if File.symlink?(link_name) && File.readlink(link_name) == TaskLogger.logs_dir FileUtils.ln_sf(TaskLogger.logs_dir, link_name) end end private def create_logs_dir! file_path.parent.mkpath end end end end # Inspired by https://stackoverflow.com/a/16184325/6403374 # but adjusted so we can use it with our own task logger # # We use __send__ to proxy IO function on Kernel. # rubocop:disable GitlabSecurity/PublicSend module Kernel [:printf, :p, :print, :puts, :warn].each do |method| name = "__#{method}__" alias_method name, method define_method(method) do |*args| logger = Support::Rake::TaskLogger.current return __send__(name, *args) unless logger gdk_rake_log_lock.synchronize do $stdout = logger.file $stderr = logger.file __send__(name, *args) ensure $stdout = STDOUT $stderr = STDERR end end end private def gdk_rake_log_lock @gdk_rake_log_lock ||= Mutex.new end end # rubocop:enable GitlabSecurity/PublicSend