Go 언어 로깅 완벽 가이드, 라이브러리 4가지 비교 분석

 

Go 언어 로깅 완벽 가이드, 라이브러리 4가지 비교 분석

 

소프트웨어 개발에서 '로깅'은 정말 중요한 부분이거든요.

애플리케이션이 어떻게 돌아가는지 속속들이 보여주고, 버그를 잡거나 모니터링할 때 결정적인 단서를 제공하는 아주 고마운 존재입니다.

Go(Golang) 언어에서는 기본으로 제공하는 표준 라이브러리부터 각기 다른 개성을 뽐내는 여러 서드파티 라이브러리까지, 선택의 폭이 꽤 넓은데요.

이번 글에서는 Go에서 로깅을 어떻게 구현하는지, 그리고 가장 인기 있는 라이브러리는 어떤 것들이 있는지 한번 제대로 파헤쳐 보겠습니다.

Go 표준 라이브러리 log 패키지 활용법

Go 언어에는 기본적으로 'log'라는 표준 라이브러리 패키지가 내장되어 있거든요.

가장 기본적인 로깅 기능을 아주 간단하게 쓸 수 있게 해주는 녀석입니다.

타임스탬프와 함께 로그 메시지를 출력할 수 있는데, 크게 세 가지 종류의 함수를 제공하는데요.

'Print'는 일반적인 정보를 기록하고, 'Panic'은 메시지를 기록한 뒤 패닉을 발생시키며, 'Fatal'은 로그를 남기고 os.Exit(1)을 호출해서 프로그램을 바로 종료시키는 역할을 합니다.

아래 간단한 예시를 보면 바로 이해가 되실 겁니다.

package main

import (
    "log"
)

func main() {
    log.Println("이것은 표준 로그 메시지입니다.")
    log.Printf("포맷팅된 문자열로 로깅하기: %d", 42)
}

기본적으로 log 패키지는 표준 에러(standard error)로 내용을 출력하는데요.

모든 로그 항목에는 날짜와 시간이 자동으로 포함됩니다.

물론 이 동작을 입맛에 맞게 바꿀 수도 있거든요.

다양한 플래그를 사용해 로거의 출력 형식을 얼마든지 제어할 수 있습니다.

'Ldate'는 날짜를, 'Ltime'은 시간을, 'Lmicroseconds'는 마이크로초 단위의 정밀한 시간을 추가해주는데요.

'Llongfile'은 전체 파일 경로와 줄 번호를, 'Lshortfile'은 파일명과 줄 번호만 간략하게 보여줍니다.

시간대를 현지 시간 대신 UTC로 맞추고 싶다면 'LUTC' 플래그를 사용하면 됩니다.

예를 들어 날짜, 시간, 그리고 짧은 파일명을 로그에 포함시키고 싶다면 이렇게 설정하면 되는데요.

아래 코드처럼 플래그를 설정해주면 간단하게 해결됩니다.

log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)

인기 있는 서드파티 로깅 라이브러리

표준 log 패키지도 기본적인 작업에는 충분하지만, 애플리케이션이 복잡해지면 좀 더 고급 기능이 필요해지거든요.

바로 '구조화된 로깅(structured logging)', '로그 레벨', 그리고 '더 뛰어난 성능' 같은 것들입니다.

다행히 이런 요구를 만족시켜주는 훌륭한 서드파티 라이브러리들이 많이 있는데요.

그중에서도 가장 대표적인 4가지를 소개해 드리겠습니다.

1. logrus

첫 번째 주자는 바로 'logrus'인데요.

Go를 위한 구조화된 로거로, 아주 유연한 API를 제공하고 로그 메시지에 원하는 필드를 자유롭게 추가할 수 있다는 점이 큰 장점입니다.

다양한 로그 레벨은 물론, 여러 로깅 시스템과 연동할 수 있는 '훅(hook)' 기능도 지원하거든요.

실제 사용 예시는 아래와 같습니다.

package main

import (
    log "github.com/sirupsen/logrus"
)

func main() {
    log.WithFields(log.Fields{
        "event": "event_name",
        "topic": "topic_name",
    }).Info("이벤트가 발생했습니다")
}

2. zap

두 번째는 우버(Uber)에서 개발한 'zap'인데요.

이 라이브러리의 가장 큰 특징은 바로 '압도적인 성능'과 '구조화된 로깅' 기능입니다.

개발 편의성을 높인 'sugared' 로거와, 성능에 모든 것을 집중한 타입-세이프(type-safe) 로거 두 가지를 모두 제공하거든요.

어떤 식으로 사용하는지 한번 살펴보겠습니다.

package main

import (
    "go.uber.org/zap"
)

func main() {
    logger, _ := zap.NewProduction()
    defer logger.Sync()
    logger.Info("이것은 정보성 메시지입니다",
        zap.String("key", "value"),
    )
}

3. zerolog

'zerolog'는 이름에서부터 강력한 포스가 느껴지는데요.

이 라이브러리는 '메모리 할당 없는(zero-allocation) JSON 로깅'을 목표로 만들어져 성능이 정말 어마어마합니다.

성능이 아주 중요한 고성능 애플리케이션을 만들고 있다면 최고의 선택지가 될 수 있거든요.

코드는 이런 느낌으로 작성할 수 있습니다.

package main

import (
    "github.com/rs/zerolog/log"
)

func main() {
    log.Info().
        Str("key", "value").
        Msg("이것은 정보성 메시지입니다")
}

4. slog

마지막으로 소개할 라이브러리는 Go 1.21 버전에 새롭게 표준 라이브러리로 추가된 'slog'인데요.

이제는 외부 의존성 없이도 간편하고 효율적으로 구조화된 로그를 처리할 수 있게 된 것입니다.

별도의 설치 없이 바로 사용할 수 있다는 점이 정말 매력적이거든요.

아래 예시처럼 간단하게 사용할 수 있습니다.

package main

import (
    "log/slog"
    "os"
)

func main() {
    logger := slog.New(slog.NewJSONHandler(os.Stdout))
    logger.Info("이것은 정보성 메시지입니다", "key", "value")
}

내게 맞는 로깅 라이브러리 선택하기

자, 그럼 이렇게 다양한 라이브러리 중에서 어떤 것을 선택해야 할까요?

Go 애플리케이션에 맞는 로깅 라이브러리를 고를 때는 몇 가지 고려해야 할 점들이 있는데요.

아래 세 가지 기준을 생각해보시면 선택에 큰 도움이 될 겁니다.

첫 번째 기준은 바로 '성능'인데요.

고성능이 필수인 애플리케이션이라면 메모리 할당이 적고 속도가 빠른 zap이나 zerolog가 정답에 가깝습니다.

두 번째는 '구조화된 로깅' 지원 여부거든요.

로그를 JSON처럼 체계적으로 관리하고 싶다면 logrus, zap, zerolog, 그리고 표준 라이브러리의 slog까지 모두 훌륭한 선택지입니다.

마지막으로 '커뮤니티와 유지보수' 역시 빼놓을 수 없는 중요한 요소인데요.

지속적인 지원과 업데이트를 받으려면, 활발하게 관리되는 라이브러리를 선택하는 것이 장기적으로 훨씬 현명한 판단입니다.

 

결론

Go 언어는 기본 log 패키지의 단순한 기능부터 logrus, zap, zerolog, 그리고 새롭게 등장한 slog 같은 라이브러리의 고급 기능까지, 정말 다채로운 로깅 생태계를 갖추고 있는데요.

여러분 애플리케이션의 요구사항을 꼼꼼히 따져보고 가장 적합한 로깅 솔루션을 선택하신다면, 앞으로 효과적인 모니터링과 디버깅이 훨씬 수월해질 것입니다.