[Final] PEP 391 - Dictionary-Based Configuration For Logging

원문 링크: PEP 391 - Dictionary-Based Configuration For Logging

상태: Final 유형: Standards Track 작성일: 15-Oct-2009

PEP 391 – 로깅을 위한 딕셔너리 기반 설정

개요

이 PEP(Python Enhancement Proposal)는 딕셔너리를 사용하여 로깅(logging) 설정을 구성하는 새로운 방법을 제안합니다.

도입 배경 (Rationale)

현재 Python의 logging 패키지를 설정하는 방법은 두 가지입니다. 하나는 로깅 API를 사용하여 프로그래밍 방식으로 설정하는 것이고, 다른 하나는 ConfigParser 기반 설정 파일을 사용하는 것입니다.

  1. 프로그래밍 방식 설정의 한계:
    • 최대한의 제어는 가능하지만, 설정이 Python 코드 내에 고정됩니다.
    • 런타임(runtime)에 설정을 쉽게 변경하기 어렵습니다.
    • 결과적으로 애플리케이션의 특정 부분에 대한 로깅 상세도(verbosity)를 유연하게 조절하는 기능이 제한됩니다.
    • 이는 문제 진단을 위한 로깅의 유용성을 떨어뜨립니다. 특히 프로덕션 환경에서는 로깅이 유일한 진단 도구가 될 수 있습니다.
  2. ConfigParser 기반 설정의 한계:
    • 사용은 가능하지만, logging 패키지의 모든 측면을 설정할 수는 없습니다. 예를 들어, Filter는 이 시스템을 사용하여 설정할 수 없습니다.
    • ConfigParser 형식 자체가 일부 사용자들 사이에서 강한 불만을 야기합니다.
    • 당시 Python 표준 라이브러리에서 유일하게 지원되는 설정 형식이었기 때문에 선택되었지만, 많은 사람들이 이 형식을 ‘낡았거나(crufty)’ ‘보기 싫다(ugly)’고 여깁니다.

새로운 딕셔너리 기반 설정의 필요성: 최근 Python 버전에는 표준 라이브러리에 JSON 지원이 포함되어 있으며, 이는 설정 형식으로도 활용될 수 있습니다. Google App Engine과 같은 다른 환경에서는 YAML이 애플리케이션 설정에 사용되며, 로깅 설정도 애플리케이션 설정의 필수적인 부분으로 간주됩니다. 표준 라이브러리에는 현재 YAML 지원이 없지만, JSON과 YAML은 모두 Python 딕셔너리로 역직렬화(deserialization)될 수 있으므로 공통된 방식으로 지원을 제공할 수 있습니다.

딕셔너리를 통해 로깅 설정을 전달하는 방법을 제공함으로써, JSON 및/또는 YAML 사용자뿐만 아니라 사용자 정의 설정 방법을 사용하는 사용자도 원하는 설정을 설명하는 공통 형식을 통해 로깅을 더 쉽게 설정할 수 있게 됩니다.

기존 ConfigParser 기반 설정 시스템의 또 다른 단점은 증분(incremental) 설정을 지원하지 않는다는 것입니다. 새로운 설정은 기존 설정을 완전히 대체합니다. 다중 스레드 환경에서 증분 설정에 대한 완전한 유연성을 제공하기는 어렵지만, 새로운 설정 메커니즘은 제한적인 증분 설정 지원을 가능하게 할 것입니다.

사양 (Specification)

사양은 두 부분으로 구성됩니다: API와 설정 정보를 전달하는 데 사용되는 딕셔너리의 형식(즉, 준수해야 하는 스키마).

명명 (Naming)

역사적으로 logging 패키지는 PEP 8을 준수하지 않았습니다. 향후 PEP 8을 준수하도록 패키지 내의 메서드 및 함수 이름을 변경하여 수정될 예정입니다. 그러나 균일성을 위해 제안된 API 추가 사항은 현재 logging에서 사용되는 방식과 일관된 명명 체계를 따릅니다.

API

logging.config 모듈에 다음 기능이 추가될 것입니다.

  • dictConfig() 함수: 설정 정보를 담고 있는 딕셔너리를 단일 인자로 받습니다. 딕셔너리 처리 중 오류가 발생하면 예외(Exception)가 발생합니다.
  • 이 API는 사용자 정의가 가능합니다 (API 사용자 정의 섹션 참조). 증분 설정은 별도의 섹션에서 다룹니다.

딕셔너리 스키마 - 개요 (Dictionary Schema - Overview)

스키마를 자세히 설명하기 전에 객체 연결, 사용자 정의 객체 지원, 외부 및 내부 객체 접근에 대해 간략히 설명합니다.

객체 연결 (Object connections)

이 스키마는 로거(loggers), 핸들러(handlers), 포매터(formatters), 필터(filters)와 같이 객체 그래프(object graph)로 서로 연결된 로깅 객체 집합을 설명하는 것을 목표로 합니다. 따라서 스키마는 객체 간의 연결을 나타내야 합니다. 예를 들어, 특정 로거에 특정 핸들러가 연결되어 있다고 가정합니다. 설정 딕셔너리에서는 각 대상 객체에 고유한 id를 부여하고, 이 id를 소스 객체의 설정에서 사용하여 소스와 대상 객체 간의 연결을 나타냅니다.

예시 (YAML 스니펫):

formatters:
  brief: # 'brief' id를 가진 포매터의 설정
  precise: # 'precise' id를 가진 포매터의 설정
handlers:
  h1: # 핸들러 id
    formatter: brief
  h2: # 다른 핸들러 id
    formatter: precise
loggers:
  foo.bar.baz:
    handlers: [h1, h2]

위 예시는 foo.bar.baz 로거에 h1h2 두 핸들러가 연결되어야 함을 나타냅니다. h1의 포매터는 brief id로, h2의 포매터는 precise id로 설명됩니다.

로거의 idfoo.bar.baz와 같이 프로그래밍 방식으로 로거에 대한 참조를 얻는 데 사용되는 로거 이름입니다. FormatterFilterid는 어떤 문자열 값(예: brief, precise)이든 될 수 있으며, 이들은 설정을 처리하고 객체 간의 연결을 결정하는 데만 의미가 있고, 설정 호출이 완료된 후에는 어디에도 유지되지 않는 일시적인 값입니다.

핸들러 id는 특별히 처리됩니다 (아래 ‘핸들러 ID’ 섹션 참조).

사용자 정의 객체 (User-defined objects)

스키마는 핸들러, 필터, 포매터를 위한 사용자 정의 객체를 지원해야 합니다. (로거는 인스턴스마다 다른 유형을 가질 필요가 없으므로, 설정에서 사용자 정의 로거 클래스에 대한 지원은 없습니다.)

설정될 객체는 일반적으로 세부 설정이 포함된 딕셔너리로 설명됩니다. 로깅 시스템은 일부 경우에 문맥(context)에서 객체가 어떻게 인스턴스화될지 추론할 수 있지만, 사용자 정의 객체가 인스턴스화될 때는 시스템이 이를 알지 못합니다. 사용자 정의 객체 인스턴스화에 대한 완전한 유연성을 제공하기 위해, 사용자는 ‘팩토리(factory)’를 제공해야 합니다. 팩토리는 설정 딕셔너리를 인자로 받아 인스턴스화된 객체를 반환하는 호출 가능(callable) 객체입니다. 이는 특수 키 '()' 아래에 팩토리로의 절대 임포트 경로(absolute import path)를 제공함으로써 이루어집니다.

예시:

formatters:
  brief:
    format: '%(message)s'
  default:
    format: '%(asctime)s %(levelname)-8s %(name)-15s %(message)s'
    datefmt: '%Y-%m-%d %H:%M:%S'
  custom:
    '()': my.package.customFormatterFactory # 사용자 정의 팩토리
    bar: baz
    spam: 99.9
    answer: 42

위 YAML 스니펫은 세 개의 포매터를 정의합니다.

  • briefdefault 포매터는 '()' 키를 포함하지 않으므로, 표준 logging.Formatter 인스턴스가 생성됩니다.
  • custom 포매터는 특수 키 '()'를 포함하며, my.package.customFormatterFactory라는 사용자 정의 팩토리 호출 가능 객체를 사용합니다. 이 팩토리는 나머지 키-값 쌍(bar, spam, answer)을 키워드 인자로 받아 호출됩니다.

'()'는 유효한 키워드 파라미터 이름이 아니므로 호출에 사용되는 키워드 인자 이름과 충돌하지 않으며, 해당 값이 callable이라는 것을 기억하기 쉽게 합니다.

외부 객체 접근 (Access to external objects)

설정이 sys.stderr와 같은 외부 객체를 참조해야 하는 경우가 있습니다. 설정 딕셔너리가 Python 코드로 구성되면 간단하지만, 설정이 텍스트 파일(예: JSON, YAML)을 통해 제공될 때 문제가 발생합니다. 텍스트 파일에서는 sys.stderr와 리터럴 문자열 'sys.stderr'를 구별하는 표준적인 방법이 없습니다.

이러한 구분을 용이하게 하기 위해, 설정 시스템은 문자열 값에서 특정 특수 접두사를 찾아 특별히 처리합니다. 예를 들어, 'ext://sys.stderr'라는 리터럴 문자열이 설정에서 값으로 제공되면, ext://가 제거되고 나머지 값은 일반적인 임포트 메커니즘을 사용하여 처리됩니다.

이러한 접두사 처리는 프로토콜 처리와 유사한 방식으로 이루어집니다: ^(?P<prefix>[a-z]+)://(?P<suffix>.*)$ 정규 표현식과 일치하는 접두사(prefix)를 찾는 일반적인 메커니즘이 있으며, 접두사가 인식되면 접미사(suffix)는 접두사에 따라 처리되고 처리 결과가 문자열 값을 대체합니다.

구현은 ext://와 같은 표준 접두사 세트를 제공하지만, 이 메커니즘을 완전히 비활성화하거나 특별한 처리를 위해 추가 또는 다른 접두사를 제공할 수도 있습니다.

내부 객체 접근 (Access to internal objects)

외부 객체 외에도 설정 내의 객체를 참조해야 할 때도 있습니다. 이는 설정 시스템이 알고 있는 항목에 대해 암묵적으로 수행됩니다. 예를 들어, 로거 또는 핸들러의 레벨(level)에 대한 문자열 값 'DEBUG'는 자동으로 logging.DEBUG 값으로 변환되며, handlers, filters, formatter 항목은 객체 id를 받아 적절한 대상 객체로 확인됩니다.

그러나 logging에 알려지지 않은 사용자 정의 객체의 경우, 더 일반적인 메커니즘이 필요합니다. 예를 들어, 다른 핸들러에 위임하는 대상(target)을 인자로 받는 logging.handlers.MemoryHandler 인스턴스를 생각해봅시다. 시스템이 이 클래스를 이미 알고 있으므로, 설정에서 주어진 target은 관련 대상 핸들러의 객체 id이기만 하면 시스템은 id로부터 핸들러를 확인합니다.

하지만 사용자가 alternate 핸들러를 가진 my.package.MyHandler를 정의하는 경우, 설정 시스템은 alternate가 핸들러를 참조한다는 것을 알지 못합니다. 이를 위해 사용자가 다음과 같이 지정할 수 있는 일반적인 해결(resolution) 시스템이 제공됩니다.

handlers:
  file: # 파일 핸들러 설정
  custom:
    '()': my.package.MyHandler
    alternate: cfg://handlers.file # 설정 내의 객체 참조

리터럴 문자열 'cfg://handlers.file'ext:// 접두사가 붙은 문자열과 유사한 방식으로 해결되지만, 임포트 네임스페이스 대신 설정 자체를 탐색합니다. 이 메커니즘은 str.format이 제공하는 방식과 유사하게 점(.) 또는 인덱스(index)를 통한 접근을 허용합니다.

핸들러 ID (Handler Ids)

일부 로깅 설정은 원하는 효과를 얻기 위해 핸들러 레벨(handler levels)을 사용해야 합니다. 그러나 이름으로 항상 식별할 수 있는 로거와 달리, 핸들러는 증분 설정 호출을 통해 레벨을 변경할 수 있는 영구적인 핸들(persistent handles)이 없습니다.

따라서 이 PEP는 핸들러에 선택적 name 속성을 추가할 것을 제안합니다. 이 속성이 사용되면, 이름과 핸들러를 매핑하는 딕셔너리에 항목이 추가됩니다. (핸들러가 닫히면 이 항목은 제거됩니다.) 증분 설정 호출이 이루어질 때, 설정 값에 따라 핸들러 레벨을 설정하기 위해 이 딕셔너리에서 핸들러가 조회됩니다. 자세한 내용은 증분 설정 섹션을 참조하십시오.

이러한 “영구 이름(persistent name)” 기능은 FilterFormatter에도 제공될 수 있지만, 이들을 증분적으로 설정할 수 있도록 하는 강력한 근거는 없습니다. 실용성이 순수성보다 중요하기 때문에 Handler에만 이 새로운 name 속성이 부여됩니다. 설정에서 핸들러의 id는 그 name이 됩니다.

핸들러 이름 조회 딕셔너리는 설정 용도로만 사용되며, 패키지의 공개 API의 일부가 되지 않을 것입니다.

딕셔너리 스키마 - 상세 (Dictionary Schema - Detail)

dictConfig()에 전달되는 딕셔너리에는 다음 키가 포함되어야 합니다.

  • version: 스키마 버전을 나타내는 정수 값으로 설정됩니다. 현재 유효한 값은 1뿐이지만, 이 키를 통해 하위 호환성을 유지하면서 스키마를 발전시킬 수 있습니다.

다른 모든 키는 선택 사항이지만, 존재하는 경우 아래 설명된 대로 해석됩니다. 아래에서 ‘설정 딕셔너리(configuring dict)’가 언급되는 모든 경우, 사용자 정의 인스턴스화가 필요한지 확인하기 위해 특수 키 '()'가 검사됩니다. 만약 그렇다면, 위에서 설명된 메커니즘이 인스턴스화에 사용되고, 그렇지 않으면 문맥을 사용하여 인스턴스화 방법을 결정합니다.

  • formatters: 해당 값은 각 키가 포매터 id이고 각 값이 해당 Formatter 인스턴스를 구성하는 방법을 설명하는 딕셔너리가 됩니다.
    • 설정 딕셔너리는 formatdatefmt 키(기본값은 None)를 찾아서 logging.Formatter 인스턴스를 구성하는 데 사용됩니다.
  • filters: 해당 값은 각 키가 필터 id이고 각 값이 해당 Filter 인스턴스를 구성하는 방법을 설명하는 딕셔너리가 됩니다.
    • 설정 딕셔너리는 name 키(기본값은 빈 문자열)를 찾아서 logging.Filter 인스턴스를 구성하는 데 사용됩니다.
  • handlers: 해당 값은 각 키가 핸들러 id이고 각 값이 해당 Handler 인스턴스를 구성하는 방법을 설명하는 딕셔너리가 됩니다.
    • 설정 딕셔너리는 다음 키를 찾습니다:
      • class (필수): 핸들러 클래스의 완전한 이름.
      • level (선택 사항): 핸들러의 레벨.
      • formatter (선택 사항): 이 핸들러의 포매터 id.
      • filters (선택 사항): 이 핸들러의 필터 id 목록.
    • 다른 모든 키는 핸들러의 생성자에 키워드 인자로 전달됩니다.
  • loggers: 해당 값은 각 키가 로거 이름이고 각 값이 해당 Logger 인스턴스를 구성하는 방법을 설명하는 딕셔너리가 됩니다.
    • 설정 딕셔너리는 다음 키를 찾습니다:
      • level (선택 사항): 로거의 레벨.
      • propagate (선택 사항): 로거의 전파(propagation) 설정.
      • filters (선택 사항): 이 로거의 필터 id 목록.
      • handlers (선택 사항): 이 로거의 핸들러 id 목록.
    • 지정된 로거는 명시된 level, propagation, filters, handlers에 따라 구성됩니다.
  • root: 루트 로거(root logger)의 설정이 됩니다. 구성 처리는 다른 로거와 동일하게 이루어지며, propagate 설정은 적용되지 않습니다.

  • incremental: 설정이 기존 설정에 대한 증분(incremental)으로 해석될지 여부를 나타냅니다. 이 값은 기본적으로 False이며, 이는 지정된 설정이 기존 fileConfig() API에서 사용되는 것과 동일한 의미로 기존 설정을 대체한다는 의미입니다.
    • 지정된 값이 True인 경우, 설정은 아래 ‘증분 설정’ 섹션에 설명된 대로 처리됩니다.
  • disable_existing_loggers: 기존 로거를 비활성화할지 여부. 이 설정은 fileConfig()의 동명 파라미터와 동일합니다. 이 파라미터가 없으면 기본값은 True입니다. incrementalTrue인 경우 이 값은 무시됩니다.

작동 예시 (A Working Example)

다음은 YAML 형식의 실제 작동하는 설정 예시입니다 (이메일 주소는 가상의 것입니다).

version: 1 # 스키마 버전
formatters:
  brief:
    format: '%(levelname)-8s: %(name)-15s: %(message)s'
  precise:
    format: '%(asctime)s %(name)-15s %(levelname)-8s %(message)s'
filters:
  allow_foo:
    name: foo # 'foo' 이름의 필터
handlers:
  console:
    class: logging.StreamHandler
    formatter: brief
    level: INFO
    stream: ext://sys.stdout # 외부 객체 sys.stdout 참조
    filters: [allow_foo]
  file:
    class: logging.handlers.RotatingFileHandler
    formatter: precise
    filename: logconfig.log
    maxBytes: 1024
    backupCount: 3
  debugfile:
    class: logging.FileHandler
    formatter: precise
    filename: logconfig-detail.log
    mode: a
  email:
    class: logging.handlers.SMTPHandler
    mailhost: localhost
    fromaddr: my_app@domain.tld
    toaddrs:
    - support_team@domain.tld
    - dev_team@domain.tld
    subject: Houston, we have a problem.
loggers:
  foo:
    level: ERROR
    handlers: [debugfile]
  spam:
    level: CRITICAL
    handlers: [debugfile]
    propagate: no
  bar.baz:
    level: WARNING
root: # 루트 로거 설정
  level: DEBUG
  handlers: [console, file]

증분 설정 (Incremental Configuration)

증분 설정에 대한 완전한 유연성을 제공하기는 어렵습니다. 예를 들어, 필터(filters) 및 포매터(formatters)와 같은 객체는 익명이므로, 일단 설정이 완료되면 설정을 보강할 때 이러한 익명 객체를 참조할 수 없습니다.

또한, 설정이 완료된 후 런타임에 로거, 핸들러, 필터, 포매터의 객체 그래프를 임의로 변경할 강력한 필요성은 없습니다. 로거와 핸들러의 상세도는 레벨(level) 설정(및 로거의 경우 전파(propagation) 플래그)만으로 제어할 수 있습니다. 다중 스레드 환경에서 객체 그래프를 임의로 안전하게 변경하는 것은 문제가 있으며, 불가능하지는 않지만 구현에 추가되는 복잡성만큼의 이점이 없습니다.

따라서, 설정 딕셔너리의 incremental 키가 존재하고 True인 경우, 시스템은 모든 formattersfilters 항목을 완전히 무시하고, handlers 항목의 level 설정, 그리고 loggersroot 항목의 levelpropagate 설정만 처리합니다.

dictConfig()가 기본값이 Falseincremental 키워드 인자를 받도록 하는 등 다른 수단으로 증분 설정을 제공하는 것도 물론 가능합니다. 설정 딕셔너리 내의 값을 사용하도록 제안하는 이유는, 설정이 소켓 리스너(socket listener)로 피클링된(pickled) 딕셔너리 형태로 전송될 수 있도록 하기 위함입니다. 이를 통해 장시간 실행되는 애플리케이션의 로깅 상세도를 애플리케이션을 중지하고 다시 시작할 필요 없이 시간이 지남에 따라 변경할 수 있습니다.

참고: 실제 경험을 바탕으로 한 증분 설정 요구 사항에 대한 피드백은 특히 환영합니다.

API 사용자 정의 (API Customization)

dictConfig() API는 모든 사용 사례에 충분하지 않을 수 있습니다. API 사용자 정의를 위한 조치는 다음을 제공함으로써 이루어집니다.

  • DictConfigurator 클래스: 생성자에는 설정에 사용되는 딕셔너리가 전달되며, configure() 메서드를 가집니다.
  • dictConfigClass 호출 가능 객체: (기본적으로) DictConfigurator로 설정됩니다. 이는 필요한 경우 DictConfigurator를 적절한 사용자 정의 구현으로 대체할 수 있도록 제공됩니다.

dictConfig() 함수는 지정된 딕셔너리를 전달하여 dictConfigClass를 호출한 다음, 반환된 객체의 configure() 메서드를 호출하여 실제로 설정을 적용합니다.

def dictConfig(config):
    dictConfigClass(config).configure()

이는 모든 사용자 정의 요구 사항을 충족시켜야 합니다. 예를 들어, DictConfigurator의 서브클래스(subclass)는 자체 __init__()에서 DictConfigurator.__init__()을 호출한 다음, 후속 configure() 호출에서 사용 가능한 사용자 정의 접두사를 설정할 수 있습니다. dictConfigClass는 서브클래스에 바인딩(bound)되고, dictConfig()는 기본, 사용자 정의되지 않은 상태와 똑같이 호출될 수 있습니다.

소켓 리스너 구현 변경 (Change to Socket Listener Implementation)

기존 소켓 리스너 구현은 다음과 같이 수정될 것입니다. 설정 메시지가 수신되면 json 모듈을 사용하여 딕셔너리로 역직렬화(deserialize)를 시도합니다. 이 단계가 실패하면 메시지는 fileConfig 형식으로 가정하고 이전과 같이 처리됩니다. 역직렬화가 성공하면 dictConfig()가 호출되어 결과 딕셔너리를 처리합니다.

설정 오류 (Configuration Errors)

설정 중에 오류가 발생하면 시스템은 적절하게 설명하는 메시지와 함께 ValueError, TypeError, AttributeError 또는 ImportError를 발생시킵니다. 다음은 오류를 발생시키는 (완전하지 않을 수 있는) 조건 목록입니다.

  • 문자열이 아니거나 실제 로깅 레벨에 해당하지 않는 문자열 레벨
  • 부울(boolean)이 아닌 propagate
  • 해당하는 대상이 없는 id
  • 증분 호출 중 발견된 존재하지 않는 핸들러 id
  • 유효하지 않은 로거 이름
  • 내부 또는 외부 객체를 해결할 수 없는 경우

커뮤니티 논의 (Discussion in the community)

이 PEP는 python-devpython-list에 공지되었습니다. 많은 논의가 있지는 않았지만, 이는 틈새 주제(niche topic)로 예상될 수 있습니다.

python-dev 논의 스레드:

  • https://mail.python.org/pipermail/python-dev/2009-October/092695.html
  • https://mail.python.org/pipermail/python-dev/2009-October/092782.html
  • https://mail.python.org/pipermail/python-dev/2009-October/093062.html

python-list 논의 스레드:

  • https://mail.python.org/pipermail/python-list/2009-October/1223658.html
  • https://mail.python.org/pipermail/python-list/2009-October/1224228.html

이 제안에 대한 긍정적인 의견이 있었고, 제안 전체에 대한 반대는 없었으며, 특정 세부 사항에 대한 질문과 반대가 있었습니다. 작성자는 이러한 문제들이 PEP 변경을 통해 해결되었다고 믿고 있습니다.

참조 구현 (Reference implementation)

변경 사항에 대한 참조 구현은 dictconfig.py 모듈과 함께 test_dictconfig.py에 포함된 단위 테스트로 다음에서 사용할 수 있습니다.

  • http://bitbucket.org/vinay.sajip/dictconfig

이것은 소켓 리스너 변경을 제외한 모든 기능을 포함합니다.

이 문서는 퍼블릭 도메인(public domain)에 있습니다.


최종 수정일: 2025-02-01 08:59:27 GMT## PEP 391 – 로깅을 위한 딕셔너리 기반 설정

개요

이 PEP(Python Enhancement Proposal)는 딕셔너리를 사용하여 로깅(logging) 설정을 구성하는 새로운 방법을 제안합니다.

도입 배경 (Rationale)

현재 Python의 logging 패키지를 설정하는 방법은 크게 두 가지입니다: 로깅 API를 통한 프로그래밍 방식 설정과 ConfigParser 기반 설정 파일 사용입니다.

  1. 프로그래밍 방식 설정의 한계:
    • 최대한의 제어를 제공하지만, 설정이 Python 코드 내에 고정되어 런타임(runtime)에 쉽게 변경하기 어렵습니다.
    • 이로 인해 애플리케이션의 특정 부분에 대한 로깅 상세도(verbosity)를 유연하게 조절하는 기능이 제한되어, 문제 진단에 어려움을 겪을 수 있습니다.
  2. ConfigParser 기반 설정의 한계:
    • 사용은 가능하지만, Filter와 같은 logging 패키지의 모든 요소를 설정할 수 없습니다.
    • ConfigParser 형식 자체가 일부 사용자들 사이에서 불만을 야기하며, 당시 표준 라이브러리에서 유일하게 지원되는 형식이었지만, 많은 사람들이 이를 ‘낡았거나(crufty)’ ‘보기 싫다(ugly)’고 여깁니다.

새로운 딕셔너리 기반 설정의 필요성: 최근 Python 버전은 표준 라이브러리에 JSON 지원을 포함하며, 이는 설정 형식으로도 활용될 수 있습니다. Google App Engine과 같은 환경에서는 YAML이 애플리케이션 설정에 사용됩니다. JSON과 YAML은 모두 Python 딕셔너리로 역직렬화(deserialization)될 수 있으므로, 딕셔너리 기반 설정은 이러한 형식을 사용하는 사용자뿐만 아니라 사용자 정의 설정 방법을 사용하는 사용자에게도 공통적이고 유연한 설정 방식을 제공할 것입니다.

또한, 기존 ConfigParser 기반 시스템은 증분(incremental) 설정을 지원하지 않아 새로운 설정이 기존 설정을 완전히 대체하는 문제가 있었습니다. 새로운 메커니즘은 제한적인 증분 설정 지원을 가능하게 하여, 다중 스레드 환경에서도 로깅 설정을 보다 유연하게 변경할 수 있도록 합니다.

사양 (Specification)

이 PEP의 사양은 API와 설정 정보를 전달하는 데 사용되는 딕셔너리 형식(스키마)으로 구성됩니다.

명명 (Naming)

logging 패키지는 역사적으로 PEP 8을 준수하지 않았지만, 제안된 API 추가 사항은 현재 logging에서 사용되는 명명 체계와 일관성을 유지합니다.

API

logging.config 모듈에 다음 함수가 추가될 예정입니다.

  • dictConfig(config): 설정 딕셔너리를 단일 인자로 받아 로깅 설정을 적용합니다. 처리 중 오류가 발생하면 예외(Exception)를 발생시킵니다.
  • 이 API는 사용자 정의가 가능하며, 증분 설정도 지원합니다.

딕셔너리 스키마 - 개요 (Dictionary Schema - Overview)

스키마는 로거, 핸들러, 포매터, 필터 등 서로 연결된 로깅 객체 그래프를 설명합니다. 객체 간의 연결은 고유한 id를 사용하여 정의됩니다.

객체 연결 (Object connections)

설정 딕셔너리에서는 각 대상 객체에 고유한 id를 부여하고, 이를 소스 객체의 설정에서 참조하여 객체 간의 연결을 나타냅니다.

예시 (YAML):

formatters:
  brief:
  precise:
handlers:
  h1:
    formatter: brief
  h2:
    formatter: precise
loggers:
  foo.bar.baz:
    handlers: [h1, h2]

위 예시에서 foo.bar.baz 로거는 h1, h2 핸들러에 연결되며, 각 핸들러는 brief 또는 precise 포매터를 사용합니다.

로거의 id는 로거 이름(foo.bar.baz)이고, FormatterFilterid는 임의의 문자열 값(brief, precise)으로, 설정 처리 중에만 유효한 일시적인 값입니다.

사용자 정의 객체 (User-defined objects)

스키마는 핸들러, 필터, 포매터를 위한 사용자 정의 객체를 지원합니다. 사용자 정의 객체 인스턴스화를 위해, 설정 딕셔너리에 특수 키 '()'와 함께 객체를 인스턴스화할 ‘팩토리(factory)’의 절대 임포트 경로를 제공해야 합니다. 팩토리는 설정 딕셔너리의 나머지 항목들을 키워드 인자로 받아 호출됩니다.

예시:

formatters:
  custom:
    '()': my.package.customFormatterFactory # 사용자 정의 팩토리
    bar: baz

여기서 '()' 키는 해당 값이 callable 객체임을 나타내며, 키워드 인자 이름과 충돌하지 않도록 선택되었습니다.

외부 객체 접근 (Access to external objects)

sys.stderr와 같은 설정 외부의 객체를 참조해야 할 경우, ext://와 같은 특수 접두사를 사용합니다. 예를 들어, 'ext://sys.stderr'sys.stderr 객체로 해석됩니다. ext:// 접두사는 제거되고 나머지는 일반적인 임포트 메커니즘으로 처리됩니다.

내부 객체 접근 (Access to internal objects)

설정 내의 객체를 참조할 때는 cfg:// 접두사를 사용합니다. 예를 들어, 'cfg://handlers.file'은 설정 딕셔너리 내의 handlers 섹션에 있는 file 핸들러를 참조합니다. 이 메커니즘은 점(.) 또는 인덱스(index)를 통한 접근을 허용합니다.

핸들러 ID (Handler Ids)

핸들러의 레벨을 증분적으로 변경할 수 있도록, 핸들러에 선택적인 name 속성을 추가할 것을 제안합니다. 설정에서 핸들러의 id가 그 name이 되며, 이 이름은 증분 설정 호출 시 핸들러를 조회하는 데 사용됩니다.

딕셔너리 스키마 - 상세 (Dictionary Schema - Detail)

dictConfig()에 전달되는 딕셔너리는 다음 키를 포함해야 합니다.

  • version: 스키마 버전을 나타내는 정수(현재는 1).
  • formatters: 포매터 id와 해당 Formatter 인스턴스 설정을 담는 딕셔너리. format, datefmt 키를 사용합니다.
  • filters: 필터 id와 해당 Filter 인스턴스 설정을 담는 딕셔너리. name 키를 사용합니다.
  • handlers: 핸들러 id와 해당 Handler 인스턴스 설정을 담는 딕셔너리. class (필수), level, formatter, filters 키를 포함합니다.
  • loggers: 로거 이름과 해당 Logger 인스턴스 설정을 담는 딕셔너리. level, propagate, filters, handlers 키를 포함합니다.
  • root: 루트 로거(root logger)의 설정. propagate 설정은 적용되지 않습니다.
  • incremental: True로 설정되면 증분 설정으로 처리됩니다(기본값은 False).
  • disable_existing_loggers: 기존 로거를 비활성화할지 여부(기본값은 True, incrementalTrue이면 무시됨).

작동 예시 (A Working Example)

PEP 391 문서에는 YAML 형식으로 작성된 상세한 로깅 설정 예시가 포함되어 있습니다. 이 예시는 formatters, filters, handlers, loggers, root 섹션을 사용하여 로깅의 다양한 측면(예: 콘솔 출력, 파일 로테이션, 이메일 알림 등)을 구성하는 방법을 보여줍니다.

증분 설정 (Incremental Configuration)

incremental 키가 True인 경우, 시스템은 formattersfilters 항목을 무시하고, handlers 항목의 level 설정, 그리고 loggersroot 항목의 levelpropagate 설정만 처리합니다. 이는 장시간 실행되는 애플리케이션의 로깅 상세도를 애플리케이션을 중지하고 다시 시작할 필요 없이 런타임에 변경할 수 있도록 합니다.

API 사용자 정의 (API Customization)

dictConfig() API의 사용자 정의를 위해 DictConfigurator 클래스와 dictConfigClass 호출 가능 객체가 제공됩니다. dictConfigClass를 사용자 정의 구현으로 대체하여 dictConfig()의 동작을 확장할 수 있습니다.

소켓 리스너 구현 변경 (Change to Socket Listener Implementation)

기존 소켓 리스너 구현은 설정 메시지를 수신했을 때 json 모듈을 사용하여 딕셔너리로 역직렬화를 시도하고, 성공하면 dictConfig()를 호출하여 처리하도록 수정될 것입니다.

설정 오류 (Configuration Errors)

설정 중에 유효하지 않은 레벨, 부울이 아닌 propagate 값, 존재하지 않는 id, 유효하지 않은 로거 이름, 객체 해결 실패 등 오류가 발생하면 ValueError, TypeError, AttributeError 또는 ImportError가 발생합니다.

커뮤니티 논의 및 참조 구현 (Discussion in the community & Reference implementation)

이 PEP는 python-devpython-list 메일링 리스트에서 논의되었으며, 제안에 대한 전반적인 반대는 없었습니다. 변경 사항에 대한 참조 구현은 dictconfig.py 모듈과 단위 테스트로 제공됩니다.


이 문서는 퍼블릭 도메인에 있습니다. 최종 수정일: 2025-02-01 08:59:27 GMT

⚠️ 알림: 이 문서는 AI를 활용하여 번역되었으며, 기술적 정확성을 보장하지 않습니다. 정확한 내용은 반드시 원문을 확인하시기 바랍니다.

Comments