먼저 재현 가능한 최소 재현 사례(small reproducible example)를 만든다.
에러/로그 → 재현 → 원인 추적 → 수정 → 재검증 순으로 접근한다.
개발 환경과 운영 환경의 설정(특히 display_errors)은 절대 같게 두지 않는다.
개발 환경(일시적):
// 코드 최상단(개발 중에만)
ini_set('display_errors', '1');
ini_set('display_startup_errors', '1');
error_reporting(E_ALL);
운영 환경(권장):
display_errors = Off
log_errors = On
error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT
error_log = /var/log/php_errors.log
운영에서는 에러를 화면에 그대로 노출하면 안 된다. log_errors로 파일/중앙 로깅으로 전송한다.
PHP-FPM/Apache/Nginx 각각의 php.ini 또는 pool 설정을 확인해야 한다 (CLI와 FPM은 다른 ini를 읽음).
예외와 에러를 중앙에서 잡아 로깅/통보하도록 설정하면 문제 원인 파악이 쉬워짐.
set_error_handler(function($severity, $message, $file, $line) {
error_log("[ERROR] $message in $file:$line");
/* 개발시에만 예외로 전환하고 싶다면 throw new ErrorException(...) */
});
set_exception_handler(function(Throwable $e){
error_log("[UNCAUGHT EXCEPTION] {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}");
error_log($e->getTraceAsString());
});
register_shutdown_function(function() {
$err = error_get_last();
if ($err && in_array($err['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])) {
error_log("[FATAL] {$err['message']} in {$err['file']}:{$err['line']}");
}
});
register_shutdown_function() + error_get_last()로 치명적 오류를 잡을 수 있다.
var_dump(), print_r(), var_export() — 구조 확인용.
HTML 출력용: <pre><?php var_dump($x); ?></pre>
error_log() — 웹 요청 중에서도 서버 로그로 보낸다.
debug_backtrace() / debug_print_backtrace() — 호출 스택 확인.
memory_get_usage(), memory_get_peak_usage() — 메모리 사용량 체크.
Xdebug는 중단점/스텝디버깅, 프로파일링, 트레이스, 향상된 var_dump 출력 등 많이 제공.
Xdebug 3 기준(php.ini 예):
zend_extension=xdebug.so
xdebug.mode = develop,debug,profile,trace
xdebug.start_with_request = trigger ; yes|no|trigger 중 선택 (trigger 권장)
xdebug.client_host = 127.0.0.1
xdebug.client_port = 9003
xdebug.output_dir = /tmp/xdebug
xdebug.log = /tmp/xdebug.log
xdebug.start_with_request=trigger이면 GET/POST/COOKIE에 XDEBUG_TRIGGER(또는 브라우저 확장)를 넣었을 때만 디버그 시작.
Docker 환경에서는 client_host를 host.docker.internal 또는 호스트 IP로 설정해야 함.
CLI에서 바로 디버깅하려면:
php -dxdebug.mode=debug -dxdebug.start_with_request=yes script.php
IDE 연동: PhpStorm, VSCode(php-debug 확장) 등에서 포트 9003을 리슨. 조건부/함수별 중단점, 변수 관찰(watch) 등 사용.
IDE에서 중단점 설정 → 요청 실행 → 코드가 중단된 지점에서 변수, 콜스택, 표현식 평가 가능.
조건부 중단점(예: $i > 1000)으로 많은 반복 중 악성 케이스만 잡아낼 수 있음.
“Step Into / Over / Out”으로 문제 지점까지 좁혀간다.
Xdebug 프로파일러(xdebug.mode=profile)로 Cachegrind 파일 생성 → KCacheGrind, QCacheGrind, Webgrind로 분석.
타사 툴: Blackfire, Tideways, New Relic, XHGui 등도 권장(상용/개발용에 따라 선택).
자주 사용하는 측정 함수:
$start = microtime(true);
// 코드
$elapsed = microtime(true) - $start;
error_log("Elapsed: {$elapsed}s");
느린 쿼리 로깅(데이터베이스 측) + PHP 프로파일링으로 병목 지점 확인.
PHP에 포함된 대화형 디버거. CLI용:
phpdbg -qrr script.php
# 내부에서는 break, run, step, next, print 등 사용
서버-사이드의 간단한 트레이싱이나 CLI 스크립트 디버깅에 유용.
PHPStan, Psalm: 타입·문법 기반 정적 분석(오류 사전 제거).
PHPCS: 코딩 스타일 검사.
PHPUnit: 단위 테스트 + 코드 커버리지(Xdebug와 연동).
CI(eg. GitHub Actions, GitLab CI)에 정적 분석과 유닛테스트를 포함시키면 회귀 방지에 도움.
PDO 사용 시 항상 예외 모드:
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
Prepared statement 파라미터 값도 로깅(수동)하여 실제 실행되는 쿼리 확인.
MySQL slow query log, general query log를 켜서 DB 쪽 병목 확인.
트랜잭션을 활용해 문제 재현(롤백 가능).
브라우저 개발자도구 (Network 탭): 요청/응답 헤더, 페이로드 확인.
curl / Postman / Insomnia로 요청 재현.
요청에 X-Request-Id 같은 correlation id를 붙여 로그에서 요청 추적.
AJAX 요청 문제는 콘솔과 네트워크 로그로 먼저 확인.
운영에서는 display_errors = Off, 에러는 로그로 전송.
민감 정보(패스워드, 토큰, 개인정보)는 절대로 로그에 그대로 남기지 않음(마스킹).
요청 식별자(Trace ID)를 생성해 로그를 연관(분산 로깅, ELK/Graylog).
에러 모니터링 서비스(Sentry 등) 연동으로 예외 알림 자동화.
브레이크포인트가 작동하지 않을 때:
Xdebug가 실제로 로드됐는가? (php -i | grep xdebug)
FPM과 CLI의 php.ini가 다른 경우가 많음(각각 확인).
xdebug.start_with_request 설정 확인 (yes|trigger).
방화벽/포트(9003) 문제 혹은 Docker 네트워크 문제.
xdebug.output_dir 권한 문제로 프로파일 파일이 안 생기는 경우.
코드 변경이 서버에 반영되지 않음:
OPcache 활성화 시 opcache.validate_timestamps=1 또는 php-fpm 재시작 필요.
에러 메시지가 불충분:
PDO::ERRMODE_EXCEPTION, set_exception_handler()로 스택트레이스 확보.
문법 체크(린트):
php -l file.php
CLI에서 빠르게 Xdebug로 실행:
php -dxdebug.mode=debug -dxdebug.start_with_request=yes script.php
Git으로 회귀 찾기:
git bisect start
git bisect bad HEAD
git bisect good v1.2.3
# 반복 테스트로 원인 커밋 찾기
로컬에서 error_reporting = E_ALL, display_errors = On으로 재현.
Xdebug로 브레이크포인트/스텝 통해 원인 추적.
로그(서버/DB)로 추가 정보 수집.
수정 후 유닛테스트/통합테스트 실행.
운영 반영 시 display_errors = Off, 로그·모니터링 확인.