내부 이벤트 추적을 위한 빠른 시작

보다 효율적이고 확장 가능하며 통합된 추적 API를 제공하기 위해, GitLab은 기존의 RedisHLL 및 Snowplow 추적을 사용 중단하고 있습니다. 대신, 새로운 track_event (백엔드) 및 trackEvent (프론트엔드) 메소드를 구현하고 있습니다.

이 접근 방식을 통해 RedisHLL 카운터를 업데이트하고 Snowplow 이벤트를 전송할 때 기본 구현에 대한 걱정 없이 작업할 수 있습니다.

내부 이벤트 추적을 코드에 적용하려면 세 가지 작업을 수행해야 합니다:

  1. 이벤트 정의

  2. 하나 이상의 메트릭 정의

  3. 이벤트 트리거

이벤트 및 메트릭 정의

이벤트 및/또는 메트릭 정의를 생성하려면 gitlab 디렉토리의 internal_events 생성기를 사용하세요:

scripts/internal_events/cli.rb

이 CLI는 특정 사용 사례에 따라 올바른 정의 파일을 생성하도록 도와주며, 계측 및 테스트를 위한 코드 예제를 제공합니다.

이벤트는 <action>_<target_of_action>_<where/when> 형식으로 이름을 지정해야 하며, 유효한 예로는 create_ci_build 또는 click_previous_blame_on_blob_page가 있습니다.

이벤트 트리거

이벤트를 트리거하고 메트릭을 업데이트하는 것은 백엔드와 프론트엔드에서 약간 다릅니다. 아래 관련 섹션을 참조하세요.

백엔드 추적

내부 이벤트를 사용한 백엔드 계측에 대한 비디오를 시청하세요.

이벤트를 트리거하려면 원하는 인수와 함께 Gitlab::InternalEventsTracking 모듈의 track_internal_event 메서드를 호출하세요:

include Gitlab::InternalEventsTracking

track_internal_event(
  "create_ci_build",
  user: user,
  namespace: namespace,
  project: project
)

이 메서드는 create_ci_build 이벤트와 관련된 모든 RedisHLL 메트릭을 자동으로 증가시키며, 모든 이름이 지정된 인수와 표준 컨텍스트(SaaS 전용)를 포함한 해당 Snowplow 이벤트를 전송합니다.

이벤트를 트리거하는 클래스의 이름이 Snowplow 이벤트의 category 속성에 저장됩니다.

unique 속성을 가진 메트릭(예: unique: project.id)을 정의한 경우 project 인수를 제공해야 합니다.

데이터 품질을 높이고 향후 메트릭 정의를 용이하게 하기 위해 가능한 한 많은 user, namespace, project 정보를 채우는 것이 좋습니다.

project는 제공되지만 namespace가 제공되지 않은 경우, project.namespace가 이벤트의 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 (숫자)

이 세 속성의 임의의 이름 지정 및 유형 지정은 데이터 추출 프로세스의 제약 때문입니다.

이 속성들이 추적하려는 데이터와 일치하지 않더라도 먼저 이 속성을 사용하는 것이 좋습니다.

이러한 속성을 메트릭 필터로 활용하려는 경우 특히 중요합니다.

추적되는 실제 데이터를 설명하기 위해 이벤트의 YAML 정의에서 description 속성을 사용할 수 있습니다. 예를 들어, create_ci_internal_pipeline.yml를 참조하세요:

additional_properties:
  label:
    description: 파이프라인의 출처, : 푸시, 일정 또는 유사한 .
  property:
    description: 구성의 출처, : 리포지토리, auto_devops 또는 유사한 .

추가 속성은 #track_event 호출에서 additional_properties 해시를 포함하여 전달됩니다:

track_internal_event(
  "create_ci_build",
  user: user,
  additional_properties: {
    label: source, # 레이블은 파이프라인의 출처를 추적합니다.
    property: config_source # 속성은 구성의 출처를 추적합니다.
  }
)

세 개 이상의 추가 속성을 전달해야 하는 경우, 사용자 정의 키가 있는 additional_properties 해시를 사용할 수 있습니다:

track_internal_event(
  "code_suggestion_accepted",
  user: user,
    label: editor_name,
    property: suggestion_type,
    value: suggestion_shown_duration,
    lang: 'ruby',
    custom_key: 'custom_value'
  }
)

사용자 정의 속성은 내장 속성에만 추가하여 사용하세요.

사용자 정의 규칙은 메트릭 필터로 사용할 수 없습니다.

컨트롤러 및 API 헬퍼

내부 이벤트를 추적하기 위해 #track_internal_event를 호출하여 특정 컨트롤러 작업에 대한 내부 이벤트를 추적하는 데 사용할 수 있는 헬퍼 모듈 ProductAnalyticsTracking이 있습니다:

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
)

백엔드 테스트

코드가 단순히 내부 이벤트를 트리거하고 관련 메트릭을 모두 증가시키는 경우, internal_event_tracking 공유 예제를 사용할 수 있습니다.

it_behaves_like 'internal event tracking' do
  let(:event) { 'update_issue_severity' }
  let(:project) { issue.project }
  let(:user) { issue.author }
  let(:additional_properties) { { label: issue.issueable_severity } }
  subject(:service_action) { described_class.new(issue).execute }
end

이것은 다음을 포함하는 컨텍스트가 필요합니다:

  • subject - 이벤트를 트리거하는 작업
  • event - 이벤트의 이름

선택적으로, 컨텍스트는 다음을 포함할 수 있습니다:

  • user
  • project
  • namespace. 제공되지 않으면 project.namespace가 사용됩니다(즉, project가 가능한 경우).
  • category
  • additional_properties
  • event_attribute_overrides - 부모 컨텍스트에서 사용할 수 있는 속성을 오버라이드할 필요가 있을 때 사용됩니다. 예를 들어:
let(:event) { 'create_new_issue' }

it_behaves_like 'internal event tracking' do
  let(:event_attribute_overrides) { { event: 'create_new_milestone'} }

  subject(:service_action) { described_class.new(issue).save }
end

이런 레거시 옵션은 이제 더 이상 사용되지 않습니다:

  • label
  • property
  • value

대신 additional_properties를 사용하는 것이 좋습니다.

조합 가능한 매처

단일 작업이 여러 번 이벤트를 트리거하거나 여러 다른 이벤트를 트리거하거나 일부 메트릭을 증가시키지만 다른 것은 증가시키지 않는 경우, 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 - 사용자 객체
  • project - 프로젝트 객체
  • namespace - 네임스페이스 객체. 제공되지 않으면 project.namespace로 설정됩니다.
  • additional_properties - 해시. 이벤트와 함께 전송될 추가 속성. 예: { label: 'scheduled', value: 20 }
  • category - 문자열. 제공되지 않으면 이벤트를 트리거하는 객체의 클래스 이름으로 설정됩니다.

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)

이벤트가 트리거되지 않았음을 테스트하려면 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')

프론트엔드 추적

모든 프론트엔드 추적 호출은 현재 페이지의 컨텍스트에서 user.id, namespace.id, 및 project.id 값을 자동으로 전달합니다.

추가 속성, 예를 들어 extra, context, label, property, 및 value를 전달해야 하는 경우, 더 이상 지원되지 않는 snowplow 구현을 사용할 수 있습니다. 이 경우, 구체적인 사용 사례에 대해 내부 이벤트 피드백 이슈에서 알려주세요.

Vue 구성 요소

Vue 구성 요소에서 추적은 Vue mixin을 사용하여 수행할 수 있습니다.

Vue 구성 요소 추적을 구현하려면:

  1. InternalEvents 라이브러리를 가져오고 mixin 메서드를 호출합니다:

      import { InternalEvents } from '~/tracking';
      const trackingMixin = InternalEvents.mixin();
    
  2. 구성 요소에서 mixin을 사용합니다:

    export default {
      mixins: [trackingMixin],
    
      data() {
        return {
          expanded: false,
        };
      },
    };
    
  3. 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>안녕하세요, 세상!</p>
          <button @click="trackEvent('click_previous_blame_on_blob_page')">다른 이벤트 추적</button>
        </div>
      </div>
    </template>
    

원시 JavaScript

임의의 프론트엔드 JavaScript 코드에서 이벤트를 직접 추적하기 위해 원시 JavaScript 모듈이 제공됩니다. 이는 Mixin을 사용할 수 없는 구성 요소 컨텍스트 외부에서 사용할 수 있습니다.

import { InternalEvents } from '~/tracking';
InternalEvents.trackEvent('click_previous_blame_on_blob_page');

데이터-이벤트 속성

이 속성은 버튼에 대한 GitLab 내부 이벤트를 추적하려는 경우, Click 핸들러에 JavaScript 코드를 작성할 필요가 없음을 보장합니다. 대신, 이벤트 값을 가진 data-event-tracking 속성을 추가하기만 하면 작동해야 합니다. 이것은 HAML 뷰와 함께 사용할 수도 있습니다.

  <gl-button
    data-event-tracking="click_previous_blame_on_blob_page"
  >
   클릭해 주세요
  </gl-button>

Haml

= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle',  data: { event_tracking: 'click_previous_blame_on_blob_page' }}) do

렌더링 시 내부 이벤트

때때로 구성 요소가 렌더링되거나 로드될 때 내부 이벤트를 전송하고 싶습니다. 이런 경우, 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
        = _("새 프로젝트")

추가 속성

추적 이벤트 시 추가 속성을 전달할 수 있습니다. 이는 주어진 이벤트와 관련된 추가 데이터를 저장하는 데 사용할 수 있습니다. 최대 세 개의 추가 속성을 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
   });

데이터-이벤트 속성의 경우:

  <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"}'
  >
   클릭해 주세요
  </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

프론트엔드 테스트

trackEvent 메서드를 직접 JavaScript 코드나 Vue 구성 요소에서 사용하고 있다면, 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() {
      // 일부 애플리케이션 로직
      // 이벤트가 발생했을 때 추적 호출 수행
      this.trackEvent('click_view_runners_button', {
        label: 'group_runner_form',
        property: 'property_value',
        value: 3,
      });
    },
  },
  i18n: {
    button1: __('샘플 버튼'),
  },
};
</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('샘플 버튼 1', () => {
    const { bindInternalEventDocument } = useMockInternalEventsTracking();
    it('샘플 버튼을 클릭하면 trackEvent 메서드를 호출해야 합니다', 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: __('샘플 버튼'),
  },
};
</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('샘플 버튼', () => {
    const { bindInternalEventDocument } = useMockInternalEventsTracking();
    it('샘플 버튼을 클릭하면 trackEvent 메서드를 호출해야 합니다', () => {
      const { triggerEvent, trackEventSpy } = bindInternalEventDocument(wrapper.element);
      triggerEvent('.sample-button');
      expect(trackEventSpy).toHaveBeenCalledWith('click_view_runners_button', {
        label: 'group_runner_form',
      });
    });
  });
});

내부 이벤트 API 사용

우리의 API를 사용하여 GitLab 인스턴스에 연결된 다른 시스템의 이벤트를 추적할 수 있습니다.

사용 데이터 API 문서를 참조하여 자세한 정보를 확인하세요.

다른 시스템의 내부 이벤트

GitLab 코드베이스 외에도, 아래에 나열된 시스템에 대해 내부 이벤트를 사용하고 있습니다.

  1. AI Gateway

  2. Switchboard