로깅

로깅 (Logging) 개요

DuckDB는 쿼리 실행, 성능 메트릭, 시스템 이벤트 같은 것에 대해 자세한 정보를 제공하는 로깅 메커니즘을 구현하고 있어요.

출처: 공식문서

로깅 켜고 끄기

DuckDB의 로깅 메커니즘은 enable_logging이라는 특수 함수로 켜거나 끌 수 있어요. 로그는 duckdb_logs라는 특별한 뷰에 저장되는데, 일반 테이블처럼 쿼리할 수 있어요.

예시:

CALL enable_logging();
-- Run some queries...
SELECT * FROM duckdb_logs;

로깅을 끄려면:

CALL disable_logging();

현재 로그를 비우려면:

CALL truncate_duckdb_logs();

로그 레벨

DuckDB는 로그의 상세 정도(verbosity)를 제어하는 여러 로그 레벨을 지원해요:

  • ERROR: 오류 메시지만 기록
  • WARN: 경고와 오류를 기록
  • INFO: 일반 정보, 경고, 오류를 기록 (기본값)
  • DEBUG: 상세한 디버깅 정보를 기록
  • TRACE: 매우 상세한 추적(tracing) 정보를 기록

로그 레벨은 다음처럼 설정해요:

CALL enable_logging(level = 'debug');

로그 타입

DuckDB에서 로그 메시지는 연관된 로그 타입(log type)을 가질 수 있어요. 로그 타입은 두 가지 주요 기능을 제공해요:

  • 로그 메시지 생성을 세밀하게 제어
  • 구조적 로깅(structured logging) 지원

특정 타입의 메시지만 기록하려면:

CALL enable_logging('HTTP');

위 함수는 올바른 로그 레벨을 자동으로 설정하고 HTTP 타입을 enabled_log_types 설정에 추가해요. 이렇게 하면 HTTP 타입의 로그 메시지만 로그에 기록되는 게 보장돼요.

여러 로그 타입을 켜려면 이렇게 전달해요:

CALL enable_logging(['HTTP', 'QueryLog']);

HTTP 같은 일부 로그 타입은 연관된 메시지 스키마가 있어요. DuckDB가 메시지를 자동으로 파싱하게 하려면 duckdb_logs_parsed() 매크로를 사용해요. 예를 들면:

SELECT request.headers FROM duckdb_logs_parsed('HTTP');

각 구조적 로그 타입의 스키마를 보려면:

DESCRIBE FROM duckdb_logs_parsed('HTTP');

로그 타입 목록

DuckDB에서 사용 가능한 로그 타입의 (완전하지 않은) 목록이에요.

로그 타입 설명 구조적(Structured)
QueryLog DuckDB에서 실행되는 쿼리를 기록 No
FileSystem DuckDB 파일시스템과의 모든 상호작용 기록 Yes
HTTP DuckDB 내부 HTTP 클라이언트의 모든 HTTP 트래픽 기록 Yes
PhysicalOperator 쿼리 실행 중 물리 연산자가 방출하는 이벤트 기록 Yes
Metrics 쿼리 실행 중 수집된 프로파일링 메트릭 기록 Yes

구조적 로그 타입은 다음 스키마를 노출해요. 언제든 DESCRIBE FROM duckdb_logs_parsed(log_type)로 확인할 수 있어요:

로그 타입 스키마
FileSystem fs VARCHAR, path VARCHAR, op VARCHAR, bytes BIGINT, pos BIGINT
HTTP request STRUCT(type, url, start_time, duration_ms, headers MAP), response STRUCT(status, reason, headers MAP)
PhysicalOperator operator_type VARCHAR, parameters MAP(VARCHAR, VARCHAR), class VARCHAR, event VARCHAR, info MAP(VARCHAR, VARCHAR)
Metrics metric VARCHAR, value VARCHAR

로그 저장소 (Log Storage)

기본적으로 DuckDB는 인메모리 로그 저장소(memory)에 기록해요. DuckDB는 서로 다른 여러 종류의 로그 저장소를 지원해요. 현재 코어 DuckDB에 구현된 로그 저장소 타입은 다음과 같아요.

로그 저장소 설명
memory (기본값) 인메모리 버퍼에 기록
stdout 현재 프로세스의 stdout에 기록 (CSV 형식)
file CSV 파일(들)에 기록

참고로 duckdb_logs 뷰는 현재 활성화된 로그 저장소를 대상으로 자동으로 업데이트돼요. 즉 로그 저장소를 바꾸면 duckdb_logs 함수가 반환하는 내용에 영향을 줄 수 있어요.

CALL enable_logging(storage = 'stdout');
CALL enable_logging(storage = 'file', storage_config = {'path': 'path/to/store/logs'});

또는 동등한 축약형을 사용해요:

CALL enable_logging(storage_path = 'path/to/store/logs');

정규화(Normalized) vs 비정규화(Denormalized)

DuckDB의 로그 저장소는 두 가지 방식으로 기록할 수 있어요: 정규화(normalized) vs 비정규화(denormalized).

비정규화 로깅에서는 로그 컨텍스트 정보가 각 로그 항목에 직접 붙고, 정규화 로깅에서는 로그 항목이 컨텍스트 정보를 참조하는 context_ids와 함께 별도로 저장돼요.

로그 저장소 정규화(Normalized)
memory yes
file configurable
stdout no

파일 저장소의 경우 정규화와 비정규화 사이를 전환할 수 있어요. .csv로 끝나는 경로를 주면 정규화, .csv 없이 주면 비정규화예요. 파일 로깅에서는 일반적으로 비정규화가 권장돼요. 성능이 향상되고 로그 전체 크기도 줄어들기 때문이에요. file 로그 저장소의 정규화를 설정하려면:

-- normalized: `/tmp/duckdb_log_contexts.csv` 와 `/tmp/duckdb_log_entries.csv` 생성
CALL enable_logging(storage_path = '/tmp');
-- denormalized: `/tmp/logs.csv` 생성
CALL enable_logging(storage_path = '/tmp/logs.csv');

정규화와 비정규화의 차이는 보통 사용자에게는 duckdb_logs 함수를 통해 숨겨져 있어요. 이 함수는 정규화된 테이블을 자동으로 하나의 통합 결과로 조인해요. 예를 들어 위 두 설정 모두 FROM duckdb_logs;로 쿼리할 수 있고 동일한 결과를 만들어내요.

버퍼링

DuckDB의 로그 저장소는 로깅 성능을 최적화하는 버퍼링 메커니즘을 구현해요. 이 구현은 메시지 로깅과 저장소 쓰기 사이에 잠재적인 지연을 도입해요. 이 지연은 실제 메시지 작성 시점을 흐릿하게 만들 수 있는데, 특히 크래시(충돌) 디버깅 시 문제가 돼요. 크래시 직전에 생성된 메시지가 기록되지 않을 수 있기 때문이에요. 이 문제를 해결하기 위해 버퍼 크기를 다음과 같이 설정할 수 있어요:

CALL enable_logging(storage_config = {'buffer_size': 0});

또는 동등한 축약형:

CALL enable_logging(storage_buffer_size = 0);

기본 버퍼 크기는 로그 저장소마다 달라요:

로그 저장소 기본 버퍼 크기
memory STANDARD_VECTOR_SIZE (2048)
file STANDARD_VECTOR_SIZE (2048)
stdout 비활성(Disabled) (0)

예를 들어 stdout 로깅 성능을 높이고 싶다면 버퍼링을 켜기만 하면 로깅이 크게 (>10x) 빨라져요:

CALL enable_logging(storage = 'stdout', storage_buffer_size = 2048);

또는 DuckDB의 크래시를 디버깅하면서 file 로거로 무슨 일이 일어나는지 이해하고 싶다고 가정해볼게요. 버퍼링을 꺼서 해결할 수 있어요:

CALL enable_logging(storage_path = '/tmp/mylogs', storage_buffer_size = 0);

문법 설탕 (Syntactic Sugar)

DuckDB에는 흔한 경로를 더 쉽게 만들어주는 문법 설탕이 있어요. 예를 들어 다음 문들은 모두 같아요:

-- 일반적인 호출 
CALL enable_logging(storage = 'file', storage_config = {'path': 'path/to/store/logs'});
-- 흔한 저장소 경로 config 파라미터의 축약형
CALL enable_logging(storage = 'file', storage_path = 'path/to/store/logs');
-- `storage = 'file'` 생략 -> `storage_config` 존재에서 추론됨
CALL enable_logging(storage_config = {'path': 'path/to/store/logs'});

더 알아보기 (Learn more)