본문 바로가기

프로그래밍/golang

[Golang] logrus로 구조화된 로깅 구현

Go 프로젝트에서는 표준 출력 fmt,log 패키지만으로는 로그를 관리하는 것에는 한계가 있습니다.
로그 레벨을 구분하고, 상황에 따라 메타데이터를 포함하는 구조화된 로깅이 반드시 필요합니다.
여러 로깅 라이브러리 중에서 "logrus"에 대해서 알아보겠습니다.

 

1. Logrus란?

logrus는 Go언어에서 가장 많이 사용되는 로깅 라이브러리 중 하나이며, 단순 텍스트를 넘어 JSON과 같은 구조화된 로그를 생성하는 것에 최적화되어 있어 ,ELK 스택(Elasticsearch, Logstash, Kibana)이나 CloudWatch 같은 로그 수집 솔루션과 연동하기가 아주 좋습니다.

 

2. 사용 방법

 

2-1 ) 설치

go get github.com/sirupsen/logrus

 

 

2-2 )  예제

package main

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

func main() {
	// 1. 로거 인스턴스 생성
	log := logrus.New()

	// 2. 포맷터를 JSON으로 설정 (운영 환경 추천)
	log.SetFormatter(&logrus.JSONFormatter{})

	// 3. 필드와 함께 로그 출력
	log.WithFields(logrus.Fields{
		"user_id": 101,
		"ip":      "192.168.0.1",
	}).Info("사용자 로그인 성공")
}

 

 

2-3 ) 출력 결과

{
    "level":"info",
    "msg":"사용자 로그인 성공",
    "user_id":101,
    "ip":"192.168.0.1",
    "time":"2026-04-27T13:40:00Z"
}

 

 

3.  핵심 기능

3-1) 7단계 로그 레벨(Logging Levels)

상황에 맞는 로그 레벨을 사용하면 나중에 로그 수집기에서 필터링을 통해서 원하는 레벨 로그만 확인 할 수 있습니다.

레벨 코드 호출 운영 관점
Trace log.Trace("...") 심층 디버깅
코드 단위의 아주 상세한 실행 경로 추적
Debug log.Debug("...") 개발
문제 해결을 위한 개발/진단 데이터
Info log.Info("...") 상태 보고
서비스의 정상적인 처리 흐름 기록
Warn log.Warn("...") 잠재적 위험
주의가 필요한 상황 (지연, 재시도 등)
Error log.Error("...") 기능 실패
특정 기능이 실패했음을 알림
Fatal log.Fatal("...") 시스템 비가용
즉시 종료가 필요한 치명적 오류
Panic log.Panic("...") 런타임 복구 불능
복구 불가능한 런타임 오류 (패닉 유발)
  • Trace : 심층 디버깅 단계.
    함수 호출 루프 내 변수 상태 등 매우 상세한 실행 경로를 기록하며, 프로덕션 환경에서는 성능 부하 방지를 위해 비활성화함.

  • Debug : 진단 및 개발 단계.
    파라미터 유효성 검사, 네트워크 요청/응답 등 문제 해결을 위한 상세 정보를 포함하며, 개발/스테이징 환경에서 사용함.

  • Info : 상태 보고 단계.
    서비스 시작/종료, 트랜잭션 처리 등 시스템의 주요 흐름을 기록하며, 운영 모니터링의 기본 데이터로 활용함.

  • Warn : 잠재적 위험 단계.
    API 지연, 재시도(Retry) 발생 등 즉각적인 조치는 불필요하나 주시가 필요한 비정상 징후를 기록함.

  • Error : 기능 실패 단계.
    DB 쿼리 실패, 서비스 로직 예외 등 특정 작업이 실패했음을 기록하며, 즉각적인 조사 및 원인 추적이 필요함.

  • Fatal : 시스템 비가용 단계.
    필수 자원 연결 불가 등 시스템이 운영될 수 없는 상태를 기록하며, 즉시 가동 중단(os.Exit) 및 알림(Alert)을 유발함.

  • Panic : 런타임 복구 불능 단계.
    스택 오버플로우 등 시스템 무결성을 위해 즉각적인 중단이 필요한 비정상 상태를 기록하며, 복구 시도 없이 종료함.

 

- 예시 코드

package main

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

func main() {
	// 로그 레벨을 Trace로 설정해야 모든 로그가 출력됨
	logrus.SetLevel(logrus.TraceLevel)
	logrus.Trace("데이터베이스 연결 설정 정보 확인중...")
	logrus.Debug("API 요청 헤더: Content-Type=application/json")
	logrus.Info("주문 서비스가 정상적으로 시작되었습니다.")
	logrus.Warn("외부 결제 서비스 응답이 2초 이상 지연되었습니다.")
	logrus.Error("주문 데이터 저장에 실패했습니다. (DB Timeout)")
	// logrus.Fatal("설정 파일이 없습니다. 시스템을 종료합니다.")
	// logrus.Panic("스택 오버플로우 발생! 시스템 복구 불가능.")
}

 

3-2) 옵션 설정

 

출력 포맷의 형태를 Json,Text 중 원하는 형태로 선택하여 출력 할 수 있으며, 여러 옵션들을 통해서 가시성을 높일 수 있다.

 

- Json

func JsonFormat() {
	log := logrus.New()

	log.SetFormatter(&logrus.JSONFormatter{
		TimestampFormat: "2006-01-02 15:04:05.000",
		FieldMap: logrus.FieldMap{
			logrus.FieldKeyTime:  "timestamp",
			logrus.FieldKeyLevel: "level",
			logrus.FieldKeyMsg:   "message",
			logrus.FieldKeyFunc:  "caller",
		},

		PrettyPrint: true, // 로그 정렬 여부
	})

	// 출력 및 레벨 설정
	log.SetOutput(os.Stdout)
	log.SetLevel(logrus.TraceLevel)

	// 파일/라인 번호 출력 옵션
	log.SetReportCaller(true)

	// 테스트 로그
	entry := log.WithFields(logrus.Fields{
		"env":     "development",
		"service": "api-gateway",
	})

	entry.Info("JSONFormatter 설정 테스트 중입니다.")
	entry.Warn("시간 포맷과 구조화된 필드가 적용되었습니다.")
	entry.Error("에러 발생 시 파일명과 라인 번호가 'caller' 필드에 담깁니다.")
}

 

 

 

- Text

func TextFormat() {
	log := logrus.New()

	log.SetFormatter(&logrus.TextFormatter{
		ForceColors:            true,                     
		ForceQuote:             true,                      
		TimestampFormat:        "2006-01-02 15:04:05.000", 
		FullTimestamp:          true,                      
		DisableLevelTruncation: false,                     
		DisableSorting:         false,                    
	})

	// 출력 및 레벨 설정
	log.SetOutput(os.Stdout)
	log.SetLevel(logrus.TraceLevel)

	// 파일/라인 번호 출력 옵션
	log.SetReportCaller(true)

	// 테스트 로그
	entry := log.WithFields(logrus.Fields{
		"env":     "development",
		"service": "api-gateway",
	})

	entry.Info("TextFormatter 설정 테스트 중입니다.")
	entry.Warn("시간 포맷과 색상이 적용되었는지 확인하세요.")
	entry.Error("에러 발생 시 파일명과 라인 번호가 표시됩니다.")
}

 

 

 

4. 도메인 기반 로깅

위에서 살펴본 간략한 예시들을 통해서 옵션 설정과 사용방법을 살펴보았습니다.


이제 살펴볼 것은 도메인 기반 로깅입니다.
로깅은 단순히 기록하는 것에도 의미가 있을 수 있겠지만 중요한 것은 "어떻게 검색할 것인가?" 도 아주 중요한 요소 입니다.
실무에서는 모든 서비스가 하나의 로깅을 공유하더라도, 각 "도메인별로 고유한 컨텍스트"를 주입하여 관리되도록 합니다.

 

각 도메인 별로 고유한 컨텍스트에 주입해서 사용하는 이유

  • 시스템 전체 일괄 적용 가능
  • 필터링 최적화 : 로그 수집기 등에서 "domain: order" 등으로 필터를 걸면 해당 서비스 로그만 바로 추적 가능
  • 가독성 유지보수 용이

 

- 예시 코드

package main

import (
	"os"

	"github.com/sirupsen/logrus"
)

// 1. 로거 설정 (모든 도메인의 기준)
func NewBaseLogger() *logrus.Logger {
	log := logrus.New()
	log.SetFormatter(&logrus.JSONFormatter{})
	log.SetOutput(os.Stdout)
	return log
}

// 2. 도메인 서비스 정의
type OrderService struct{ log *logrus.Entry }
type UserService struct{ log *logrus.Entry }

func main() {
	baseLog := NewBaseLogger()

	orderSvc := &OrderService{log: baseLog.WithField("domain", "order")}
	userSvc := &UserService{log: baseLog.WithField("domain", "user")}

	orderSvc.log.Info("주문이 생성되었습니다.")
	userSvc.log.WithField("username", "Reo").Info("회원가입이 완료되었습니다.")
}

 

5. 코드 보러 가기

https://github.com/reochoi109/go-handbook/blob/main/log/logrus/basic/main.go

 

go-handbook/log/logrus/basic/main.go at main · reochoi109/go-handbook

A personal handbook of Go patterns and best practices. Lightweight, practical code snippets for real-world backend development. - reochoi109/go-handbook

github.com