InfoGrab DocsInfoGrab Docs

Internal Event 추적 빠른 시작

요약

보다 효율적이고 확장 가능하며 통합된 추적 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를 제공하지 않으면, 이벤트의 namespaceproject.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는 그대로 전달되며, Procexecute 결과를 받습니다:

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_eventsincrement_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에 대한 테스트 케이스는 다음과 같습니다:

  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를 사용합니다.

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를 제공하지 않으면, 이벤트의 namespaceproject.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는 그대로 전달되며, Procexecute 결과를 받습니다:

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_eventsincrement_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에 대한 테스트 케이스는 다음과 같습니다:

  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를 사용합니다.