커스텀 이벤트로 트래킹 강화하기

커스텀 이벤트 API로 다양한 종류의 커스텀 이벤트를 캡처하세요

커스텀 이벤트로 트래킹 강화하기

커스텀 이벤트는 트래커가 별도의 추가 작업 없이 기본으로 제공하는 간단하면서도 강력한 개념입니다. 비즈니스 로직과 관련된 커스텀 오류든 사용자의 행동을 파악하기 위한 단순한 이벤트든, 필요한 어떤 데이터로도 추적 데이터를 확장할 수 있습니다.

트래커는 기본적으로 유용한 오류를 포함해 다양한 항목을 추적하지만, 이것만으로는 충분하지 않을 수 있습니다. 그래서 커스텀 이벤트가 있습니다.

이 예제에서는 일반적인 e-커머스 사이트에 이벤트를 추가하여 사용자가 상품을 장바구니에 담는 시점을 파악해 보겠습니다.

기본적으로 OpenReplay는 이 정보를 추적하지 않습니다. 하지만 커스텀 이벤트를 사용하면 쉽게 추적할 수 있습니다.

지금 알아 두어야 할 것은, 여러 함수를 통해 트래커와 상호작용할 수 있게 해 주는 컨텍스트 프로바이더를 만든다는 점뿐입니다.

특히 logIssue와 logEvent가 중요합니다. 이 함수들로 커스텀 이슈나 이벤트를 플랫폼으로 보낼 수 있습니다.

  • 이벤트는 사용자별 동작을 기록하기 위한 것입니다. 이번 예제에서는 상품이 장바구니에 추가되는 것을 기록합니다.
  • 이슈는 트래커가 자동으로 포착하지 못하는 오류를 기록하기 위한 것입니다. 이번 예제에서는 네트워크 오류로 서드 파티 API에 접근하지 못하는 상황을 시뮬레이션합니다. 이런 일이 발생하면 플랫폼에 이슈를 기록합니다.
import { createContext, useCallback } from 'react'
import Tracker from '@openreplay/tracker'
import { v4 as uuidV4 } from 'uuid'
import { useReducer } from 'react'

export const TrackerContext = createContext()
function defaultGetUserId() {
  return uuidV4()
}
function newTracker(config) {
  const getUserId =
    config?.userIdEnabled && config?.getUserId
      ? config.getUserId
      : defaultGetUserId
  let userId = null
  const trackerConfig = {
    projectKey:
      config?.projectKey || process.env.NEXT_PUBLIC_OPENREPLAY_PROJECT_KEY,
 
  }
  if (config?.ingestPoint || process.env.NEXT_PUBLIC_OPENREPLAY_INGEST_POINT) {
    trackerConfig.ingestPoint =
      config?.ingestPoint || process.env.NEXT_PUBLIC_OPENREPLAY_INGEST_POINT
  }

  console.log('Tracker configuration: ')
  console.log(trackerConfig)
  const tracker = new Tracker(trackerConfig)
  if (config?.userIdEnabled) {
    userId = getUserId()
    tracker.setUserID(userId)
  }
  return tracker
}
function reducer(state, action) {
  switch (action.type) {
    case 'init': {
      if (!state.tracker) {
        console.log('Instantiaing the tracker for the first time...')
        let t = newTracker(state.config)
        let pluginsReturnedValue = {}
        if (state.config.plugins) {
          state.config.plugins.forEach((p) => {
            console.log('Using plugin...')
            pluginsReturnedValue[p.name] = t.use(p.fn(p.config))
          })
        }
        return {
          ...state,
          pluginsReturnedValue: pluginsReturnedValue,
          tracker: t,
        }
      }
      return state
    }
    case 'start': {
      console.log('Starting tracker...')
      state.tracker.start()
      return state
    }
    case 'logEvent': {
      console.log('Logging event')
      state.tracker?.event(action.payload?.name, action.payload?.data)
      return state
    }
    case 'logIssue': {
      console.log('Logging issue')
      state.tracker?.issue(action.payload?.name, action.payload?.data)
      return state
    }
  }
}
export default function TrackerProvider({ children, config = {} }) {
  let [state, dispatch] = useReducer(reducer, {
    tracker: null,
    pluginsReturnedValue: {},
    config,
  })
  let value = {
    startTracking: () => dispatch({ type: 'start' }),
    initTracker: () => dispatch({ type: 'init' }),
    logEvent: (evnt) => dispatch({ type: 'logEvent', payload: evnt }),
    logIssue: (evnt) => dispatch({ type: 'logIssue', payload: evnt }),
    pluginsReturnedValues: { ...state.pluginsReturnedValue },
  }
  return (
    <TrackerContext.Provider value={value}>{children}</TrackerContext.Provider>
  )
}

logEvent와 logIssue 함수는 시그니처가 같으며, name과 data 속성을 가진 객체를 전달합니다. name은 OpenReplay UI에서 레코드를 식별하는 데 사용되고, data에는 기록할 정보가 담깁니다.

기억하세요: data 속성에는 직렬화 가능한 객체가 들어 있어야 합니다.

이제 _app.tsx 파일에서 이 프로바이더를 다음과 같이 설정할 수 있습니다.


//imports here...

export default function MyApp({ Component, pageProps }: AppProps) {
  const Layout = (Component as any).Layout || Noop

  useEffect(() => {
    document.body.classList?.remove('loading')
  }, [])

  return (
    <TrackerProvider config={{}}>
      <Head />
      <ManagedUIContext>
        <Layout pageProps={pageProps}>
          <Component {...pageProps} />
        </Layout>
      </ManagedUIContext>
    </TrackerProvider>
  )
}

이렇게 준비가 끝났으니 이제 이벤트를 발생시키는 단계로 넘어가겠습니다.

커스텀 이슈와 이벤트 기록

Section titled 커스텀 이슈와 이벤트 기록

이를 위해 UI를 활용합니다.

상품 페이지

사용자가 상품을 장바구니에 담을 때마다(기본적으로 “ADD TO CART” 버튼을 누를 때) 새 이벤트를 기록합니다.

그리고 사용자가 먼저 사이즈를 선택하지 않고 담는 경우에는 이슈를 기록합니다.

이 컴포넌트의 전체 소스 코드는 여기에서 확인할 수 있지만, 여기서는 추가할 로직에 집중하겠습니다.

컴포넌트 시작 부분에서 useContext 훅을 사용합니다.

//outside the component
import { TrackerContext } from '../../../context/trackerProvider'

//inside the component
const { logEvent, logIssue } = useContext(TrackerContext)

addToCart 함수 안에서 유효한 사이즈가 선택되지 않았는지 확인하는 다음 로직을 추가합니다.

const validSizes = product.options
      .filter((o) => o.id == 'option-size')
      .map((o) => o.values)[0]

    let pickedSized = validSizes.find((s) => {
      return selectedOptions.size == s.label.toLowerCase()
    })

    if (!pickedSized) {
      logIssue({
        name: 'Product added without a size',
        data: {
          product_id: product.id,
          added_date: new Date(),
          available_options: validSizes,
        },
      })
    }

코드의 핵심은 마지막 IF입니다. 유효한 사이즈가 선택되지 않았음을 확인하면, 앞서 useContext 호출로 가져온 logIssue 함수를 호출합니다.

이벤트의 경우, 같은 함수의 더 안쪽에 다음을 추가합니다.

logEvent({
        name: 'product_added',
        data: {
          id: product.id,
          date_added: new Date(),
        },
      })

필요한 작업은 이것이 전부입니다. 이제 OpenReplay에서 세션 녹화를 찾아 Events 섹션을 살펴보세요.

커스텀 이벤트 패널

세부 정보를 보려면 행 위에 마우스를 올렸을 때 나타나는 “DETAILS” 링크를 클릭하면 됩니다.

커스텀 이벤트의 세부 정보

이것이 상품을 추가할 때 기록한 세부 정보입니다.

이 예제의 전체 소스 코드를 확인하고 싶다면 여기에서 볼 수 있습니다.

궁금한 점이 있으신가요?

Section titled 궁금한 점이 있으신가요?

프로젝트에서 커스텀 이벤트와 관련된 문제가 생기면 Slack 커뮤니티에 문의하여 개발자에게 직접 질문해 보세요!