보통 테마는 테마의 루트에 style.css 를 둔다. 그러면 코어는 style.css 파일에서 테마의 정보를 추출한다. 이게 정석이다.
그러나 오늘 sage라는 테마를 보다 보니, sage/resources 디렉토리에 functions.php 파일과 style.css 파일이 발견되었다. 이게 어떻게 가능한 가 싶어 뜯어 보니, wp-includes/themes.php 파일에 정의된 search_theme_directories() 함수에서 이런 동작을 지원하는 것이었다.
wp-content/themes 의 디렉토리를 찾아, 해당 디렉토리에 있는 style.css 파일을 발견하면 메타데이터를 읽는다.
style.css 파일이 발견되지 않는다면 해당 디렉토리의 하위 디렉토리에서 style.css를 검색한다. 파일이 빌견되면 그렇다면 테마의 루트는 해당 디렉토리가 된다.
예를 들어 wp-content/themes/foo 라는 테마 루트 디렉토리에 bar라는 하위 디렉토리를 두고 거기에 style.css를 둔다. 그러면 wp-content/themes/foo/bar/style.css로 경로가 만들어질 것이다. 이 때 코어는 wp-content/themes/foo/bar를 테마 루트로 인식하게 된다.
최근 테마 루트에 composer.json, package.json등 여러 설정 파일들이 존재하게 되고 이것들은 이 디렉토리를 포기할 수 없다. 그런데 이런 설정 파일과 테마의 템플릿이 서로 뒤섞이면 매우 지저분하다. 이것을 의식한 건 아닌가 모르겠다. 아무튼 이렇게 하면 템플릿 코드와 여러 설정 파일을 깔끔히 분리할 수 있어 좋은 방법이라고 생각한다.
프로그래밍에서 상수와 참조는 중요하다. 변하지 않는다는 약속을 하는 상수는 에러로 가득한 코드에서 유일한 희망이고, 참조는 효율적인 관리를 위한 기본이다.
그런데 곰곰히 생각하면 상수은 변하지 않는다는 약속을 전제로 정의하긴 하지만, 그 기반이 부서지면 상수는 효력을 발휘하기 어렵다. 참조 또한 마찬가지. 참조의 대상이 되는 원본이 변화하는 상황이면 참조는 신뢰를 잃게 된다.
데이터베이스는 ID, 주 키 (primary key)를 기준으로 참조를 진행한다. 해당 데이터베이스 상에서는 유일한 식별자이기 때문에 의미가 있다. 그러나 워드프레스 입장에서 볼 때 이것이 유일하다는 보장을 하기 어렵다. 여러 워드프레스 사이트가 있다고 하자. 각 사이트끼리 비슷한 DB 테이블 구조와 스키마를 가지고 있다. 사이트간에는 이런 ID는 큰 의미가 없다. 그 레코드의 유일함을 보장할 수 없기 때문이다.
그러므로 우리는 워드프레스에서 유사한 개체임을 식별하기 위한 수단으로 문자열을 사용하게 된다. 텀에서는 슬러그, 포스트에서는 메타 키나 포스트 타입을 이용하게 된다.
결국 이런 것들은 문자열이다. 규약은 사람들의 약속으로 이뤄지며 중복의 방지는 제안하는 이가 최대한 중복되지 않도록 노력해야 한다.
개발을 진행하며 플러그인과 테마를 개발할 때도 이 사실은 매우 중요하다. OOP나 기타 세련된 개발 기법을 적용할 때 이 관점을 잘 녹여내는 것이 매우 중요하다. 워드프레스가 가진 기능을 액션, 필터의 콜백을 이용해 확장할 시 깔끔하게 각각의 기능을 모듈화하는 것이 매우 어려워 보인다. 결국 이런 기능들이 여러 조각으로 분산되며 프로그래머들은 이런 분산이 불편해 보일 수도 있다.
그러나 완벽한 구조는 존재하지 않는다. 워드프레스의 기본은 결국 문자열도 된 식별자이다. 정의된 문자열에 의한 식별자를 통해 각각의 모듈은 서로 교차되고 상호 작용할 수 있다고 생각해야 한다.
워드프레스 5.2에 들어오면서 여러 개선점이 있었습니다. 릴리즈 노트에 나열된 사항 중 단연 사이트 상태 점검(Site Health Check), 치명적인 에러에서 코어 동작을 보호하는 기능 (PHP Error Protection) 이 눈에 띕니다.
사이트 동작 체크는 도구 > Site Health 에서 확인할 수 있으며, 기능 자체는 Health Check & Troubleshooting에서 많은 것을 가져온 것으로 보입니다. 코어 버전과 플러그인 버전의 차이는 문제 진단 기능(troubleshooting)의 유무로 보입니다. 이 기능에 대해서는 별도로 정리하기로 하지요. 지금까지 여러 플러그인들이 시스템 정보 수집 페이지를 각자 개발했었습니다. 이제 앞으로 이런 시스템 정보는 코어에서 잘 통합될 것으로 보입니다.
코어에 새로 추가된 사이트 정보 화면
업데이트 이후 갑자기 서버가 죽는 일. 워드프레스를 장기적으로 운영할 때 가장 골치아픈 문제일 겁니다. 코어가 아무리 안정적으로 발전한다 하더라도, 세상에 완벽은 없습니다. 언제 어디서든 잘못된 코드를 작성할 위험은 도사리고 있습니다. 어느 순간 업데이트를 끝마치고 페이지를 재접속했더니 텅 빈 화면만 나온다고 생각해 보세요. 영어권에서는 이것을 WSOD라고 부르더군요. White Screen of Death.
그렇다고 그게 무서워 업데이트를 마냥 미뤄 놓을 수는 없습니다. 아주 가끔 실수가 있다고는 하지만, 전반적으로는 업데이트란 모든 플러그인과 테마가 지금보다 더욱 편하고 좋은 환경을 만들기 위해 노력한 결과물입니다. 보안상 중요한 문제가 일어날 수도 있어 외면해서는 절대 안 되겠죠.
각설하고, 이번 워드프레스 코어 개발진이 이에 적극적으로 대응하기로 결정했나 봅니다. 5.2부터는 PHP 코드에 의도치 않은 치명적인 에러가 발생하도 무조건 WSOD 상태로 빠지지 않도록 동작합니다. 사용자에게도, 개발자에게도 매우 환영할 만한 기능입니다. 이 포스트를 통해 이 기능이 어떻게 동작하는지, 또 어떻게 에러로부터 사용자를 보호하는지 기록해 보려 합니다.
register_shutdown_function() 은 PHP가 완전히 종료하기 전에 특정 기능을 실행할 수 있도록 하는 함수입니다. 그리고 error_get_last() 함수는 PHP 실행 중 현재 시점에서 가장 마지막에 발생한 에러를 조회하는 함수입니다.
코어는 이 두 함수를 이용해 다음 과정을 실행할 것입니다.
register_shutdown_function() 함수를 이용해 스크립트 종료 직전 에러 핸들러를 등록합니다.
스크립트 종료 직전 에러 핸들러가 error_get_last() 함수를 이용해 스크립트 실행 중 심각한 에러가 발생했는지 아닌지 점검합니다. 만약 별다른 에러가 없다면 임무는 여기까지입니다.
그러나 어떤 에러를 발견하였다면 에러 대응을 시도합니다.
아주 간략하게 3단계로 축약한 기본 개념입니다. 이 부분을 구현한 코드는 wp-includes/error-protection.php 파일의 wp_register_fatal_error_handler() 함수를 참고하시면 됩니다.
코어의 구체적인 에러 복구
에러를 만나게 되면 이 에러가 난 위치를 파악하여 테마면 테마 에러로, 플러그인 에러면 플러그인 에러로 DB 옵션 테이블에 기록합니다. 관리자에겐 복구 모드를 접근할 수 있는 링크를 이메일로 전달합니다.
복구 모드는 쿠키로 구워진 토큰값을 통해 인식됩니다. 복구 모드를 통해 관리자 패널에 접속하면 해당 사용자는 에러가 난 테마나 플러그인을 중단시킨 상태에서 접속하게 됩니다. 복구 모드에서는 그림처럼 어드민 바 상단 우측에 어드민 바임을 알리는 띠가 붙습니다. 브라우저 제목도 ‘Recovery Mode’라고 나오지요.
복구 모드 중인 관리자 화면
이 모드에서 관리자는 테마 혹은 플러그인의 에러를 확인할 수 있습니다.
테마 에러 진단. Themes 페이지 (wp-admin/themes.php)의 밑바닥에서 출력됩니다.플러그인의 에러 진단. Plugins 페이지 (wp-admin/plugins.php)의 플러그인 목록에서 발견됩니다.
테마는 에러 메시지가 조금 불친절하군요. 반면 플러그인은 파일 몇 번째 줄에서 어떤 에러가 나오는지까지 나오네요. 테마 메시지는 좀 더 개선되어야 할 필요가 있어 보입니다.
여기서 관리자는 해당 오류를 수정하여 ‘resume’을 선택하든지, 아니면 ‘deactivate’를 선택하여 해당 플러그인 기능을 비활성화하더라도 전체 사이트가 마비되는 것을 막든지 선택할 수 있습니다.
어떻게 복구 모드로 진입하는가?
관리자 페이지에서도 심각한 에러를 만나게 되면, 코어는 관리자에게 이메일을 보내어 사이트의 에러 상태, 그리고 복구를 위한 링크를 전달합니다. 이메일은 아래 그림과 유사합니다.
에러 탐지가 되면 이런 메일이 날아옵니다
메일 중간에 있는 임시 URL로 접근하면 복구 모드로 동작할 수 있는 로그인 페이지를 만나게 됩니다. 이 페이지로 로그인을 하면 로그인 세션 토큰 외 특별한 토큰을 쿠키로 전달받게 됩니다.
워드프레스 로그인 세션 쿠키
wordpress_rec_로 시작하는 것이 바로 복구 모드를 위해 발급된 쿠키입니다. 이 쿠키값이 올바르면 사용자는 복구 모드로 동작하는 것입니다.
또한 쿠키 값은 특별한 규칙이 있고, 이 규칙대로 잘 디코딩하면 세션 ID라는 것이 있습니다. 에러가 나면 에러 내용을 DB 옵션 테이블에 기록하는데, 옵션 테이블 이름은 이 세션 ID에 의해 조금씩 달라집니다. 세션 ID는 보통 난수적으로 선택됩니다. 실험을 위해 일부러 에러를 내고 복구모드에 들어왔을 때 옵션 테이블에는 아래와 같은 에러 내용이 기록된 것을 확인했습니다.
에러 탐지와 복구 모드에 대한 설명이 어느 정도 된 것 같네요. 이제 이와 관련된 define 상수나 설정값을 메모하면서 포스팅을 마치기로 합니다.
WP_DISABLE_FATAL_ERROR_HANDLER: 참일 때는 아예 에러 보호 기능이 동작하지 않습니다. 개발 중에는 유용하게 쓸 수 있겠네요.
RECOVERY_MODE_EMAIL: 에러가 생길 때 복구 모드 안내를 위한 메일 주소를 설정하는데 쓰입니다. 선언되지 않으면 워드프레스 관리자 이메일입니다.
WP_RECOVERY_MODE_SESSION_ID: 난수로 발생되는 세션 ID를 고정할 수 있습니다.
wp-content/php-error.php 파일: 기본 에러 화면을 변경할 수 있는 템플릿 파일로 사용할 수 있습니다. 원한다면 WP_Fatal_Error_Handler::display_default_error_template() 메소드를 참고하여 사이트 고유의 에러 화면으로 만들 수 있습니다.
wp-content/fatal-error-handler.php 파일: WP_Fatal_Error_Handler 가 아닌 다른 에러 핸들러 클래스를 정의할 수 있습니다. 반드시 ‘handle()’이라는 퍼블릭 메소드가 정의되어 있어야 합니다.