Skip to content

구조화 필드 ​

DD 는 20 종의 타입 안전 필드 생성자, 통일된 Field 타입, 선택적 필드 키 검증 메커니즘을 제공하여 구조화 로그 출력에 사용합니다.

Field 타입 ​

Field는 구조화 로그 필드 타입으로, internal.Field의 타입 별칭으로 외부에 노출됩니다.

go
type Field = internal.Field

// 실제 구조 (internal/fields.go)
type Field struct {
    Key   string  // 필드 키
    Value any     // 필드 값 (임의 타입)
}

모든 필드 생성자는 Field 값을 반환하며, 포맷터 (internal.FormatFields) 가 Key=Value 형식으로 출력합니다. 기본 타입 (string / 수치 / bool / time.Duration / time.Time / nil) 은 빠른 경로를 사용하고, 슬라이스, 배열, map, struct 등 '복잡한 타입'은 JSON 직렬화로 폴백 (internal.IsComplexValue 판정) 하며, 그 외 타입 (fmt.Stringer 또는 error 인터페이스를 구현한 값 등) 은 fmt.Fprint를 사용합니다.

기본 필드 ​

생성자시그니처설명
Any(key string, value any) Field임의 타입
String(key, value string) Field문자열
Bool(key string, value bool) Field불리언
Err(err error) Field오류 (key 고정 "error"; err == nil이면 Value 가 nil, 그렇지 않으면 err.Error())
ErrWithKey(key string, err error) Field커스텀 key 의 오류 (Err과 동일, err == nil이면 Value 가 nil)
ErrWithStack(err error) Field호출 스택을 포함한 오류 (key 가 "error", err == nil이면 Value 가 nil; 스택 프레임은 runtime/과 dd 패키지 내부 프레임을 필터링, 캡처 시 약간의 오버헤드 있음)

수치 필드 ​

생성자타입예
Intintdd.Int("count", 42)
Int8int8dd.Int8("flags", 1)
Int16int16dd.Int16("port", 8080)
Int32int32dd.Int32("code", 200)
Int64int64dd.Int64("id", 123456789)
Uintuintdd.Uint("size", 1024)
Uint8uint8dd.Uint8("level", 3)
Uint16uint16dd.Uint16("year", 2026)
Uint32uint32dd.Uint32("seq", 1000)
Uint64uint64dd.Uint64("hash", 0xABCD)
Float32float32dd.Float32("rate", 0.95)
Float64float64dd.Float64("elapsed", 1.234)

시간 필드 ​

생성자시그니처설명
Time(key string, value time.Time) Field타임스탬프 (RFC3339 형식으로 포맷팅)
Duration(key string, value time.Duration) Field기간 (Duration.String() 호출)

오류 필드 ​

go
// 표준 오류 필드 (key 는 "error"로 고정, nil error → Value 가 nil)
dd.Err(err)

// 커스텀 key
dd.ErrWithKey("db_error", err)

// 스택 정보 포함 (스택 프레임은 runtime/과 dd 자체 프레임 필터링)
dd.ErrWithStack(err)

사용 방식 ​

InfoWith 와 조합 ​

go
dd.InfoWith("사용자 로그인",
    dd.String("username", "admin"),
    dd.Time("login_at", time.Now()),
    dd.Bool("mfa", true),
    dd.String("ip", "192.168.1.1"),
)

WithFields 와 체인 호출 ​

go
entry := logger.WithFields(
    dd.String("service", "api"),
    dd.Int("pid", os.Getpid()),
)
entry.Info("서비스 시작")

Entry 에 추가 ​

go
base := logger.WithFields(dd.String("req_id", id))
base.InfoWith("응답",
    dd.Int("status", 200),
    dd.Duration("elapsed", took),
    dd.Err(err),
)

필드 검증 ​

DD 는 필드 키 검증 메커니즘을 제공하여 명명 규칙 검사와 보안 검증 (Log4Shell 주입, 동형자 공격, overlong UTF-8) 을 지원합니다. 검증 구성 FieldValidationConfig는 Config.FieldValidation에 연결해 생성 시 적용하거나, 런타임에 Logger.SetFieldValidation으로 동적 교체할 수 있습니다. 매 *With 호출 시 각 필드의 Key 에 대해 ValidateFieldKey가 호출되며, Strict 모드에서 실패 시 로그 형식으로 오류가 보고됩니다 (로그 메서드 자체는 error 를 반환하지 않음).

FieldValidationMode ​

검증 모드, 검증 실패 시 처리 방식을 결정합니다.

go
type FieldValidationMode int

const (
    FieldValidationNone   FieldValidationMode = iota // 검증 비활성화 (기본, 모든 검사를 단락)
    FieldValidationWarn                              // 명명 불일치 시 warning 로그 기록
    FieldValidationStrict                            // 명명 불일치 시 error 로그 기록
)

FieldValidationMode의 String() 메서드는 "none" / "warn" / "strict"을 반환 (알 수 없는 값은 "unknown" 반환) 합니다.

FieldNamingConvention ​

명명 규칙.

go
type FieldNamingConvention int

const (
    NamingConventionAny         FieldNamingConvention = iota // 임의의 유효한 키 수락 (기본)
    NamingConventionSnakeCase                                // snake_case: user_id
    NamingConventionCamelCase                                // camelCase: userId
    NamingConventionPascalCase                               // PascalCase: UserId
    NamingConventionKebabCase                                // kebab-case: user-id
)

FieldNamingConvention의 String() 메서드는 "any" / "snake_case" / "camelCase" / "PascalCase" / "kebab-case"를 반환 (알 수 없는 값은 "unknown" 반환) 합니다.

FieldValidationConfig ​

필드 검증 구성.

go
type FieldValidationConfig struct {
    Mode                     FieldValidationMode    // 검증 모드
    Convention               FieldNamingConvention  // 명명 규칙
    AllowCommonAbbreviations bool                   // 일반 약어 허용 (ID, URL, HTTP, JSON 등)
    EnableSecurityValidation bool                   // 보안 검증 활성화 (Log4Shell / 동형자 / overlong UTF-8)
}

경고 제로값 함정

리터럴 FieldValidationConfig{}는 EnableSecurityValidation=false가 되어 보안 검증을 조용히 끕니다 - DefaultFieldValidationConfig 생성자를 우선 사용하세요 (이 항목을 true로 설정). 또한 Mode == FieldValidationNone이면 보안 검증 이전에 단락되어, EnableSecurityValidation을 켜도 실행되지 않습니다.

사전 설정 구성 ​

go
// 기본 구성: 명명 검증 비활성화, 보안 검증 활성화
func DefaultFieldValidationConfig() *FieldValidationConfig

// 엄격한 snake_case
func StrictSnakeCaseConfig() *FieldValidationConfig

// 엄격한 camelCase
func StrictCamelCaseConfig() *FieldValidationConfig

세 가지 사전 설정 모두 AllowCommonAbbreviations=true, EnableSecurityValidation=true이며, 뒤의 두 가지는 Mode=FieldValidationStrict입니다.

ValidateFieldKey ​

go
func (c *FieldValidationConfig) ValidateFieldKey(key string) error

필드 키가 구성에 일치하는지 검증합니다. 실패 시 원인을 설명하는 error 를 반환하며, 통과 시 nil을 반환합니다. 리시버가 nil이거나 Mode == FieldValidationNone이면 직접 nil을 반환합니다. 검증 순서:

  1. 빈 키 → "field key cannot be empty" 반환
  2. EnableSecurityValidation 활성화 시 internal.ValidateFieldKeyStrict 실행 (Log4Shell / 동형자 / overlong UTF-8)
  3. Convention == NamingConventionAny → 명명 검사 건너뜀
  4. AllowCommonAbbreviations 활성화이고 키가 일반 약어 표 (id/url/http/json/jwt 등, 또는 _id/_url/_uri/_ip/_api로 끝남) 에 해당 → 통과
  5. 규칙별 검증: snake_case / camelCase / PascalCase / kebab-case
go
package main

import (
    "fmt"

    "github.com/cybergodev/dd"
)

func main() {
    // 엄격한 snake_case 사전 설정
    cfg := dd.StrictSnakeCaseConfig()

    if err := cfg.ValidateFieldKey("user_id"); err != nil {
        fmt.Println("user_id:", err)
    } else {
        fmt.Println("user_id OK")
        // 출력: user_id OK
    }

    if err := cfg.ValidateFieldKey("userId"); err != nil {
        fmt.Println("userId:", err)
        // 출력: userId: field key "userId" does not match snake_case convention
    }

    // 일반 약어 면제: URL 은 snake_case 가 아니지만 약어 표에 해당하므로 통과
    if err := cfg.ValidateFieldKey("URL"); err != nil {
        fmt.Println("URL:", err)
    } else {
        fmt.Println("URL OK (약어 면제)")
        // 출력: URL OK (약어 면제)
    }

    // 기본 구성은 Mode=None 이며 명명을 검증하지 않음
    defaultCfg := dd.DefaultFieldValidationConfig()
    if err := defaultCfg.ValidateFieldKey("anyKey"); err != nil {
        fmt.Println("anyKey:", err)
    } else {
        fmt.Println("anyKey OK (Mode=None)")
        // 출력: anyKey OK (Mode=None)
    }
}

다음 단계 ​