Chrome DevTools를 제대로 활용하자
console.log로 디버깅하는 것은 가장 기본적인 방법이지만, 복잡한 버그를 추적할 때는 한계가 있습니다. Chrome DevTools는 브레이크포인트, 네트워크 분석, 메모리 프로파일링, 성능 측정 등 강력한 디버깅 도구를 제공합니다. 이 글에서는 DevTools의 핵심 기능을 실전 예제와 함께 정리합니다.
console API 제대로 사용하기
console.log 외에도 다양한 메서드가 있습니다. 상황에 맞는 메서드를 사용하면 디버깅 효율이 크게 향상됩니다.
// console.table — 배열/객체를 표 형태로 출력
const users = [
{ name: "홍길동", role: "admin", active: true },
{ name: "김영희", role: "editor", active: false },
{ name: "이철수", role: "viewer", active: true },
];
console.table(users);
// ┌─────────┬──────────┬──────────┬────────┐
// │ (index) │ name │ role │ active │
// ├─────────┼──────────┼──────────┼────────┤
// │ 0 │ "홍길동" │ "admin" │ true │
// │ 1 │ "김영희" │ "editor" │ false │
// │ 2 │ "이철수" │ "viewer" │ true │
// └─────────┴──────────┴──────────┴────────┘
// console.group — 관련 로그를 그룹으로 묶기
console.group("사용자 데이터 로딩");
console.log("API 호출 시작");
console.log(`URL: /api/users`);
console.warn("캐시 미스 — 서버에서 가져옴");
console.groupEnd();
// console.time — 실행 시간 측정
console.time("데이터 처리");
const processed = users.filter((u) => u.active);
console.timeEnd("데이터 처리");
// 데이터 처리: 0.012ms
// console.assert — 조건이 false일 때만 출력
console.assert(users.length > 0, "사용자 목록이 비어있습니다");
console.assert(users.length > 10, "10명 미만입니다"); // 출력됨
// console.trace — 호출 스택 추적
function innerFunction() {
console.trace("여기서 호출됨");
}
function outerFunction() {
innerFunction();
}
outerFunction();
// Trace: 여기서 호출됨
// at innerFunction (app.js:35)
// at outerFunction (app.js:38)
| 메서드 | 용도 | 팁 |
|---|---|---|
console.table() | 배열/객체를 표로 출력 | API 응답 데이터 확인에 유용 |
console.group() | 로그 그룹화 | 중첩된 처리 과정 추적 |
console.time() | 실행 시간 측정 | 성능 병목 지점 파악 |
console.assert() | 조건부 출력 | 불변 조건 검증 |
console.trace() | 호출 스택 출력 | 함수 호출 경로 추적 |
console.count() | 호출 횟수 카운트 | 이벤트 발생 빈도 확인 |
브레이크포인트 활용
브레이크포인트는 코드 실행을 특정 지점에서 멈추고 변수 상태를 검사할 수 있는 강력한 도구입니다.
// 1. debugger 키워드 — 코드에서 직접 브레이크포인트 설정
function processOrder(order) {
const total = order.items.reduce(
(sum, item) => sum + item.price * item.quantity,
0
);
// 총액이 음수인 경우에만 멈춤
if (total < 0) {
debugger; // DevTools가 열려 있을 때만 동작
}
const tax = total * 0.1;
const grandTotal = total + tax;
return { total, tax, grandTotal };
}
// 2. 조건부 브레이크포인트 (DevTools에서 설정)
// Sources 패널 → 줄 번호 우클릭 → "Add conditional breakpoint"
// 조건 예시: order.total > 100000
// 3. DOM 변경 브레이크포인트
// Elements 패널 → 요소 우클릭 → "Break on"
// - subtree modifications: 자식 요소 변경 시
// - attribute modifications: 속성 변경 시
// - node removal: 요소 삭제 시
// 4. XHR/Fetch 브레이크포인트
// Sources 패널 → XHR/fetch Breakpoints → URL 패턴 추가
// 예: "api/users" 포함하는 요청에서 멈춤
// 5. 이벤트 리스너 브레이크포인트
// Sources 패널 → Event Listener Breakpoints
// 예: Mouse → click 체크 → 클릭 이벤트 발생 시 멈춤
네트워크 패널 활용
네트워크 요청의 상세 정보를 확인하고, 느린 요청이나 실패한 요청을 추적합니다.
// fetch 인터셉터 — 모든 네트워크 요청 로깅
const originalFetch = window.fetch;
window.fetch = async function (...args) {
const url = typeof args[0] === "string" ? args[0] : args[0].url;
const method =
args[1]?.method || "GET";
console.group(`[Fetch] ${method} ${url}`);
console.time("응답 시간");
try {
const response = await originalFetch.apply(this, args);
console.log("상태:", response.status);
console.log("헤더:", Object.fromEntries(response.headers));
console.timeEnd("응답 시간");
if (!response.ok) {
console.error(`HTTP 에러: ${response.status} ${response.statusText}`);
}
console.groupEnd();
return response;
} catch (error) {
console.error("네트워크 에러:", error.message);
console.timeEnd("응답 시간");
console.groupEnd();
throw error;
}
};
// 네트워크 패널 주요 기능:
// - 필터: XHR, Fetch, JS, CSS, Img 등 타입별 필터링
// - Throttling: 느린 네트워크 시뮬레이션 (Slow 3G, Offline)
// - Waterfall: 요청 타이밍 시각화
// - Preserve log: 페이지 이동 시에도 로그 유지
성능 프로파일링
Performance 패널로 렌더링 병목을 찾고, 프레임 드롭의 원인을 파악합니다.
// Performance API로 커스텀 마커 설정
function measureRender(componentName) {
performance.mark(`${componentName}-start`);
return {
end() {
performance.mark(`${componentName}-end`);
performance.measure(
componentName,
`${componentName}-start`,
`${componentName}-end`
);
const entries = performance.getEntriesByName(componentName);
const duration = entries[entries.length - 1].duration;
console.log(`[성능] ${componentName}: ${duration.toFixed(2)}ms`);
// 16ms(60fps) 초과 시 경고
if (duration > 16) {
console.warn(
`[성능 경고] ${componentName}이 ` +
`${duration.toFixed(0)}ms 소요됨 (16ms 초과)`
);
}
return duration;
},
};
}
// 사용 예시
const measure = measureRender("사용자목록렌더링");
// ... 렌더링 로직 ...
const duration = measure.end();
// [성능] 사용자목록렌더링: 23.45ms
// [성능 경고] 사용자목록렌더링이 23ms 소요됨 (16ms 초과)
// Web Vitals 측정
function observeWebVitals() {
// Largest Contentful Paint (LCP)
new PerformanceObserver((list) => {
const entries = list.getEntries();
const lastEntry = entries[entries.length - 1];
console.log(`LCP: ${lastEntry.startTime.toFixed(0)}ms`);
}).observe({ type: "largest-contentful-paint", buffered: true });
// Cumulative Layout Shift (CLS)
let clsScore = 0;
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (!entry.hadRecentInput) {
clsScore += entry.value;
}
}
console.log(`CLS: ${clsScore.toFixed(4)}`);
}).observe({ type: "layout-shift", buffered: true });
}
메모리 디버깅
메모리 누수는 장시간 실행되는 SPA에서 흔히 발생합니다. DevTools의 Memory 패널로 탐지합니다.
// 메모리 누수 일반적인 원인과 해결
// 1. 해제되지 않은 이벤트 리스너
class BadComponent {
constructor() {
// 누수: 컴포넌트 제거 후에도 리스너 남음
window.addEventListener("resize", this.handleResize);
}
handleResize = () => {
console.log("resize");
};
}
class GoodComponent {
#controller = new AbortController();
constructor() {
// AbortController로 리스너 일괄 해제 가능
window.addEventListener("resize", this.handleResize, {
signal: this.#controller.signal,
});
}
handleResize = () => {
console.log("resize");
};
destroy() {
this.#controller.abort(); // 모든 리스너 한 번에 해제
}
}
// 2. 클로저에 의한 누수
function createLeakyHandler() {
const hugeData = new Array(1000000).fill("leak");
// hugeData가 클로저에 의해 유지됨
return () => console.log(hugeData.length);
}
// 3. DOM 참조에 의한 누수
const detachedNodes = new Map();
function removeElement(id) {
const element = document.getElementById(id);
detachedNodes.set(id, element); // DOM에서 제거해도 참조 유지
element.remove();
// 해결: detachedNodes.delete(id);
}
// Memory 패널 사용법:
// 1. Heap snapshot → 메모리 사용 현황 캡처
// 2. 작업 수행 후 두 번째 snapshot
// 3. Comparison 뷰로 증가한 객체 확인
// 4. Retainers 확인 → 어떤 참조가 GC를 막는지 파악
유용한 단축키
// DevTools 열기: F12 또는 Ctrl+Shift+I (Windows)
// Console 패널 단축키
// $_ : 마지막 평가된 표현식의 결과
// $0 : Elements 패널에서 선택한 요소
// $("selector") : document.querySelector 단축
// $$("selector") : document.querySelectorAll 단축
// copy(obj) : 객체를 클립보드에 복사
// monitor(fn) : 함수 호출 시마다 로그 출력
// monitorEvents(element, "click") : 이벤트 모니터링
// 실전 활용 예시
// $0.style.border = "2px solid red" // 선택한 요소에 빨간 테두리
// copy(JSON.stringify(data, null, 2)) // JSON 데이터 클립보드 복사
// monitor(fetchUser) // fetchUser 호출 추적
실전 팁
- console.log 대신 브레이크포인트: 복잡한 버그는 코드 흐름을 한 줄씩 따라가며 변수 상태를 확인하는 것이 훨씬 효율적입니다
- Logpoints 활용: 브레이크포인트 우클릭에서 “Add logpoint”를 선택하면 코드 수정 없이 로그를 출력할 수 있습니다
- Blackboxing: Sources 패널에서 라이브러리 파일을 블랙박스 처리하면 내 코드만 디버깅됩니다
- Network 조건부 필터:
status-code:500이나larger-than:1M로 특정 조건의 요청만 필터링합니다 - Snippets: Sources 패널의 Snippets에 자주 사용하는 디버깅 코드를 저장해둡니다
- Performance Monitor:
Ctrl+Shift+P에서 “Performance Monitor”를 검색하면 실시간 CPU, 힙, DOM 노드 수를 모니터링할 수 있습니다 - Coverage:
Ctrl+Shift+P에서 “Coverage”를 검색하면 사용되지 않는 CSS/JS 코드를 식별할 수 있습니다