summaryrefslogtreecommitdiff
path: root/lib/gitlab/ci/pipeline/logger.rb
blob: 4b7cbae5004eb7e899b1453267ec675fccc09598 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
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
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# frozen_string_literal: true

module Gitlab
  module Ci
    module Pipeline
      class Logger
        include ::Gitlab::Utils::StrongMemoize

        def self.current_monotonic_time
          ::Gitlab::Metrics::System.monotonic_time
        end

        def initialize(project:, destination: Gitlab::AppJsonLogger)
          @started_at = current_monotonic_time
          @project = project
          @destination = destination
          @log_conditions = []

          yield(self) if block_given?
        end

        def log_when(&block)
          log_conditions.push(block)
        end

        def instrument(operation)
          return yield unless enabled?

          raise ArgumentError, 'block not given' unless block_given?

          op_started_at = current_monotonic_time

          result = yield

          observe("#{operation}_duration_s", current_monotonic_time - op_started_at)

          result
        end

        def instrument_with_sql(operation, &block)
          op_start_db_counters = current_db_counter_payload

          result = instrument(operation, &block)

          observe_sql_counters(operation, op_start_db_counters, current_db_counter_payload)

          result
        end

        def observe(operation, value)
          return unless enabled?

          observations[operation.to_s].push(value)
        end

        def commit(pipeline:, caller:)
          return unless log?

          attributes = {
            class: self.class.name.to_s,
            pipeline_creation_caller: caller,
            project_id: project&.id, # project is not available when called from `/ci/lint`
            pipeline_persisted: pipeline.persisted?,
            pipeline_source: pipeline.source,
            pipeline_creation_service_duration_s: age
          }

          if pipeline.persisted?
            attributes[:pipeline_builds_tags_count] = pipeline.tags_count
            attributes[:pipeline_builds_distinct_tags_count] = pipeline.distinct_tags_count
            attributes[:pipeline_id] = pipeline.id
          end

          attributes.compact!
          attributes.stringify_keys!
          attributes.merge!(observations_hash)

          destination.info(attributes)
        end

        def observations_hash
          observations.transform_values do |values|
            next if values.empty?

            {
              'count' => values.size,
              'min' => values.min,
              'max' => values.max,
              'sum' => values.sum,
              'avg' => values.sum / values.size
            }
          end.compact
        end

        private

        attr_reader :project, :destination, :started_at, :log_conditions

        delegate :current_monotonic_time, to: :class

        def age
          current_monotonic_time - started_at
        end

        def log?
          return false unless enabled?
          return true if log_conditions.empty?

          log_conditions.any? { |cond| cond.call(observations) }
        end

        def enabled?
          strong_memoize(:enabled) do
            ::Feature.enabled?(:ci_pipeline_creation_logger, project, type: :ops)
          end
        end

        def observations
          @observations ||= Hash.new { |hash, key| hash[key] = [] }
        end

        def observe_sql_counters(operation, start_db_counters, end_db_counters)
          end_db_counters.each do |key, value|
            result = value - start_db_counters.fetch(key, 0)
            next if result == 0

            observe("#{operation}_#{key}", result)
          end
        end

        def current_db_counter_payload
          ::Gitlab::Metrics::Subscribers::ActiveRecord.db_counter_payload
        end
      end
    end
  end
end