HyperDX: Client Session (RUM)

[์›๋ณธ ๋งํฌ]

HyperDX - Clickstack์€ ํด๋ผ์ด์–ธํŠธ ์ˆ˜์ค€์˜ ๋…นํ™” ๊ธฐ๋Šฅ๋„ ์ œ๊ณตํ•œ๋‹ค. RUM(Real User Monitoring)์ด๋ผ๊ณ ๋„ ๋ถˆ๋ฆฌ๋Š” ๊ทธ ๊ธฐ๋Šฅ์ด๋‹ค.
๋ธŒ๋ผ์šฐ์ € ํ™˜๊ฒฝ๊ณผ React Native ๊ธฐ๋ฐ˜์˜ ์•ฑ ํ™˜๊ฒฝ๋งŒ์„ ์ง€์›ํ•œ๋‹ค.

hyperdx๋ฅผ ์…‹์—…ํ•˜๊ณ  ๋“ค์–ด๊ฐ€๋ฉด, Client Sessions์ด๋ผ๋Š” ํƒญ์ด ์žˆ๋‹ค.

์—ฌ๊ธฐ์„œ clickhouse์— ์ €์žฅ๋˜์–ด์žˆ๋Š” ๋ฆฌํ”Œ๋ ˆ์ด ๋ฐ์ดํ„ฐ๋ฅผ ์กฐํšŒํ•˜๊ณ , ์žฌ์ƒํ•ด๋ณผ ์ˆ˜ ์žˆ๋Š” ๊ฒƒ์ด๋‹ค.




๋ธŒ๋ผ์šฐ์ €์— ์—์ด์ „ํŠธ ์…‹์—…ํ•˜๊ธฐ

์„ค์ •์€ ๊ทธ๋ ‡๊ฒŒ ์–ด๋ ต์ง€๋Š” ์•Š์€ ํŽธ์ด๋‹ค.
๋ชจ๋“ˆ ํ•˜๋‚˜ ๊น”๊ณ 

npm i @hyperdx/browser

์ด๋Ÿฐ ์‹์œผ๋กœ ์ดˆ๊ธฐํ™” ์ฒ˜๋ฆฌ๋งŒ ํ•ด์ฃผ๋ฉด ๋œ๋‹ค.

'use client';

import HyperDX from '@hyperdx/browser';

const isEnabled =
  typeof window !== 'undefined' && process.env.NEXT_PUBLIC_HYPERDX_ENABLED === 'true';

if (isEnabled) {
  HyperDX.init({
    url: 'https://clickhouse-otel-exporter.๋„๋ฉ”์ธ.co',
    // Managed ClickStack ์€ ingestion key ๋ถˆํ•„์š” โ€” env ๋ฏธ์„ค์ • ์‹œ ๋นˆ ๊ฐ’
    apiKey: process.env.NEXT_PUBLIC_HYPERDX_API_KEY ?? '',
    service: process.env.NEXT_PUBLIC_HYPERDX_SERVICE_NAME ?? 'test-app',

    tracePropagationTargets: [/\/api\//i, /https:\/\/.*\.๋„๋ฉ”์ธ\.ai/i],

    consoleCapture: true,
    advancedNetworkCapture: true,
  });
}

์—”๋“œํฌ์ธํŠธ๋Š” otel collector์˜ ์—”๋“œํฌ์ธํŠธ๋ฅผ ๋„ฃ์œผ๋ฉด ๋˜๊ณ , API Key๋„ otel collector์˜ ์—”๋“œํฌ์ธํŠธ๋ฅผ ๋„ฃ์œผ๋ฉด ๋œ๋‹ค. ๋กœ๊ทธ/ํŠธ๋ ˆ์ด์Šค์™€ ๋™์ผํ•œ ์ธ์ฆ/API ๊ฒฝ๋กœ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.
๊ทธ๋ฆฌ๊ณ  ์ด Ingestion Key๋Š” ๋ถˆ๊ฐ€ํ”ผํ•˜๊ฒŒ ํด๋ผ์ด์–ธํŠธ ์ˆ˜์ค€์—์„œ ๋…ธ์ถœ๋œ๋‹ค. ๊ฐ์•ˆํ•ด์•ผ ํ•œ๋‹ค.

https://clickhouse.com/docs/use-cases/observability/clickstack/sdks/browser




๊ด€์ธก

์œ„ ๊ณผ์ •์„ ๊ฑฐ์ณ ์„ธํŒ…์„ ํ•˜๊ณ  ๋Œ๋ ค์„œ ์ œ๋Œ€๋กœ ์Œ“์•˜๋‹ค๋ฉด, ๋Œ€์‹œ๋ณด๋“œ์—์„œ ์กฐํšŒ๊ฐ€ ๊ฐ€๋Šฅํ•ด์ง„๋‹ค.

๊ฐ๊ฐ์˜ ์„ธ์…˜ ๋‹จ์œ„๋กœ ๋ˆ„์ ์ด ๋˜๊ณ  (๋ธŒ๋ผ์šฐ์ € ์„ธ์…˜ ๋‹จ์œ„)


๊ฐœ๋ณ„ ์„ธ์…˜์„ ํด๋ฆญํ•˜๋ฉด, ์–ด๋–ค ํ–‰๋™์„ ํ–ˆ๋Š”์ง€๋ฅผ ๋น„๋””์˜ค๋กœ ๋ณด๊ณ , ์–ด๋–ค API๋ฅผ ์ผ๊ณ , ์–ด๋–ค ๋กœ๊ทธ๊ฐ€ ์ฐํ˜”๋Š”์ง€ ๋“ฑ์„ ์ „๋ถ€ ํ›”์ณ๋ณผ ์ˆ˜ ์žˆ๋‹ค.




์˜์ƒ ํ€„๋ฆฌํ‹ฐ ์กฐ์ •

๊ณ ํ™”์งˆ ์˜์ƒ์„ ์›ํ•˜๋Š”์ง€, ๋š๋š ๋Š๊ฒจ๋„ ์ž‘๊ฒŒ ์ €์žฅ๋˜๋ฉด ์ถฉ๋ถ„ํ•œ์ง€์— ๋”ฐ๋ผ์„œ ์ˆ˜์ง‘ ์˜ต์…˜์„ ์ปค์Šคํ…€ํ•  ์ˆ˜ ์žˆ๋‹ค.
sampling์ด๋ผ๋Š” ์˜ต์…˜์ด๋‹ค.

    sampling: {
      mousemove: 50, // ๋งˆ์šฐ์Šค ์ด๋™์„ 50ms ๊ฐ„๊ฒฉ์œผ๋กœ๋งŒ ๊ธฐ๋ก (false๋ฉด ์•„์˜ˆ ์•ˆ ๊ธฐ๋ก)
      scroll: 150, // ์Šคํฌ๋กค ์ด๋ฒคํŠธ throttle (ms)
      input: 'last', // ํƒ€์ดํ•‘ ์ค‘๊ฐ„๊ฐ’ ๋ฒ„๋ฆฌ๊ณ  ์ตœ์ข…๊ฐ’๋งŒ
      media: 800, // ์˜ค๋””์˜ค/๋น„๋””์˜ค ์ƒํƒœ ๊ธฐ๋ก ๊ฐ„๊ฒฉ
      mouseInteraction: { MouseUp: false, MouseDown: false }, // ์ด๋ฒคํŠธ ์ข…๋ฅ˜๋ณ„ on/off
      canvas: 15, // ์บ”๋ฒ„์Šค ๋…นํ™” FPS (recordCanvas ์ผฐ์„ ๋•Œ๋งŒ ์˜๋ฏธ)
    },



User Attribute ์„ค์ •

๋”ฐ๋กœ ์„ธ์…˜์— ๋Œ€ํ•œ ์„ค์ •์„ ํ•˜์ง€ ์•Š๋Š”๋‹ค๋ฉด, ๊ทธ๊ฑด ๊ทธ๋ƒฅ ์ต๋ช… ์„ธ์…˜์œผ๋กœ๋งŒ ๋ˆ„์ ์ด ๋œ๋‹ค.
๊ทธ๋ž˜์„œ ๊ฐ€๋Šฅํ•˜๋ฉด ํ•ด๋‹น ์œ ์ €์— ๋Œ€ํ•œ ์†์„ฑ์„ ๊ณ ์ •ํ•ด์„œ ์‹๋ณ„ ๊ฐ€๋Šฅํ•˜๋„๋ก ๋งŒ๋“œ๋Š” ๊ฒƒ์ด ์ผ๋ฐ˜์ ์ธ ํŒจํ„ด์ด๋‹ค.

์ด๋Ÿฐ ์‹์œผ๋กœ ์ด๋ฉ”์ผ์ด๋‚˜ ๋‹‰๋„ค์ž„ ๋“ฑ์„ ๋ถ™์—ฌ์ฃผ๋ฉด ์šฐ๋ฆฌ๊ฐ€ ๋Œ€์‹œ๋ณด๋“œ์—์„œ ๋ณผ ๋•Œ๋„ ๊ทธ ์ด๋ฆ„์œผ๋กœ ๋ณด์ธ๋‹ค.




์„ธ์…˜ ์ƒ˜ํ”Œ๋ง

๋‹น์—ฐํ•˜์ง€๋งŒ ์ด๊ฑด ๋””์Šคํฌ๋ฅผ ๋ฌด์‹ํ•˜๊ฒŒ ๋จน๋Š” ๊ธฐ๋Šฅ์ด๋‹ค.
๊ทธ๋ž˜์„œ ์‹ค์„œ๋น„์Šค ํ™˜๊ฒฝ์—์„œ๋Š” ์ƒ˜ํ”Œ๋ง์„ ๋„ฃ์–ด์„œ ์ผ์ • ํ™•๋ฅ ๋กœ ์ˆ˜์ง‘์„ ํ•˜๋„๋ก ํ•˜๋Š” ํŽธ์ด ์ข‹๋‹ค.

์•„๋ž˜๋Š” ๊ฐ„๋‹จํ•œ ์˜ˆ์‹œ ์ฝ”๋“œ๋‹ค.

// app/hyperdx-init.tsx ๊ฒŒ์ดํŒ…์— ์ถ”๊ฐ€ํ•˜๋Š” ํ˜•ํƒœ
const SAMPLE_RATE = 0.2; // 20%

function isSampled(): boolean {
  const KEY = 'hdx-sampled';
  const TTL = 4 * 60 * 60 * 1000; // HyperDX ์„ธ์…˜ ์ตœ๋Œ€ ๊ธธ์ด(4h)์™€ ๋งž์ถค

  const stored = localStorage.getItem(KEY);
  if (stored) {
    const { value, expires } = JSON.parse(stored);
    if (Date.now() < expires) return value;
  }
  const value = Math.random() < SAMPLE_RATE;
  localStorage.setItem(KEY, JSON.stringify({ value, expires: Date.now() + TTL }));
  return value;
}

if (isEnabled && isSampled()) {
  HyperDX.init({ ... });
}



๋ณด์•ˆ ๋ฐ ๋งˆ์Šคํ‚น

ํ•„์š”ํ•˜๋‹ค๋ฉด API๋‚˜ ์ฝ˜์†”๋กœ๊ทธ๋ฅผ ์ „๋ถ€ ์ˆ˜์ง‘ํ•ด์„œ ์Œ“์„ ์ˆ˜ ์žˆ๋Š”๋ฐ, ๊ฒฝ์šฐ์— ๋”ฐ๋ผ์„œ๋Š” ํŒจ์Šค์›Œ๋“œ ๊ฐ™์€ ๋ฏผ๊ฐํ•œ ๊ฐ’์€ ์ˆ˜์ง‘ํ•˜์ง€ ์•Š๋„๋ก ์กฐ์ •์„ ํ•ด์•ผํ•  ์ˆ˜ ์žˆ๋‹ค. ๋”ฐ๋กœ ์„ค์ •์„ ํ•˜์ง€ ์•Š๋Š”๋‹ค๋ฉด ๋ฌด์ž‘์ • ๋‹ค ๋„ฃ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

์ผ๋‹จ DOM ์ˆ˜์ค€์—์„œ๋Š” ๊ธฐ๋ณธ์ ์ธ ๋งˆ์Šคํ‚น์„ ํ•ด์ค€๋‹ค.
maskAllInputs๊ฐ€ input ํƒœ๊ทธ ๋‚ด์˜ ๊ฐ’์„ ๋งˆ์Šคํ‚นํ•ด์ฃผ๊ธด ํ•˜๋Š”๋ฐ, ๋ฌธ์ œ๋Š” ์œ„์ฒ˜๋Ÿผ request body๋กœ ์ˆ˜์ง‘๋˜๋Š” ๊ฒƒ์ด๋‹ค.

์ด ๊ฒฝ์šฐ ์ผ๋ถ€ ๋ฏผ๊ฐํ•œ URL๋งŒ ์ˆ˜์ง‘ ๋Œ€์ƒ์—์„œ ์ œ์™ธํ•˜๊ฑฐ๋‚˜, ์•„์˜ˆ body ์ˆ˜์ง‘ ์ž์ฒด๋ฅผ ๋น„ํ™œ์„ฑํ™”ํ•ด์•ผํ•œ๋‹ค.



์ฐธ์กฐ
https://clickhouse.com/docs/use-cases/observability/clickstack/sdks/browser

Search