구조화 로그
구조화 로그는 키 - 값 쌍 필드로 컨텍스트 정보를 기록하여 로그를 프로그램이 파싱, 검색, 분석할 수 있게 합니다. DD 는 타입 안전한 필드 생성자와 유연한 체인 호출 메커니즘을 제공합니다.
필드 생성자
DD 는 20 종의 타입 안전 필드 생성자를 제공합니다.
기본 타입
go
dd.InfoWith("사용자 등록",
dd.String("username", "alice"),
dd.Int("age", 25),
dd.Float64("score", 98.5),
dd.Bool("verified", true),
)시간 관련
go
dd.InfoWith("스케줄 작업 실행",
dd.Time("scheduled_at", time.Now()),
dd.Duration("elapsed", 150*time.Millisecond),
)정수 타입 계열
go
dd.InfoWith("패킷 처리",
dd.Int8("flags", 0x0F),
dd.Int32("seq", 1001),
dd.Int64("total_bytes", 1<<20),
dd.Uint16("port", 8080),
dd.Uint32("src_ip", 0xC0A80101),
)오류 처리
go
// 기본 key 는 "error"
dd.ErrorWith("쿼리 실패", dd.Err(err))
// 커스텀 key
dd.ErrorWith("데이터베이스 오류", dd.ErrWithKey("db_error", dbErr))
// 스택 정보 포함
dd.ErrorWith("치명적 오류", dd.ErrWithStack(err))임의 타입
go
// 임의 타입, fmt.Sprintf 로 포맷팅
dd.InfoWith("요청 페이로드", dd.Any("body", requestBody))경고 성능 알림
Any는 원시 타입 (int/string/bool/time 등) 에 추가 오버헤드가 없지만, struct/map/slice 등 복잡한 타입의 경우 필터와 포맷팅 단계에서 리플렉션이 필요하여 타입이 명확한 생성자보다 성능이 낮습니다. 고빈도 경로에서는 구체적인 타입을 우선 사용하세요.
체인 호출
Logger → Entry
go
// 사전 설정 필드를 가진 Entry 생성
reqLog := logger.WithFields(
dd.String("service", "api"),
dd.String("version", "1.0"),
)
// Entry 는 자동으로 사전 설정 필드를 포함
reqLog.Info("서비스 시작")
reqLog.Warn("메모리 사용량 높음")
reqLog.ErrorWith("요청 실패",
dd.String("path", "/api/users"),
dd.Err(err),
)Entry → Entry(다중 계층 중첩)
go
// 서비스 수준
svcLog := logger.WithFields(dd.String("service", "order"))
// 모듈 수준 (서비스 수준 필드 상속)
dbLog := svcLog.WithFields(dd.String("module", "database"))
// 작업 수준 (상위 모든 필드 상속)
queryLog := dbLog.WithFields(dd.String("operation", "query"))
queryLog.InfoWith("쿼리 완료",
dd.Int("rows", 42),
dd.Duration("elapsed", 10*time.Millisecond),
)
// 필드: service=order module=database operation=query rows=42 elapsed=10ms패키지 수준 함수 체인 호출
go
dd.WithFields(
dd.String("app", "myapp"),
dd.String("env", "production"),
).Info("애플리케이션 시작")필드 명명 규칙
DD 는 필드 명명 규칙을 구성하여 개발 단계에서 자동 검사를 지원합니다.
내장 규칙
go
// snake_case(권장, 가장 범용)
cfg := dd.StrictSnakeCaseConfig()
// camelCase
cfg := dd.StrictCamelCaseConfig()
// 제한 없음 (기본)
cfg := dd.DefaultFieldValidationConfig()구성에서 활성화
go
logger, err := dd.New(dd.Config{
FieldValidation: dd.StrictSnakeCaseConfig(),
})
if err != nil {
log.Fatal(err)
}
defer logger.Close()활성화 후, 규칙에 맞지 않는 필드명은 stderr에 오류 알림 (Strict 모드) 또는 경고 알림 (Warn 모드) 을 생성하며, 로그 행 자체는 영향을 받지 않습니다.
go
logger.InfoWith("테스트",
dd.String("UserName", "alice"), // PascalCase → stderr 오류 트리거 (로그는 여전히 기록됨)
dd.String("user_name", "alice"), // snake_case → 정상
)일반 패턴
HTTP 요청 로그
go
func loggingMiddleware(logger *dd.Logger) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
reqLog := logger.WithFields(
dd.String("method", r.Method),
dd.String("path", r.URL.Path),
dd.String("remote_addr", r.RemoteAddr),
dd.String("user_agent", r.UserAgent()),
)
next.ServeHTTP(w, r)
reqLog.InfoWith("요청 완료",
dd.Duration("elapsed", time.Since(start)),
)
})
}
}서비스 계층화 로그
go
type UserService struct {
log *dd.LoggerEntry
}
func NewUserService(logger *dd.Logger) *UserService {
return &UserService{
log: logger.WithFields(dd.String("component", "user_service")),
}
}
func (s *UserService) CreateUser(ctx context.Context, name string) error {
s.log.InfoWith("사용자 생성",
dd.String("name", name),
)
if err := s.validate(name); err != nil {
s.log.ErrorWith("사용자 생성 실패",
dd.String("name", name),
dd.Err(err),
)
return err
}
return nil
}조건부 로그 (불필요한 계산 회피)
go
// 방법 1: 먼저 레벨 확인
if logger.IsDebugEnabled() {
data := computeExpensiveDebugInfo()
logger.DebugWith("디버그 데이터", dd.Any("data", data))
}
// 방법 2: WithFields 의 지연 계산 특성 활용
reqLog := logger.WithFields(dd.String("request_id", reqID))
// WithFields 는 필드만 구성할 뿐 I/O 오버헤드는 없음
// Info/Error 등 메서드를 실제로 호출할 때만 로그를 쓰기출력 형식
텍스트 형식 (기본)
text
[2026-04-16T21:16:48+08:00 INFO] logger.go:1567 요청 완료 method=GET status=200 elapsed=150ms정보 caller 필드 설명
caller 필드는 호출 위치를 기록합니다. *Logger 메서드 (예: logger.InfoWith(...)) 로 호출하면 caller 는 라이브러리 내부 호출 프레임 (예: logger.go:1567) 으로 해석되고, 패키지 수준 함수 (예: dd.InfoWith) 로 호출하면 사용자 코드로 해석됩니다.
JSON 형식
go
logger, err := dd.New(dd.JSONConfig())
if err != nil {
log.Fatal(err)
}
defer logger.Close()
logger.InfoWith("요청 완료",
dd.String("method", "GET"),
dd.Int("status", 200),
)json
{"timestamp":"2026-04-16T21:16:48+08:00","level":"INFO","caller":"logger.go:1567","message":"요청 완료","fields":{"method":"GET","status":200}}다음 단계
- 파일 출력과 로테이션 -- 로그를 파일에 쓰기
- 민감 데이터 필터링 -- 민감 정보 자동 마스킹
- API 레퍼런스 - 필드 -- 모든 필드 생성자
- API 레퍼런스 - LoggerEntry -- Entry 의 완전한 메서드