PHP / 에러 출력하는 방법, 에러 출력하지 않는 방법

PHP 오류를 개발 화면에 표시하려면 error_reporting(E_ALL)과 display_errors=On을 사용합니다. 공개 서비스에서는 display_errors와 display_startup_errors를 Off로 두고 log_errors를 On으로 설정해 세부 오류를 서버 로그에 남기는 것이 기본입니다. 화면에 숨기는 설정은 오류를 고치거나 실패한 요청을 성공으로 바꾸는 기능은 아닙니다.

오류 선택·화면 표시·로그 기록 구분

PHP의 오류 및 로그 설정에서 error_reporting은 보고할 오류의 종류, display_errors는 응답에 세부 오류를 보여 줄지, log_errors는 오류를 기록할지를 정합니다. error_log는 파일 경로 또는 사용 중인 로깅 체계의 목적지와 연결됩니다. 개발·운영 환경을 나눌 때는 이 항목들을 함께 확인해야 합니다.

설정 로컬 개발 예 공개 서비스 예
error_reporting E_ALL E_ALL
display_errors On Off
display_startup_errors 필요 시 로컬 설정에서 On Off
log_errors 필요한 로그 정책에 따라 On On

운영에서 error_reporting(0)으로 보고 대상을 모두 없애면 문제 추적에 필요한 오류까지 사라질 수 있습니다. error_reporting()에는 버전별 숫자를 직접 외워 넣기보다 E_ALL 같은 이름 있는 상수를 사용합니다. 프레임워크의 자체 오류 처리기·로거는 별도 설정을 가질 수 있으므로 PHP 설정만으로 모든 화면 출력을 제어할 수 있다고 가정하지 않습니다.

로컬에서 오류 표시와 숨김을 비교하는 전체 예제

PHP 8 명령행 실행이 가능한 PC에서 새 error-demo 폴더를 만들고 그 아래 public과 logs 폴더를 만듭니다. 로그는 공개 문서 루트 public 밖의 logs에 저장하며, PHP 실행 계정이 이 폴더에 쓸 수 있어야 합니다. public/development.php에 다음 전체 코드를 저장합니다.

<?php
declare(strict_types=1);
error_reporting(E_ALL);
ini_set('display_errors', '1');
ini_set('log_errors', '0');
ini_set('html_errors', '0');
header('Content-Type: text/plain; charset=UTF-8');

trigger_error('DEMO_WARNING', E_USER_WARNING);
echo 'Script continued.', PHP_EOL;

이 파일은 일부러 사용자 경고 DEMO_WARNING을 발생시킵니다. 경고를 화면에서 보기 위한 로컬 실습이며 실제 서비스에 이런 테스트 파일을 배포하지 않습니다. 이어서 public/production.php에는 다음 전체 코드를 저장합니다.

<?php
declare(strict_types=1);
error_reporting(E_ALL);
ini_set('display_errors', '0');
ini_set('display_startup_errors', '0');
ini_set('log_errors', '1');
ini_set('error_log', dirname(__DIR__) . '/logs/php-errors.log');
header('Content-Type: text/plain; charset=UTF-8');

trigger_error('DEMO_WARNING', E_USER_WARNING);
echo 'Script continued.', PHP_EOL;

error-demo 폴더에서 아래 명령으로 로컬 개발 서버를 시작합니다. 서버는 127.0.0.1에만 바인딩하며 운영 웹 서버로 사용하지 않습니다. 포트가 사용 중이면 다른 로컬 포트를 정하고 URL도 맞춰 바꿉니다.

php -S 127.0.0.1:8080 -t public
  1. 브라우저에서 http://127.0.0.1:8080/development.php를 엽니다. DEMO_WARNING과 발생 위치가 보이고 뒤에 Script continued.가 출력되는지 확인합니다.
  2. http://127.0.0.1:8080/production.php를 엽니다. 본문에는 Script continued.만 나오고 경고 세부 정보는 보이지 않아야 합니다.
  3. error-demo/logs/php-errors.log를 열어 DEMO_WARNING 기록이 추가되었는지 확인합니다. 파일이 없다면 PHP 실행 계정의 폴더 쓰기 권한과 실제 error_log 경로를 확인합니다.
  4. 확인이 끝나면 터미널에서 Ctrl+C로 개발 서버를 종료합니다. 이 실습 로그와 테스트 파일을 실제 웹 제공 폴더로 옮기지 않습니다.

이 경고는 실행을 멈추지 않으므로 두 요청 모두 정상 상태 코드로 끝날 수 있습니다. 반면 치명적인 오류·처리되지 않은 예외는 실행을 중단할 수 있습니다. 오류 표시를 끄면 코드가 계속 정상 실행된다는 뜻이 아닙니다.

중단되는 오류도 로그에 남는지 확인

같은 실습 구조에서 public/fatal-demo.php를 다음 내용으로 만듭니다. 실제 업무 데이터가 없는 로컬에서만 요청합니다.

<?php
declare(strict_types=1);
error_reporting(E_ALL);
ini_set('display_errors', '0');
ini_set('log_errors', '1');
ini_set('error_log', dirname(__DIR__) . '/logs/php-errors.log');
header('Content-Type: text/plain; charset=UTF-8');

throw new RuntimeException('DEMO_FATAL');

개발 서버가 실행 중일 때 /fatal-demo.php를 열면 DEMO_FATAL이나 스택 경로가 응답 본문에 드러나지 않아야 합니다. 브라우저 개발자 도구의 Network에서 해당 요청을 선택해 HTTP 500 상태인지 확인하고, logs/php-errors.log에서는 DEMO_FATAL 기록을 확인합니다. 빈 화면이나 일반 오류 화면만 보고 요청이 성공했다고 판단하지 마세요.

실제 서비스에서는 실패 응답에 맞는 상태 코드와 사용자가 이해할 수 있는 일반 안내를 애플리케이션의 예외 처리 계층에서 제공합니다. 상세 스택·내부 경로·접속 정보는 사용자 응답에 그대로 출력하지 않습니다. log_errors=On이어도 저장 공간 부족·권한·잘못된 경로 때문에 로그가 남지 않을 수 있으므로, 설정값과 실제 기록을 각각 확인해야 합니다.

php.ini에서 공통 설정하고 적용 결과 확인

런타임 ini_set()은 그 줄이 실행된 뒤부터 영향을 줍니다. 같은 파일의 파싱 오류나 PHP 시작 단계의 문제는 해당 줄까지 실행되지 않으므로 파일 상단의 ini_set()만으로 해결할 수 없습니다. 이런 문제는 PHP 시작 전에 읽는 설정과 로그, 문법 검사로 확인합니다. php.ini 탐색 규칙에 따라 실행 방식마다 읽는 파일이 달라질 수 있습니다.

php --ini

이 명령은 CLI가 읽는 설정을 보여 줍니다. 웹 서버가 사용하는 PHP-FPM·Apache 모듈의 설정 경로와 같다고 단정하지 마세요. 웹 쪽은 접근이 제한된 점검 환경에서 다음을 config-check.php로 잠깐 실행해 SAPI(Server Application Programming Interface, PHP 실행 인터페이스)와 읽힌 설정 파일을 확인합니다. 경로와 로그 위치가 출력되므로 공개 운영 URL에 남겨 두지 않습니다.

<?php
declare(strict_types=1);
header('Content-Type: text/plain; charset=UTF-8');
echo 'SAPI: ', PHP_SAPI, PHP_EOL;
echo 'INI: ', php_ini_loaded_file() ?: '(none)', PHP_EOL;
foreach (['display_errors', 'display_startup_errors', 'log_errors', 'error_log'] as $key) {
    echo $key, ': ', ini_get($key), PHP_EOL;
}
echo 'error_reporting: ', error_reporting(), PHP_EOL;

운영용 php.ini의 기본 예는 아래와 같습니다. error_log 경로는 예시이며 실제 서버에서 공개 문서 루트 밖에 있고 PHP 계정에 필요한 쓰기 권한이 부여된 위치로 바꿉니다. 호스팅 환경에서 파일 설정을 허용하지 않는다면 제공하는 로그 화면이나 시스템 로그 경로를 사용합니다.

error_reporting = E_ALL
display_errors = Off
display_startup_errors = Off
log_errors = On
error_log = /var/log/my-app/php-errors.log

설정 파일을 고치기 전에 원본을 보관합니다. PHP-FPM 환경이면 사용 중인 풀·PHP 버전의 설정 검사와 reload 절차를, Apache PHP 모듈이면 해당 웹 서버의 검사·재시작 절차를 따릅니다. CLI는 다음 실행에서 설정을 읽습니다. 정확한 서비스 이름은 설치 방식과 버전에 따라 다르므로 다른 서버의 경로·서비스 이름을 추정해 실행하지 않습니다. 적용 뒤 config-check.php의 실제 값과 시험 오류의 응답·로그를 다시 확인하고 점검 파일을 제거합니다.

설정했는데 오류가 보이지 않거나 숨겨지지 않을 때

  1. 문법부터 확인합니다. php -l public/development.php처럼 파일을 검사합니다. php -l은 구문 검사이며 모든 실행 경로·의존성·연결 성공을 검증하지 않습니다. 런타임 설정 코드 자체가 파싱되지 않는 경우에는 외부 설정이 필요합니다.
  2. 다른 설정을 수정했는지 봅니다. CLI와 웹 PHP 버전·SAPI·설정 파일이 같은지 확인합니다. 추가로 읽힌 ini 파일이나 FPM 풀의 관리 설정이 원하는 값을 제한할 수도 있습니다.
  3. 자체 오류 출력을 구분합니다. 프레임워크 디버그 화면, var_dump(), echo, 웹 서버·프록시 오류 페이지는 display_errors 하나로 모두 사라지지 않습니다. 해당 계층의 운영 설정을 확인합니다.
  4. 로그 파일을 실제로 확인합니다. error_log()의 기본 메시지 유형은 함수가 true를 반환해도 기록 성공까지 보장하지 않습니다. 반환값 하나만 믿지 말고 테스트 표식이 실제 목적지에 들어갔는지 확인합니다.

운영 로그에는 오류를 추적할 최소한의 정보를 남기고 비밀번호·인증 토큰·전체 요청 본문을 무조건 기록하지 않습니다. 오류를 숨기려고 @ 연산자나 E_ALL 제외 목록을 전역적으로 늘리기보다 원인을 수정하고, 화면 비표시와 운영자의 추적 가능성을 함께 유지하세요. 변경 후 문제가 생기면 보관한 설정으로 복구하고 같은 검사·적용·응답 확인 절차를 반복합니다.