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
|
# frozen_string_literal: true
module Gitlab
module InternalEvents
UnknownEventError = Class.new(StandardError)
InvalidPropertyError = Class.new(StandardError)
InvalidPropertyTypeError = Class.new(StandardError)
SNOWPLOW_EMITTER_BUFFER_SIZE = 100
class << self
include Gitlab::Tracking::Helpers
include Gitlab::Utils::StrongMemoize
def track_event(event_name, category: nil, send_snowplow_event: true, **kwargs)
raise UnknownEventError, "Unknown event: #{event_name}" unless EventDefinitions.known_event?(event_name)
validate_property!(kwargs, :user, User)
validate_property!(kwargs, :namespace, Namespaces::UserNamespace, Group)
validate_property!(kwargs, :project, Project)
project = kwargs[:project]
kwargs[:namespace] ||= project.namespace if project
increase_total_counter(event_name)
increase_weekly_total_counter(event_name)
update_unique_counter(event_name, kwargs)
trigger_snowplow_event(event_name, category, kwargs) if send_snowplow_event
if Feature.enabled?(:internal_events_for_product_analytics)
send_application_instrumentation_event(event_name, kwargs)
end
rescue StandardError => e
extra = {}
kwargs.each_key do |k|
extra[k] = kwargs[k].is_a?(::ApplicationRecord) ? kwargs[k].try(:id) : kwargs[k]
end
Gitlab::ErrorTracking.track_and_raise_for_dev_exception(e, event_name: event_name, kwargs: extra)
nil
end
private
def validate_property!(kwargs, property_name, *class_names)
return unless kwargs.has_key?(property_name)
return if kwargs[property_name].nil?
return if class_names.include?(kwargs[property_name].class)
raise InvalidPropertyTypeError, "#{property_name} should be an instance of #{class_names.join(', ')}"
end
def increase_total_counter(event_name)
redis_counter_key =
Gitlab::Usage::Metrics::Instrumentations::TotalCountMetric.redis_key(event_name)
Gitlab::Redis::SharedState.with { |redis| redis.incr(redis_counter_key) }
end
def increase_weekly_total_counter(event_name)
redis_counter_key =
Gitlab::Usage::Metrics::Instrumentations::TotalCountMetric.redis_key(event_name, Date.today)
Gitlab::Redis::SharedState.with { |redis| redis.incr(redis_counter_key) }
end
def update_unique_counter(event_name, kwargs)
unique_property = EventDefinitions.unique_property(event_name)
return unless unique_property
unique_method = :id
unless kwargs.has_key?(unique_property)
message = "#{event_name} should be triggered with a named parameter '#{unique_property}'."
Gitlab::AppJsonLogger.warn(message: message)
return
end
unique_value = kwargs[unique_property].public_send(unique_method) # rubocop:disable GitlabSecurity/PublicSend
UsageDataCounters::HLLRedisCounter.track_event(event_name, values: unique_value)
end
def trigger_snowplow_event(event_name, category, kwargs)
user = kwargs[:user]
project = kwargs[:project]
namespace = kwargs[:namespace]
standard_context = Tracking::StandardContext.new(
project_id: project&.id,
user_id: user&.id,
namespace_id: namespace&.id,
plan_name: namespace&.actual_plan_name
).to_context
service_ping_context = Tracking::ServicePingContext.new(
data_source: :redis_hll,
event: event_name
).to_context
track_struct_event(event_name, category, contexts: [standard_context, service_ping_context])
end
def track_struct_event(event_name, category, contexts:)
category ||= 'InternalEventTracking'
tracker = Gitlab::Tracking.tracker
tracker.event(category, event_name, context: contexts)
rescue StandardError => error
Gitlab::ErrorTracking
.track_and_raise_for_dev_exception(error, snowplow_category: category, snowplow_action: event_name)
end
def send_application_instrumentation_event(event_name, kwargs)
return if gitlab_sdk_client.nil?
user = kwargs[:user]
gitlab_sdk_client.identify(user&.id)
gitlab_sdk_client.track(event_name, { project_id: kwargs[:project]&.id, namespace_id: kwargs[:namespace]&.id })
end
def gitlab_sdk_client
app_id = ENV['GITLAB_ANALYTICS_ID']
host = ENV['GITLAB_ANALYTICS_URL']
return unless app_id.present? && host.present?
GitlabSDK::Client.new(app_id: app_id, host: host, buffer_size: SNOWPLOW_EMITTER_BUFFER_SIZE)
end
strong_memoize_attr :gitlab_sdk_client
end
end
end
|