Home Golang에서 klog 사용하기
Post
Cancel

Golang에서 klog 사용하기

들어가며

Golang에서 로깅을 구현할 때 여러 라이브러리를 사용할 수 있습니다. log 패키지부터 logrus, zap, zerolog 등 다양한 선택지가 있는데, Kubernetes 생태계에서 널리 사용되는 klog는 구조화된 로깅과 다양한 로그 레벨을 제공하는 강력한 도구입니다.

이 글에서는 Golang에서 klog를 사용하는 방법을 실무 예제와 함께 알아보겠습니다.

klog란?

klog는 Kubernetes 프로젝트에서 사용하는 구조화된 로깅 라이브러리입니다. Google의 glog 라이브러리를 기반으로 하며, 다음과 같은 특징을 가지고 있습니다:

  • 구조화된 로깅: 로그 레벨별로 체계적인 로깅 지원
  • 성능 최적화: 비동기 로깅으로 애플리케이션 성능에 미치는 영향 최소화
  • 유연한 설정: 플래그를 통한 런타임 로그 레벨 조정
  • 파일 출력: 로그를 파일로 저장하여 관리 용이

설치하기

klog를 사용하기 위해서는 먼저 패키지를 설치해야 합니다:

1
go get k8s.io/klog/v2

또는 Go modules를 사용하는 경우:

1
go mod tidy

기본 사용법

간단한 예제

가장 기본적인 사용법부터 살펴보겠습니다:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import (
    "flag"
    "k8s.io/klog/v2"
)

func main() {
    // 플래그 파싱 (klog가 내부적으로 플래그를 사용)
    klog.InitFlags(nil)
    flag.Parse()
    defer klog.Flush()

    klog.Info("애플리케이션이 시작되었습니다")
    klog.Infof("사용자 ID: %d", 12345)
    klog.Warning("경고 메시지입니다")
    klog.Error("에러가 발생했습니다")
}

로그 레벨

klog는 다음과 같은 로그 레벨을 제공합니다:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package main

import (
    "flag"
    "k8s.io/klog/v2"
)

func main() {
    klog.InitFlags(nil)
    flag.Parse()
    defer klog.Flush()

    // Info 레벨: 일반적인 정보성 로그
    klog.Info("정보 메시지")
    klog.Infof("포맷된 정보: %s", "값")

    // Warning 레벨: 경고 메시지
    klog.Warning("경고 메시지")
    klog.Warningf("포맷된 경고: %d", 42)

    // Error 레벨: 에러 메시지
    klog.Error("에러 메시지")
    klog.Errorf("포맷된 에러: %v", err)

    // Fatal 레벨: 치명적 에러 (프로그램 종료)
    klog.Fatal("치명적 에러 - 프로그램 종료")
    klog.Fatalf("포맷된 치명적 에러: %s", "메시지")

    // V 레벨: 상세 로그 (Verbose)
    klog.V(1).Info("상세 로그 레벨 1")
    klog.V(2).Info("상세 로그 레벨 2")
    klog.V(3).Infof("상세 로그 레벨 3: %s", "값")
}

로그 레벨 설정

klog는 명령줄 플래그를 통해 로그 레벨을 조정할 수 있습니다:

1
2
3
4
5
6
7
8
9
10
11
# 기본 실행
go run main.go

# 로그 레벨 설정
go run main.go -v=2

# 특정 모듈의 로그 레벨 설정
go run main.go -v=2 -vmodule=user=3

# 로그를 파일로 출력
go run main.go -logtostderr=false -log_dir=./logs

코드에서 로그 레벨 설정

프로그램 내에서도 로그 레벨을 설정할 수 있습니다:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main

import (
    "flag"
    "k8s.io/klog/v2"
)

func main() {
    klog.InitFlags(nil)
    
    // 로그 레벨을 코드에서 설정
    flag.Set("v", "2")
    flag.Set("logtostderr", "true")
    
    flag.Parse()
    defer klog.Flush()

    klog.V(1).Info("이 메시지는 v=1 이상에서만 출력됩니다")
    klog.V(2).Info("이 메시지는 v=2 이상에서만 출력됩니다")
}

구조화된 로깅

klog는 구조화된 로깅을 위한 InfoS, ErrorS 등의 함수를 제공합니다:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package main

import (
    "flag"
    "k8s.io/klog/v2"
)

func main() {
    klog.InitFlags(nil)
    flag.Parse()
    defer klog.Flush()

    // 키-값 쌍으로 구조화된 로깅
    klog.InfoS("사용자 로그인 성공",
        "userID", 12345,
        "username", "hyungsun",
        "ip", "192.168.1.1",
    )

    klog.ErrorS(nil, "데이터베이스 연결 실패",
        "host", "localhost",
        "port", 5432,
        "database", "mydb",
    )

    // 에러 객체와 함께 로깅
    err := someFunction()
    if err != nil {
        klog.ErrorS(err, "함수 실행 실패",
            "function", "someFunction",
            "retryCount", 3,
        )
    }
}

실무 예제

HTTP 서버에서 klog 사용하기

실제 웹 서버에서 klog를 사용하는 예제를 살펴보겠습니다:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
package main

import (
    "encoding/json"
    "flag"
    "net/http"
    "time"

    "k8s.io/klog/v2"
)

type User struct {
    ID   int    `json:"id"`
    Name string `json:"name"`
}

func main() {
    klog.InitFlags(nil)
    flag.Parse()
    defer klog.Flush()

    http.HandleFunc("/users", handleUsers)
    http.HandleFunc("/health", handleHealth)

    klog.InfoS("HTTP 서버 시작",
        "port", 8080,
        "env", "production",
    )

    if err := http.ListenAndServe(":8080", nil); err != nil {
        klog.Fatalf("서버 시작 실패: %v", err)
    }
}

func handleUsers(w http.ResponseWriter, r *http.Request) {
    start := time.Now()

    klog.V(2).InfoS("사용자 목록 요청",
        "method", r.Method,
        "path", r.URL.Path,
        "remoteAddr", r.RemoteAddr,
    )

    // 비즈니스 로직
    users := []User{
        {ID: 1, Name: "Alice"},
        {ID: 2, Name: "Bob"},
    }

    w.Header().Set("Content-Type", "application/json")
    if err := json.NewEncoder(w).Encode(users); err != nil {
        klog.ErrorS(err, "JSON 인코딩 실패",
            "path", r.URL.Path,
        )
        http.Error(w, "Internal Server Error", http.StatusInternalServerError)
        return
    }

    duration := time.Since(start)
    klog.V(1).InfoS("사용자 목록 응답 완료",
        "path", r.URL.Path,
        "status", http.StatusOK,
        "duration", duration,
    )
}

func handleHealth(w http.ResponseWriter, r *http.Request) {
    klog.V(3).Info("헬스 체크 요청")
    w.WriteHeader(http.StatusOK)
    w.Write([]byte("OK"))
}

데이터베이스 작업 로깅

데이터베이스 작업을 로깅하는 예제:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
package main

import (
    "database/sql"
    "flag"
    "time"

    _ "github.com/lib/pq"
    "k8s.io/klog/v2"
)

type DBService struct {
    db *sql.DB
}

func (s *DBService) GetUser(id int) (*User, error) {
    start := time.Now()
    
    klog.V(2).InfoS("사용자 조회 시작",
        "userID", id,
    )

    var user User
    query := "SELECT id, name FROM users WHERE id = $1"
    err := s.db.QueryRow(query, id).Scan(&user.ID, &user.Name)
    
    duration := time.Since(start)
    
    if err != nil {
        klog.ErrorS(err, "사용자 조회 실패",
            "userID", id,
            "query", query,
            "duration", duration,
        )
        return nil, err
    }

    klog.V(1).InfoS("사용자 조회 성공",
        "userID", id,
        "duration", duration,
    )

    return &user, nil
}

func (s *DBService) CreateUser(user *User) error {
    start := time.Now()
    
    klog.InfoS("사용자 생성 시작",
        "username", user.Name,
    )

    query := "INSERT INTO users (name) VALUES ($1) RETURNING id"
    err := s.db.QueryRow(query, user.Name).Scan(&user.ID)
    
    duration := time.Since(start)
    
    if err != nil {
        klog.ErrorS(err, "사용자 생성 실패",
            "username", user.Name,
            "query", query,
            "duration", duration,
        )
        return err
    }

    klog.InfoS("사용자 생성 성공",
        "userID", user.ID,
        "username", user.Name,
        "duration", duration,
    )

    return nil
}

로그 파일 관리

klog는 로그를 파일로 저장하고 로테이션하는 기능을 제공합니다:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main

import (
    "flag"
    "k8s.io/klog/v2"
)

func main() {
    klog.InitFlags(nil)
    
    // 로그를 파일로 저장
    flag.Set("logtostderr", "false")
    flag.Set("log_dir", "./logs")
    flag.Set("alsologtostderr", "true") // 파일과 stderr 모두에 출력
    
    flag.Parse()
    defer klog.Flush()

    klog.Info("이 로그는 파일과 stderr 모두에 기록됩니다")
}

실행 시 플래그로도 설정 가능합니다:

1
go run main.go -logtostderr=false -log_dir=./logs -alsologtostderr=true

주의사항

1. klog.Flush() 호출

프로그램 종료 전에 반드시 klog.Flush()를 호출해야 합니다. klog는 비동기로 로그를 처리하기 때문에, Flush를 호출하지 않으면 일부 로그가 손실될 수 있습니다:

1
2
3
4
5
6
7
func main() {
    klog.InitFlags(nil)
    flag.Parse()
    defer klog.Flush() // 반드시 호출!

    // ... 애플리케이션 로직 ...
}

2. 플래그 파싱 순서

klog는 내부적으로 flag 패키지를 사용하므로, 다른 플래그와 충돌할 수 있습니다. klog.InitFlags(nil)을 먼저 호출하고, 그 다음에 flag.Parse()를 호출해야 합니다:

1
2
3
4
5
6
7
8
9
func main() {
    // 올바른 순서
    klog.InitFlags(nil)
    flag.Parse()
    
    // 잘못된 순서 (플래그 충돌 가능)
    // flag.Parse()
    // klog.InitFlags(nil)
}

3. V 레벨 사용 시 주의

V 레벨은 조건부 로깅을 위한 것이므로, 비용이 많이 드는 연산을 피해야 합니다:

1
2
3
4
5
6
7
// 좋은 예: 문자열 포맷팅은 V 레벨 체크 후에만 실행
if klog.V(2).Enabled() {
    klog.V(2).Infof("복잡한 포맷팅: %s", expensiveStringFormat())
}

// 나쁜 예: 항상 expensiveStringFormat()이 실행됨
klog.V(2).Infof("복잡한 포맷팅: %s", expensiveStringFormat())

다른 로깅 라이브러리와 비교

klog vs logrus

특징kloglogrus
구조화된 로깅✅✅
성능높음중간
Kubernetes 통합✅❌
설정 복잡도낮음중간

klog vs zap

특징klogzap
성능높음매우 높음
구조화된 로깅✅✅
Kubernetes 통합✅❌
학습 곡선낮음중간

결론

klog는 Kubernetes 생태계에서 검증된 강력한 로깅 라이브러리입니다. 특히 Kubernetes 관련 프로젝트나 구조화된 로깅이 필요한 경우에 매우 유용합니다.

주요 장점

  • 간단한 API: 직관적이고 사용하기 쉬운 인터페이스
  • 성능: 비동기 로깅으로 애플리케이션 성능에 미치는 영향 최소화
  • 유연성: 런타임에 로그 레벨 조정 가능
  • Kubernetes 통합: Kubernetes 프로젝트와의 자연스러운 통합

사용 권장 사례

  • Kubernetes 관련 프로젝트
  • 구조화된 로깅이 필요한 애플리케이션
  • 로그 레벨을 런타임에 조정해야 하는 경우
  • 성능이 중요한 프로덕션 환경

klog를 활용하여 더 나은 로깅 시스템을 구축해보세요! 🚀

This post is licensed under CC BY 4.0 by the author.