1. 데이터의 형상에 집중하기: console.table
우리는 보통 API 응답을 확인하기 위해 습관적으로 console.log(response)를 타이핑하곤 합니다.
하지만 응답 데이터가 수십 개의 필드를 가진 객체 배열이라면 어떨까요?
브라우저 콘솔에서 화살표를 일일이 클릭하며 필드를 확인하는 과정은 그 자체로 인지 부하를 일으킵니다.
”표(Table)는 인지 부하를 줄이는 가장 강력한 도구입니다”
Toss Tech의 글에서 인터페이스를 ‘계약’으로 바라보듯, 저는 로그 역시 ‘데이터의 계약’을 확인하는 과정이라고 생각합니다. console.table은 이 계약의 이행 여부를 가장 명확하게 보여줍니다.
// 복잡한 주문 목록 데이터
const orders = [
{ id: 'ORD-001', product: 'MacBook Pro', price: 2500000, status: 'PAID', customer: 'Jungjin' },
{ id: 'ORD-002', product: 'iPad Air', price: 900000, status: 'PENDING', customer: 'Antigravity' },
{ id: 'ORD-003', product: 'iPhone 15', price: 1200000, status: 'FAILED', customer: 'Toss' },
]
// 단순히 log로 찍으면 객체의 깊이에 갇히게 되지만,
// table은 데이터의 '형상'을 한눈에 드러냅니다.
console.table(orders, ['id', 'product', 'status'])
실무에서의 가치: 단순히 예쁘게 보여주는 것을 넘어, 콘솔창의 표 헤더를 클릭해 데이터를 정렬해 볼 수 있다는 점이 핵심입니다. 특정 상태(FAILED)의 데이터만 모아보거나 가격 순으로 정렬해 보는 행위는 별도의 로직 추가 없이도 즉각적인 분석을 가능하게 합니다.
2. 맥락(Context)을 잃지 않는 디버깅: console.group
비동기 작업이 연달아 일어나는 환경에서 로그는 순서가 뒤섞이기 마련입니다. A 작업의 로그와 B 작업의 로그가 콘솔창에서 뒤엉키는 순간, 우리는 디버깅의 ‘맥락’을 잃어버립니다.
”로그에도 계층이 필요합니다”
저는 복잡한 로직을 수행할 때 console.group을 통해 로그에 명확한 경계를 긋습니다. 이는 마치 코드에서 함수를 분리하는 것과 같은 논리적 단위의 구분을 콘솔에서도 실현하는 것입니다.
async function processPayment(orderId) {
console.group(`💳 결제 프로세스 시작: ${orderId}`)
try {
console.log('1. 재고 확인 중...')
// ... 재고 확인 로직
console.groupCollapsed('🔍 상세 검증 데이터')
console.log('할인율: 10%')
console.log('포인트 사용: 5,000p')
console.groupEnd()
console.log('2. PG사 결제 요청 전송')
// ... 결제 요청 로직
} finally {
console.groupEnd()
}
}
왜 groupCollapsed인가?: 모든 정보를 처음부터 다 보여주는 것은 정보 과잉입니다. groupCollapsed를 사용하면 평소에는 핵심 흐름만 보여주다가, 문제가 생겼을 때만 상세 내용을 펼쳐볼 수 있는 ‘선택적 노출’이 가능해집니다. 이는 디버깅 시 시각적 소음을 줄이는 실용적인 전략입니다.
3. 보이지 않는 범인 찾기: console.count & console.dir
가끔은 로직이 의도치 않게 여러 번 실행되거나, DOM 객체의 내부 속성이 궁금할 때가 있습니다. 이때 log는 우리에게 너무 많은 정보를 주거나, 혹은 너무 적은 정보를 줍니다.
”횟수를 세는 것만으로도 버그는 잡힙니다”
리액트 컴포넌트가 왜 이렇게 자주 리렌더링되는지 궁금할 때, console.count는 가장 원초적이면서도 확실한 도구가 됩니다.
function MyComponent() {
// 컴포넌트가 렌더링될 때마다 횟수를 기록합니다.
console.count('🖥️ MyComponent 렌더링 횟수')
return <div>Hello World</div>
}
또한, DOM 요소를 console.log로 찍으면 HTML 태그 형태로 보이지만, console.dir로 찍으면 해당 요소가 가진 모든 자바스크립트 속성을 계층적으로 확인할 수 있습니다.
// 태그가 아니라 '객체'로서의 속성이 궁금할 때
const button = document.querySelector('button')
console.dir(button)
4. 나만의 디버깅 테마: CSS Styling (%c)
로그가 너무 많아 중요한 메시지가 묻힌다면, 아예 눈에 띄게 스타일을 입힐 수도 있습니다. 이는 단순한 재미를 넘어, 대규모 프로젝트에서 특정 모듈의 로그를 시각적으로 구분하는 데 매우 유용합니다.
const successStyle =
'color: white; background: green; font-weight: bold; padding: 2px 5px; border-radius: 3px;'
const errorStyle =
'color: white; background: red; font-weight: bold; padding: 2px 5px; border-radius: 3px;'
console.log('%c SUCCESS ', successStyle, '데이터 동기화 완료')
console.log('%c ERROR ', errorStyle, '네트워크 연결 실패')
실용적 포인트: 라이브러리를 개발하거나 공통 모듈을 만들 때, 해당 모듈에서 발생하는 로그에 고유한 색상을 부여해 보세요. 다른 개발자들이 로그를 확인할 때 “아, 이건 그 모듈에서 보낸 거구나”라고 즉각적으로 인지할 수 있게 됩니다.
5. 성능과 추적, 그 이상의 신뢰: console.time & trace
가끔은 “코드가 돌아가긴 하는데, 왜 이렇게 느리지?” 혹은 “이 함수가 왜 여기서 호출됐지?”라는 근본적인 의문이 들 때가 있습니다. 이때 우리는 감(Intuition)에 의존하기보다 데이터(Data)에 의존해야 합니다.
”측정할 수 없다면 개선할 수 없습니다”
console.time은 코드의 특정 구간이 차지하는 물리적 시간을 측정해 줍니다. 특히 리액트의 리렌더링 최적화나 대량의 데이터 가공 로직에서 병목 지점을 찾는 데 탁월합니다.
console.time('🔥 데이터 가공 성능 테스트')
const processedData = data.filter((item) => item.active).map(transform)
console.timeEnd('🔥 데이터 가공 성능 테스트')
또한, console.trace는 호출 스택(Call Stack)을 그대로 노출합니다. 이는 특히 공통 컴포넌트나 유틸리티 함수가 의도치 않은 곳에서 호출되어 부수 효과(Side Effect)를 일으킬 때, 그 범인을 찾는 가장 확실한 증거가 됩니다.
마치며: 콘솔은 우리와 코드 사이의 대화창입니다
Toss Tech의 SDK 개발기가 ‘안정성’과 ‘명확성’을 향한 여정이었듯, 우리의 디버깅 과정 역시 코드에 대한 신뢰를 쌓아가는 과정이어야 합니다. console.log 한 줄에 의존하던 습관에서 벗어나 상황에 맞는 적절한 메서드를 선택하는 것, 그것이 바로 ‘실용주의 개발자’로서 우리가 가져야 할 태도가 아닐까 생각합니다.
단순히 텍스트를 찍는 행위를 넘어, 시스템의 상태를 구조적으로 관측하고 분석하는 도구로서 console을 바라봐 보세요. 여러분의 콘솔창이 소음이 아닌, 명확한 신호(Signal)로 가득 차길 응원합니다.