빠른 흐름
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
logger.Info(
"request completed",
"method", r.Method,
"path", r.URL.Path,
"status", http.StatusOK,
)운영 로그는 문자열을 남기는 일이 아니라 나중에 검색하고 집계할 수 있는 사건 기록으로 설계합니다.
기본 흐름
간단한 로그는 log로 충분할 수 있다
log.Println("server started")작은 스크립트나 로컬 도구는 표준 log 패키지만으로도 충분합니다. 다만 서버나 배치처럼 로그를 수집 시스템에서 읽어야 하면 key-value 기반 구조화 로그가 더 잘 맞습니다.
구조화 로그는 slog를 쓴다
logger.Info("user created", "user_id", user.ID, "role", user.Role)slog는 메시지와 속성을 분리합니다. "user_id"처럼 안정적인 key를 쓰면 로그 검색, 알림, 대시보드 구성이 쉬워집니다.
출력 형식은 handler가 정한다
textLogger := slog.New(slog.NewTextHandler(os.Stdout, nil))
jsonLogger := slog.New(slog.NewJSONHandler(os.Stdout, nil))로컬 개발에서는 text handler가 읽기 쉽고, 운영 수집 환경에서는 JSON handler가 기계 처리에 유리합니다.
운영 기준
context가 있는 로그는 InfoContext를 쓴다
logger.InfoContext(ctx, "db query completed", "duration_ms", duration.Milliseconds())요청 단위 로그는 context를 같이 넘길 수 있는 메서드를 쓰면 tracing, request scope, cancellation과 연결하기 쉽습니다.
logger는 의존성으로 넘긴다
type Server struct {
logger *slog.Logger
}전역 logger만 쓰면 테스트와 구성 변경이 어려워질 수 있습니다. 서버, repository, worker 같은 경계에 logger를 주입하면 출력 형식과 레벨을 환경에 맞춰 바꾸기 좋습니다.
선택 기준
| 상황 | 먼저 떠올릴 선택 |
|---|---|
| 작은 CLI나 임시 도구 | log.Println |
| 서버 운영 로그 | log/slog |
| 사람이 직접 읽는 로컬 로그 | text handler |
| 수집 시스템으로 보내는 로그 | JSON handler |
| 요청 단위 로그 | InfoContext/ErrorContext |
| 공통 필드 추가 | logger.With(...) |
주의할 점
로그 key는 나중에 검색 기준이 되므로 자주 바꾸지 않는 편이 좋습니다. 비밀번호, 토큰, 개인정보 같은 민감 정보는 로그에 남기지 않습니다. 또한 오류 객체를 문자열로만 흘려보내기보다 실패 위치, 대상 id, duration 같은 운영 단서를 안정적인 key로 같이 남기는 편이 추적에 도움이 됩니다.
참고 링크
2 sources