logging.rb 4.7 KB
Newer Older
1
# frozen_string_literal: true
2

3 4 5
require "active_support/core_ext/string/filters"
require "active_support/tagged_logging"
require "active_support/logger"
6

C
Cristian Bica 已提交
7
module ActiveJob
M
mo khan 已提交
8
  module Logging #:nodoc:
9
    extend ActiveSupport::Concern
C
Cristian Bica 已提交
10

11
    included do
12
      cattr_accessor :logger, default: ActiveSupport::TaggedLogging.new(ActiveSupport::Logger.new(STDOUT))
13

14
      around_enqueue do |_, block|
C
Cristian Bica 已提交
15 16 17 18 19
        tag_logger do
          block.call
        end
      end

20
      around_perform do |job, block|
C
Cristian Bica 已提交
21
        tag_logger(job.class.name, job.job_id) do
22
          payload = { adapter: job.class.queue_adapter, job: job }
C
Cristian Bica 已提交
23
          ActiveSupport::Notifications.instrument("perform_start.active_job", payload.dup)
24
          ActiveSupport::Notifications.instrument("perform.active_job", payload) do
C
Cristian Bica 已提交
25 26 27 28 29
            block.call
          end
        end
      end

30
      around_enqueue do |job, block|
C
Cristian Bica 已提交
31
        if job.scheduled_at
32 33
          ActiveSupport::Notifications.instrument("enqueue_at.active_job",
            adapter: job.class.queue_adapter, job: job, &block)
34
        else
35 36
          ActiveSupport::Notifications.instrument("enqueue.active_job",
            adapter: job.class.queue_adapter, job: job, &block)
37 38 39
        end
      end
    end
C
Cristian Bica 已提交
40 41 42 43 44

    private
      def tag_logger(*tags)
        if logger.respond_to?(:tagged)
          tags.unshift "ActiveJob" unless logger_tagged_by_active_job?
45
          logger.tagged(*tags) { yield }
C
Cristian Bica 已提交
46 47 48 49 50 51 52 53 54
        else
          yield
        end
      end

      def logger_tagged_by_active_job?
        logger.formatter.current_tags.include?("ActiveJob")
      end

55 56 57 58 59 60
      class LogSubscriber < ActiveSupport::LogSubscriber #:nodoc:
        def enqueue(event)
          info do
            job = event.payload[:job]
            "Enqueued #{job.class.name} (Job ID: #{job.job_id}) to #{queue_name(event)}" + args_info(job)
          end
61
        end
62

63 64 65 66 67
        def enqueue_at(event)
          info do
            job = event.payload[:job]
            "Enqueued #{job.class.name} (Job ID: #{job.job_id}) to #{queue_name(event)} at #{scheduled_at(event)}" + args_info(job)
          end
68
        end
C
Cristian Bica 已提交
69

70 71 72
        def perform_start(event)
          info do
            job = event.payload[:job]
73
            "Performing #{job.class.name} (Job ID: #{job.job_id}) from #{queue_name(event)}" + args_info(job)
74
          end
75
        end
76

77
        def perform(event)
S
Steven Bull 已提交
78 79 80 81 82 83 84 85 86 87
          job = event.payload[:job]
          ex = event.payload[:exception_object]
          if ex
            error do
              "Error performing #{job.class.name} (Job ID: #{job.job_id}) from #{queue_name(event)} in #{event.duration.round(2)}ms: #{ex.class} (#{ex.message}):\n" + Array(ex.backtrace).join("\n")
            end
          else
            info do
              "Performed #{job.class.name} (Job ID: #{job.job_id}) from #{queue_name(event)} in #{event.duration.round(2)}ms"
            end
88
          end
89
        end
90

91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118
        def enqueue_retry(event)
          job = event.payload[:job]
          ex = event.payload[:error]
          wait = event.payload[:wait]

          error do
            "Retrying #{job.class} in #{wait} seconds, due to a #{ex.class}. The original exception was #{ex.cause.inspect}."
          end
        end

        def retry_stopped(event)
          job = event.payload[:job]
          ex = event.payload[:error]

          error do
            "Stopped retrying #{job.class} due to a #{ex.class}, which reoccurred on #{job.executions} attempts. The original exception was #{ex.cause.inspect}."
          end
        end

        def discard(event)
          job = event.payload[:job]
          ex = event.payload[:error]

          error do
            "Discarded #{job.class} due to a #{ex.class}. The original exception was #{ex.cause.inspect}."
          end
        end

119 120 121 122
        private
          def queue_name(event)
            event.payload[:adapter].class.name.demodulize.remove("Adapter") + "(#{event.payload[:job].queue_name})"
          end
123

124 125 126 127 128 129 130
          def args_info(job)
            if job.arguments.any?
              " with arguments: " +
                job.arguments.map { |arg| format(arg).inspect }.join(", ")
            else
              ""
            end
131
          end
C
Cristian Bica 已提交
132

133 134 135 136 137 138 139 140 141 142 143
          def format(arg)
            case arg
            when Hash
              arg.transform_values { |value| format(value) }
            when Array
              arg.map { |value| format(value) }
            when GlobalID::Identification
              arg.to_global_id rescue arg
            else
              arg
            end
144 145
          end

146 147 148
          def scheduled_at(event)
            Time.at(event.payload[:job].scheduled_at).utc
          end
149

150 151 152 153
          def logger
            ActiveJob::Base.logger
          end
      end
C
Cristian Bica 已提交
154 155
  end
end
156 157

ActiveJob::Logging::LogSubscriber.attach_to :active_job