우버(Uber)가 만든 고성능 Go 로깅! Zap(자프) 사용법 완벽 정리 (설치부터 파일 분리, 색상 출력까지)
자프(Zap)란 무엇일까요?
자프(Zap)는 세계적인 기업 우버(Uber)에서 만든 Go 언어용 로깅 라이브러리입니다.
가장 큰 특징은 정말 빠르다는 것과 구조화된(structured) 로깅, 그리고 로그 레벨(log-leveled) 기능을 지원한다는 점인데요.
이게 왜 중요하냐면, 로그는 프로그램이 어떻게 돌아가는지 기록하는 일기 같은 건데, 이 기록을 빠르고 체계적으로 남겨야 나중에 문제가 생겼을 때 원인을 찾거나 성능을 분석하기가 훨씬 수월하기 때문입니다.
우버(Uber)의 자프(Zap) 공식 문서에 따르면, 비슷한 다른 구조화된 로깅 라이브러리보다 성능이 더 좋을 뿐만 아니라, Go 언어의 기본 로깅 라이브러리보다도 훨씬 빠르다고 합니다.
얼마나 빠른지 궁금하시다면 깃허브(GitHub)에서 직접 성능 테스트 결과를 확인해 보실 수도 있습니다.
깃허브(GitHub) 주소: https://github.com/uber-go/zap
GitHub - uber-go/zap: Blazing fast, structured, leveled logging in Go.
Blazing fast, structured, leveled logging in Go. Contribute to uber-go/zap development by creating an account on GitHub.
github.com
이제 본격적으로 자프(Zap)를 어떻게 사용하는지 단계별로 알아볼까요?
1. 로거(Logger) 인스턴스 만들기
자프(Zap)를 사용하려면 먼저 로거(Logger)라는 것을 만들어야 하는데요.
쉽게 말해 로그를 기록하는 역할을 하는 친구를 만드는 과정입니다.
자프(Zap)에서는 주로 세 가지 방법으로 로거를 만들 수 있습니다.
zap.NewProduction()
: 실제 서비스 환경(운영 환경)에 적합한 로거입니다. 성능에 최적화되어 있고, JSON 형식으로 로그를 남깁니다.zap.NewDevelopment()
: 개발 환경에 적합한 로거입니다. 사람이 읽기 편한 형식으로 로그를 남기고, 디버깅에 유용한 정보들을 더 많이 포함합니다.zap.Example()
: 간단한 예제용 로거입니다. 기본적인 기능만 보여줍니다.
이 세 가지 방법은 기록되는 정보의 내용과 형식에 차이가 있는데요.
코드를 통해 직접 확인해 보겠습니다.
참고로 이 함수들에 전달하는 파라미터는 문자열(string) 타입만 가능합니다.
// 코드
import "go.uber.org/zap" // 자프 패키지를 가져옵니다.
var log *zap.Logger // log 변수를 선언합니다.
// 예제 로거 생성
log = zap.NewExample()
log.Debug("이것은 DEBUG 메시지입니다")
log.Info("이것은 INFO 메시지입니다")
// 개발용 로거 생성
log, _ = zap.NewDevelopment()
log.Debug("이것은 DEBUG 메시지입니다")
log.Info("이것은 INFO 메시지입니다")
// 운영용 로거 생성
log, _ = zap.NewProduction()
log.Debug("이것은 DEBUG 메시지입니다") // 운영용에서는 기본적으로 Debug 레벨은 기록되지 않습니다.
log.Info("이것은 INFO 메시지입니다")
log.Info("필드가 추가된 INFO 메시지입니다", zap.Strings("region", []string{"us-west"}), zap.Int("id", 2)) // 추가 정보를 함께 기록할 수 있습니다.
각 로거의 출력 결과는 다음과 같습니다.
// Example 출력 결과 (JSON 형식)
{"level":"debug","msg":"이것은 DEBUG 메시지입니다"}
{"level":"info","msg":"이것은 INFO 메시지입니다"}
// Development 출력 결과 (사람이 읽기 편한 형식)
2025-01-28T00:00:00.000+0800 DEBUG development/main.go:7 이것은 DEBUG 메시지입니다
2025-01-28T00:00:00.000+0800 INFO development/main.go:8 이것은 INFO 메시지입니다
// Production 출력 결과 (JSON 형식, 추가 정보 포함)
{"level":"info","ts":1737907200.0000000,"caller":"production/main.go:8","msg":"이것은 INFO 메시지입니다"}
{"level":"info","ts":1737907200.0000000,"caller":"production/main.go:9","msg":"필드가 추가된 INFO 메시지입니다","region":["us-west"],"id":2}
세 가지 생성 방법 비교:
Example
과Production
로거는 기계가 처리하기 좋은 JSON(제이슨) 형식으로 로그를 출력합니다. 반면Development
로거는 개발자가 터미널에서 바로 확인하기 편하도록 한 줄씩 일반 텍스트 형식으로 출력합니다.Development
로거는 경고(Warning) 레벨 이상의 로그가 발생하면 문제 추적을 돕기 위해 스택(stack) 정보까지 출력해 줍니다. 또한 로그가 발생한 패키지/파일/코드 라인 번호(호출 정보)를 항상 포함합니다. 추가적인 필드 정보는 줄 끝에 JSON 문자열 형태로 붙여줍니다. 로그 레벨 이름(DEBUG, INFO 등)은 대문자로 표시하고, 시간은 밀리초(ms) 단위의 ISO8601 형식으로 나타냅니다.Production
로거는 성능을 위해 기본적으로 디버그(Debug) 레벨 메시지는 기록하지 않습니다. 에러(Error)나 심각한 에러(Dpanic) 레벨 로그에서는 스택 정보를 추적하지만, 경고(Warn) 레벨에서는 추적하지 않습니다. 로그 호출 정보는 항상 파일 경로와 함께 추가하며, 시간은 타임스탬프(timestamp) 형식으로 표시하고, 로그 레벨 이름은 소문자로 나타냅니다.
2. 형식화된 출력 (printf 스타일)
자프(Zap)에는 *zap.Logger
와 *zap.SugaredLogger
라는 두 가지 타입의 로거가 있습니다.
기본 로거(*zap.Logger
)는 성능에 극도로 최적화되어 있지만, 로그를 기록할 때 항상 zap.String("key", "value")
처럼 필드 이름을 명시해야 해서 조금 번거로울 수 있습니다.
이럴 때 사용하는 것이 바로 SugaredLogger
인데요. 기존 로거의 .Sugar()
메서드를 호출하면 얻을 수 있습니다.
SugaredLogger
를 사용하면 우리가 흔히 사용하는 printf
스타일처럼 형식을 지정해서 좀 더 간편하게 로그를 남길 수 있습니다.
예를 들어, 변수 값을 로그에 포함시키고 싶을 때 유용합니다.
package main
import (
"errors" // 에러 생성을 위해 추가
"go.uber.org/zap"
)
var sugarLogger *zap.SugaredLogger
func InitLogger() {
logger, _ := zap.NewProduction() // 운영용 로거 생성
sugarLogger = logger.Sugar() // SugaredLogger로 변환
}
func main() {
InitLogger()
defer sugarLogger.Sync() // 프로그램 종료 전에 버퍼에 남은 로그를 확실히 기록합니다.
url := "http://example.com"
err := errors.New("네트워크 연결 실패") // 예시 에러
// printf 스타일 (%s, %s 위치에 변수 값이 들어감)
sugarLogger.Errorf("URL %s 가져오기 실패 : 에러 = %s", url, err)
// 출력 예시: {"level":"error","ts":...,"caller":"...","msg":"URL http://example.com 가져오기 실패 : 에러 = 네트워크 연결 실패","stacktrace":"..."}
}
SugaredLogger
는 약간의 성능 저하가 있을 수 있지만, 개발 편의성이 훨씬 높아서 많은 경우에 유용하게 사용됩니다.
3. 로그 파일에 기록하기
기본적으로 자프(Zap) 로그는 프로그램이 실행되는 콘솔(터미널 화면)에 출력됩니다.
하지만 서비스가 계속 운영되면 로그가 계속 쌓이는데, 나중에 특정 시점의 로그를 찾아보거나 분석하려면 파일로 저장하는 것이 훨씬 편리합니다.
로그를 파일에 기록하려면 앞서 설명한 zap.NewProduction()
같은 간편 생성 함수 대신, zap.New()
함수를 사용해서 로거를 좀 더 세밀하게 설정해야 합니다.
이때 zapcore
라는 패키지를 함께 사용하게 됩니다.
package main
import (
"go.uber.org/zap"
"go.uber.org/zap/zapcore" // 로거 코어 설정을 위해 필요합니다.
"os" // 파일 생성을 위해 필요합니다.
)
var log *zap.Logger
func main() {
// 1. 로그를 저장할 파일을 지정합니다. 여기서는 info.log 파일에 저장합니다.
// os.Create는 파일을 생성하고 파일 핸들(쓰기 권한)을 반환합니다.
writeSyncer, _ := os.Create("./info.log") // "./"는 현재 디렉토리를 의미합니다.
// 2. 로그 형식을 설정합니다. (Encoder 설정)
// NewProductionEncoderConfig는 운영 환경에 맞는 기본 설정을 제공합니다.
encoderConfig := zap.NewProductionEncoderConfig()
// 시간 형식을 ISO8601 표준 형식으로 변경합니다. (예: 2025-01-28T00:00:00.000+0800)
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
// 로그 레벨을 대문자로 표시합니다. (예: INFO, ERROR)
encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
// 3. 어떤 형식으로 로그를 기록할지 선택합니다. (Encoder 생성)
// NewConsoleEncoder는 사람이 읽기 편한 텍스트 형식으로 출력합니다.
// 만약 JSON 형식으로 저장하고 싶다면 NewJSONEncoder()를 사용합니다.
encoder := zapcore.NewConsoleEncoder(encoderConfig)
// 4. 로거의 핵심(Core)을 생성합니다.
// NewCore는 세 가지 주요 요소를 결합합니다:
// - encoder: 로그 형식 (위에서 만든 encoder 사용)
// - writeSyncer: 로그 기록 위치 (위에서 만든 파일 핸들 사용)
// - zapcore.DebugLevel: 기록할 최소 로그 레벨 (여기서는 Debug 레벨 이상 모두 기록)
// 만약 zapcore.ErrorLevel로 설정하면 Error 레벨 이상의 로그만 파일에 기록됩니다.
core := zapcore.NewCore(encoder, writeSyncer, zapcore.DebugLevel)
// 5. 최종 로거를 생성합니다.
// zap.New() 함수에 위에서 만든 core를 전달합니다.
// zap.AddCaller() 옵션을 추가하면 로그가 발생한 파일명과 라인 번호를 함께 기록해 줍니다.
log = zap.New(core, zap.AddCaller())
defer log.Sync() // 프로그램 종료 전 로그 동기화
// 로그 기록 테스트
log.Info("헬로 월드")
log.Error("헬로 월드")
}
이렇게 실행하면 현재 디렉토리에 info.log
파일이 생성되고, 그 안에 다음과 같은 형식으로 로그가 기록됩니다.
// info.log 파일 내용 예시:
2025-01-28T00:00:00.000+0800 INFO main/main.go:41 헬로 월드
2025-01-28T00:00:00.000+0800 ERROR main/main.go:42 헬로 월드
4. 콘솔과 파일 모두에 출력하기
개발 중에는 콘솔에서 실시간으로 로그를 확인하고 싶고, 동시에 나중을 위해 파일에도 로그를 남기고 싶을 수 있습니다.
자프(Zap)는 이 기능도 간단하게 지원합니다. zapcore.NewCore
함수를 약간만 수정하면 됩니다.
핵심은 zapcore.NewMultiWriteSyncer
를 사용하는 것인데요.
이 함수는 여러 개의 출력 대상(WriteSyncer)을 하나로 묶어주는 역할을 합니다.
아래 예제에서는 콘솔 출력(os.Stdout
)과 파일 출력(fileWriteSyncer
)을 동시에 사용합니다.
또한, 파일 관리를 좀 더 편하게 하기 위해 lumberjack
이라는 외부 패키지를 함께 사용하는 방법을 보여줍니다.
package main
import (
// 파일 로테이션 및 관리를 위한 lumberjack 패키지
"github.com/natefinch/lumberjack"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"os" // 콘솔 출력을 위해 필요합니다. (os.Stdout)
)
var log *zap.Logger
func main() {
// 인코더 설정 (이전 예제와 동일)
encoderConfig := zap.NewProductionEncoderConfig()
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
encoder := zapcore.NewConsoleEncoder(encoderConfig)
// 파일 쓰기 설정 (lumberjack 사용)
fileWriteSyncer := zapcore.AddSync(&lumberjack.Logger{
Filename: "./info.log", // 로그 파일 경로
MaxSize: 1, // 로그 파일 최대 크기 (단위: MB)
MaxBackups: 5, // 보관할 최대 로그 파일 개수
MaxAge: 30, // 로그 파일 보관 기간 (단위: 일)
Compress: false, // 압축 여부
})
// 핵심(Core) 생성 시, 여러 출력 대상을 지정합니다.
// zapcore.NewMultiWriteSyncer를 사용하여 파일(fileWriteSyncer)과 콘솔(os.Stdout) 모두에 쓰도록 설정합니다.
// zapcore.AddSync(os.Stdout)은 콘솔 출력을 WriteSyncer 인터페이스에 맞게 감싸줍니다.
core := zapcore.NewCore(encoder, zapcore.NewMultiWriteSyncer(fileWriteSyncer, zapcore.AddSync(os.Stdout)), zapcore.DebugLevel)
// 로거 생성 (AddCaller 옵션 추가)
log = zap.New(core, zap.AddCaller())
defer log.Sync()
log.Info("헬로 월드 (콘솔과 파일 모두 출력)")
log.Error("헬로 월드 (콘솔과 파일 모두 출력)")
}
이제 프로그램을 실행하면 콘솔 화면에도 로그가 출력되고, 동시에 info.log
파일에도 똑같은 내용이 기록됩니다.
5. 로그 파일 분할 (File Splitting)
로그 파일은 시간이 지남에 따라 계속 커지게 마련입니다.
만약 로그 파일 하나가 너무 커지면 디스크 공간을 다 차지해버리거나, 파일을 열어서 내용을 확인하기 어려워지는 문제가 생길 수 있습니다.
그래서 보통 로그 파일은 특정 조건(예: 파일 크기, 시간)에 따라 자동으로 분할하고 오래된 파일은 삭제하는 '로그 로테이션(Log Rotation)' 기능을 사용합니다.
아쉽게도 자프(Zap) 자체에는 파일 분할 기능이 내장되어 있지 않습니다.
하지만 걱정 마세요! 자프(Zap)에서 공식적으로 추천하는 lumberjack
이라는 훌륭한 패키지가 있습니다.
바로 위 예제에서도 잠깐 사용했었죠?
lumberjack.Logger
를 사용하면 아주 쉽게 로그 파일 분할 설정을 할 수 있습니다.
// 파일 쓰기 설정 (lumberjack 사용 예시)
fileWriteSyncer := zapcore.AddSync(&lumberjack.Logger{
Filename: "./log/app.log", // 로그 파일을 저장할 경로와 이름입니다. 폴더가 없으면 자동으로 생성됩니다.
MaxSize: 1, // 파일 하나당 최대 크기 제한입니다. (단위: 메가바이트(MB)) 이 크기를 넘으면 새 파일이 생성됩니다.
MaxBackups: 5, // 오래된 로그 파일을 최대 몇 개까지 보관할지 설정합니다. 여기서는 5개까지 보관합니다.
MaxAge: 30, // 로그 파일을 며칠 동안 보관할지 설정합니다. 여기서는 30일 동안 보관하고 이후에는 삭제됩니다.
Compress: false, // 오래된 로그 파일을 압축할지 여부를 설정합니다. true로 설정하면 gzip으로 압축됩니다.
})
lumberjack
의 설정 옵션들은 직관적이어서 이해하기 쉬울 겁니다.
Filename
으로 경로와 파일명을 지정하고, MaxSize
, MaxBackups
, MaxAge
를 적절히 조합해서 디스크 용량 걱정 없이 로그를 관리할 수 있습니다.
6. 로그 레벨별로 다른 파일에 기록하기
로그를 관리하다 보면, 일반적인 정보성 로그(Info, Debug)와 심각한 문제 상황을 나타내는 에러 로그(Error, Fatal 등)를 분리해서 저장하고 싶을 때가 많습니다.
예를 들어, 평소에는 info.log
파일만 가끔 확인하고, 문제가 발생했을 때만 error.log
파일을 집중적으로 분석하는 식으로 활용할 수 있습니다.
자프(Zap)에서는 zapcore.NewCore
함수의 세 번째 인자인 '로그 레벨' 설정을 이용해서 이를 구현할 수 있습니다.
특정 레벨의 로그만 처리하는 코어(Core)를 여러 개 만들고, 이 코어들을 합쳐서 최종 로거를 만드는 방식입니다.
package main
import (
"github.com/natefinch/lumberjack"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"os"
)
var log *zap.Logger
func main() {
var coreArr []zapcore.Core // 여러 개의 코어를 담을 슬라이스를 준비합니다.
// 인코더 설정 (아래에서 설명할 색상 출력 기능 추가)
encoderConfig := zap.NewProductionEncoderConfig()
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
// 로그 레벨에 따라 다른 색상으로 출력합니다. (콘솔 출력 시 적용)
// 색상 기능이 필요 없다면 zapcore.CapitalLevelEncoder를 사용합니다.
encoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder
// 전체 파일 경로를 표시하고 싶다면 주석 해제:
// encoderConfig.EncodeCaller = zapcore.FullCallerEncoder
encoder := zapcore.NewConsoleEncoder(encoderConfig)
// 로그 레벨별로 처리할 함수를 정의합니다.
// 에러 레벨 이상만 처리 (Error, Fatal, Panic)
highPriority := zap.LevelEnablerFunc(func(lev zapcore.Level) bool {
return lev >= zap.ErrorLevel
})
// 정보(Info) 및 디버그(Debug) 레벨만 처리 (Debug가 가장 낮은 레벨)
lowPriority := zap.LevelEnablerFunc(func(lev zapcore.Level) bool {
return lev < zap.ErrorLevel && lev >= zap.DebugLevel
})
// Info/Debug 로그를 위한 설정 (info.log 파일 + 콘솔 출력)
infoFileWriteSyncer := zapcore.AddSync(&lumberjack.Logger{
Filename: "./log/info.log", // 정보 로그 파일 경로
MaxSize: 1,
MaxBackups: 5,
MaxAge: 30,
Compress: false,
})
// lowPriority(Info, Debug 레벨만) 로그를 info.log 파일과 콘솔에 출력하는 코어 생성
infoFileCore := zapcore.NewCore(encoder, zapcore.NewMultiWriteSyncer(infoFileWriteSyncer, zapcore.AddSync(os.Stdout)), lowPriority)
// Error 로그를 위한 설정 (error.log 파일 + 콘솔 출력)
errorFileWriteSyncer := zapcore.AddSync(&lumberjack.Logger{
Filename: "./log/error.log", // 에러 로그 파일 경로
MaxSize: 1,
MaxBackups: 5,
MaxAge: 30,
Compress: false,
})
// highPriority(Error 레벨 이상만) 로그를 error.log 파일과 콘솔에 출력하는 코어 생성
errorFileCore := zapcore.NewCore(encoder, zapcore.NewMultiWriteSyncer(errorFileWriteSyncer, zapcore.AddSync(os.Stdout)), highPriority)
// 생성된 코어들을 슬라이스에 추가합니다.
coreArr = append(coreArr, infoFileCore)
coreArr = append(coreArr, errorFileCore)
// zapcore.NewTee(...) 함수는 여러 개의 코어를 하나로 합쳐줍니다.
// 각 코어는 자신이 처리할 수 있는 레벨의 로그만 처리하게 됩니다.
// zap.AddCaller()는 로그 발생 위치 표시 옵션이며, 생략 가능합니다.
log = zap.New(zapcore.NewTee(coreArr...), zap.AddCaller())
defer log.Sync()
// 로그 기록 테스트
log.Info("이것은 Info 레벨 로그입니다.") // info.log + 콘솔에 기록됨
log.Debug("이것은 Debug 레벨 로그입니다.") // info.log + 콘솔에 기록됨
log.Error("이것은 Error 레벨 로그입니다.") // error.log + 콘솔에 기록됨
}
이렇게 설정하면, Info
와 Debug
레벨 로그는 ./log/info.log
파일과 콘솔에 기록되고, Error
레벨 이상의 로그는 ./log/error.log
파일과 콘솔에 별도로 기록됩니다. 정말 편리하죠?
7. 콘솔 출력 시 레벨별 색상 표시하기
개발할 때 콘솔에서 로그를 실시간으로 확인할 때, 로그 레벨별로 색상이 다르게 표시되면 가독성이 훨씬 좋아집니다.
예를 들어 에러 로그는 빨간색, 경고 로그는 노란색, 정보 로그는 파란색 등으로 표시되면 문제를 훨씬 빨리 인지할 수 있습니다.
자프(Zap)에서는 인코더(Encoder) 설정에서 이 기능을 쉽게 켤 수 있습니다.
EncoderConfig
의 EncodeLevel
필드를 zapcore.CapitalColorLevelEncoder
로 설정해주기만 하면 됩니다.
// 인코더 설정 부분
encoderConfig := zap.NewProductionEncoderConfig()
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
// 이 부분을 CapitalColorLevelEncoder로 설정하면 됩니다!
encoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder // 레벨별 색상 표시
encoder := zapcore.NewConsoleEncoder(encoderConfig)
만약 색상 표시 기능이 필요 없다면, 원래대로 zapcore.CapitalLevelEncoder
를 사용하면 됩니다.
8. 파일 경로 및 라인 번호 표시하기
로그를 분석할 때, 해당 로그가 코드의 어느 부분에서 발생했는지 아는 것은 매우 중요합니다.
앞선 예제들에서 계속 사용했던 zap.AddCaller()
옵션이 바로 이 역할을 합니다.
zap.New()
함수를 호출할 때 이 옵션을 추가해주면, 로그 출력 시 자동으로 파일명과 라인 번호가 포함됩니다.
// 로거 생성 시 zap.AddCaller() 옵션 추가
log = zap.New(core, zap.AddCaller())
기본적으로 zap.AddCaller()
는 파일명(예: main.go:42
)만 표시하는데요.
만약 전체 파일 경로(예: /home/user/project/main.go:42
)를 표시하고 싶다면, 인코더 설정에서 EncodeCaller
필드를 지정해주면 됩니다.
// 인코더 설정 부분
encoderConfig := zap.NewProductionEncoderConfig()
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
encoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder
// 전체 파일 경로를 표시하도록 설정합니다.
encoderConfig.EncodeCaller = zapcore.FullCallerEncoder
encoder := zapcore.NewConsoleEncoder(encoderConfig)
9. 완성된 코드 예시
지금까지 설명한 기능들(레벨별 파일 분리, 콘솔/파일 동시 출력, 색상 표시, 호출자 정보 표시, lumberjack을 이용한 로그 로테이션)을 모두 적용한 최종 코드 예시입니다.
이 코드를 바탕으로 여러분의 프로젝트에 맞게 설정을 조금씩 변경해서 사용하시면 됩니다.
package main
import (
"github.com/natefinch/lumberjack"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"os"
"time" // 데모를 위해 잠시 멈추는 용도
)
var log *zap.Logger
func main() {
var coreArr []zapcore.Core
// --- 인코더 설정 ---
encoderConfig := zap.NewProductionEncoderConfig()
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder // 시간 형식
encoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder // 레벨별 색상
// encoderConfig.EncodeCaller = zapcore.FullCallerEncoder // 전체 경로 표시 (필요시 주석 해제)
encoder := zapcore.NewConsoleEncoder(encoderConfig) // 콘솔 형식 인코더
// --- 로그 레벨 정의 ---
highPriority := zap.LevelEnablerFunc(func(lev zapcore.Level) bool { // Error 레벨 이상
return lev >= zap.ErrorLevel
})
lowPriority := zap.LevelEnablerFunc(func(lev zapcore.Level) bool { // Debug, Info 레벨
return lev < zap.ErrorLevel && lev >= zap.DebugLevel
})
// --- Info/Debug 로그 설정 ---
infoFileWriteSyncer := zapcore.AddSync(&lumberjack.Logger{
Filename: "./log/info.log", // 정보 로그 파일
MaxSize: 2, // 최대 2MB
MaxBackups: 100, // 최대 100개 보관
MaxAge: 30, // 30일간 보관
Compress: false, // 압축 안 함
})
// Info/Debug 레벨 로그를 info.log 파일과 콘솔에 출력하는 코어
infoFileCore := zapcore.NewCore(encoder, zapcore.NewMultiWriteSyncer(infoFileWriteSyncer, zapcore.AddSync(os.Stdout)), lowPriority)
// --- Error 로그 설정 ---
errorFileWriteSyncer := zapcore.AddSync(&lumberjack.Logger{
Filename: "./log/error.log", // 에러 로그 파일
MaxSize: 1, // 최대 1MB
MaxBackups: 5, // 최대 5개 보관
MaxAge: 30, // 30일간 보관
Compress: false, // 압축 안 함
})
// Error 레벨 이상 로그를 error.log 파일과 콘솔에 출력하는 코어
errorFileCore := zapcore.NewCore(encoder, zapcore.NewMultiWriteSyncer(errorFileWriteSyncer, zapcore.AddSync(os.Stdout)), highPriority)
// --- 코어 합치기 및 로거 생성 ---
coreArr = append(coreArr, infoFileCore)
coreArr = append(coreArr, errorFileCore)
// NewTee로 여러 코어를 합치고, AddCaller로 호출자 정보 추가
log = zap.New(zapcore.NewTee(coreArr...), zap.AddCaller())
// Sync()는 프로그램 종료 시 버퍼에 남은 로그를 확실히 기록하기 위해 호출합니다.
// main 함수가 끝나기 직전에 실행되도록 defer 키워드를 사용합니다.
defer log.Sync()
// --- 로그 기록 테스트 ---
log.Info("서버 시작", zap.String("ip", "127.0.0.1"), zap.Int("port", 8080))
log.Debug("디버그 메시지: 사용자 인증 확인 중...")
// 일부러 에러 발생 시뮬레이션
_, err := os.Open("non_existent_file.txt")
if err != nil {
log.Error("파일 열기 실패!", zap.Error(err))
}
// 여러 번 로그를 남겨서 파일 분할 테스트 (실제로는 더 많은 로그가 필요)
for i := 0; i < 5; i++ {
log.Info("반복 작업 중...", zap.Int("반복 횟수", i+1))
time.Sleep(10 * time.Millisecond) // 로그 사이에 약간의 시간 간격
}
log.Info("서버 종료")
}
오늘은 우버(Uber)에서 만든 고성능 Go 로깅 라이브러리, 자프(Zap)에 대해 자세히 알아봤습니다.
기본적인 로거 생성부터 파일 기록, 레벨별 분리, 로그 로테이션, 그리고 출력 형식 커스터마이징까지 다양한 기능들을 살펴봤는데요.
자프(Zap)를 사용하면 Go 애플리케이션의 로그를 정말 효율적으로 관리할 수 있습니다.
'Go' 카테고리의 다른 글
고랭(Golang) 리플렉션, 정말 느린가요? 알아볼까요? (0) | 2025.04.28 |
---|---|
[Go 언어 탐구] 슬라이스 용량(Capacity)은 어떻게 늘어날까? append의 비밀 파헤치기 (Go 1.23 기준) (0) | 2025.04.27 |
Go 1.22 버전, `http.ServeMux` 하나면 충분할까요? (0) | 2025.04.25 |
Golang 웹 프레임워크 7종 비교분석 (Gin, Echo, Beego, Revel, Fiber, Gorilla Mux, go-zero/rest) (0) | 2025.03.29 |
Go 언어의 난수, 왜 예측 가능할까요? (math/rand vs crypto/rand 깊이 파헤치기) (0) | 2025.03.24 |