본문으로 건너뛰기
Abseil Code Review · 8/79

absl::LogSeverity — 로그 레벨 타입

· Hawk · 3분 읽기

한 줄 요약: absl::LogSeverity는 4단계 로그 심각도를 표현하는 enum이고, 로깅 시스템의 다른 모든 부분이 이 type 위에 서 있다. NormalizeLogSeverity는 외부 시스템의 정수 코드를 안전하게 변환한다.

#어떤 문제를 푸는가

로그 심각도는 “어디서나 같은 것”처럼 보이지만 실제로는 라이브러리·시스템마다 다르다. syslog는 0~7의 8단계, log4j는 6단계, Python logging은 5단계. Abseil은 가장 단순한 4단계로 정한다.

namespace absl {
enum class LogSeverity : int {
kInfo = 0,
kWarning = 1,
kError = 2,
kFatal = 3,
};
}

왜 4단계인가. Google의 경험상 더 많은 단계는 일관되게 쓰이지 않는다. DEBUG 같은 추가 단계는 VLOG라는 별도 메커니즘으로 분리된다.

#4단계의 의미

단계의미처리
kInfo일상적인 정보기록만, 알람 없음
kWarning잠재적 문제기록, 일부 시스템에서 알람
kError명확한 실패기록, 알람, 메트릭
kFatal회복 불가기록 후 abort()

kFatal이 특별하다. 로그를 남긴 직후 프로세스를 종료한다. LOG(FATAL) 이후의 코드는 실행되지 않는다.

LOG(INFO) << "Server started on port " << port;
LOG(WARNING) << "Retry attempt " << n;
LOG(ERROR) << "Failed to connect: " << s;
LOG(FATAL) << "Invariant violated"; // 이 후로 코드 실행 안 됨

#API

#enum과 utility 함수

#include "absl/base/log_severity.h"
absl::LogSeverity s = absl::LogSeverity::kError;
// 이름 얻기
const char* name = absl::LogSeverityName(s); // "ERROR"
// 외부 정수를 안전하게 변환
int external_code = 2;
absl::LogSeverity normalized = absl::NormalizeLogSeverity(external_code);
// out-of-range는 가장 가까운 valid 값으로 clamp

#LogSeverityAtLeast

특정 임계값 이상의 심각도만 통과시키는 enum.

enum class LogSeverityAtLeast : int {
kInfo = static_cast<int>(LogSeverity::kInfo),
kWarning = static_cast<int>(LogSeverity::kWarning),
kError = static_cast<int>(LogSeverity::kError),
kFatal = static_cast<int>(LogSeverity::kFatal),
kInfinity = 1000,
};

kInfinity가 추가되어 있어 “어떤 로그도 통과시키지 않음”을 표현할 수 있다.

// FLAGS_minloglevel 같은 설정
absl::SetMinLogLevel(absl::LogSeverityAtLeast::kWarning);
// 이제 INFO 로그는 무시됨

#LogSeverityAtMost

반대 방향.

enum class LogSeverityAtMost : int {
kNegativeInfinity = -1000,
kInfo = static_cast<int>(LogSeverity::kInfo),
kWarning = static_cast<int>(LogSeverity::kWarning),
kError = static_cast<int>(LogSeverity::kError),
kFatal = static_cast<int>(LogSeverity::kFatal),
};

특정 임계값 이하만 처리. testing에서 유용. “FATAL은 죽이지 말고 WARNING으로 다뤄라” 같은 패턴.

#NormalizeLogSeverity의 역할

외부 시스템과 통합할 때 정수 값을 받게 된다.

// 외부 protobuf에서 받은 enum 정수
int proto_severity = proto.log_severity(); // 0, 1, 2, 3 또는 invalid
// 직접 cast하면 위험
absl::LogSeverity bad = static_cast<absl::LogSeverity>(proto_severity);
// proto_severity가 99라면? — UB 가능성
// Good
absl::LogSeverity good = absl::NormalizeLogSeverity(proto_severity);
// 0~3 범위 밖이면 kInfo(아래) 또는 kFatal(위)로 clamp

구현은 단순하다.

// 의사 코드
constexpr LogSeverity NormalizeLogSeverity(int s) {
if (s < static_cast<int>(LogSeverity::kInfo)) return LogSeverity::kInfo;
if (s > static_cast<int>(LogSeverity::kFatal)) return LogSeverity::kFatal;
return static_cast<LogSeverity>(s);
}

오버로드도 있다.

constexpr LogSeverity NormalizeLogSeverity(LogSeverity s); // identity
constexpr LogSeverity NormalizeLogSeverity(int s); // clamp

#LogSeverity::kInfo인가 — naming

Abseil은 enum class를 쓰면서 멤버 이름에 k 접두사를 붙인다. Google C++ Style Guide의 enum 멤버 명명 규칙.

// Abseil 스타일
enum class LogSeverity {
kInfo,
kWarning,
kError,
kFatal,
};
// 다른 라이브러리에서 흔히 보는 스타일
enum class Severity {
INFO,
WARNING,
ERROR,
FATAL,
};

대문자 매크로(INFO, ERROR)와의 충돌을 피하기 위해서다. Windows.h가 ERROR를 매크로로 정의해 두기 때문에 그냥 Severity::ERROR는 일부 환경에서 깨진다. k 접두사가 이 충돌을 우회한다.

호환을 위해 별칭이 제공된다.

// LogSeverity::kInfo도 가능, INFO 매크로 호환도 LOG(INFO)에서 처리
LOG(INFO) << "..."; // 내부적으로 absl::LogSeverity::kInfo로 변환

#외부 시스템과의 매핑

대표적인 매핑 예시.

#syslog와의 매핑

int ToSyslog(absl::LogSeverity s) {
switch (s) {
case absl::LogSeverity::kInfo: return 6; // LOG_INFO
case absl::LogSeverity::kWarning: return 4; // LOG_WARNING
case absl::LogSeverity::kError: return 3; // LOG_ERR
case absl::LogSeverity::kFatal: return 2; // LOG_CRIT
}
}

#glog와의 매핑 (호환)

Abseil 로깅이 glog의 후속이므로 glog 사용자는 거의 그대로 마이그레이션 가능하다.

// glog
google::INFO // == 0
google::WARNING // == 1
google::ERROR // == 2
google::FATAL // == 3
// Abseil — 같은 정수 값
absl::LogSeverity::kInfo // 0
absl::LogSeverity::kWarning // 1
absl::LogSeverity::kError // 2
absl::LogSeverity::kFatal // 3

#Python logging과의 매핑

int ToPython(absl::LogSeverity s) {
switch (s) {
case absl::LogSeverity::kInfo: return 20; // INFO
case absl::LogSeverity::kWarning: return 30; // WARNING
case absl::LogSeverity::kError: return 40; // ERROR
case absl::LogSeverity::kFatal: return 50; // CRITICAL
}
}

DEBUG(10) 단계는 별도로 처리한다. Abseil은 VLOG(level)로 매핑한다.

#코드 리뷰 포인트

// 회피 — 외부 정수를 직접 cast
auto s = static_cast<absl::LogSeverity>(user_input);
// Good — Normalize
auto s = absl::NormalizeLogSeverity(user_input);
// 회피 — LogSeverity를 int로 비교
if (static_cast<int>(severity) >= 2) {
// 매직 넘버. 의도 불명확.
}
// Good
if (severity >= absl::LogSeverity::kError) {
// enum class의 operator>= 사용
}
// 회피 — switch에 default를 두고 새 case 추가를 잊는 코드
switch (s) {
case absl::LogSeverity::kInfo: return Handle(s);
default: return HandleDefault();
}
// LogSeverity가 5단계가 되면? — default가 silent하게 잡아먹음
// Good — 모든 case 명시, default 없음
switch (s) {
case absl::LogSeverity::kInfo: return HandleInfo();
case absl::LogSeverity::kWarning: return HandleWarning();
case absl::LogSeverity::kError: return HandleError();
case absl::LogSeverity::kFatal: return HandleFatal();
}
// 컴파일러가 -Wswitch로 누락된 case 경고

#자주 보는 안티패턴

// 회피 — LOG(FATAL) 이후의 코드
LOG(FATAL) << "Bad state";
DoCleanup(); // 실행 안 됨
return 0; // 도달 불가능
// Good — FATAL이 abort한다는 걸 명시
LOG(FATAL) << "Bad state"; // [[noreturn]] 효과
// 함수 끝
// 회피 — LOG(FATAL)을 control flow에 의존
if (something_bad) {
LOG(FATAL) << "...";
} else {
DoNormal();
}
return Result();
// FATAL은 unrecoverable. 정상 종료 경로에 두지 말 것.
// CHECK 매크로가 더 적합할 때가 많다.
// 회피 — LogSeverity를 enum이 아니라 string으로
std::string severity = "ERROR";
if (severity == "ERROR") { ... }
// 오타에 취약. enum class를 쓰자.

#정리

  • absl::LogSeverity는 4단계 enum class. INFO/WARNING/ERROR/FATAL.
  • kFatal은 abort를 동반. 그 뒤의 코드는 실행되지 않음.
  • 외부 정수는 NormalizeLogSeverity로 clamp.
  • enum 멤버에 k 접두사. Windows.h의 ERROR 매크로 충돌 회피.
  • glog와 ABI 호환되는 정수 값.

#다음 편

Part 2-04에서 Abseil의 type_traits를 본다. C++17에서 표준화된 negation, conjunction, void_t 같은 utility를 C++14에서도 쓰기 위한 polyfill이 무엇이고, 어떻게 구현되어 있는지.

#관련 항목

Abseil Code Review · 9 of 79

  1. 1 Abseil Code Review — Google production-grade C++ 라이브러리 분석
  2. 2 Abseil 개요 — Google이 std를 보완한 이유
  3. 3 Abseil 설계 철학 — std 호환과 추가 기능의 균형
  4. 4 Abseil 빌드와 의존성 — Bazel vs CMake
  5. 5 Abseil LTS vs HEAD 릴리스 모델 분석
  6. 6 Abseil Versioning과 ABI 호환성 정책
  7. 7 Abseil 매크로 — ABSL_HAVE_*·ABSL_ATTRIBUTE_*
  8. 8 Abseil ABSL_PREDICT_TRUE/FALSE — branch hint
  9. 9 absl::LogSeverity — 로그 레벨 타입
  10. 10 Abseil type_traits — negation·conjunction·void_t
  11. 11 Abseil Conformance·Policy 분석
  12. 12 Abseil Memory utilities 분석
  13. 13 Abseil raw_logging — heap-free 로깅
  14. 14 Abseil thread_annotations — clang TSA 통합
  15. 15 absl::Status — exception-free error handling
  16. 16 absl::StatusOr<T> — 값 또는 에러
  17. 17 absl status_macros — ASSIGN_OR_RETURN·RETURN_IF_ERROR
  18. 18 absl::Status payload — 구조화된 에러 컨텍스트
  19. 19 absl::Status ↔ exception 변환 패턴
  20. 20 absl::string_view — non-owning 문자열 참조
  21. 21 absl::string_view 함정 — dangling·c_str·임시 객체
  22. 22 absl::StrCat — 가변 인자 문자열 연결과 AlphaNum
  23. 23 absl::StrSplit — Delimiter·Predicate·컨테이너 변환
  24. 24 absl::StrJoin — 컨테이너 결합과 Formatter
  25. 25 absl::StrFormat — type-safe printf·FormatSpec
  26. 26 Abseil ASCII 함수 — locale-free 분류·대소문자 변환
  27. 27 Abseil Escape — CEscape·HexEscape·Base64
  28. 28 absl::flat_hash_map — Swiss Table 기반 hash map
  29. 29 absl::flat_hash_set — set 버전 Swiss Table
  30. 30 absl::node_hash_map — stable pointer가 필요할 때
  31. 31 absl::btree_map — sorted·cache-friendly B-tree
  32. 32 absl::FixedArray — 런타임 크기 stack 배열
  33. 33 absl::InlinedVector — small buffer optimization
  34. 34 Abseil Swiss Table internals — control byte·SIMD probing
  35. 35 absl::Mutex — reader-writer·fairness·deadlock 검출
  36. 36 absl::Mutex Conditional Critical Section — Await로 cv 없애기
  37. 37 absl::Notification — once-only signal
  38. 38 absl::BlockingCounter·Barrier — 다중 thread 조율
  39. 39 absl::Mutex annotations — clang thread-safety로 race를 컴파일 타임에
  40. 40 absl::Time·Duration 분석 — 단단한 type
  41. 41 absl::Time Format·Parse
  42. 42 absl::CivilTime 분석
  43. 43 absl::time_zone 분석
  44. 44 absl::Time mocking — 테스트 친화 시간
  45. 45 absl::BitGen — 모던 난수 생성기
  46. 46 Abseil Random Distributions — Uniform·Exponential
  47. 47 Abseil Mocking Random — 테스트 결정성
  48. 48 Abseil Random Seeding·Entropy
  49. 49 absl::int128·uint128 분석
  50. 50 absl::bits — popcount·countl_zero
  51. 51 absl::optional vs std::optional
  52. 52 absl::variant 분석
  53. 53 absl::span 분석
  54. 54 absl::any 분석
  55. 55 absl::compare — three-way 비교
  56. 56 Abseil utility — apply·in_place
  57. 57 Abseil AbslHashValue 분석
  58. 58 Abseil HashState chaining
  59. 59 Abseil Custom hashable 구현
  60. 60 Abseil LOG·VLOG·CHECK 분석
  61. 61 Abseil LogSink 분석
  62. 62 Abseil LogEntry·structured logging
  63. 63 Abseil Stack trace·failure_signal_handler
  64. 64 ABSL_FLAG 정의 분석
  65. 65 Abseil ParseCommandLine 동작
  66. 66 Abseil Flag introspection·validation
  67. 67 Google 스타일의 Abseil 사용 패턴
  68. 68 Abseil 자주 보는 anti-pattern
  69. 69 std → absl 마이그레이션 전략
  70. 70 absl::Cleanup — 함수 종료 시 실행 보장
  71. 71 Abseil algorithm container 확장 — c_sort·c_find_if·c_count_if
  72. 72 absl::function_ref와 any_invocable — 함수 객체 전달의 두 축
  73. 73 absl::bind_front와 Overload — 함수 객체 보조 도구
  74. 74 absl::Cord — 분산 시스템용 대용량 문자열
  75. 75 absl::from_chars·SimpleAtoi — 빠른 숫자 변환
  76. 76 absl::Cord vs std::string — 선택 기준과 메모리 프로파일
  77. 77 absl::GetStackTrace와 Symbolize — crash 시 readable stack
  78. 78 absl::ComputeCrc32c — 하드웨어 가속 체크섬
  79. 79 absl::PeriodicSampler — 적응형 샘플링·jitter 회피