Internal Event 추적 빠른 시작
GitLab v19.1보다 효율적이고 확장 가능하며 통합된 추적 API를 제공하기 위해 GitLab은 기존의 RedisHLL 및 Snowplow 추적을 더 이상 사용하지 않을 예정입니다. 중요: GitLab 18.0부터 Self-Managed 인스턴스에서도 이벤트 데이터를 수집하여, 이전 버전에서 수집된 집계 메트릭보다 더 상세한 인사이트를 제공합니다.
보다 효율적이고 확장 가능하며 통합된 추적 API를 제공하기 위해 GitLab은 기존의 RedisHLL 및 Snowplow 추적을 더 이상 사용하지 않을 예정입니다.
대신, 새로운 track_event(백엔드) 및 trackEvent(프론트엔드) 메서드를 구현하고 있습니다.
이 접근 방식을 사용하면 기본 구현에 대한 걱정 없이 RedisHLL 카운터를 업데이트하고 Snowplow 이벤트를 전송할 수 있습니다.
중요: GitLab 18.0부터 Self-Managed 인스턴스에서도 이벤트 데이터를 수집하여, 이전 버전에서 수집된 집계 메트릭보다 더 상세한 인사이트를 제공합니다.
Internal Events 추적으로 코드를 계측하려면 세 가지 작업이 필요합니다:
-
이벤트 정의
-
하나 이상의 메트릭 정의
-
이벤트 트리거
이벤트 및 메트릭 정의#
이벤트 및/또는 메트릭 정의를 생성하려면 gitlab_internal_events_cli gem을 사용합니다.
> gem install gitlab_internal_events_cli
> gem exec gitlab_internal_events_cli
이 CLI gem은 특정 사용 사례에 맞는 올바른 정의 파일을 생성하는 데 도움을 주며, 계측 및 테스트를 위한 코드 예제도 제공합니다.
이벤트 이름은 <action>_<target_of_action>_<where/when> 형식으로 지정해야 하며, 유효한 예로는 create_ci_build 또는 click_previous_blame_on_blob_page가 있습니다.
이벤트 트리거#
이벤트를 트리거하고 메트릭을 업데이트하는 방법은 백엔드와 프론트엔드에서 약간 다릅니다. 아래의 관련 섹션을 참조하세요.
백엔드 추적#
Internal Events를 사용한 백엔드 계측에 대한 동영상을 시청하세요.
이벤트를 트리거하려면 Gitlab::InternalEventsTracking 모듈의 track_internal_event 메서드를 원하는 인수와 함께 호출합니다:
include Gitlab::InternalEventsTracking
track_internal_event(
"create_ci_build",
user: user,
namespace: namespace,
project: project
)
이 메서드는 create_ci_build 이벤트와 관련된 모든 RedisHLL 메트릭을 자동으로 증가시키고, 명명된 인수 및 표준 컨텍스트(GitLab.com 전용)와 함께 해당 Snowplow 이벤트를 전송합니다.
또한, 이벤트를 트리거하는 클래스의 이름이 Snowplow 이벤트의 category 속성에 저장됩니다.
unique: project.id와 같이 unique 속성으로 메트릭을 정의한 경우, project 인수를 반드시 제공해야 합니다.
데이터 품질을 높이고 향후 메트릭 정의를 더 쉽게 할 수 있도록, user, namespace, project를 최대한 많이 채우는 것을 권장합니다.
project만 제공하고 namespace를 제공하지 않으면, 이벤트의 namespace로 project.namespace가 사용됩니다.
경우에 따라 category를 수동으로 지정하거나 전혀 지정하지 않을 수도 있습니다. 그 경우에는 모듈을 사용하는 대신 InternalEvents.track_event 메서드를 직접 호출할 수 있습니다.
기능이 여러 네임스페이스를 통해 활성화되어 있고 기능이 활성화된 이유를 추적해야 하는 경우, 네임스페이스 id 배열과 함께 선택적 feature_enabled_by_namespace_ids 파라미터를 전달할 수 있습니다.
track_internal_event(
...
feature_enabled_by_namespace_ids: [namespace_one.id, namespace_two.id]
)
추가 속성#
이벤트를 추적할 때 추가 속성을 전달할 수 있습니다. 추가 속성은 특정 이벤트와 관련된 추가 데이터를 저장하는 데 사용할 수 있습니다.
추적 클래스에는 이미 세 가지 내장 속성이 있습니다:
-
label(문자열) -
property(문자열) -
value(숫자)
내장 속성이 적합하지 않거나 설명적이지 않은 경우, 임의의 이름을 가진 속성을 사용할 수 있습니다.
additional_properties:
label:
description: The source of the pipeline, e.g. a push, a schedule or similar.
property:
description: The source of the config, e.g. the repository, auto_devops or similar.
agent:
description: Type of the execution agent.
추가 속성은 #track_event 호출에 additional_properties 해시를 포함하여 전달합니다:
track_internal_event(
"create_ci_build",
user: user,
additional_properties: {
label: source, # The label is tracking the source of the pipeline
property: config_source, # The property is tracking the source of the configuration
agent: agent
}
)
추가 속성에 민감한 정보가 포함되지 않도록 주의하세요. 자세한 내용은 데이터 분류 표준을 참조하세요.
컨트롤러 및 API 헬퍼#
컨트롤러를 위한 헬퍼 모듈 ProductAnalyticsTracking이 있으며, 이를 사용하여 #track_internal_event를 호출함으로써 특정 컨트롤러 액션에 대한 internal 이벤트를 추적할 수 있습니다:
class Projects::PipelinesController < Projects::ApplicationController
include ProductAnalyticsTracking
track_internal_event :charts, name: 'visit_charts_on_ci_cd_pipelines', conditions: -> { should_track_ci_cd_pipelines? }
def charts
...
end
private
def should_track_ci_cd_pipelines?
params[:chart].blank? || params[:chart] == 'pipelines'
end
end
헬퍼가 이벤트의 현재 프로젝트와 네임스페이스를 가져올 수 있도록 컨트롤러 본문에 다음 두 메서드를 추가해야 합니다:
private
def tracking_namespace_source
project.namespace
end
def tracking_project_source
project
end
또한, API 헬퍼도 있습니다:
track_event(
event_name,
user: current_user,
namespace_id: namespace_id,
project_id: project_id
)
서비스 헬퍼#
서비스 객체의 경우, Gitlab::InternalEvents::ServiceTracking concern이 선언적 track_internal_event 헬퍼를 제공합니다.
이는 execute 메서드를 래핑하고 반환 값에 따라 자동으로 추적 호출을 실행합니다.
class Labels::CreateService < Labels::BaseService
include Gitlab::InternalEvents::ServiceTracking
track_internal_event 'label_created', on: :success
def execute(target_params)
# ... returns an ActiveRecord model or ServiceResponse
end
end
이 헬퍼는 execute의 반환 값을 가로채고 결과가 성공인지 오류인지 판단합니다:
-
ServiceResponse:result.success?를 확인합니다. -
ActiveRecord 객체:
result.persisted?를 확인합니다. -
기타 값:
result.present?를 확인합니다.
track_internal_event 옵션#
| 파라미터 | 타입 | 설명 |
|---|---|---|
| on | Symbol, Proc | 실행 시점: :success (기본값), :error, :always, 결과를 받는 Proc, 또는 인스턴스 메서드 이름을 나타내는 Symbol. |
| conditions | Symbol, Proc, Array | 추가 가드 조건. 이벤트를 실행하려면 모두 참을 반환해야 합니다. |
| additional_properties | Hash, Proc | 이벤트의 추가 속성. Proc은 결과를 받아 Hash를 반환합니다. |
동일한 서비스에 여러 이벤트를 등록할 수 있습니다:
track_internal_event 'thing_created', on: :success
track_internal_event 'thing_creation_failed', on: :error
기본 추적 소스#
이 concern은 user, project, namespace를 자동으로 추출하는 기본 구현을 제공합니다:
-
User: 서비스가 응답하는 경우
current_user를 사용합니다. -
Project:
ServiceResponse페이로드 값을 반복하여project에 응답하는 첫 번째 객체를 반환합니다. -
Namespace: 페이로드 값을 반복하여
namespace또는group에 응답하는 첫 번째 객체를 반환합니다.
기본값은 Hash 페이로드(예: payload: { board: board }) 또는 execute에서 직접 반환된 일반 객체가 필요합니다.
Hash가 아닌 페이로드를 가진 ServiceResponse는 소스에서 자동으로 nil을 반환합니다. 페이로드를 Hash로 감싸거나 소스 메서드를 재정의하세요.
추적 소스를 커스터마이즈하려면 private 메서드를 재정의합니다:
private
def tracking_user_source
actor
end
def tracking_project_source(result)
result&.payload&.dig(:board)&.project
end
def tracking_namespace_source(result)
result&.payload&.dig(:board)&.group
end
조건 및 추가 속성#
conditions:를 사용하여 하나 이상의 검사로 이벤트 실행을 가드합니다.
:symbol 형식은 서비스의 인스턴스 메서드를 지정합니다:
class Labels::CreateService < Labels::BaseService
include Gitlab::InternalEvents::ServiceTracking
track_internal_event 'label_created', on: :success, conditions: :not_template?
def execute(target_params)
# ... returns the created Label
end
private
def not_template?
!params[:template]
end
end
additional_properties:를 사용하여 추가 이벤트 데이터를 첨부합니다.
Hash는 그대로 전달되며, Proc은 execute 결과를 받습니다:
track_internal_event 'integration_configured', on: :success,
additional_properties: { label: 'static_label' }
# Proc form - receives the result of execute (e.g., a ServiceResponse):
track_internal_event 'integration_configured', on: :success,
additional_properties: ->(result) { { label: result.payload[:type] } }
배치 처리#
여러 이벤트를 한 번에 내보낼 때, with_batched_redis_writes를 사용하여 모든 이벤트를 단일 Redis 호출로 배치 처리합니다.
Gitlab::InternalEvents.with_batched_redis_writes do
incr.times { Gitlab::InternalEvents.track_event(event) }
end
총 카운터에 대한 업데이트만 배치 처리된다는 점에 유의하세요. n개의 고유 메트릭과 m개의 총 카운터 메트릭이 정의된 경우, incr * n + m번의 Redis 쓰기가 발생합니다.
백엔드 테스트#
internal 이벤트를 트리거하거나 메트릭을 증가시키는 코드를 테스트할 때, 블록 인수에 trigger_internal_events 및 increment_usage_metrics 매처를 사용할 수 있습니다.
expect { subject }
.to trigger_internal_events('web_ide_viewed')
.with(user: user, project: project, namespace: namespace)
.and increment_usage_metrics('counts.web_views')
trigger_internal_events 매처는 receive 매처와 동일한 체인 메서드(#once, #at_most 등)를 허용합니다. 기본적으로 제공된 이벤트가 한 번만 트리거될 것으로 예상합니다.
체인 메서드 #with는 다음 파라미터를 허용합니다:
-
user- User 객체 -
project- Project 객체 -
namespace- Namespace 객체. 제공하지 않으면project.namespace로 설정됩니다. -
additional_properties- Hash. 이벤트와 함께 전송될 추가 속성. 예:{ label: 'scheduled', value: 20 } -
category- String. 제공하지 않으면 이벤트를 트리거하는 객체의 클래스 이름으로 설정됩니다.
increment_usage_metrics 매처는 change 매처와 동일한 체인 메서드(#by, #from, #to 등)를 허용합니다. 기본적으로 제공된 메트릭이 1씩 증가할 것으로 예상합니다.
expect { subject }
.to trigger_internal_events('web_ide_viewed')
.with(user: user, project: project, namespace: namespace)
.exactly(3).times
두 매처 모두 블록에 적용되는 다른 매처(예: change 매처)와 함께 사용할 수 있습니다.
expect { subject }
.to trigger_internal_events('mr_created')
.with(user: user, project: project, category: category, additional_properties: { label: label } )
.and increment_usage_metrics('counts.deployments')
.at_least(:once)
.and change { mr.notes.count }.by(1)
디버깅 팁: 예상한 대로 메트릭이 증가하지 않아 새 테스트가 실패하는 경우, 예제 간에 Redis 캐시를 초기화하기 위해 :clean_gitlab_redis_shared_state 트레잇을 적용해야 할 수 있습니다.
이벤트가 트리거되지 않았음을 테스트하려면 not_trigger_internal_events 매처를 사용할 수 있습니다. 이 매처는 메시지 체인을 허용하지 않습니다.
expect { subject }.to trigger_internal_events('mr_created')
.with(user: user, project: project, namespace: namespace)
.and increment_usage_metrics('counts.deployments')
.and not_trigger_internal_events('pipeline_started')
또는 not_to 구문을 사용할 수 있습니다:
expect { subject }.not_to trigger_internal_events('mr_created', 'member_role_created')
trigger_internal_events 매처는 데이터 속성을 사용한 Haml 테스트에도 사용할 수 있습니다.
프론트엔드 추적#
모든 프론트엔드 추적 호출은 페이지의 현재 컨텍스트에서 user.id, namespace.id, project.id 값을 자동으로 전달합니다.
Vue 컴포넌트#
Vue 컴포넌트에서는 Vue mixin을 사용하여 추적할 수 있습니다.
Vue 컴포넌트 추적을 구현하려면:
InternalEvents 라이브러리를 임포트하고 mixin 메서드를 호출합니다:
import { InternalEvents } from '~/tracking';
const trackingMixin = InternalEvents.mixin();
컴포넌트에서 mixin을 사용합니다:
export default {
mixins: [trackingMixin],
data() {
return {
expanded: false,
};
},
};
trackEvent 메서드를 호출합니다. 추적 옵션은 두 번째 파라미터로 전달할 수 있습니다:
this.trackEvent('click_previous_blame_on_blob_page');
또는 템플릿에서 trackEvent 메서드를 사용합니다:
<template>
<div>
<button data-testid="toggle" @click="toggle">Toggle</button>
<div v-if="expanded">
<p>Hello world!</p>
<button @click="trackEvent('click_previous_blame_on_blob_page')">Track another event</button>
</div>
</div>
</template>
일반 JavaScript#
임의의 프론트엔드 JavaScript 코드에서 직접 이벤트를 추적하기 위한 일반 JavaScript 모듈이 제공됩니다. Mixin을 활용할 수 없는 컴포넌트 컨텍스트 외부에서 사용할 수 있습니다.
import { InternalEvents } from '~/tracking';
InternalEvents.trackEvent('click_previous_blame_on_blob_page');
data-event 속성#
이 속성을 사용하면 버튼에 대한 GitLab internal 이벤트를 추적할 때 Click 핸들러에 JavaScript 코드를 작성할 필요가 없습니다. 대신, 이벤트 값과 함께 data-event-tracking 속성을 추가하기만 하면 됩니다. HAML 뷰에서도 사용할 수 있습니다.
<gl-button
data-event-tracking="click_previous_blame_on_blob_page"
>
Click Me
</gl-button>
Haml#
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle', data: { event_tracking: 'click_previous_blame_on_blob_page' }}) do
렌더링 시 internal 이벤트#
컴포넌트가 렌더링되거나 로드될 때 internal 이벤트를 전송하려는 경우, data-event-tracking-load="true" 속성을 추가할 수 있습니다:
= render Pajamas::ButtonComponent.new(button_options: { data: { event_tracking_load: 'true', event_tracking: 'click_previous_blame_on_blob_page' } }) do
= _("New project")
추가 속성#
이벤트에 추가 데이터를 저장하기 위해 추가 속성을 포함할 수 있습니다. 추가 속성을 포함하는 경우 additional_properties 필드에 각 추가 속성을 정의해야 합니다. label(문자열), property(문자열), value(숫자) 키를 가진 세 가지 내장 추가 속성을 전송할 수 있으며, 내장 속성이 사용 사례에 적합하지 않거나 설명적이지 않은 경우 커스텀 추가 속성을 사용할 수 있습니다.
페이지 URL이나 페이지 경로를 추가 속성으로 전달하지 마세요. 각 이벤트에 대해 이미 가명화된 페이지 URL을 추적하고 있습니다.
window.location에서 URL을 가져오면 문서화된 것처럼 프로젝트 및 네임스페이스 정보가 가명화되지 않습니다.
Vue Mixin의 경우:
this.trackEvent('click_view_runners_button', {
label: 'group_runner_form',
property: dynamicPropertyVar,
value: 20
});
일반 JavaScript의 경우:
InternalEvents.trackEvent('click_view_runners_button', {
label: 'group_runner_form',
property: dynamicPropertyVar,
value: 20
});
data-event 속성의 경우:
<gl-button
data-event-tracking="click_view_runners_button"
data-event-label="group_runner_form"
:data-event-property=dynamicPropertyVar
data-event-additional='{"key1": "value1", "key2": "value2"}'
>
Click Me
</gl-button>
Haml의 경우:
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle', data: { event_tracking: 'action', event_label: 'group_runner_form', event_property: dynamic_property_var, event_value: 2, event_additional: '{"key1": "value1", "key2": "value2"}' }}) do
프론트엔드 테스트#
JavaScript/Vue#
일반 JavaScript 또는 Vue 컴포넌트에서 trackEvent 메서드를 사용하는 경우, useMockInternalEventsTracking 헬퍼 메서드를 사용하여 trackEvent가 호출되는지 확인할 수 있습니다.
예를 들어, 아래의 Vue 컴포넌트를 테스트해야 하는 경우:
<script>
import { GlButton } from '@gitlab/ui';
import { InternalEvents } from '~/tracking';
import { __ } from '~/locale';
export default {
components: {
GlButton,
},
mixins: [InternalEvents.mixin()],
methods: {
handleButtonClick() {
// some application logic
// when some event happens fire tracking call
this.trackEvent('click_view_runners_button', {
label: 'group_runner_form',
property: 'property_value',
value: 3,
});
},
},
i18n: {
button1: __('Sample Button'),
},
};
</script>
<template>
<div style="display: flex; height: 90vh; align-items: center; justify-content: center">
<gl-button class="sample-button" @click="handleButtonClick">
{{ $options.i18n.button1 }}
</gl-button>
</div>
</template>
위 컴포넌트에 대한 테스트 케이스는 다음과 같습니다.
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import DeleteApplication from '~/admin/applications/components/delete_application.vue';
import { useMockInternalEventsTracking } from 'helpers/tracking_internal_events_helper';
describe('DeleteApplication', () => {
/** @type {import('helpers/vue_test_utils_helper').ExtendedWrapper} */
let wrapper;
const createComponent = () => {
wrapper = shallowMountExtended(DeleteApplication);
};
beforeEach(() => {
createComponent();
});
describe('sample button 1', () => {
const { bindInternalEventDocument } = useMockInternalEventsTracking();
it('should call trackEvent method when clicked on sample button', async () => {
const { trackEventSpy } = bindInternalEventDocument(wrapper.element);
await wrapper.find('.sample-button').vm.$emit('click');
expect(trackEventSpy).toHaveBeenCalledWith(
'click_view_runners_button',
{
label: 'group_runner_form',
property: 'property_value',
value: 3,
},
undefined,
);
});
});
});
Vue/View 템플릿에서 아래와 같이 추적 속성을 사용하는 경우:
<script>
import { GlButton } from '@gitlab/ui';
import { InternalEvents } from '~/tracking';
import { __ } from '~/locale';
export default {
components: {
GlButton,
},
mixins: [InternalEvents.mixin()],
i18n: {
button1: __('Sample Button'),
},
};
</script>
<template>
<div style="display: flex; height: 90vh; align-items: center; justify-content: center">
<gl-button
class="sample-button"
data-event-tracking="click_view_runners_button"
data-event-label="group_runner_form"
>
{{ $options.i18n.button1 }}
</gl-button>
</div>
</template>
위 컴포넌트에 대한 테스트 케이스는 다음과 같습니다.
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import DeleteApplication from '~/admin/applications/components/delete_application.vue';
import { useMockInternalEventsTracking } from 'helpers/tracking_internal_events_helper';
describe('DeleteApplication', () => {
/** @type {import('helpers/vue_test_utils_helper').ExtendedWrapper} */
let wrapper;
const createComponent = () => {
wrapper = shallowMountExtended(DeleteApplication);
};
beforeEach(() => {
createComponent();
});
describe('sample button', () => {
const { bindInternalEventDocument } = useMockInternalEventsTracking();
it('should call trackEvent method when clicked on sample button', () => {
const { triggerEvent, trackEventSpy } = bindInternalEventDocument(wrapper.element);
triggerEvent('.sample-button');
expect(trackEventSpy).toHaveBeenCalledWith('click_view_runners_button', {
label: 'group_runner_form',
});
});
});
});
데이터 속성을 사용한 Haml#
Haml 레이어에서 internal 이벤트를 추적하기 위해 데이터 속성을 사용하는 경우, trigger_internal_events 매처를 사용하여 예상 속성이 존재하는지 확인할 수 있습니다.
예를 들어, 아래의 Haml을 테스트해야 하는 경우:
%div{ data: { testid: '_testid_', event_tracking: 'some_event', event_label: 'some_label' } }
have_css 매처와 호환되는 렌더링된 HTML에 대해 어설션을 호출할 수 있습니다.
이벤트가 트리거될 시점을 나타내려면 :on_click 및 :on_load 체인 메서드를 사용합니다.
위 Haml에 대한 테스트 케이스는 다음과 같습니다:
- 렌더링된 HTML이
String인 경우 (RSpec views)
it 'assigns the tracking items' do
render
expect(rendered).to trigger_internal_events('some_event').on_click
.with(additional_properties: { label: 'some_label' })
end
- 렌더링된 HTML이
Capybara::Node::Simple인 경우 (ViewComponent)
it 'assigns the tracking items' do
render_inline(component)
expect(page.find_by_testid('_testid_'))
.to trigger_internal_events('some_event').on_click
.with(additional_properties: { label: 'some_label' })
end
- 렌더링된 HTML이
Nokogiri::HTML4::DocumentFragment인 경우 (ViewComponent)
it 'assigns the tracking items' do
expect(render_inline(component))
.to trigger_internal_events('some_event').on_click
.with(additional_properties: { label: 'some_label' })
end
또는 not_to 구문을 사용할 수 있습니다:
it 'assigns the tracking items' do
render_inline(component)
expect(page).not_to trigger_internal_events
end
부정형으로 사용하는 경우, 매처는 추가 체인 메서드나 인수를 허용하지 않습니다. 이는 추적 속성이 사용되지 않음을 확인합니다.
Internal Events API 사용#
API를 사용하여 GitLab 인스턴스에 연결된 다른 시스템에서도 이벤트를 추적할 수 있습니다. 자세한 내용은 사용 데이터 API 문서를 참조하세요.
다른 시스템의 Internal Events#
GitLab 코드베이스 외에도, 아래에 나열된 시스템에서 Internal Events를 사용합니다.