PEP 391 – 로깅을 위한 딕셔너리 기반 설정
- Author:
- Vinay Sajip <vinay_sajip at red-dove.com>
- Status:
- Final
- Type:
- Standards Track
- Created:
- 15-Oct-2009
- Python-Version:
- 2.7, 3.2
- Post-History:
Table of Contents
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
초록
이 PEP는 설정 정보를 담는 딕셔너리를 사용하여 로깅을 설정하는 새로운 방법을 설명합니다.
근거
현재 Python의 logging 패키지를 설정하는 방법은 logging API를 사용하여 프로그래밍 방식으로 로깅을 설정하거나, ConfigParser 기반 설정 파일을 사용하는 것입니다.
프로그래밍 방식의 설정은 최대한의 제어권을 제공하지만, 설정을 Python 코드 안에 고정합니다. 이 때문에 런타임에 설정을 쉽게 변경하기 어렵고, 결과적으로 로깅을 사용하는 애플리케이션의 각 부분별로 로깅의 상세 수준을 유연하게 높이거나 낮추는 능력을 잃게 됩니다. 이는 문제 진단을 돕는 수단으로서 로깅의 유용성을 제한합니다. 그리고 때로는 로깅이 운영 환경에서 사용할 수 있는 유일한 진단 수단이기도 합니다.
ConfigParser 기반 설정 시스템은 사용할 수는 있지만, 사용자가 logging 패키지의 모든 측면을 설정할 수 있도록 하지는 않습니다. 예를 들어, 이 시스템으로는 Filter를 설정할 수 없습니다. 게다가 ConfigParser 형식은 일부에서 반감(때로는 강한 반감)을 사는 것으로 보입니다. 이 형식은 당시 Python 표준에서 지원하는 유일한 설정 형식이었기 때문에 선택되었지만, 많은 사람들은 이 형식(또는 아마도 로깅 설정을 위해 선택된 특정 스키마만)을 ‘낡았다’거나 ‘보기 흉하다’고 여기며, 어떤 경우에는 순전히 미적인 이유 때문인 것으로 보입니다.
최신 버전의 Python은 표준 라이브러리에 JSON 지원을 포함하고 있으며, 이 역시 설정 형식으로 사용할 수 있습니다. Google App Engine과 같은 다른 환경에서는 애플리케이션을 설정하는 데 YAML을 사용하며, 보통 로깅 설정은 애플리케이션 설정의 필수적인 부분으로 간주됩니다. 현재 표준 라이브러리에는 YAML 지원이 포함되어 있지 않지만, 두 직렬화 형식 모두 Python 딕셔너리로 역직렬화할 수 있으므로 JSON과 YAML 모두에 대한 지원을 공통된 방식으로 제공할 수 있습니다.
설정을 딕셔너리로 전달하여 로깅을 설정하는 방법을 제공하면, 원하는 설정을 기술할 공통 형식이 마련되므로 JSON 및/또는 YAML 사용자뿐만 아니라 사용자 정의 설정 방법을 쓰는 사용자도 로깅을 더 쉽게 설정할 수 있게 됩니다.
현재 ConfigParser 기반 설정 시스템의 또 다른 단점은 점진적 설정을 지원하지 않는다는 것입니다. 새 설정이 기존 설정을 완전히 대체합니다. 다중 스레드 환경에서 점진적 설정에 대한 완전한 유연성을 제공하기는 어렵지만, 새로운 설정 메커니즘은 점진적 설정을 제한적으로 지원할 수 있게 합니다.
명세
명세는 두 부분으로 구성됩니다. 하나는 API이고, 다른 하나는 설정 정보를 전달하는 데 사용되는 딕셔너리의 형식(즉, 딕셔너리가 따라야 하는 스키마)입니다.
명명
역사적으로 logging 패키지는 PEP 8을 준수하지 않았습니다. 향후 언젠가 PEP 8을 준수하도록 패키지의 메서드 및 함수 이름을 변경하여 이를 바로잡을 것입니다. 하지만 통일성을 위해, API에 제안된 추가 사항은 logging이 현재 사용하는 명명 방식과 일치하는 명명 방식을 사용합니다.
API
logging.config 모듈에는 다음이 추가됩니다:
dictConfig()라는 함수로, 설정을 담은 딕셔너리 하나를 인자로 받습니다. 딕셔너리를 처리하는 중에 오류가 있으면 예외가 발생합니다.
이 API는 사용자 정의할 수 있습니다. API Customization 절을 참조하십시오. Incremental Configuration은 별도의 절에서 다룹니다.
딕셔너리 스키마 - 개요
스키마를 자세히 설명하기 전에, 객체 연결, 사용자 정의 객체 지원, 외부 및 내부 객체 접근에 대해 몇 가지 짚고 넘어갈 필요가 있습니다.
객체 연결
이 스키마는 객체 그래프 안에서 서로 연결된 로깅 객체들, 즉 로거, 핸들러, 포매터, 필터의 집합을 기술하기 위한 것입니다. 따라서 스키마는 객체 사이의 연결을 표현할 수 있어야 합니다. 예를 들어, 구성이 끝나면 특정 로거에 특정 핸들러가 연결되어 있다고 가정해 봅시다. 이 논의에서는 둘 사이의 연결에서 로거가 출발점을, 핸들러가 도착점을 나타낸다고 할 수 있습니다. 물론 구성된 객체에서는 로거가 핸들러에 대한 참조를 가지는 방식으로 이것이 표현됩니다. 구성 딕셔너리에서는 각 도착점 객체에 그 객체를 모호함 없이 식별하는 id를 부여한 다음, 출발점 객체의 구성에서 그 id를 사용하여 출발점 객체와 해당 id를 가진 도착점 객체 사이에 연결이 존재함을 나타내는 방식으로 이를 표현합니다.
예를 들어, 다음 YAML 코드 조각을 살펴보십시오.:
formatters:
brief:
# configuration for formatter with id 'brief' goes here
precise:
# configuration for formatter with id 'precise' goes here
handlers:
h1: #This is an id
# configuration of handler with id 'h1' goes here
formatter: brief
h2: #This is another id
# configuration of handler with id 'h2' goes here
formatter: precise
loggers:
foo.bar.baz:
# other configuration for logger 'foo.bar.baz'
handlers: [h1, h2]
(참고: 이 문서에서는 YAML을 사용합니다. 동등한 딕셔너리를 Python 소스 형태로 나타낸 것보다 YAML이 조금 더 읽기 쉽기 때문입니다.)
로거의 id는 프로그램에서 해당 로거에 대한 참조를 얻을 때 사용하는 로거 이름입니다. 예를 들면 foo.bar.baz입니다. 포매터와 필터의 id는 임의의 문자열 값(위의 brief, precise 등)일 수 있으며 일시적입니다. 즉, 구성 딕셔너리를 처리할 때에만 의미가 있고 객체 사이의 연결을 결정하는 데에만 사용되며, 구성 호출이 끝나면 어디에도 보존되지 않습니다.
핸들러 id는 특별하게 취급됩니다. 아래의 Handler Ids 절을 참고하십시오.
위 코드 조각은 foo.bar.baz라는 이름의 로거에 핸들러 두 개가 연결되어야 하며, 이 핸들러들은 핸들러 id h1과 h2로 기술된다는 것을 나타냅니다. h1의 포매터는 id brief로 기술되는 포매터이고, h2의 포매터는 id precise로 기술되는 포매터입니다.
사용자 정의 객체
스키마는 핸들러, 필터, 포매터에 대해 사용자 정의 객체를 지원해야 합니다. (로거는 인스턴스마다 다른 타입을 가질 필요가 없으므로, 구성에서는 사용자 정의 로거 클래스를 지원하지 않습니다.)
구성할 객체는 일반적으로 그 구성을 상세히 기술하는 딕셔너리로 기술됩니다. 어떤 곳에서는 로깅 시스템이 객체를 어떻게 인스턴스화할지를 문맥에서 추론할 수 있지만, 사용자 정의 객체를 인스턴스화해야 할 때에는 시스템이 그 방법을 알지 못합니다. 사용자 정의 객체의 인스턴스화에 완전한 유연성을 제공하려면, 사용자는 ‘팩토리’, 즉 구성 딕셔너리를 인자로 받아 호출되고 인스턴스화된 객체를 반환하는 호출 가능 객체를 제공해야 합니다. 이는 특수 키 '()' 아래에 팩토리의 절대 임포트 경로를 제공하는 것으로 표시됩니다. 다음은 구체적인 예입니다.:
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 코드 조각은 포매터 세 개를 정의합니다. id가 brief인 첫 번째 포매터는 지정된 형식 문자열을 가진 표준 logging.Formatter 인스턴스입니다. id가 default인 두 번째 포매터는 더 긴 형식을 가지며 시간 형식도 명시적으로 정의하고, 그 결과 이 두 형식 문자열로 초기화된 logging.Formatter가 만들어집니다. Python 소스 형태로 나타내면, brief와 default 포매터는 다음과 같은 설정 하위 딕셔너리를 가집니다:
{
'format' : '%(message)s'
}
그리고:
{
'format' : '%(asctime)s %(levelname)-8s %(name)-15s %(message)s',
'datefmt' : '%Y-%m-%d %H:%M:%S'
}
이며, 이 딕셔너리들에는 특수 키 '()'가 들어 있지 않으므로 인스턴스화 방식은 문맥에서 추론됩니다. 그 결과 표준 logging.Formatter 인스턴스가 생성됩니다. id가 custom인 세 번째 포매터의 구성 하위 딕셔너리는 다음과 같으며:
{
'()' : 'my.package.customFormatterFactory',
'bar' : 'baz',
'spam' : 99.9,
'answer' : 42
}
여기에는 특수 키 '()'가 들어 있는데, 이는 사용자 정의 인스턴스화를 원한다는 뜻입니다. 이 경우 지정된 팩토리 호출 가능 객체가 사용됩니다. 그것이 실제 호출 가능 객체라면 그대로 사용되고, 그렇지 않고 (예제에서처럼) 문자열을 지정하면 일반적인 임포트 메커니즘을 사용하여 실제 호출 가능 객체를 찾습니다. 호출 가능 객체는 구성 하위 딕셔너리의 나머지 항목들을 키워드 인자로 받아 호출됩니다. 위 예제에서 id가 custom인 포매터는 다음 호출이 반환한 것으로 간주됩니다.:
my.package.customFormatterFactory(bar='baz', spam=99.9, answer=42)
'()' 키는 유효한 키워드 매개변수 이름이 아니어서 호출에 사용되는 키워드 인자의 이름과 충돌하지 않으므로 특수 키로 사용되었습니다. '()'는 해당 값이 호출 가능 객체라는 것을 떠올리게 하는 연상 기호 역할도 합니다.
외부 객체에 대한 접근
설정이 설정 외부의 객체(예를 들어 sys.stderr)를 참조해야 할 때가 있습니다. 설정 딕셔너리를 Python 코드로 구성한다면 이는 간단하지만, 설정이 텍스트 파일(예: JSON, YAML)로 제공될 때는 문제가 생깁니다. 텍스트 파일에서는 sys.stderr와 리터럴 문자열 'sys.stderr'를 구별할 표준 방법이 없습니다. 이 구별을 쉽게 하기 위해, 설정 시스템은 문자열 값에서 특정한 특수 접두사를 찾아 특별하게 처리합니다. 예를 들어 리터럴 문자열 'ext://sys.stderr'가 설정의 값으로 제공되면, ext://가 제거되고 값의 나머지 부분은 일반적인 임포트 메커니즘으로 처리됩니다.
이러한 접두사는 프로토콜 처리와 유사한 방식으로 처리됩니다. 정규식 ^(?P<prefix>[a-z]+)://(?P<suffix>.*)$와 일치하는 접두사를 찾는 범용 메커니즘이 있으며, 이 메커니즘에서는 prefix가 인식되면 suffix가 접두사에 따른 방식으로 처리되고 그 처리 결과가 문자열 값을 대체합니다. 접두사가 인식되지 않으면 문자열 값은 그대로 유지됩니다.
구현은 ext:// 같은 표준 접두사 집합을 제공하지만, 이 메커니즘을 완전히 비활성화하거나 특수 처리를 위한 추가 접두사 또는 다른 접두사를 제공할 수도 있습니다.
내부 객체에 대한 접근
외부 객체뿐만 아니라 설정 안의 객체를 참조해야 할 때도 있습니다. 설정 시스템이 알고 있는 항목에 대해서는 설정 시스템이 이를 암묵적으로 처리합니다. 예를 들어 로거나 핸들러의 level에 대한 문자열 값 'DEBUG'는 자동으로 logging.DEBUG 값으로 변환되며, handlers, filters, formatter 항목은 객체 id를 받아 적절한 대상 객체로 해석됩니다.
그러나 logging이 알지 못하는 사용자 정의 객체의 경우를 위해 더 범용적인 메커니즘이 제공되어야 합니다. 예를 들어 logging.handlers.MemoryHandler의 인스턴스를 생각해 보십시오. 이 인스턴스는 위임할 다른 핸들러인 target을 받습니다. 시스템이 이미 이 클래스를 알고 있으므로 설정에서 주어진 target은 관련 대상 핸들러의 객체 id이기만 하면 되며, 시스템이 그 id로부터 핸들러를 찾아냅니다. 그러나 사용자가 alternate 핸들러를 가진 my.package.MyHandler를 정의한다면, 설정 시스템은 alternate가 핸들러를 가리킨다는 것을 알 수 없습니다. 이를 위해, 사용자가 다음과 같이 지정할 수 있게 하는 범용 해석 시스템이 제공됩니다:
handlers:
file:
# configuration of file handler goes here
custom:
(): my.package.MyHandler
alternate: cfg://handlers.file
리터럴 문자열 'cfg://handlers.file'은 ext:// 접두사가 붙은 문자열과 유사한 방식으로 해석되지만, 임포트 네임스페이스가 아니라 설정 자체에서 찾습니다. 이 메커니즘은 str.format이 제공하는 것과 비슷한 방식으로 점이나 인덱스를 통한 접근을 허용합니다. 따라서 다음 코드 조각이:
handlers:
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.
설정에 있을 때, 문자열 'cfg://handlers'는 키 handlers를 가진 딕셔너리로 해석되고, 문자열 'cfg://handlers.email은 handlers 딕셔너리 안에서 키 email을 가진 딕셔너리로 해석되는 식입니다. 문자열 'cfg://handlers.email.toaddrs[1]은 'dev_team.domain.tld'로 해석되고, 문자열 'cfg://handlers.email.toaddrs[0]'은 값 'support_team@domain.tld'로 해석됩니다. subject 값에는 'cfg://handlers.email.subject' 또는 이와 동등한 'cfg://handlers.email[subject]'를 사용하여 접근할 수 있습니다. 후자의 형식은 키에 공백이나 영숫자가 아닌 문자가 포함된 경우에만 사용하면 됩니다. 인덱스 값이 십진수 숫자로만 이루어져 있으면 해당하는 정수 값을 사용하여 접근을 시도하고, 필요하면 문자열 값으로 대체합니다.
문자열 cfg://handlers.myhandler.mykey.123이 주어지면, 이는 config_dict['handlers']['myhandler']['mykey']['123']으로 해석됩니다. 문자열이 cfg://handlers.myhandler.mykey[123]으로 지정되면, 시스템은 config_dict['handlers']['myhandler']['mykey'][123]에서 값을 가져오려고 시도하고, 실패하면 config_dict['handlers']['myhandler']['mykey']['123']으로 대체합니다.
핸들러 ID
일부 특정 로깅 구성에서는 원하는 효과를 얻기 위해 핸들러 수준을 사용해야 합니다. 그러나 항상 이름으로 식별할 수 있는 로거와 달리, 핸들러에는 증분 구성 호출을 통해 수준을 변경할 수 있게 해 주는 영구적인 핸들이 없습니다.
따라서 이 PEP는 핸들러에 선택적인 name 속성을 추가할 것을 제안합니다. 이 속성을 사용하면 이름을 핸들러에 매핑하는 딕셔너리에 항목이 추가됩니다. (이 항목은 핸들러가 닫힐 때 제거됩니다.) 증분 구성 호출이 이루어지면 이 딕셔너리에서 핸들러를 조회하여 구성의 값에 따라 핸들러 수준을 설정합니다. 자세한 내용은 Incremental Configuration 절을 참조하십시오.
이론적으로는 이러한 “영구적인 이름” 기능을 Filter와 Formatter에도 제공할 수 있습니다. 그러나 이들을 증분 방식으로 구성할 수 있어야 한다는 강력한 근거는 없습니다. 실용성이 순수성보다 낫다는 원칙에 따라, 이 새로운 name 속성은 핸들러에만 부여됩니다. 구성에서 핸들러의 id가 그 핸들러의 name이 됩니다.
핸들러 이름 조회 딕셔너리는 구성 용도로만 사용되며, 패키지 공개 API의 일부가 되지 않습니다.
딕셔너리 스키마 - 세부 사항
dictConfig()에 전달되는 딕셔너리에는 다음 키가 반드시 포함되어야 합니다:
version- 스키마 버전을 나타내는 정수 값으로 설정합니다. 현재 유효한 값은 1뿐이지만, 이 키가 있으면 하위 호환성을 유지하면서 스키마를 발전시킬 수 있습니다.
그 밖의 모든 키는 선택 사항이지만, 존재하는 경우 아래에 설명된 대로 해석됩니다. 아래에서 ‘구성 딕셔너리’가 언급되는 모든 경우에는 사용자 정의 인스턴스 생성이 필요한지 확인하기 위해 특수한 '()' 키가 있는지 검사합니다. 필요한 경우 위에서 설명한 메커니즘을 사용하여 인스턴스를 생성하고, 그렇지 않으면 문맥을 사용하여 인스턴스 생성 방법을 결정합니다.
formatters- 대응하는 값은 딕셔너리이며, 이 딕셔너리의 각 키는 포매터 id이고 각 값은 해당 Formatter 인스턴스를 구성하는 방법을 기술하는 딕셔너리입니다.구성 딕셔너리에서
format및datefmt키(기본값은None)를 찾고, 이를 사용하여logging.Formatter인스턴스를 생성합니다.filters- 대응하는 값은 딕셔너리이며, 이 딕셔너리의 각 키는 필터 id이고 각 값은 해당 Filter 인스턴스를 구성하는 방법을 기술하는 딕셔너리입니다.구성 딕셔너리에서
name키(기본값은 빈 문자열)를 찾고, 이를 사용하여logging.Filter인스턴스를 생성합니다.handlers- 대응하는 값은 딕셔너리이며, 이 딕셔너리의 각 키는 핸들러 id이고 각 값은 해당 Handler 인스턴스를 구성하는 방법을 기술하는 딕셔너리입니다.구성 딕셔너리에서 다음 키를 찾습니다:
class(필수). 핸들러 클래스의 완전히 정규화된 이름입니다.level(선택). 핸들러의 수준입니다.formatter(선택). 이 핸들러에 사용할 포매터의 id입니다.filters(선택). 이 핸들러에 사용할 필터들의 id 목록입니다.
다른 모든 키는 핸들러 생성자에 키워드 인자로 그대로 전달됩니다. 예를 들어, 다음 코드 조각이 주어지면:
handlers: console: class : logging.StreamHandler formatter: brief level : INFO filters: [allow_foo] stream : ext://sys.stdout file: class : logging.handlers.RotatingFileHandler formatter: precise filename: logconfig.log maxBytes: 1024 backupCount: 3id가
console인 핸들러는sys.stdout을 기반 스트림으로 사용하는logging.StreamHandler로 인스턴스화됩니다. id가file인 핸들러는filename='logconfig.log', maxBytes=1024, backupCount=3키워드 인자를 사용하여logging.handlers.RotatingFileHandler로 인스턴스화됩니다.loggers- 대응하는 값은 딕셔너리이며, 이 딕셔너리의 각 키는 로거 이름이고 각 값은 해당 Logger 인스턴스를 구성하는 방법을 기술하는 딕셔너리입니다.구성 딕셔너리에서 다음 키를 찾습니다:
level(선택 사항). 로거의 레벨입니다.propagate(선택 사항). 로거의 전파 설정입니다.filters(선택 사항). 이 로거에 대한 필터 id의 목록입니다.handlers(선택 사항). 이 로거에 대한 핸들러 id의 목록입니다.
지정된 로거는 지정된 레벨, 전파, 필터, 핸들러에 따라 구성됩니다.
root- 루트 로거에 대한 구성입니다. 구성 처리는propagate설정이 적용되지 않는다는 점을 제외하면 다른 로거와 동일합니다.incremental- 구성을 기존 구성에 대한 증분으로 해석할지 여부입니다. 이 값의 기본값은False이며, 이는 지정된 구성이 기존fileConfig()API에서 사용하는 것과 같은 의미로 기존 구성을 대체한다는 뜻입니다.지정된 값이
True이면, 구성은 아래의 Incremental Configuration 절에서 설명한 대로 처리됩니다.disable_existing_loggers- 기존 로거를 비활성화할지 여부입니다. 이 설정은fileConfig()의 같은 이름의 매개변수를 반영합니다. 생략되면 이 매개변수의 기본값은True입니다.incremental이True이면 이 값은 무시됩니다.
동작하는 예제
다음은 YAML 형식으로 된 실제로 동작하는 구성입니다(단, 이메일 주소는 가짜입니다).:
formatters:
brief:
format: '%(levelname)-8s: %(name)-15s: %(message)s'
precise:
format: '%(asctime)s %(name)-15s %(levelname)-8s %(message)s'
filters:
allow_foo:
name: foo
handlers:
console:
class : logging.StreamHandler
formatter: brief
level : INFO
stream : ext://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 키가 존재하고 그 값이 True이면, 시스템은 formatters 및 filters 항목을 완전히 무시하고, handlers 항목의 level 설정과 loggers 및 root 항목의 level 및 propagate 설정만 처리합니다.
물론 다른 방법으로도 증분 구성을 제공할 수 있습니다. 예를 들어 dictConfig()가 기본값이 False인 incremental 키워드 인자를 받게 할 수 있습니다. 구성 딕셔너리 안의 값을 사용하자고 제안하는 이유는 이렇게 하면 구성을 피클된 딕셔너리 형태로 네트워크를 거쳐 소켓 리스너에 보낼 수 있기 때문입니다. 따라서 오래 실행되는 애플리케이션을 중지했다가 다시 시작하지 않고도 그 애플리케이션의 로깅 상세 수준을 필요할 때마다 바꿀 수 있습니다.
참고: 실제 경험에서 나온 증분 구성 요구 사항에 대한 피드백을 특히 환영합니다.
API 사용자 정의
최소한의 기능만 갖춘 dictConfig() API로는 모든 사용 사례를 충족하지 못할 것입니다. 다음을 제공하여 API를 사용자 정의할 수 있게 합니다:
DictConfigurator라는 클래스입니다. 이 클래스의 생성자에는 구성에 사용할 딕셔너리가 전달되며, 이 클래스는configure()메서드를 가집니다.dictConfigClass라는 호출 가능 객체입니다. 이 객체는 (기본적으로)DictConfigurator로 설정됩니다. 이는 원하는 경우DictConfigurator를 적절한 사용자 정의 구현으로 교체할 수 있도록 제공됩니다.
dictConfig() 함수는 지정된 딕셔너리를 전달하여 dictConfigClass를 호출한 다음, 반환된 객체의 configure() 메서드를 호출하여 구성을 실제로 적용합니다:
def dictConfig(config):
dictConfigClass(config).configure()
이것으로 모든 사용자 정의 요구 사항을 충족할 수 있을 것입니다. 예를 들어 DictConfigurator의 서브클래스는 자신의 __init__()에서 DictConfigurator.__init__()를 호출한 다음, 이후의 configure() call에서 사용할 수 있는 사용자 정의 접두사를 설정할 수 있습니다. dictConfigClass를 해당 서브클래스에 바인딩하면, 사용자 정의하지 않은 기본 상태에서와 똑같이 dictConfig()를 호출할 수 있습니다.
소켓 리스너 구현 변경
기존 소켓 리스너 구현은 다음과 같이 수정됩니다. 구성 메시지를 받으면 json 모듈을 사용하여 딕셔너리로 역직렬화를 시도합니다. 이 단계가 실패하면 메시지가 fileConfig 형식이라고 간주하고 이전과 같이 처리합니다. 역직렬화에 성공하면 dictConfig()를 호출하여 결과 딕셔너리를 처리합니다.
구성 오류
구성 중에 오류가 발생하면 시스템은 적절한 설명이 담긴 메시지와 함께 ValueError, TypeError, AttributeError 또는 ImportError를 발생시킵니다. 다음은 오류를 발생시키는 조건의 목록입니다(빠진 조건이 있을 수 있습니다):
- 문자열이 아니거나, 실제 로깅 수준에 해당하지 않는 문자열인
level - 불리언이 아닌
propagate값 - 대응하는 대상이 없는 id
- 증분 호출 중에 발견된 존재하지 않는 핸들러 id
- 유효하지 않은 로거 이름
- 내부 또는 외부 객체로 해석할 수 없는 경우
커뮤니티 논의
이 PEP는 python-dev와 python-list에 공지되었습니다. 논의가 아주 많지는 않았지만, 틈새 주제라는 점을 생각하면 예상할 수 있었던 일일 것입니다.
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를 수정하여 이러한 사항들을 해결했다고 판단합니다.
참조 구현
변경 사항의 참조 구현은 dictconfig.py 모듈로 제공되며, 이에 딸린 단위 테스트는 test_dictconfig.py에 있습니다. 다음 위치에서 받을 수 있습니다:
http://bitbucket.org/vinay.sajip/dictconfig
이 구현에는 소켓 리스너 변경을 제외한 모든 기능이 포함되어 있습니다.
Copyright
This document has been placed in the public domain.