Abseil LogSink 분석
#LogSink의 자리
LOG(INFO) << "..."이 stderr뿐 아니라 파일·syslog·원격 시스템으로 가게 하려면 LogSink를 등록한다.
#include "absl/log/log_sink.h"#include "absl/log/log_sink_registry.h"
class MySink : public absl::LogSink {public: void Send(const absl::LogEntry& entry) override { // 한 줄 로그가 들어올 때마다 호출 std::cerr << "[CUSTOM] " << entry.text_message() << "\n"; }};
MySink my_sink;absl::AddLogSink(&my_sink);// ... LOG 호출들 ...absl::RemoveLogSink(&my_sink);여러 sink가 동시에 등록 가능. 각 LOG 한 번에 모든 sink의 Send가 호출된다.
#기본 sink
Abseil은 다음을 기본 제공.
| Sink | 용도 |
|---|---|
| stderr sink | --stderrthreshold 이상 로그를 stderr로 |
| file sink (옵션) | --log_dir 지정 시 파일에 기록 |
stderr sink는 자동 활성. 파일 sink는 명시적으로 만들거나 flag로 활성 한다.
#LogSink 인터페이스
class LogSink {public: virtual ~LogSink() = default;
// 주된 메서드 — 매 로그 entry마다 호출 virtual void Send(const absl::LogEntry& entry) = 0;
// 선택적 — Send 후 flush 보장 필요 시 virtual void Flush() {}};Send는 동기적 으로 호출되며 호출자 스레드 안에서 실행된다. 비싼 작업(네트워크 전송)은 큐로 분리.
#비동기 큐 sink — 표준 패턴
network sink는 보통 다음과 같이 구성.
class AsyncSink : public absl::LogSink {public: void Send(const absl::LogEntry& entry) override { // 1. 동기적으로 가벼운 직렬화 Record r{ .severity = entry.log_severity(), .timestamp = entry.timestamp(), .message = std::string(entry.text_message()), }; // 2. 락 한 번 + queue push absl::MutexLock lock(&mu_); queue_.push_back(std::move(r)); cv_.SignalAll(); }
void Flush() override { absl::MutexLock lock(&mu_); while (!queue_.empty()) cv_.Wait(&mu_); }
private: void Worker() { // 별도 스레드에서 큐를 비우고 네트워크로 전송 }
absl::Mutex mu_; std::deque<Record> queue_ ABSL_GUARDED_BY(mu_); absl::CondVar cv_;};Send는 짧게 끝내고, 실제 네트워크 IO는 worker 스레드. LOG 호출이 IO 지연으로 막히지 않게 한다.
#등록 / 해제
absl::AddLogSink(&my_sink); // 등록absl::RemoveLogSink(&my_sink); // 해제등록·해제는 thread-safe. 다만 RemoveLogSink 호출 후 Send가 한 번 더 일어날 수 있다(RemoveLogSink는 이전 호출의 완료를 보장하지 않음). 안전한 종료는 다음 패턴.
absl::RemoveLogSink(&my_sink);my_sink.Flush(); // 진행 중 Send 완료 보장absl::FlushLogSinks(); // 전역 flush// 이제 my_sink 파괴 가능#ScopedMockLog — 테스트 캡처
테스트에서 특정 로그가 일어났는지 검증.
#include "absl/log/scoped_mock_log.h"
TEST(MyTest, LogsWarning) { absl::ScopedMockLog log; EXPECT_CALL(log, Log(absl::LogSeverity::kWarning, ::testing::_, ::testing::HasSubstr("slow"))); log.StartCapturingLogs();
DoSlowOperation(); // LOG(WARNING) << "slow ..." 발생 예상}ScopedMockLog는 destructor에서 자동 해제. gmock의 EXPECT_CALL 패턴과 호환.
#File sink — 직접 만들기
Abseil은 file sink를 기본 노출하지 않는다(설계상 사용자가 정책 결정). 간단한 직접 구현:
class FileSink : public absl::LogSink {public: explicit FileSink(absl::string_view path) : f_(std::fopen(std::string(path).c_str(), "a")) {} ~FileSink() { if (f_) std::fclose(f_); }
void Send(const absl::LogEntry& entry) override { absl::MutexLock lock(&mu_); if (!f_) return; std::string formatted = absl::StrCat( entry.text_message_with_prefix(), "\n"); std::fwrite(formatted.data(), 1, formatted.size(), f_); }
void Flush() override { absl::MutexLock lock(&mu_); if (f_) std::fflush(f_); }
private: absl::Mutex mu_; std::FILE* f_ ABSL_GUARDED_BY(mu_);};production에서는 rotation·crash-safety 등을 고려한 lib(예: spdlog) 위에 sink wrapper를 두는 게 보통.
#다중 destination 통합
StderrSink stderr_sink; // 콘솔FileSink file_sink("/var/log/svc.log");AsyncSink remote_sink(...); // Loki, Splunk 등
absl::AddLogSink(&stderr_sink);absl::AddLogSink(&file_sink);absl::AddLogSink(&remote_sink);각 sink는 모든 로그를 받는다. 특정 severity만 받고 싶으면 sink 안에서 분기.
void Send(const absl::LogEntry& entry) override { if (entry.log_severity() < absl::LogSeverity::kWarning) return; // ...}#회피 패턴
// 회피 — Send 안에서 비싼 IOvoid Send(const absl::LogEntry& entry) override { HttpPost("/log", absl::StrCat(...)); // ❌ LOG 호출자가 네트워크 대기}
// Good — 큐 + worker// 회피 — sink 안에서 다시 LOG 호출void Send(const absl::LogEntry& entry) override { LOG(ERROR) << "sink error: " << ...; // ❌ 재진입 — 데드락 가능}
// Good — std::cerr 또는 raw_loggingABSL_RAW_LOG(ERROR, "sink error: %s", ...);// 회피 — sink lifetime 관리 부주의{ MySink s; absl::AddLogSink(&s);} // ❌ s 소멸 후에도 등록 상태// → 다음 LOG에서 댕글링스택 변수를 sink로 쓸 때는 destructor에서 반드시 RemoveLogSink.
#정리
absl::LogSink를 상속해Send(const LogEntry&)만 구현하면 새 destination.AddLogSink/RemoveLogSink로 동적 등록·해제. 여러 sink 동시 가능.- 비싼 IO는 큐 + worker 패턴.
Send는 짧게. - sink 안에서 LOG 재호출 금지 — 데드락.
raw_logging사용. - 테스트에는
ScopedMockLog로 매처 기반 검증.
#다음 장 예고
Part 11-03: LogEntry / structured logging — 메타데이터 활용.
#관련 항목
Abseil Code Review · 61 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 Stack trace·failure_signal_handler
absl::Symbolize, GetStackTrace, InstallFailureSignalHandler — crash 시점에 stack을 찍어 남기는 진단 인프라.
같은 시리즈에서 이어 읽기
Abseil LogEntry·structured logging
absl::LogEntry — 한 로그 항목의 메타데이터(severity, timestamp, source location, message). 구조화 로깅의 기반.
같은 시리즈에서 이어 읽기
Abseil LOG·VLOG·CHECK 분석
Abseil logging 기본 매크로 — severity, verbose level, fatal check. glog의 후속이자 Google 표준.
같은 시리즈에서 이어 읽기