에러 로깅

에러 로깅 (Error Logging)

SQLite 는 내부적으로 발생한 문제를 추적할 수 있게 해주는 에러 로깅 콜백을 제공해요. 이 콜백을 설정하면 개발 과정에서 예상치 못한 동작을 빠르게 발견할 수 있어요.

출처: Error Logging

본문

1. 에러 로깅 콜백 설정하기

프로세스당 하나의 에러 로깅 콜백만 존재할 수 있어요. 에러 로깅 콜백은 시작 시점에 다음과 유사한 C 코드로 등록해요:

sqlite3_config(SQLITE_CONFIG_LOG, errorLogCallback, pData);

에러 로거 콜백 함수는 다음과 같이 생겼을 거예요:

void errorLogCallback(void *pArg, int iErrCode, const char *zMsg){
  fprintf(stderr, "(%d) %s\n", iErrCode, zMsg);
}

위 예는 에러 로거 콜백의 시그니처를 보여줘요. 하지만 임베디드 애플리케이션에서는 보통 stderr 에 메시지를 출력하지 않아요. 대신 미리 할당된 원형 버퍼(circular buffer)에 메시지를 저장해 두고, 디버깅 중 진단 정보가 필요할 때 접근할 수도 있어요. 아니면 Syslog 로 메시지를 보낼 수도 있어요. 어쨌든 메시지는 최종 사용자에게 보여주는 것이 아니라, 개발자가 접근할 수 있는 곳에 저장돼야 해요.

오해하지 마세요: 에러 로거 메시지를 최종 사용자에게 보여주는 데 기술적으로 잘못된 점은 없어요. 메시지에는 허가 없는 열람으로부터 보호해야 할 민감하거나 개인적인 정보가 들어있지 않아요. 단지 메시지가 기술적 성격을 띠어서 일반 사용자에게 유용하거나 의미 있지 않을 뿐이에요. 에러 로거에서 나오는 메시지는 데이터베이스 전문가를 위한 것이에요. 그에 맞게 처리하세요.

2. 인터페이스 상세

sqlite3_config(SQLITE_CONFIG_LOG,...) 인터페이스의 세 번째 인자(위 예의 "pData" 인자)는 임의 데이터를 가리키는 포인터예요. SQLite 는 이 포인터를 그대로 에러 로거 콜백의 첫 번째 인자로 전달해요. 이 포인터는 원하면 애플리케이션별 설정이나 상태 정보를 전달하는 데 사용할 수 있어요. 아니면 콜백이 무시하는 NULL 포인터여도 돼요.

에러 로거 콜백의 두 번째 인자는 정수 확장 에러 코드(extended error code)예요. 세 번째 인자는 에러 메시지의 텍스트예요. 에러 메시지 텍스트는 호출 함수의 고정 길이 스택 버퍼에 저장되므로, 에러 로거 콜백 함수가 실행되는 동안에만 유효해요. 메시지를 보관해야 한다면 에러 로거는 이 메시지를 영구 저장소에 복사해 두어야 해요.

에러 로거 콜백은 시그널 핸들러처럼 취급해야 해요. 애플리케이션은 에러를 저장해 두거나 처리한 다음 가능한 한 빨리 반환해야 해요. 에러 로거에서 직접 또는 간접적으로 다른 SQLite API 를 호출하면 안 돼요. SQLite 는 에러 로거 콜백을 통해 재진입(reentrant)하지 않아요. 특히 메모리 할당이 실패할 때 에러 로거 콜백이 호출되므로, 에러 로거 안에서 메모리를 할당하려는 것은 일반적으로 나쁜 생각이에요. 에러 메시지를 다른 SQLite 데이터베이스에 저장하려는 시도는 아예 하지 않는 게 좋아요.

애플리케이션은 원하면 sqlite3_log(E,F,..) API 를 사용해 로그에 새 메시지를 보낼 수 있지만, 권장되지는 않아요. sqlite3_log() 인터페이스는 애플리케이션이 아니라 확장 기능만을 위한 것이에요.

3. 다양한 에러 메시지

에러 로거로 보낼 수 있는 에러 메시지와 정확한 형식은 릴리스마다 바뀔 수 있어요. 따라서 애플리케이션은 특정 에러 메시지 텍스트 형식이나 에러 코드에 의존하면 안 돼요. 변경이 변덕스럽게 일어나지는 않지만, 때로는 바뀔 수 있어요.

다음은 에러 로거 콜백에 나타날 수 있는 메시지 종류의 일부 목록이에요:

  • SQL 문을 컴파일할 때(sqlite3_prepare_v2() 와 그 형제 함수 사용)나 실행할 때(sqlite3_step() 사용) 에러가 있을 때마다 그 에러가 기록돼요.

  • 준비된 문장을 다시 파싱하고 다시 준비해야 하는 스키마 변경이 발생하면, 그 이벤트는 SQLITE_SCHEMA 에러 코드로 기록돼요. 재파싱과 재준비는 보통 자동으로 이루어지고(원래 sqlite3_prepare_v2() 로 문장을 준비했다고 가정하며, 권장됨), 따라서 이런 로깅 이벤트가 보통 재준비가 일어나고 있다는 것을 알 수 있는 유일한 방법이에요.

  • 이전 작성자가 트랜잭션을 완료하지 않고 크래시했기 때문에 데이터베이스를 복구해야 할 때마다 SQLITE_NOTICE 메시지가 기록돼요. 롤백 저널(rollback journal)을 복구할 때는 SQLITE_NOTICE_RECOVER_ROLLBACK, write-ahead log(WAL)를 복구할 때는 SQLITE_NOTICE_RECOVER_WAL 에러 코드가 사용돼요.

  • 데이터베이스 파일이 데이터베이스 손상으로 이어질 수 있는 방식으로 이름이 바뀌거나 별칭(alias)이 지정되면 SQLITE_WARNING 메시지가 기록돼요. (추가 정보는 참고 링크를 확인하세요.)

  • 메모리 부족(OOM) 에러 조건은 SQLITE_NOMEM 에러 코드와, 실패한 할당이 요청한 메모리 바이트 수를 알려주는 메시지로 에러 로깅 이벤트를 생성해요.

  • OS 인터페이스의 I/O 에러는 에러 로깅 이벤트를 생성해요. 이 이벤트의 메시지는 에러가 발생한 소스 코드의 줄 번호와, 해당 파일이 있을 때 그 이벤트와 연관된 파일명을 알려줘요.

  • 데이터베이스 손상이 감지되면 SQLITE_CORRUPT 에러 로거 콜백이 호출돼요. I/O 에러와 마찬가지로 에러 메시지 텍스트에는 에러가 처음 감지된 원본 소스 코드의 줄 번호가 포함돼요.

  • SQLITE_MISUSE 에러에 대해 에러 로거 콜백이 호출돼요. 이는 애플리케이션 코드에서 반환 코드를 일관되게 검사하지 않을 때 애플리케이션 설계 문제를 감지하는 데 유용해요.

SQLite 는 에러 로거 트래픽을 낮게 유지하려고 노력하며, 정말 문제가 있을 때만 에러 로거에 메시지를 보내요. 애플리케이션은 신경 쓰지 않는 특정 클래스의 에러 메시지를 의도적으로 무시함으로써 에러 메시지 트래픽을 더 줄일 수 있어요. 예를 들어 데이터베이스 스키마를 자주 변경하는 애플리케이션이라면 모든 SQLITE_SCHEMA 에러를 무시하고 싶을 수 있어요.

4. 요약 (Summary)

에러 로거 콜백을 사용하는 것을 강력히 권장해요. 에러 로거가 제공하는 디버깅 정보는 애플리케이션이 실제 운영 환경에 들어간 뒤 발생하는 난해한 문제를 추적하는 데 아주 유용하다는 것이 입증됐어요. 에러 로거 콜백은 API 반환 코드를 일관되게 검사하지 않아 애플리케이션이 놓치는 가끔 발생하는 에러를 잡는 데도 유용하다는 것이 입증됐어요. 개발자는 예상치 못한 동작을 빨리 발견하기 위해 개발 주기 초기에 에러 로거 콜백을 구현하고, 배포까지 켜둔 채로 유지하는 것이 좋아요. 에러 로거가 문제를 발견하지 못한다면 아무 해가 없어요. 하지만 적절한 에러 로거를 설정하지 않으면 나중에 진단 능력이 손상될 수 있어요.

더 알아보기 (Learn more)