Skip to content

Split View: 언어별 디버깅 실전 가이드: Python · JavaScript/TypeScript · Go · Java에서 브레이크포인트, 실행, 프로파일링까지

✨ Learn with Quiz
|

언어별 디버깅 실전 가이드: Python · JavaScript/TypeScript · Go · Java에서 브레이크포인트, 실행, 프로파일링까지

이 글은 디버깅 실전 시리즈 5편 중 1편이다.

  1. 언어별 디버깅 가이드 ← 현재 글
  2. 프레임워크별 디버깅 실전
  3. IDE별 디버깅 완전정리
  4. 언어×프레임워크 장애 사례집
  5. 원격 디버깅 실전 가이드

왜 언어별 디버깅 전략이 달라야 할까

디버깅의 본질은 같지만(재현 → 관찰 → 가설 → 검증), 언어마다 런타임/메모리 모델/툴 체인이 달라서 접근이 달라진다.

  • Python: 동적 타입 + 인터프리터 중심. 런타임에 거의 모든 것을 들여다볼 수 있지만, 타입 관련 버그가 런타임까지 숨어 있다.
  • JavaScript/TypeScript: 이벤트 루프/비동기 콜스택. Promise 체인에서 에러 원인이 사라지기 쉽다.
  • Go: 고루틴/채널/간결한 런타임. 동시성 버그(race condition, 데드락)가 주요 난관이다.
  • Java: JVM 기반, 프로파일링 생태계 성숙. 메모리 누수와 GC 튜닝이 주요 과제다.

아래는 실무에서 바로 쓰는 방식만 압축해서 정리했다.


언어별 디버깅 도구 비교

항목PythonJavaScript/TSGoJava
기본 디버거pdb / breakpoint()Chrome DevTools / --inspectDelve (dlv)JDWP (jdb) / IDE 내장
프로파일러cProfile, py-spy--cpu-prof, Chrome PerfpprofJFR, async-profiler
메모리 분석memory-profiler, objgraph--heapsnapshot, Chrome Memorypprof heapVisualVM, Eclipse MAT
비동기 추적asyncio debug modeAsync Stack Traces (DevTools)goroutine dump, GODEBUGThread dump, Virtual Threads
정적 분석mypy, pylinttsc --strict, ESLintgo vet, staticcheckSpotBugs, Error Prone

언어별 흔한 버그 패턴과 진단법

언어흔한 버그 패턴진단 방법예방 전략
PythonTypeError/AttributeError (동적 타입), N+1 쿼리breakpoint()로 타입 확인, django-debug-toolbar타입 힌트 + mypy strict, select_related
JS/TSUnhandled Promise rejection, 메모리 누수 (클로저)async stack trace, heap snapshot 비교eslint-plugin-promise, WeakRef 활용
Gorace condition, goroutine 누수, 채널 데드락-race 플래그, pprof goroutine, stack dumpgo vet, context 기반 취소, errgroup
JavaNPE, OOM (메모리 누수), 스레드 데드락exception breakpoint, heap dump 분석, jstackOptional 활용, try-with-resources, Virtual Threads

1) Python 디버깅

실행 방법

# 기본 실행
python app.py

# pdb로 즉시 디버그 모드 실행
python -m pdb app.py

# pytest에서 실패 지점 진입 (첫 실패에서 멈추고 pdb 열기)
pytest -x --pdb

# asyncio 디버그 모드 활성화
PYTHONASYNCIODEBUG=1 python app.py

브레이크포인트

def calculate_total(items):
    subtotal = sum(i.price for i in items)
    breakpoint()  # Python 3.7+ 기본 내장
    return subtotal

조건부 브레이크포인트

def process_order(order):
    # 특정 조건에서만 디버거 진입 — 대량 루프에서 유용
    if order.user_id == "U-9999":
        breakpoint()

    # 또는 pdb 조건부 설정 (pdb 프롬프트에서)
    # (Pdb) b process_order, order.status == "FAILED"

    result = validate(order)
    return result

실무 팁:

  • 루프 안 브레이크포인트는 반드시 조건부로 제한한다. 만 건 루프에서 무조건 걸면 작업이 멈춘다.
  • 데이터 크기 큰 객체는 len()/핵심 필드만 확인한다. print(huge_dict) 한 번이면 터미널이 멈출 수 있다.
  • PYTHONBREAKPOINT=0으로 프로덕션에서 breakpoint를 무시시킬 수 있다.

메모리 누수 탐지

import tracemalloc

# 메모리 추적 시작
tracemalloc.start()

# --- 의심 구간 실행 ---
process_large_dataset()
# --- 끝 ---

# 현재 메모리 할당 스냅샷 생성
snapshot = tracemalloc.take_snapshot()

# 메모리를 가장 많이 사용하는 상위 10개 위치 출력
top_stats = snapshot.statistics('lineno')
for stat in top_stats[:10]:
    print(stat)
# objgraph로 순환 참조 탐지
import objgraph

# 가장 많이 생성된 객체 타입 상위 20개
objgraph.show_most_common_types(limit=20)

# 특정 타입의 참조 그래프 시각화
objgraph.show_backrefs(
    objgraph.by_type('MyLeakingClass')[:3],
    filename='refs.png'
)

프로파일링

CPU

# cProfile로 함수 단위 CPU 시간 측정
python -m cProfile -o out.prof app.py
python -m pstats out.prof

# 운영 환경 친화적 샘플링 (py-spy — 프로세스 중단 없음)
py-spy top --pid <PID>
py-spy record -o profile.svg --pid <PID>

# line_profiler로 함수 내부 라인별 시간 측정
pip install line-profiler
kernprof -l -v slow_function.py

메모리

pip install memory-profiler
python -m memory_profiler app.py

# 데코레이터 방식으로 특정 함수만 측정
# @profile 데코레이터를 함수 위에 붙이면 라인별 메모리 사용량 출력

2) JavaScript/TypeScript (Node.js) 디버깅

실행 방법

# 디버그 포트 오픈 (기본 9229)
node --inspect src/index.js

# 첫 줄에서 멈춤 — 초기화 과정 디버깅에 유용
node --inspect-brk src/index.js

# TypeScript (source map 활성화 상태 전제)
node --inspect-brk dist/index.js

# ts-node로 직접 디버깅 (개발환경)
node --inspect-brk -r ts-node/register src/index.ts

브레이크포인트

async function syncUser(id: string) {
  const user = await repo.findById(id)
  debugger // DevTools/IDE 연결 시 중단
  return user
}

조건부 브레이크포인트 (Chrome DevTools)

// Chrome DevTools에서 브레이크포인트 우클릭 → "Edit breakpoint" 선택
// 조건 예시:
//   user.role === "admin"
//   items.length > 100
//   error.code === "TIMEOUT"

// 코드에서 조건부로 debugger 사용
function handleRequest(req: Request) {
  // 특정 헤더가 있을 때만 디버거 진입
  if (req.headers['x-debug'] === 'true') {
    debugger
  }
  // ...
}

비동기 디버깅 팁

// 나쁜 예: Promise 체인에서 에러 원인 추적 불가
fetchUser(id)
  .then((user) => fetchOrders(user.id))
  .then((orders) => process(orders))
  .catch((err) => console.log(err)) // 어디서 실패했는지 알 수 없음

// 좋은 예: async/await로 콜스택 유지
async function syncUserOrders(id: string) {
  try {
    const user = await fetchUser(id) // 실패 시 이 라인에서 멈춤
    const orders = await fetchOrders(user.id) // 여기서 멈추면 원인 명확
    return await process(orders)
  } catch (err) {
    // 스택트레이스에 원인 라인이 남음
    console.error('syncUserOrders failed:', err)
    throw err
  }
}
// 이벤트 루프 블로킹 감지
// --prof 플래그로 V8 프로파일 생성
// node --prof dist/index.js
// node --prof-process isolate-*.log > processed.txt

// 또는 Clinic.js Doctor로 자동 진단
// npx clinic doctor -- node dist/index.js

메모리 누수 탐지

// Heap snapshot 3-snapshot 기법
// 1) 초기 상태 snapshot
// 2) 의심 작업 반복 수행
// 3) 작업 후 snapshot
// DevTools Memory 탭에서 Comparison 뷰로 증가한 객체 확인

// 코드에서 강제 GC 후 메모리 확인
// node --expose-gc dist/index.js
if (global.gc) {
  global.gc()
}
console.log(process.memoryUsage())
// { rss, heapTotal, heapUsed, external, arrayBuffers }

프로파일링

# CPU profile 파일 생성
node --cpu-prof dist/index.js

# Heap snapshot (SIGUSR2 시그널로 트리거)
node --heapsnapshot-signal=SIGUSR2 dist/index.js
kill -USR2 <PID>

# Clinic.js로 종합 진단
npx clinic doctor -- node dist/index.js
npx clinic flame -- node dist/index.js
npx clinic bubbleprof -- node dist/index.js

Chrome DevTools에서:

  • Performance 탭: CPU 병목, 이벤트 루프 지연 확인
  • Memory 탭: Heap snapshot 비교로 누수 추적
  • Sources 탭: Async Stack Traces 활성화하여 비동기 콜스택 확인

3) Go 디버깅

실행 방법

go run ./cmd/api

Delve 디버거:

# 디버그 모드로 실행
dlv debug ./cmd/api

# 테스트 디버깅
dlv test ./...

# 실행 중인 프로세스에 연결
dlv attach <PID>

# 원격 디버깅 (헤드리스 모드)
dlv debug ./cmd/api --headless --listen=:2345 --api-version=2

브레이크포인트

(dlv) break main.main
(dlv) break service/user.go:42
(dlv) continue
(dlv) print userID

조건부 브레이크포인트

# 특정 조건에서만 중단
(dlv) break service/order.go:55
(dlv) condition 1 order.Status == "FAILED"

# hit count 조합 — 100번째 호출에서만 멈춤
(dlv) condition 1 hitcount % 100 == 0

# 고루틴 확인
(dlv) goroutines          # 전체 고루틴 목록
(dlv) goroutine 15        # 특정 고루틴으로 전환
(dlv) bt                  # 현재 고루틴 스택 트레이스

비동기(고루틴) 디버깅 팁

# race detector — 동시성 버그의 첫 관문
go test -race ./...
go run -race ./cmd/api
// 고루틴 누수 탐지: runtime.NumGoroutine() 모니터링
import "runtime"

func debugGoroutines() {
    // 주기적으로 고루틴 수 확인
    ticker := time.NewTicker(5 * time.Second)
    for range ticker.C {
        log.Printf("Active goroutines: %d", runtime.NumGoroutine())
    }
}
// 데드락 진단: SIGQUIT로 전체 고루틴 스택 덤프
// kill -QUIT <PID> 또는 Ctrl+\ 로 트리거
// GOTRACEBACK=all 환경변수로 모든 고루틴 포함

// context 기반 타임아웃으로 데드락 예방
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

select {
case result := <-ch:
    return result, nil
case <-ctx.Done():
    return nil, fmt.Errorf("operation timed out: %w", ctx.Err())
}

프로파일링 (pprof)

import _ "net/http/pprof"
import "net/http"

go func() {
  _ = http.ListenAndServe(":6060", nil)
}()
# CPU 30초 프로파일링
go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30

# Heap 메모리 분석
go tool pprof http://localhost:6060/debug/pprof/heap

# 고루틴 덤프 — 누수 확인에 필수
go tool pprof http://localhost:6060/debug/pprof/goroutine

# 웹 UI로 시각화
go tool pprof -http=:8080 http://localhost:6060/debug/pprof/profile?seconds=30

4) Java 디버깅

실행 방법

./gradlew bootRun
# 또는
java -jar app.jar

원격 디버그 포트:

# JDK 9+ 원격 디버그 설정
java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005 -jar app.jar

# Docker 컨테이너에서 디버그 포트 노출
# docker run -p 5005:5005 -e JAVA_TOOL_OPTIONS="-agentlib:jdwp=..." app

브레이크포인트

// IntelliJ에서 조건부 브레이크포인트 설정
// 브레이크포인트 우클릭 → Condition 입력:
//   orderId.equals("ORD-12345")
//   items.size() > 100
//   user != null && user.getRole().equals("ADMIN")

// Logpoint (중단 없이 로그만 출력)
// Evaluate and log: "Order processed: " + orderId + ", total=" + total
  • 예외 브레이크포인트(NullPointerException, IllegalStateException 등) 적극 사용
  • 조건부 브레이크포인트로 대량 트래픽 중 특정 케이스만 포착
  • logpoint로 서비스 중단 없이 값 관찰

메모리 누수 탐지

# Heap dump 생성
jcmd <PID> GC.heap_dump /tmp/heapdump.hprof

# OOM 발생 시 자동 heap dump
java -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/ -jar app.jar

# Eclipse MAT (Memory Analyzer Tool)로 분석
# Leak Suspects Report → Dominator Tree → Retained Size 기준 정렬
// WeakReference로 캐시 누수 예방
import java.lang.ref.WeakReference;
import java.util.WeakHashMap;

// 나쁜 예: static Map에 계속 추가 → 메모리 누수
static Map<String, Object> cache = new HashMap<>();

// 좋은 예: WeakHashMap 사용 → GC가 필요시 수거
static Map<String, Object> cache = new WeakHashMap<>();

프로파일링

JFR (Java Flight Recorder)

# 프로덕션에서 120초 프로파일링 (오버헤드 1% 미만)
jcmd <PID> JFR.start name=onprod settings=profile duration=120s filename=app.jfr
jcmd <PID> JFR.stop name=onprod

# JDK Mission Control (JMC)로 .jfr 파일 분석
# Hot Methods → Call Tree → 상위 핫스팟 함수 확인

async-profiler

# CPU 프로파일링 (30초, flamegraph SVG 출력)
./profiler.sh -d 30 -f cpu.svg <PID>

# 메모리 할당 프로파일링
./profiler.sh -e alloc -d 30 -f alloc.svg <PID>

# Lock contention 프로파일링
./profiler.sh -e lock -d 30 -f lock.svg <PID>

# wall-clock 프로파일링 (I/O 대기 포함)
./profiler.sh -e wall -d 30 -f wall.svg <PID>

공통 디버깅 루틴 (언어 무관)

  1. 재현 조건 고정: 입력/버전/환경 변수 캡처. 재현 안 되면 디버깅 불가능하다.
  2. 관측 지점 최소화: 브레이크포인트 남발 금지. 경계 지점에만 배치한다.
  3. 가설 1개씩 검증: 한 번에 하나만 바꿔보기. 두 가지 동시에 바꾸면 원인을 못 잡는다.
  4. 프로파일링 우선순위: CPU → I/O → 메모리 순으로 의심 영역을 좁힌다.
  5. 사후 문서화: 원인/탐지 신호/재발 방지 기록. 같은 버그를 두 번 겪지 않기 위해 필수다.

팀 운영 체크리스트

  • PR 템플릿에 "재현 방법" 필드 추가
  • 장애 리포트에 flamegraph/JFR 첨부
  • 디버깅 세션에서 시간 제한(예: 30분) 후 접근 전환
  • 로컬/스테이징 디버그 설정 템플릿 공유

종합 트러블슈팅 체크리스트

재현 및 환경 확인

  • 문제가 로컬에서 재현되는가?
  • 재현 조건(입력값, 환경변수, 버전)을 정확히 기록했는가?
  • 최근 배포/변경 사항과의 연관성을 확인했는가?
  • 다른 환경(스테이징, 프로덕션)에서도 동일하게 발생하는가?

관측 및 분석

  • 에러 로그/스택 트레이스를 확인했는가?
  • 관련 메트릭(CPU, 메모리, 응답시간)에 이상이 있는가?
  • 브레이크포인트를 경계 지점(입력/출력/외부 호출)에 배치했는가?
  • 조건부 브레이크포인트로 범위를 좁혔는가?

프로파일링

  • CPU 프로파일을 캡처했는가? (py-spy / --cpu-prof / pprof / JFR)
  • 메모리 프로파일(heap dump/snapshot)을 확인했는가?
  • I/O 병목(네트워크, 디스크, DB)을 확인했는가?
  • flamegraph에서 상위 핫스팟 함수를 식별했는가?

해결 및 후속

  • 근본 원인(root cause)을 식별했는가?
  • 수정 사항에 대한 회귀 테스트를 작성했는가?
  • 원인/탐지 신호/재발 방지를 문서화했는가?
  • 모니터링/알림 규칙을 추가 또는 개선했는가?
  • 팀에 postmortem을 공유했는가?

증상별 빠른 진단 표

현장에서 증상을 보고 어떤 언어·도구를 먼저 의심해야 하는지 즉시 찾을 수 있는 표다.

증상PythonJavaScript/TSGoJava
응답 느림 (CPU 바운드)py-spy top, cProfile--cpu-prof, Clinic flamepprof profileJFR, async-profiler
메모리 단조 증가tracemalloc, objgraphHeap Snapshot 비교pprof heapjcmd GC.heap_dump, MAT
간헐적 행(hang)PYTHONASYNCIODEBUG=1Async Stack Tracesgoroutine dump (SIGQUIT)jstack, Thread dump
동시성 버그threading.enumerate()unhandledRejection 핸들러go test -raceDeadlock detection (IntelliJ)
타입/Null 오류mypy strict + breakpoint()tsc --strictgo vet, staticcheckSpotBugs, Exception BP
N+1 쿼리django-debug-toolbarPrisma query loggingsqlx loggingHibernate SQL + p6spy

디버깅 환경 셋업 원라이너

새 프로젝트에서 디버깅 환경을 빠르게 구성하는 명령 모음이다.

# Python — 디버깅 + 프로파일링 도구 일괄 설치
pip install debugpy py-spy line-profiler memory-profiler objgraph ipdb

# Node.js — 진단 도구 설치
npm install -D @clinic/doctor why-did-you-render

# Go — Delve 디버거 + 정적 분석 도구
go install github.com/go-delve/delve/cmd/dlv@latest && go install honnef.co/go/tools/cmd/staticcheck@latest

# Java — async-profiler 다운로드 (Linux x64)
curl -L https://github.com/async-profiler/async-profiler/releases/latest/download/async-profiler-3.0-linux-x64.tar.gz | tar xz

마무리

좋은 디버거는 "툴을 많이 아는 사람"이 아니라 문제를 빠르게 좁히는 사람이다. 언어별 도구는 달라도, 핵심은 같다:

관찰 가능한 상태를 만들고, 근거 기반으로 한 단계씩 좁혀라.

각 언어의 런타임 특성을 이해하고, 해당 생태계에서 검증된 도구를 능숙하게 쓸 수 있으면 대부분의 버그는 30분 안에 원인을 찾을 수 있다. 도구를 배우는 데 드는 시간은 디버깅에서 절약되는 시간으로 몇 배 이상 돌아온다.

다음 글에서는 프레임워크별 디버깅 실전을 다룬다. 언어에 프레임워크가 얹어지면 실행 흐름이 어떻게 바뀌고, 브레이크포인트를 어디에 찍어야 하는지 정리했다.

Debugging by Language: Python, JavaScript/TypeScript, Go, and Java Practical Guide

This post is part 1 of a 5-part debugging series.

  1. Debugging by Language
  2. Debugging by Framework
  3. IDE Debugging Playbook
  4. Language×Framework Casebook
  5. Remote Debugging Guide

Why this guide matters

Most debugging failures are not caused by missing tools, but by missing workflow discipline:

  • reproducibility (same input, same env, same version)
  • observability (logs + metrics + traces + breakpoints)
  • hypothesis-driven narrowing (one change at a time)

This article focuses on execution patterns that work under production pressure.

Practical workflow

  1. Freeze reproduction conditions.
  2. Add breakpoint/logpoints only on boundaries.
  3. Capture one profile (CPU or memory) before changing code.
  4. Verify fix with regression test + replay scenario.
  5. Record root cause and prevention rule in team docs.

Operational checklist

  • Reproducible command/script exists.
  • Failure signal is measurable (error rate/latency/memory).
  • Profiling artifact is attached (flamegraph/heap/thread dump).
  • Rollback strategy is prepared before risky deploy.
  • Postmortem includes prevention action owner and due date.

Korean original

For deeper examples and Korean explanations, read the original:

Quiz

Q1: What is the main topic covered in "Debugging by Language: Python, JavaScript/TypeScript, Go, and Java Practical Guide"?

Part 1 of the debugging series. A practical, copy-paste-friendly guide with reproducible steps, breakpoint strategy, profiling checkpoints, and team-level checklists.

Q2: Why this guide matters? Most debugging failures are not caused by missing tools, but by missing workflow discipline: reproducibility (same input, same env, same version) observability (logs + metrics + traces + breakpoints) hypothesis-driven narrowing (one change at a time) This article focuses on execu...

Q3: Explain the core concept of Practical workflow. Freeze reproduction conditions. Add breakpoint/logpoints only on boundaries. Capture one profile (CPU or memory) before changing code. Verify fix with regression test + replay scenario. Record root cause and prevention rule in team docs.

Q4: What are the key aspects of Operational checklist?[ ] Reproducible command/script exists. [ ] Failure signal is measurable (error rate/latency/memory). [ ] Profiling artifact is attached (flamegraph/heap/thread dump). [ ] Rollback strategy is prepared before risky deploy.

Q5: How does Korean original work? For deeper examples and Korean explanations, read the original: /blog/devops/2026-03-07-devops-debugging-by-language-python-javascript-go-java