require 'forwardable' require 'flydata-core/errors' require 'flydata-core/thread_context' module FlydataCore class LogContext THREAD_LOCAL_KEY_LOG_CONTEXT = 'flydata_log_context' THREAD_LOCAL_KEY_LOGGER = 'flydata_logger' class << self extend Forwardable def_delegators :log_context, :[], :[]=, :each, :merge, :merge!, :delete def log_context if h = Thread.current[THREAD_LOCAL_KEY_LOG_CONTEXT] h else Thread.current[THREAD_LOCAL_KEY_LOG_CONTEXT] = {} end end def reset_log_context Thread.current[THREAD_LOCAL_KEY_LOG_CONTEXT] = nil end def logger Thread.current[THREAD_LOCAL_KEY_LOGGER] end def set_logger(new_logger) Thread.current[THREAD_LOCAL_KEY_LOGGER] = new_logger end def reset reset_log_context Thread.current[THREAD_LOCAL_KEY_LOGGER] = nil end end end module Logger attr_accessor :logger def initialize_log_context(new_logger, log_items) reset_log_context set_log_context_logger(new_logger) set_log_context_items(log_items) end def log_context_items LogContext.log_context end def log_context_item(k) LogContext[k] end def set_log_context_item(k, v) LogContext[k] = v end def delete_log_context_item(*keys) keys.each do |k| LogContext.delete(k) end end def set_log_context_items(items) reset_log_context_items add_log_context_items(items) end def add_log_context_items(items) LogContext.merge!(items) end def reset_log_context_items LogContext.reset_log_context end def log_context_logger LogContext.logger end def set_log_context_logger(new_logger) LogContext.set_logger(new_logger) end def reset_log_context LogContext.reset end # for override def custom_log_items {} end def filter_log_params(log_params) log_params end # log methods def log_common(level, message, log_params = {}, options = {}) # check keys if e = log_params[:error] message += " error_class=#{e.class.to_s} error=\"#{e.to_s.strip.gsub(/\n/, ' ')}\"" log_params.delete(:error) end # get prefix text prefix = if log_params[:prefix] log_params[:prefix] elsif log_context_items[:prefix] log_context_items[:prefix] elsif custom_log_items[:prefix] custom_log_items[:prefix] else '' end prefix = "#{prefix} " unless prefix.to_s == '' # build suffix text suffix = [build_log_text(filter_log_params(log_params)), build_log_text, build_log_text(custom_log_items)].compact.join(' ') suffix = " #{suffix}" unless suffix.empty? msg = "#{prefix}#{message}#{suffix}" # add backtrace of error if e and options[:backtrace] backtrace = e.backtrace ? e.backtrace.join("\n") : 'backtrace is empty.' msg += " backtrace:\n#{backtrace}" end # get logger lg = options[:logger] || logger || log_context_logger || $log lg.send(level, msg) end def log_debug(message, log_params = {}, options = {}) log_common(:debug, message, log_params, options) end def log_info(message, log_params = {}, options = {}) log_common(:info, message, log_params, options) end def log_warn(message, log_params = {}, options = {}) log_common(:warn, message, log_params, options) end def log_error(message, log_params = {}, options = {}) log_common(:error, message, log_params, options) end def log_error_with_backtrace(message, log_params = {}, options = {}) log_common(:error, message, log_params, options.merge(backtrace: true)) end def build_log_text(items = log_context_items) str_list = [] items.each do |k, v| next if k == :prefix case v when Array str_list << "#{k}:[#{v.join(',')}]" when Hash str_list << build_log_text(v) else str_list << if k.to_s.start_with?('__') v.to_s.empty? ? nil : v.to_s elsif %w|' " ( [|.include?(v.to_s[0]) or (v.kind_of?(Numeric)) "#{k}:#{v}" else "#{k}:\"#{v}\"" end end end ret = str_list.compact.join(' ') ret.empty? ? nil : ret end # Log an error for the given exception. It does not log a RetryableError if # retry_count is less than retry_alert_limit. # It returns the original exception of a RetryableError it has one. # This method requires method 'logger' which returns a Ruby Logger instance or # a compatible one. def log_retryable_error(exception, message = "unknown error occured.", retry_count = -1, retry_alert_limit = 13) is_retryable_error = false if (exception.kind_of?(FlydataCore::RetryableError)) is_retryable_error = true retry_alert_limit = exception.retry_alert_limit unless exception.retry_alert_limit.nil? exception = exception.original_exception unless exception.original_exception.nil? end unless is_retryable_error && retry_count < retry_alert_limit log_error_with_backtrace(message, retry_count: retry_count, error: exception) end exception end end end