Abseil raw_logging — heap-free 로깅
한 줄 요약:
ABSL_RAW_LOG는 heap·exception·mutex 없이 동작하는 로깅이다. signal handler, OOM 경로, ASan early init 같은 “일반 로깅이 깨질 수 있는” 상황을 위해 만들어졌다.
#어떤 문제를 푸는가
일반 로깅 시스템은 다음에 의존한다.
- heap 할당 — 메시지 buffer, sink 관리
- mutex — 멀티스레드 동기화
- exception — 일부 실패 경로
- static initialization — sink 등록
대부분의 시간엔 문제없다. 그러나 몇 가지 경로에서는 이 의존성이 깨진다.
| 시나리오 | 깨지는 것 |
|---|---|
| signal handler 안 | mutex (async-signal-safety 위반) |
| OOM 상황 | heap |
| static 초기화 중 | 다른 static에 대한 의존 |
| ASan/MSan 초기화 중 | 모든 라이브러리 |
| 어셈블리·아주 낮은 레벨 코드 | 모두 |
이런 곳에서 안전한 로깅이 ABSL_RAW_LOG다.
#API
#include "absl/base/internal/raw_logging.h"
ABSL_RAW_LOG(INFO, "message");ABSL_RAW_LOG(ERROR, "failed: %d", error_code);ABSL_RAW_LOG(FATAL, "fatal: %s", msg);
ABSL_RAW_CHECK(condition, "assertion message");C-style format string을 쓴다. C++ stream-style이 아닌 이유는 stream 객체가 heap 할당을 할 수 있기 때문이다.
#내부 구현
raw_logging은 다음 함수에만 의존한다.
// 의사 코드namespace absl::raw_log_internal {
void RawLog(LogSeverity severity, const char* file, int line, const char* format, ...);
void RawLogDoLog(LogSeverity severity, const char* file, int line, const char* format, va_list args) { char buffer[kLogBufSize]; // stack에 fixed-size buffer int n = vsnprintf(buffer, kLogBufSize, format, args);
// 단순 write() syscall — heap, exception, mutex 없음 write(STDERR_FILENO, buffer, std::min(n, kLogBufSize - 1)); write(STDERR_FILENO, "\n", 1);
if (severity == LogSeverity::kFatal) { std::abort(); }}
} // namespace absl::raw_log_internal세 가지 핵심 결정.
- stack buffer — heap 할당 없음. 메시지 크기 제한 (대략 1KB).
- write() syscall 직접 호출 — fprintf 같은 buffered I/O 안 씀.
- mutex 없음 — 다른 스레드의 raw_log와 메시지가 섞일 수 있음. 정확성보다 안전성 우선.
#사용 시나리오
#Signal handler 안
void SegvHandler(int sig) { // signal handler는 async-signal-safe 함수만 호출 가능 // 일반 LOG는 mutex 잡으므로 unsafe ABSL_RAW_LOG(ERROR, "SIGSEGV at pid %d", getpid()); abort();}
signal(SIGSEGV, SegvHandler);#OOM 경로
void* AllocateOrDie(size_t n) { void* p = malloc(n); if (ABSL_PREDICT_FALSE(!p)) { // 일반 LOG가 heap을 더 쓸 수 있다. RAW_LOG가 안전. ABSL_RAW_LOG(FATAL, "OOM: failed to allocate %zu bytes", n); } return p;}#Static 초기화
namespace {// global static 객체의 ctorstruct InitOnStartup { InitOnStartup() { // 다른 static 객체의 초기화 순서 보장 안 됨 // 일반 LOG는 자기 초기화에 의존하므로 깨질 수 있음 ABSL_RAW_LOG(INFO, "Module X initializing"); }} init_x;}#ASan early init
ASan 같은 sanitizer는 자신의 초기화 과정에서 메모리 할당을 후크한다. 사용자 코드의 일반 로깅이 그 시점에 동작하지 않을 수 있다. ASan 자체가 RAW_LOG에 의존하는 사례가 있다.
#ABSL_RAW_CHECK
assertion 매크로의 RAW 버전.
ABSL_RAW_CHECK(ptr != nullptr, "ptr must not be null");// 조건이 false면 RAW_LOG(FATAL) + abort()CHECK 매크로가 일반 로깅에 의존하는 반면, RAW_CHECK는 위 경로 어디서나 안전하다.
#의도적으로 빠진 기능
raw_logging이 일반 로깅보다 단순한 만큼 못 하는 것이 많다.
- sink 미지원 — 항상 stderr로만.
- structured log 미지원 — key-value 페어 없음.
- C++ stream syntax 미지원 —
<<연산자 없음. - filter 미지원 — severity 기반 컷오프만.
- rate limit 미지원 — 같은 메시지가 폭주할 수 있음.
이 모든 미지원은 의도된 것이다. 더 적은 의존성을 위한 트레이드오프.
#코드 리뷰 포인트
// 회피 — signal handler에서 일반 LOGvoid Handler(int sig) { LOG(ERROR) << "got signal " << sig; // unsafe abort();}
// Goodvoid Handler(int sig) { ABSL_RAW_LOG(ERROR, "got signal %d", sig); abort();}// 회피 — RAW_LOG를 일상적으로 사용void Process() { ABSL_RAW_LOG(INFO, "processing"); // sink, formatting 등 못 씀}
// Good — 일반 코드는 일반 LOGvoid Process() { LOG(INFO) << "processing";}// 회피 — RAW_LOG에 stream syntaxABSL_RAW_LOG(INFO, "value: " << x); // 컴파일 에러// RAW_LOG는 printf-style.
// GoodABSL_RAW_LOG(INFO, "value: %d", x);리뷰에서:
- signal handler 안에서 일반 LOG를 쓰는가 — async-signal-safety 검토.
- OOM/static-init 경로에서 일반 LOG를 쓰는가 — RAW_LOG로 옮길지.
- RAW_LOG를 unnecessary하게 쓰는가 — 일반 코드는 LOG이 적합.
#자주 보는 안티패턴
// 회피 — RAW_LOG에 std::stringstd::string msg = absl::StrCat("value: ", x);ABSL_RAW_LOG(INFO, "%s", msg.c_str());// std::string 자체가 heap을 쓸 수 있다. RAW의 의도와 모순.
// Good — char bufferchar buf[64];snprintf(buf, sizeof(buf), "value: %d", x);ABSL_RAW_LOG(INFO, "%s", buf);// 또는 format string에 직접ABSL_RAW_LOG(INFO, "value: %d", x);// 회피 — RAW_LOG를 hot path에for (int i = 0; i < 1000000; ++i) { ABSL_RAW_LOG(INFO, "iter %d", i); // syscall * 1M회}// stderr buffered I/O이 아니므로 매우 느림.
// Good — 일반 LOG이거나 logging 자체를 제거// 회피 — large messagechar buf[10000];snprintf(buf, sizeof(buf), "...");ABSL_RAW_LOG(INFO, "%s", buf);// RAW_LOG의 내부 buffer는 보통 1KB. 잘림.
// Good — 짧은 핵심만 RAW_LOGABSL_RAW_LOG(ERROR, "OOM at module %s", module_name);#std와의 비교
표준 C++에는 비교할 만한 것이 없다.
std::cerr— buffered, mutex 잡힘.fprintf(stderr, ...)— buffer가 있을 수 있음. heap 미사용은 보장.write(STDERR_FILENO, ...)— RAW_LOG의 기반. format을 직접 처리해야 함.
ABSL_RAW_LOG는 write() syscall에 format string 처리만 얹은 가장 얇은 계층이다.
#다른 라이브러리의 유사 도구
- glog의 RAW_LOG — Abseil이 거의 그대로 이어받음.
- Folly의
folly::log_to_stderr_async_safe— async-signal-safe 보장. - Linux 커널의
printk— 비슷한 철학(분리된 ring buffer + 단순 write).
#정리
ABSL_RAW_LOG는 heap·mutex·exception 없이 동작하는 로깅.- signal handler, OOM, static init, ASan early init 같은 곳에서만 사용.
- C-style format string만. C++ stream syntax 없음.
- 일반 코드는 일반 LOG가 적합. RAW_LOG의 트레이드오프는 안전성을 위해 기능을 포기한 것.
#다음 편
Part 2-08에서 thread_annotations를 본다. clang의 thread safety analysis가 mutex 사용을 어떻게 컴파일 시점에 검사하는지, ABSL_GUARDED_BY 같은 매크로의 효과를 다룬다.
#관련 항목
Abseil Code Review · 13 of 79
- 1 Abseil Code Review — Google production-grade C++ 라이브러리 분석
- 2 Abseil 개요 — Google이 std를 보완한 이유
- 3 Abseil 설계 철학 — std 호환과 추가 기능의 균형
- 4 Abseil 빌드와 의존성 — Bazel vs CMake
- 5 Abseil LTS vs HEAD 릴리스 모델 분석
- 6 Abseil Versioning과 ABI 호환성 정책
- 7 Abseil 매크로 — ABSL_HAVE_*·ABSL_ATTRIBUTE_*
- 8 Abseil ABSL_PREDICT_TRUE/FALSE — branch hint
- 9 absl::LogSeverity — 로그 레벨 타입
- 10 Abseil type_traits — negation·conjunction·void_t
- 11 Abseil Conformance·Policy 분석
- 12 Abseil Memory utilities 분석
- 13 Abseil raw_logging — heap-free 로깅
- 14 Abseil thread_annotations — clang TSA 통합
- 15 absl::Status — exception-free error handling
- 16 absl::StatusOr<T> — 값 또는 에러
- 17 absl status_macros — ASSIGN_OR_RETURN·RETURN_IF_ERROR
- 18 absl::Status payload — 구조화된 에러 컨텍스트
- 19 absl::Status ↔ exception 변환 패턴
- 20 absl::string_view — non-owning 문자열 참조
- 21 absl::string_view 함정 — dangling·c_str·임시 객체
- 22 absl::StrCat — 가변 인자 문자열 연결과 AlphaNum
- 23 absl::StrSplit — Delimiter·Predicate·컨테이너 변환
- 24 absl::StrJoin — 컨테이너 결합과 Formatter
- 25 absl::StrFormat — type-safe printf·FormatSpec
- 26 Abseil ASCII 함수 — locale-free 분류·대소문자 변환
- 27 Abseil Escape — CEscape·HexEscape·Base64
- 28 absl::flat_hash_map — Swiss Table 기반 hash map
- 29 absl::flat_hash_set — set 버전 Swiss Table
- 30 absl::node_hash_map — stable pointer가 필요할 때
- 31 absl::btree_map — sorted·cache-friendly B-tree
- 32 absl::FixedArray — 런타임 크기 stack 배열
- 33 absl::InlinedVector — small buffer optimization
- 34 Abseil Swiss Table internals — control byte·SIMD probing
- 35 absl::Mutex — reader-writer·fairness·deadlock 검출
- 36 absl::Mutex Conditional Critical Section — Await로 cv 없애기
- 37 absl::Notification — once-only signal
- 38 absl::BlockingCounter·Barrier — 다중 thread 조율
- 39 absl::Mutex annotations — clang thread-safety로 race를 컴파일 타임에
- 40 absl::Time·Duration 분석 — 단단한 type
- 41 absl::Time Format·Parse
- 42 absl::CivilTime 분석
- 43 absl::time_zone 분석
- 44 absl::Time mocking — 테스트 친화 시간
- 45 absl::BitGen — 모던 난수 생성기
- 46 Abseil Random Distributions — Uniform·Exponential
- 47 Abseil Mocking Random — 테스트 결정성
- 48 Abseil Random Seeding·Entropy
- 49 absl::int128·uint128 분석
- 50 absl::bits — popcount·countl_zero
- 51 absl::optional vs std::optional
- 52 absl::variant 분석
- 53 absl::span 분석
- 54 absl::any 분석
- 55 absl::compare — three-way 비교
- 56 Abseil utility — apply·in_place
- 57 Abseil AbslHashValue 분석
- 58 Abseil HashState chaining
- 59 Abseil Custom hashable 구현
- 60 Abseil LOG·VLOG·CHECK 분석
- 61 Abseil LogSink 분석
- 62 Abseil LogEntry·structured logging
- 63 Abseil Stack trace·failure_signal_handler
- 64 ABSL_FLAG 정의 분석
- 65 Abseil ParseCommandLine 동작
- 66 Abseil Flag introspection·validation
- 67 Google 스타일의 Abseil 사용 패턴
- 68 Abseil 자주 보는 anti-pattern
- 69 std → absl 마이그레이션 전략
- 70 absl::Cleanup — 함수 종료 시 실행 보장
- 71 Abseil algorithm container 확장 — c_sort·c_find_if·c_count_if
- 72 absl::function_ref와 any_invocable — 함수 객체 전달의 두 축
- 73 absl::bind_front와 Overload — 함수 객체 보조 도구
- 74 absl::Cord — 분산 시스템용 대용량 문자열
- 75 absl::from_chars·SimpleAtoi — 빠른 숫자 변환
- 76 absl::Cord vs std::string — 선택 기준과 메모리 프로파일
- 77 absl::GetStackTrace와 Symbolize — crash 시 readable stack
- 78 absl::ComputeCrc32c — 하드웨어 가속 체크섬
- 79 absl::PeriodicSampler — 적응형 샘플링·jitter 회피
관련 글
Abseil Memory utilities 분석
Part 2-06: absl::memory — make_unique polyfill, allocator_traits, RawPtr, uninitialized helpers.
같은 시리즈에서 이어 읽기
Abseil Conformance·Policy 분석
Part 2-05: Abseil의 platform conformance 정책 — 지원 컴파일러, 표준 버전, deprecated_if_unavailable.
같은 시리즈에서 이어 읽기
absl::LogSeverity — 로그 레벨 타입
Part 2-03: absl::LogSeverity — INFO/WARNING/ERROR/FATAL 4단계, NormalizeLogSeverity, 외부 시스템 연동.
같은 시리즈에서 이어 읽기
이 글을 참조하는 글 (8)
- absl::GetStackTrace와 Symbolize — crash 시 readable stack — Abseil Code Review
- Abseil Stack trace·failure_signal_handler — Abseil Code Review
- Abseil LogSink 분석 — Abseil Code Review
- Abseil LOG·VLOG·CHECK 분석 — Abseil Code Review
- absl::Status — exception-free error handling — Abseil Code Review
- Abseil thread_annotations — clang TSA 통합 — Abseil Code Review
- Abseil Memory utilities 분석 — Abseil Code Review
- absl::LogSeverity — 로그 레벨 타입 — Abseil Code Review