목차
1. logrus란?
2. logrus의 특징
3. logrus 사용 예시

Logrus란?

logrus는 golang에서 사용할 수 있는 로깅 모듈 중의 하나로, docker와 Prometheus와 같은 많은 유명 오픈 소스 프로젝트가 이를 사용하여 로그를 기록하는 것으로 알려졌다.

 

Logrus의 특징

logrus는 다음과 같은 특징이 있다.

  • golang 표준 라이브러리의 로깅 모듈과 완벽하게 호환된다. logrus에는 6가지 로그 레벨이 존재하며, 이는 golang 표준 라이브러리의 로그 모듈 API의 상위 집합이다. 프로젝트에서 표준 라이브러리의 log 모듈을 사용하는 경우 저렴한 비용으로 logrus로 마이그레이션할 수 있다.
  • 확장 가능한 hook 매커니즘: 사용자가 hook을 통해 로컬 파일 시스템, 표준 출력, logstash, elastic search 또는 MQ와 같은 위치에 로그를 배포하거나 hook을 통해 로그 내용 및 형식을 정의할 수 있다. 
  • 선택적 로그 출력 형식: logrus에는 jsonformatter 및 textformatter의 두 가지 기본 제공 로그 형식이 있다. 이 두 형식으로도 충분하지 않은 경우 인터페이스 포맷터를 직접 구현하여 고유한 로그 형식을 정의할 수도 있다.
  • 필드 매커니즘: logrus는 긴 메세지를 통한 로깅보다 필드 매커니즘을 통해 상세하고 구조화된 로깅을 지원하고 권장한다.

 

Logrus 사용 예시

먼저, logrus를 사용하기 위해 모듈을 가져온다.

$ go get -u github.com/sirupsen/logrus

 

#1 Logrus를 사용하는 간단한 예

package main

import log "github.com/sirupsen/logrus"

func main() {
	log.WithFields(log.Fields{
		"animal": "tiger",
		"habitat": "mountain",
	}).Info("A tiger appears")
}

결과 

logrus를 사용하는 가장 간단한 예시이다. 이처럼 필드를 통해 로그의 내용을 표현하고, 로그를 7단계로 구분한다.

기존 6단계에서 trace가 추가되어 7단계로 변경되었다.

 

logrus 로그의 7단계

log.Trace("Something very low level.")                  // 1단계: Trace
log.Debug("Useful debugging information.")              // 2단계: Debug
log.Info("Something noteworthy happened!")              // 3단계: Info
log.Warn("You should probably take a look at this.")	// 4단계: Warn
log.Error("Something failed but I'm not quitting.")     // 5단계: Error
log.Fatal("Bye.")                                       // 6단계: Fatal, 로깅 이후 os.Exit(1) 호출
log.Panic("I'm bailing.")                               // 7단계: Panic, 로깅 이후 Panic() 호출

위의 코드 블록에서 다소 오해가 발생할 여지가 있는 부분이 있다. 구분을 위해, Trace를 1 단계라고 명시했지만, 낮다를 표현하고자 하였을 뿐 정해진 바는 없다. 즉, Panic이 1 단계일 수도 있다. Trace에서 Panic으로 7단계 구분이 이뤄졌다는 점만 기억하면 되겠다.

 

#2 다양한 옵션 설정도 가능하다.

대표적으로 로그 형식과 로그 출력 방식을 지정할 수 있다. 또 로그 레벨의 하한을 결정하여, 그 이하의 로그는 출력하지 않도록 할 수도 있다. 그 외에도 여기에는 등장하지 않았지만, 로그와 관련된 훅 같은 옵션도 적용할 수 있으나, 주제가 무거우니 향후 가능하다면 별도의 포스팅으로 분리하는 것으로 하자.

package main

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

func init() {
	// 로그 형식을 JSON 형식으로 지정
	log.SetFormatter(&log.JSONFormatter{})

	// log 출력을 표준 출력으로 설정 (기본 설정은 stderr, 표준 에러)
	log.SetOutput(os.Stdout)

	// 로그 레벨을 warn 단계 위로 설정
	log.SetLevel(log.WarnLevel)
}

func logrusOne() {
	log.WithFields(log.Fields{
		"animal": "tiger",
		"distance": 100,
	}).Info("A tiger appears")

	log.WithFields(log.Fields{
		"animal": "tiger",
		"distance": 10,
	}).Warn("A tiger coming to you")

	log.WithFields(log.Fields{
		"animal": "tiger",
		"distance": 0,
	}).Fatal("A tiger wants to play with you. Good Luck.")
}

func main() {
	logrusOne()
}

결과 

 

# 3 logrus는 log를 기록할 인스턴스를 정의하여, 각 인스턴스에 대해서 다른 옵션을 적용하는 것도 가능하다.

앞서 logrus에서 log에 대한 옵션 설정이 어떻게 이뤄지는지를 보였다. 여기에 더해 logrus의 logrus.Entry 인스턴스를 지정하여, 각각의 인스턴스마다 다른 옵션을 적용할 수도 있다.

package main

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

// Logrus 모듈에서는 new() 함수를 통해 logrus.Entry 인스턴스를 만들 수 있다.
// 프로젝트 내에서 원하는 수만큼 logrus.Entry인스턴스를 만들 수 있다.
var (
	logInstanceOne = log.New()
	logInstanceTwo = log.New()
)

func init() {
	// 생성한 logrus 인스턴스의 출력이 어떻게 이뤄질지를 결정한다.
	// logrus 인스턴스 출력은 io.writer라면 모두 가능하다.
	logInstanceOne.Out = os.Stdout
	logInstanceTwo.Out = os.Stderr

	// 생성한 logrus 인스턴스의 출력 형식이 어떻게 이뤄질지를 결정한다.
	// 이와 비슷하게, logrus 인스턴스 별로 "로그 레벨" 또는 "훅"을 설정할 수 있다.
	logInstanceOne.Formatter = &log.JSONFormatter{}
	logInstanceTwo.Formatter = &log.TextFormatter{}
}

func main() {
	logInstanceOne.WithFields(log.Fields{
		"animal": "tiger",
		"habitat": "mountain",
	}).Info("A tiger appears")

	logInstanceTwo.WithFields(log.Fields{
		"animal": "tiger",
		"habitat": "mountain",
	}).Info("A tiger appears")
}

 

결과

 

#4 logrus의 필드

logrus에서는 로깅을 위한 장황한 메세지를 사용하는 것을 권장하지 않는다. 대신 Fields 형식을 통한 상세하고 구조화된 로깅을 권장한다.

 

기존 표준 log 라이브러리를 사용했을 경우의 로깅

log.Fatalf("Failed to send event %s to topic %s with key %d", event, topic, key)

 

logrus 모듈에서 권장하는 필드를 활용한 로깅

log.WithFields(log.Fields{
 "event": event,
 "topic": topic,
 "key": key,
}).Fatal("Failed to send event")

WithFields 메서드를 사용하면 좀 더 상세한 정보와 함께 원하는 방식으로 로깅할 수 있겠으나, WithFields 메서드는 어디까지나 필수는 아니다. 몇몇 시나리오에서는 필드가 필요하지 않을 수 있고, 기존 log 모듈에서도 가능하듯 간단한 메시징만 필요할 수도 있다.

 

애플리케이션 내부에서는 종종 고정된 필드와 함께 로깅을 해야하는 상황이 있을 수 있다. 예를 들어 HTTP 요청을 처리할 경우, 모든 로그는 request_id user_ip가 존재한다.

이 경우, 매 번 log.WithFields(log.Fields{"request_id": request_id, "user_ip": user_ip})를 붙이는 것을 피하기 위해, logrus.Entry 인스턴스를 생성하고, 해당 인스턴스에 Field를 디폴트로 설정할 수 있다. 

 

requestLogger := log.WithFields(log.Fields{"request_id": request_id, "user_ip": user_ip})
requestLogger.Info("something happened on that request")
requestLogger.Warn("something not great happened")

 

'Programming Language > Go' 카테고리의 다른 글

grpc 소개  (0) 2022.11.15
protocol 버퍼에 대한 소개  (0) 2022.11.15
[go 모듈 소개 - 1. gorm] Golang을 위한 ORM 라이브러리  (0) 2022.11.15