Logger

Logger

프로그램의 이벤트 로그를 하나 이상 만들 수 있게 해 주는, 단순하면서도 정교한 로깅 유틸리티 클래스예요.

출처: Ruby 3.3 API

본문

Logger 클래스는 프로그램의 활동 기록을 제공하는 이벤트 로그를 하나 이상 만들 수 있게 해요. 각 로그는 프로그램 활동의 기록인 항목(entry)들의 시간 순서대로된 열을 담아요.

예시에 관해 (About the Examples)

이 페이지의 모든 예시는 Logger가 require되었다고 가정해요.

require 'logger'

요약 (Synopsis)

Logger.new로 로그를 만들어요.

# 단일 로그 파일.
logger = Logger.new('t.log')
# 크기 기반 로테이션 로깅: 10메가바이트 파일 3개.
logger = Logger.new('t.log', 3, 10485760)
# 주기 기반 로테이션 로깅: 일별(역시 'weekly', 'monthly'도 허용).
logger = Logger.new('t.log', 'daily')
# IO 스트림으로 로깅.
logger = Logger.new($stdout)

Logger#add로 (레벨, 메시지) 항목을 추가해요.

logger.add(Logger::DEBUG, 'Maximal debugging info')
logger.add(Logger::INFO, 'Non-error information')
logger.add(Logger::WARN, 'Non-error warning')
logger.add(Logger::ERROR, 'Non-fatal error')
logger.add(Logger::FATAL, 'Fatal error')
logger.add(Logger::UNKNOWN, 'Most severe')

Logger#close로 로그를 닫아요.

logger.close

항목 (Entries)

Logger#add 메서드로 항목을 추가할 수 있어요.

logger.add(Logger::DEBUG, 'Maximal debugging info')
logger.add(Logger::INFO, 'Non-error information')
logger.add(Logger::WARN, 'Non-error warning')
logger.add(Logger::ERROR, 'Non-fatal error')
logger.add(Logger::FATAL, 'Fatal error')
logger.add(Logger::UNKNOWN, 'Most severe')

이런 단축(shorthand) 메서드로도 항목을 추가해요.

logger.debug('Maximal debugging info')
logger.info('Non-error information')
logger.warn('Non-error warning')
logger.error('Non-fatal error')
logger.fatal('Fatal error')
logger.unknown('Most severe')

이 메서드들 중 하나를 호출하면, 항목이 로그에 쓰일 수도 있고 아닐 수도 있어요. 항목의 심각도(severity)와 로그 레벨(log level)에 달려 있어요. "로그 레벨 (Log Level)"을 참고하세요.

항목은 항상 다음을 가져요.

  • 심각도 (add의 필수 인자).
  • 자동으로 만들어진 타임스탬프.

그리고 다음도 가질 수 있어요.

  • 메시지.
  • 프로그램 이름.

예시:

logger = Logger.new($stdout)
logger.add(Logger::INFO, 'My message.', 'mung')
# => I, [2022-05-07T17:21:46.536234 #20536]  INFO -- mung: My message.

항목의 기본 형식은 다음과 같아요.

"%s, [%s #%d] %5s -- %s: %s\n"

형식화될 값들은 다음이에요.

  • 심각도 (한 글자).
  • 타임스탬프.
  • 프로세스 id.
  • 심각도 (단어).
  • 프로그램 이름.
  • 메시지.

다른 항목 형식을 쓰는 방법은 두 가지예요.

  • 커스텀 형식 proc을 설정하는 것 (이후 항목에 영향); formatter= 참고.

  • 위 메서드들 중 하나를 블록과 함께 호출하는 것 (오직 그 항목 하나에만 영향). 이렇게 하면 두 가지 이점이 있어요.

    • 컨텍스트: 블록이 전체 프로그램 컨텍스트를 평가해서 컨텍스트에 의존하는 메시지를 만들 수 있어요.
    • 성능: 블록은 로그 레벨이 항목을 실제로 쓸 수 있게 허용할 때만 평가돼요.
    logger.error { my_slow_message_generator }
    

    문자열 형태와 대조해 보면, 문자열은 항상 로그 레벨과 무관하게 평가돼요.

    logger.error("#{my_slow_message_generator}")
    

심각도 (Severity)

로그 항목의 심각도는 두 가지 효과가 있어요.

  • 항목이 로그에 포함되도록 선택되는지 결정해요. "로그 레벨 (Log Level)" 참고.
  • 로그를 읽는 사람(사람이든 프로그램이든)에게 항목의 상대적 중요도를 알려줘요.

타임스탬프 (Timestamp)

로그 항목의 타임스탬프는 항목이 만들어질 때 자동으로 생성돼요.

로깅된 타임스탬프는 Time#strftime 메서드가 이 형식 문자열을 사용해 형식화해요.

'%Y-%m-%dT%H:%M:%S.%6N'

예시:

logger = Logger.new($stdout)
logger.add(Logger::INFO)
# => I, [2022-05-07T17:04:32.318331 #20536]  INFO -- : nil

datetime_format= 메서드로 다른 형식을 설정할 수 있어요.

메시지 (Message)

메시지는 항목 메서드의 선택 사항 인자예요.

logger = Logger.new($stdout)
logger.add(Logger::INFO, 'My message')
# => I, [2022-05-07T18:15:37.647581 #20536]  INFO -- : My message

기본 항목 포매터인 Logger::Formatter에서 메시지 객체는 다음일 수 있어요.

  • 문자열: 그대로 쓰여요.
  • Exception: message.message가 쓰여요.
  • 그 외의 것: message.inspect가 쓰여요.

참고: Logger::Formatter는 전달된 메시지를 이스케이프하거나 정화(sanitize)하지 않아요. 개발자는 메시지에 악성 데이터(사용자 입력)가 있을 수 있음을 인지하고, 신뢰할 수 없는 데이터를 명시적으로 이스케이프해야 해요. 메시지 데이터를 이스케이프하려면 커스텀 포매터를 쓸 수 있어요. formatter=의 예시를 참고하세요.

프로그램 이름 (Program Name)

프로그램 이름은 항목 메서드의 선택 사항 인자예요.

logger = Logger.new($stdout)
logger.add(Logger::INFO, 'My message', 'mung')
# => I, [2022-05-07T18:17:38.084716 #20536]  INFO -- mung: My message

새 로거의 기본 프로그램 이름은 Logger.new 호출에서 선택 키워드 인자 progname으로 설정할 수 있어요.

logger = Logger.new('t.log', progname: 'mung')

기존 로거의 기본 프로그램 이름은 progname= 메서드 호출로 설정할 수 있어요.

logger.progname = 'mung'

현재 프로그램 이름은 progname 메서드로 가져올 수 있어요.

logger.progname # => "mung"

로그 레벨 (Log Level)

로그 레벨 설정은 항목의 심각도에 기반해 항목이 실제로 로그에 쓰일지 결정해요.

정의된 심각도는 (덜 심각한 것부터 가장 심각한 것까지) 다음과 같아요.

logger = Logger.new($stdout)
logger.add(Logger::DEBUG, 'Maximal debugging info')
# => D, [2022-05-07T17:57:41.776220 #20536] DEBUG -- : Maximal debugging info
logger.add(Logger::INFO, 'Non-error information')
# => I, [2022-05-07T17:59:14.349167 #20536]  INFO -- : Non-error information
logger.add(Logger::WARN, 'Non-error warning')
# => W, [2022-05-07T18:00:45.337538 #20536]  WARN -- : Non-error warning
logger.add(Logger::ERROR, 'Non-fatal error')
# => E, [2022-05-07T18:02:41.592912 #20536] ERROR -- : Non-fatal error
logger.add(Logger::FATAL, 'Fatal error')
# => F, [2022-05-07T18:05:24.703931 #20536] FATAL -- : Fatal error
logger.add(Logger::UNKNOWN, 'Most severe')
# => A, [2022-05-07T18:07:54.657491 #20536]   ANY -- : Most severe

기본 초기 레벨 설정은 가장 낮은 레벨인 Logger::DEBUG인데, 이것은 심각도와 무관하게 모든 항목이 쓰여진다는 뜻이에요.

logger = Logger.new($stdout)
logger.level # => 0
logger.add(0, "My message")
# => D, [2022-05-11T15:10:59.773668 #20536] DEBUG -- : My message

새 로거에서 키워드 인자 level에 적절한 값을 넣어 다른 설정을 지정할 수 있어요.

logger = Logger.new($stdout, level: Logger::ERROR)
logger = Logger.new($stdout, level: 'error')
logger = Logger.new($stdout, level: :error)
logger.level # => 3

이 레벨에서 심각도 Logger::ERROR 이상의 항목은 쓰여지고, 그보다 낮은 심각도의 항목은 쓰여지지 않아요.

logger = Logger.new($stdout, level: Logger::ERROR)
logger.add(3)
# => E, [2022-05-11T15:17:20.933362 #20536] ERROR -- : nil
logger.add(2) # Silent.

기존 로거의 로그 레벨은 level= 메서드로 설정할 수 있어요.

logger.level = Logger::ERROR

이런 단축 메서드들도 레벨을 설정해요.

logger.debug! # => 0
logger.info!  # => 1
logger.warn!  # => 2
logger.error! # => 3
logger.fatal! # => 4

로그 레벨은 level 메서드로 가져올 수 있어요.

logger.level = Logger::ERROR
logger.level # => 3

이런 메서드들은 주어진 레벨이 쓰여질지 여부를 돌려줘요.

logger.level = Logger::ERROR
logger.debug? # => false
logger.info?  # => false
logger.warn?  # => false
logger.error? # => true
logger.fatal? # => true

로그 파일 로테이션 (Log File Rotation)

기본적으로 로그 파일은 (명시적으로 닫힐 때까지) 무한정 커지는 단일 파일이에요. 파일 로테이션은 없지요.

로그 파일을 관리하기 좋은 크기로 유지하려면 여러 로그 파일을 사용하는 로그 파일 로테이션을 쓸 수 있어요.

  • 각 로그 파일에는 겹치지 않는 시간 구간의 항목들이 있어요.
  • 가장 최근 로그 파일만 열려 있고 활성화돼 있어요. 나머지는 닫혀 있고 비활성화돼 있어요.

크기 기반 로테이션 (Size-Based Rotation)

크기 기반 로그 파일 로테이션을 쓰려면 Logger.new를 다음으로 호출해요.

  • logdev 인자: 파일 경로.
  • shift_age 인자: 양의 정수 — 로테이션에 들어갈 로그 파일 수.
  • shift_size 인자: 양의 정수 — 각 로그 파일의 최대 크기(바이트). 기본값은 1048576 (1메가바이트).

예시:

logger = Logger.new('t.log', 3)           # 1메가바이트 파일 3개.
logger = Logger.new('t.log', 5, 10485760) # 10메가바이트 파일 5개.

이 예시들에서 다음을 가정해 봐요.

logger = Logger.new('t.log', 3)

로깅은 새 로그 파일 t.log에서 시작돼요. 새 항목이 파일 크기를 shift_size를 초과하게 만들 때 로그 파일이 "가득 차서" 로테이션 준비가 돼요.

t.log가 처음 가득 차면:

  • t.log는 닫히고 t.log.0으로 이름이 바뀌어요.
  • 새 파일 t.log가 열려요.

t.log가 두 번째 가득 차면:

  • t.log.0t.log.1로 이름이 바뀌어요.
  • t.log는 닫히고 t.log.0으로 이름이 바뀌어요.
  • 새 파일 t.log가 열려요.

이후 t.log가 가득 찰 때마다 로그 파일들이 로테이션돼요:

  • t.log.1이 제거돼요.
  • t.log.0t.log.1로 이름이 바뀌어요.
  • t.log는 닫히고 t.log.0으로 이름이 바뀌어요.
  • 새 파일 t.log가 열려요.

주기적 로테이션 (Periodic Rotation)

주기적 로테이션을 쓰려면 Logger.new를 다음으로 호출해요.

  • logdev 인자: 파일 경로.
  • shift_age 인자: 문자열 주기 지시자.

예시:

logger = Logger.new('t.log', 'daily')   # 매일 로그 파일 로테이션.
logger = Logger.new('t.log', 'weekly')  # 매주 로그 파일 로테이션.
logger = Logger.new('t.log', 'monthly') # 매월 로그 파일 로테이션.

예시:

logger = Logger.new('t.log', 'daily')

주어진 주기가 만료되면:

  • 기본 로그 파일 t.log는 닫히고 t.log.20220509 같은 날짜 기반 접미사로 이름이 바뀌어요.
  • 새 로그 파일 t.log가 열려요.
  • 아무것도 제거되지 않아요.

접미사의 기본 형식은 '%Y%m%d'인데, 위와 비슷한 접미사를 만들어요. 생성 시 옵션 shift_period_suffix로 다른 형식을 설정할 수 있어요. 자세한 내용과 제안은 Time#strftime을 참고하세요.

Constants (상수)

  • ProgName
  • SEV_LABEL — 로깅용 심각도 라벨 (최대 5자).
  • VERSION

Attributes (속성)

formatter [RW]

로거 항목 포매터 proc을 설정하거나 가져와요.

formatternil이면 로거는 Logger::Formatter를 사용해요.

formatter가 proc이면 새 항목은 그 proc으로 형식화되는데, proc은 네 개의 인자와 함께 호출돼요.

  • severity: 항목의 심각도.
  • time: 항목의 타임스탬프를 나타내는 Time 객체.
  • progname: 항목의 프로그램 이름.
  • msg: 항목의 메시지 (문자열 또는 문자열로 변환 가능한 객체).

proc은 형식화된 항목을 담은 문자열을 돌려줘야 해요.

이 커스텀 포매터는 String#dump로 메시지 문자열을 이스케이프해요.

logger = Logger.new($stdout, progname: 'mung')
original_formatter = logger.formatter || Logger::Formatter.new
logger.formatter = proc { |severity, time, progname, msg|
  original_formatter.call(severity, time, progname, msg.dump)
}
logger.add(Logger::INFO, "hello \n ''")
logger.add(Logger::INFO, "\f\x00\xff\\\"")

출력:

I, [2022-05-13T13:16:29.637488 #8492]  INFO -- mung: "hello \n ''"
I, [2022-05-13T13:16:29.637610 #8492]  INFO -- mung: "\f\x00\xFF\\\""

progname [RW]

로그 메시지에 포함할 프로그램 이름.

Public Class Methods

new(logdev, shift_age = 0, shift_size = 1048576, **options)

단일 인자 logdev로, 모든 기본 옵션을 가진 새 로거를 돌려줘요.

Logger.new('t.log') # => #<Logger:0x000001e685dc6ac8>

인자 logdev는 반드시 다음 중 하나여야 해요.

  • 문자열 파일 경로: 항목들이 그 경로의 파일에 쓰여져요. 그 경로의 파일이 존재하면 새 항목들이 추가(append)돼요.
  • IO 스트림 (보통 $stdout, $stderr, 또는 열린 파일): 항목들이 주어진 스트림에 쓰여져요.
  • nil 또는 File::NULL: 항목이 쓰여지지 않아요.

예시:

Logger.new('t.log')
Logger.new($stdout)

키워드 옵션은 다음과 같아요.

  • level: 로그 레벨 설정. 기본값은 Logger::DEBUG. "로그 레벨 (Log Level)" 참고: Logger.new('t.log', level: Logger::ERROR)
  • progname: 기본 프로그램 이름 설정. 기본값은 nil. "프로그램 이름 (Program Name)" 참고: Logger.new('t.log', progname: 'mung')
  • formatter: 항목 포매터 설정. 기본값은 nil. formatter= 참고.
  • datetime_format: 항목 타임스탬프 형식 설정. 기본값은 nil. datetime_format= 참고.
  • binmode: 로거가 바이너리 모드로 쓰는지 설정. 기본값은 false.
  • shift_period_suffix: 주기적 로그 파일 로테이션의 파일 이름 접미사 형식 설정. 기본값은 '%Y%m%d'. "주기적 로테이션 (Periodic Rotation)" 참고.

Public Instance Methods

<< (msg)

주어진 msg를 형식 없이 로그에 써요. 쓰여진 문자 수를 돌려주거나, 로그 장치가 없으면 nil을 돌려줘요.

logger = Logger.new($stdout)
logger << 'My message.' # => 10

출력:

My message.

add (severity, message = nil, progname = nil) { || ... }

로그 항목을 만들어요. 항목의 심각도와 로그 레벨에 따라 로그에 쓰일 수도 있고 아닐 수도 있어요. 자세한 내용은 "로그 레벨 (Log Level)"과 "항목 (Entries)"을 참고하세요.

예시:

logger = Logger.new($stdout, progname: 'mung')
logger.add(Logger::INFO)
logger.add(Logger::ERROR, 'No good')
logger.add(Logger::ERROR, 'No good', 'gnum')

출력:

I, [2022-05-12T16:25:31.469726 #36328]  INFO -- mung: mung
E, [2022-05-12T16:25:55.349414 #36328] ERROR -- mung: No good
E, [2022-05-12T16:26:35.841134 #36328] ERROR -- gnum: No good

이런 편의 메서드들은 암묵적 심각도를 가져요.

  • debug
  • info
  • warn
  • error
  • fatal
  • unknown

close ()

로거를 닫고 nil을 돌려줘요.

logger = Logger.new('t.log')
logger.close       # => nil
logger.info('foo') # Prints "log writing failed. closed stream"

관련: Logger#reopen.

datetime_format ()

날짜-시간 형식을 돌려줘요. datetime_format= 참고.

datetime_format= (datetime_format)

날짜-시간 형식을 설정해요. 인자 datetime_format은 다음 중 하나여야 해요.

  • Time#strftime 메서드의 형식으로 쓰기에 적합한 문자열.
  • nil: 로거가 '%Y-%m-%dT%H:%M:%S.%6N'을 사용해요.

debug (progname = nil, &block)

심각도 Logger::DEBUGadd를 호출하는 것과 같아요.

debug! ()

로그 레벨을 Logger::DEBUG로 설정해요. "로그 레벨 (Log Level)" 참고.

debug? ()

로그 레벨이 심각도 Logger::DEBUG의 항목을 쓰는 것을 허용하면 true, 아니면 false를 돌려줘요. "로그 레벨 (Log Level)" 참고.

error (progname = nil, &block)

심각도 Logger::ERRORadd를 호출하는 것과 같아요.

error! ()

로그 레벨을 Logger::ERROR로 설정해요. "로그 레벨 (Log Level)" 참고.

error? ()

로그 레벨이 심각도 Logger::ERROR의 항목을 쓰는 것을 허용하면 true, 아니면 false를 돌려줘요. "로그 레벨 (Log Level)" 참고.

fatal (progname = nil, &block)

심각도 Logger::FATALadd를 호출하는 것과 같아요.

fatal! ()

로그 레벨을 Logger::FATAL로 설정해요. "로그 레벨 (Log Level)" 참고.

fatal? ()

로그 레벨이 심각도 Logger::FATAL의 항목을 쓰는 것을 허용하면 true, 아니면 false를 돌려줘요. "로그 레벨 (Log Level)" 참고.

info (progname = nil, &block)

심각도 Logger::INFOadd를 호출하는 것과 같아요.

info! ()

로그 레벨을 Logger::INFO로 설정해요. "로그 레벨 (Log Level)" 참고.

info? ()

로그 레벨이 심각도 Logger::INFO의 항목을 쓰는 것을 허용하면 true, 아니면 false를 돌려줘요. "로그 레벨 (Log Level)" 참고.

level ()

로깅 심각도 임계값(threshold, 예: Logger::INFO).

level= (severity)

로그 레벨을 설정하고 severity를 돌려줘요. "로그 레벨 (Log Level)" 참고. 인자 severity는 정수, 문자열, 또는 심볼일 수 있어요.

logger.level = Logger::ERROR # => 3
logger.level = 3             # => 3
logger.level = 'error'       # => "error"
logger.level = :error        # => :error

Logger#sev_threshold=Logger#level=의 별칭이에요.

log (severity, message = nil, progname = nil)

reopen (logdev = nil)

로거의 출력 스트림을 설정해요.

  • logdevnil이면 현재 출력 스트림을 다시 열어요.
  • logdev가 파일 경로이면 해당 파일을 append용으로 열어요.
  • logdev가 IO 스트림(보통 $stdout, $stderr, 또는 열린 File 객체)이면 그 스트림을 append용으로 열어요.

예시:

logger = Logger.new('t.log')
logger.add(Logger::ERROR, 'one')
logger.close
logger.add(Logger::ERROR, 'two') # Prints 'log writing failed. closed stream'
logger.reopen
logger.add(Logger::ERROR, 'three')
logger.close
File.readlines('t.log')
# =>
# ["# Logfile created on 2022-05-12 14:21:19 -0500 by logger.rb/v1.5.0\n",
#  "E, [2022-05-12T14:21:27.596726 #22428] ERROR -- : one\n",
#  "E, [2022-05-12T14:23:05.847241 #22428] ERROR -- : three\n"]

sev_threshold ()

sev_threshold= (severity)

unknown (progname = nil, &block)

심각도 Logger::UNKNOWN으로 add를 호출하는 것과 같아요.

warn (progname = nil, &block)

심각도 Logger::WARN으로 add를 호출하는 것과 같아요.

warn! ()

로그 레벨을 Logger::WARN으로 설정해요. "로그 레벨 (Log Level)" 참고.

warn? ()

로그 레벨이 심각도 Logger::WARN의 항목을 쓰는 것을 허용하면 true, 아니면 false를 돌려줘요. "로그 레벨 (Log Level)" 참고.

with_level (severity) { || ... }

현재 Fiber에 대해서만 블록을 실행하는 동안 로그 레벨을 조정해요.

logger.with_level(:debug) do
  logger.debug { "Hello" }
end

Private Instance Methods

format_message (severity, datetime, progname, msg)

format_severity (severity)