Computer >> 컴퓨터 >  >> 프로그래밍 >> Ruby

Ruby에서 로그 다루기: Logger부터 Lograge까지 완벽 가이드

Ruby에서 로그 다루기: Logger부터 Lograge까지

로깅은 애플리케이션이 반드시 처리해야 하는 핵심 작업 중 하나입니다. 로그는 다음과 같은 경우에 활용됩니다.

  • 애플리케이션 내부에서 무슨 일이 일어나는지 확인할 때
  • 애플리케이션을 모니터링할 때
  • 특정 데이터에 대한 지표(metric)를 수집할 때

새로운 프로그래밍 언어를 배울 때 로그를 남기기 위해 가장 먼저 선택하게 되는 것은 보통 해당 언어의 네이티브 메커니즘입니다. 사용법이 쉽고, 문서화가 잘 되어 있으며, 커뮤니티 전반에 널리 알려져 있기 때문입니다.

로그 데이터는 회사, 비즈니스, 애플리케이션의 유형에 따라 크게 달라집니다. 따라서 여러분과 팀이 선택한 로깅 솔루션이 전체 운영에 어떤 영향을 미치는지 이해하는 것이 매우 중요합니다.

이 글에서는 Ruby와 Rails에서 사용할 수 있는 로깅 옵션을 내장 기능부터 사랑받는 서드파티 커뮤니티 프레임워크까지 차례대로 살펴보겠습니다. 시작해 볼까요!

Ruby의 내장 로깅 옵션

Ruby에는 로깅을 처리하기 위한 두 가지 내장 옵션이 있습니다. 바로 콘솔 출력 명령(명령줄 환경을 위해 설계됨)과 Logger 클래스입니다.

각각 자세히 살펴보겠습니다.

콘솔에 출력하기

Ruby에는 텍스트를 콘솔에 출력하는 네 가지 대표적인 방법이 있습니다. puts, print, p, pp입니다.

puts 메서드는 전달받은 값을 출력한 뒤 새 줄을 추가합니다.

2.7.0 :001 > puts "Hey, I'm a log!"
Hey, I'm a log!
 => nil

print 메서드는 puts와 비슷하지만, 항상 to_s 메서드를 사용해 인수를 문자열로 변환한다는 차이가 있습니다.

마지막으로 ppp 메서드는 값을 변환하지 않고 객체를 그대로(raw) 출력합니다. 둘의 유일한 차이점은 pp(pretty print)가 출력을 더 읽기 좋게 들여쓰기 형태로 포맷한다는 점입니다.

애플리케이션 로그

서버에서 실행되는 애플리케이션의 경우 단순히 콘솔에 출력하는 방식은 적합하지 않습니다.

대신 훨씬 유연한 Logger 클래스를 사용합니다.

"debug" 로그를 출력하는 기본적인 사용 예시는 다음과 같습니다.

require "logger"
logger = Logger.new(STDOUT)
logger.debug("I'm a debug log")

필요하다면 STDOUT 대신 파일에 로그를 저장하거나, 로그 수집기(log aggregator)로 전송하도록 구성할 수도 있습니다.

참고로 위 예시의 "debug"는 로그 레벨(log level)입니다. 로그 레벨을 통해 "이 로그 메시지는 특정 종류의 이벤트와 관련이 있다"는 것을 시스템에 알려줄 수 있습니다. Ruby에는 Fatal, Error, Warn, Info, Debug, Unknown의 여섯 가지 내장 로그 레벨이 있습니다.

logger.debug("I'm a debug log")
logger.info("I'm an info log")
logger.warn("I'm a warn log")
logger.error("I'm an error log: error message")
logger.fatal("I'm a fatal log")

로그 출력을 확인해 보면 각 줄마다 로그 레벨, 프로세스 ID(pid), 타임스탬프가 함께 기록된 것을 볼 수 있습니다.

Ruby에서 로그 다루기: Logger부터 Lograge까지 완벽 가이드

또 하나 유용한 기능은 Logger 객체의 최소 로그 레벨을 지정할 수 있다는 점입니다.

logger.level = Logger::WARN

이렇게 설정하면 Loggerwarn 레벨 이상의 로그만 처리합니다. 프로덕션 환경에서 용량이나 보안상의 이유로 "debug" 로그를 저장하고 싶지 않을 때 매우 유용합니다.

설정 후 출력 결과는 다음과 같습니다.

Ruby에서 로그 다루기: Logger부터 Lograge까지 완벽 가이드

로그 커스터마이징하기

로그를 커스터마이징할 수 있는 방법은 다양합니다. 예를 들어 날짜와 시간 형식을 변경하려면 기본값을 덮어쓰면 됩니다.

logger.datetime_format = "%Y-%m-%d %H:%M"

출력 예시는 다음과 같습니다.

W, [2020-07-28 10:50#87786]  WARN -- : I'm a warn log

datetime 형식은 표준 시간 포맷 사양을 따르므로, 변경할 때는 해당 사양 문서를 참고하는 것이 좋습니다.

하지만 때로는 로그 형식 전체를 완전히 바꿔야 할 수도 있습니다. 이럴 때 Ruby가 제공하는 것이 Formatter 클래스입니다. 개발자가 Logger가 로그를 출력할 때 사용하는 기본 템플릿을 직접 덮어쓸 수 있게 해줍니다.

예를 들어 애플리케이션이 중앙 집중식 로그 분석 도구를 사용하고 있어서 모든 로그를 name=value 형식으로 출력해야 한다고 가정해 보겠습니다. 이 경우 새로운 포매터 코드는 다음과 같습니다.

logger.formatter = proc do |severity, datetime, progname, msg|
    date_format = datetime.strftime("%Y-%m-%d %H:%M:%S")
    "date=[#{date_format}] severity=#{severity.ljust(5)} pid=##{Process.pid} message='#{msg}'\n"
end

여기서는 proc을 사용해 모든 로그를 가로채고, 기본 출력 방식을 원하는 형식으로 변경했습니다. 그 결과는 다음과 같습니다.

Ruby에서 로그 다루기: Logger부터 Lograge까지 완벽 가이드

단 몇 줄의 코드만으로 모든 애플리케이션 로그를 분석 도구에서 인덱싱할 수 있게 됩니다.

JSON 형식으로 로깅하기

Formatter의 강력한 기능을 활용하면 원하는 어떤 출력 형식으로도 로그를 손쉽게 커스터마이징할 수 있습니다. 예를 들어 JSON 모듈을 사용해 로그를 JSON 형식으로 출력할 수 있습니다.

require "json"
logger.formatter = proc do |severity, datetime, progname, msg|
    date_format = datetime.strftime("%Y-%m-%d %H:%M:%S")
    JSON.dump(date: "#{date_format}", severity:"#{severity.ljust(5)}", pid:"##{Process.pid}", message: msg) + "\n"
end

결과적으로 생성되는 로그는 다음과 같습니다.

Ruby에서 로그 다루기: Logger부터 Lograge까지 완벽 가이드

파일에 로깅하기

앞서 살펴본 것처럼 Logger 생성자는 첫 번째 인수로 로그가 저장될 위치를 받습니다.

로그를 파일에 저장하려면 다음과 같이 지정하면 됩니다.

require 'logger'
logger = Logger.new('my_logs.log')

이렇게 하면 해당 코드가 포함된 Ruby 파일과 같은 디렉터리에 my_logs.log라는 새 파일이 생성됩니다.

Ruby에서 로그 다루기: Logger부터 Lograge까지 완벽 가이드

또한 Logger 클래스는 로그 보존 정책(retention policy) 설정과 같은 다양한 커스텀 옵션도 제공합니다. 다음 예시를 살펴보세요.

# 현재 주간 데이터 보관
Logger.new('my_weekly_logs.log', 'weekly')
# 오늘부터 과거 2개월간 데이터 보관
Logger.new('my_latest_2_months_logs.log', 2, 'monthly')
# 파일 크기가 20GB를 초과하면 로그 초기화
Logger.new('my_custom_logs.log', 0, 20 * 1024 * 1024 * 1024)

두 번째 매개변수로 Logger는 로그를 얼마나 자주 보관하거나 삭제할지에 대한 규칙을 받습니다.

첫 번째 객체는 로그를 1주일 동안만 보관합니다. 두 번째는 2개월 범위로 저장하고, 마지막 예제는 세 번째 매개변수를 기준으로 파일 크기만 검사합니다(즉, 20GB에 도달하면 로그를 폐기하고 새로 시작).

Ruby on Rails에서의 로깅

지금까지 소개한 Logger의 대부분의 장점은 Rails 애플리케이션에서도 그대로 활용할 수 있습니다. Rails의 Logger는 사실상 Ruby 네이티브 클래스의 단순한 확장판입니다.

그뿐만 아니라 Rails는 로그를 여러 로거에 동시에 전달(broadcast)할 수 있는 편리한 기능도 추가로 제공합니다. 따라서 둘 이상의 로깅 라이브러리를 함께 사용하고 싶다면 Rails의 logger 안에서 손쉽게 처리할 수 있습니다.

custom_logger = Logger.new(STDOUT)
Rails.logger.extend(ActiveSupport::Logger.broadcast(custom_logger))

이 기능은 로그의 목적지를 지능적으로 관리해야 할 때도 유용합니다. 애플리케이션의 일부 영역은 서로 다른 위치나 다른 형식의 로그가 필요할 수 있기 때문입니다.

서드파티 로깅 프레임워크 선택하기

Ruby의 내장 로거가 요구사항을 정확히 충족하지 못한다면 다른 선택지도 있습니다.

서드파티 로깅 프레임워크는 종류가 매우 다양하며, 그중 가장 인기 있는 것이 바로 Lograge입니다. 지금부터 살펴보겠습니다!

Lograge 시작하기

Lograge는 Rails 애플리케이션을 위해 만들어졌습니다. 순수 Ruby(vanilla Ruby) 프로젝트에서는 좋은 선택이 아닐 수 있습니다.

설치는 다음 gem 명령 한 줄이면 끝납니다.

bundle add lograge

그다음 config/initializers/lograge.rb 경로에 다음 코드로 새 설정 파일을 생성합니다.

Rails.application.configure do
    config.lograge.enabled = true
    config.lograge.custom_options = lambda do |event|
        { time: event.time }
    end
end

이 설정은 Lograge를 활성화하고, 프레임워크에서 하나의 이벤트로 취급되는 로그가 출력될 시간을 정의합니다.

Rails 앱을 실행하고 첫 엔드포인트에 접속하면 Lograge가 요청 요약(request summary)을 출력합니다. Lograge 설정 전후의 로그를 비교하면 아래와 같은 차이를 확인할 수 있습니다.

Ruby에서 로그 다루기: Logger부터 Lograge까지 완벽 가이드

다시 강조하지만, Lograge는 만능 해결책(silver bullet)이 아닙니다. 실제로 매우 독선적(opinionated)인 프레임워크이기 때문에, 아마도 내장 Logger나 선호하는 다른 프레임워크와 함께 사용하는 것이 좋습니다.

마무리

결국 로깅은 프로젝트의 핵심적인 부분일 뿐만 아니라, 그중에서도 가장 과소평가되는 영역이라는 사실이 분명해집니다. 네이티브 기능이든 아니든, 사용하는 도구를 깊이 이해하는 것이 더 나은 결과를 얻는 지름길입니다.

새로운 프레임워크를 도입하기 전에는 반드시 충분한 테스트를 거치세요. 때로는 플랫폼 자체에 이미 훌륭한 내장 옵션이 포함되어 있습니다. 그렇지 않다면, 기존에 내장된 로깅 시스템과 통합해 다른 프레임워크를 사용할 수 있는지 먼저 확인하는 것이 현명합니다.

행운을 빕니다!