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