Skip to content

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

한국어
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.
원문 렌더가 준비되기 전까지 텍스트 가이드로 표시합니다.

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

>

> 1. **[언어별 디버깅 가이드](/blog/devops/2026-03-07-devops-debugging-by-language-python-javascript-go-java)** ← 현재 글

> 2. [프레임워크별 디버깅 실전](/blog/devops/2026-03-07-devops-debugging-by-framework-spring-django-fastapi-react-nextjs)

> 3. [IDE별 디버깅 완전정리](/blog/devops/2026-03-07-devops-debugging-by-ide-vscode-intellij-pycharm)

> 4. [언어×프레임워크 장애 사례집](/blog/devops/2026-03-07-devops-debugging-casebook-language-framework-combos)

> 5. [원격 디버깅 실전 가이드](/blog/devops/2026-03-07-devops-remote-debugging-guide-ssh-tunnel-vscode-intellij-pycharm)

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

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

- **Python**: 동적 타입 + 인터프리터 중심. 런타임에 거의 모든 것을 들여다볼 수 있지만, 타입 관련 버그가 런타임까지 숨어 있다.

- **JavaScript/TypeScript**: 이벤트 루프/비동기 콜스택. Promise 체인에서 에러 원인이 사라지기 쉽다.

- **Go**: 고루틴/채널/간결한 런타임. 동시성 버그(race condition, 데드락)가 주요 난관이다.

- **Java**: JVM 기반, 프로파일링 생태계 성숙. 메모리 누수와 GC 튜닝이 주요 과제다.

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

언어별 디버깅 도구 비교

| 항목 | Python | JavaScript/TS | Go | Java |

| --------------- | ------------------------- | ----------------------------- | ----------------------- | ---------------------------- |

| **기본 디버거** | pdb / breakpoint() | Chrome DevTools / --inspect | Delve (dlv) | JDWP (jdb) / IDE 내장 |

| **프로파일러** | cProfile, py-spy | --cpu-prof, Chrome Perf | pprof | JFR, async-profiler |

| **메모리 분석** | memory-profiler, objgraph | --heapsnapshot, Chrome Memory | pprof heap | VisualVM, Eclipse MAT |

| **비동기 추적** | asyncio debug mode | Async Stack Traces (DevTools) | goroutine dump, GODEBUG | Thread dump, Virtual Threads |

| **정적 분석** | mypy, pylint | tsc --strict, ESLint | go vet, staticcheck | SpotBugs, Error Prone |

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

| 언어 | 흔한 버그 패턴 | 진단 방법 | 예방 전략 |

| ------ | ------------------------------------------------- | ---------------------------------------------- | -------------------------------------------------- |

| Python | TypeError/AttributeError (동적 타입), N+1 쿼리 | breakpoint()로 타입 확인, django-debug-toolbar | 타입 힌트 + mypy strict, select_related |

| JS/TS | Unhandled Promise rejection, 메모리 누수 (클로저) | async stack trace, heap snapshot 비교 | eslint-plugin-promise, WeakRef 활용 |

| Go | race condition, goroutine 누수, 채널 데드락 | -race 플래그, pprof goroutine, stack dump | go vet, context 기반 취소, errgroup |

| Java | NPE, OOM (메모리 누수), 스레드 데드락 | exception breakpoint, heap dump 분석, jstack | Optional 활용, 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를 무시시킬 수 있다.

메모리 누수 탐지

메모리 추적 시작

tracemalloc.start()

--- 의심 구간 실행 ---

process_large_dataset()

--- 끝 ---

현재 메모리 할당 스냅샷 생성

snapshot = tracemalloc.take_snapshot()

메모리를 가장 많이 사용하는 상위 10개 위치 출력

top_stats = snapshot.statistics('lineno')

for stat in top_stats[:10]:

print(stat)

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() 모니터링

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)

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로 캐시 누수 예방

// 나쁜 예: 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을 공유했는가?

증상별 빠른 진단 표

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

| 증상 | Python | JavaScript/TS | Go | Java |

| -------------------------- | -------------------------- | -------------------------- | -------------------------- | ----------------------------- |

| **응답 느림 (CPU 바운드)** | `py-spy top`, cProfile | `--cpu-prof`, Clinic flame | `pprof profile` | JFR, async-profiler |

| **메모리 단조 증가** | tracemalloc, objgraph | Heap Snapshot 비교 | `pprof heap` | `jcmd GC.heap_dump`, MAT |

| **간헐적 행(hang)** | `PYTHONASYNCIODEBUG=1` | Async Stack Traces | goroutine dump (`SIGQUIT`) | `jstack`, Thread dump |

| **동시성 버그** | threading.enumerate() | unhandledRejection 핸들러 | `go test -race` | Deadlock detection (IntelliJ) |

| **타입/Null 오류** | mypy strict + breakpoint() | tsc --strict | go vet, staticcheck | SpotBugs, Exception BP |

| **N+1 쿼리** | django-debug-toolbar | Prisma query logging | sqlx logging | Hibernate 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분 안에 원인을 찾을 수 있다. 도구를 배우는 데 드는 시간은 디버깅에서 절약되는 시간으로 몇 배 이상 돌아온다.

> 다음 글에서는 [프레임워크별 디버깅 실전](/blog/devops/2026-03-07-devops-debugging-by-framework-spring-django-fastapi-react-nextjs)을 다룬다. 언어에 프레임워크가 얹어지면 실행 흐름이 어떻게 바뀌고, 브레이크포인트를 어디에 찍어야 하는지 정리했다.

현재 단락 (1/238)

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

작성 글자: 0원문 글자: 10,851작성 단락: 0/238