mirror of
https://github.com/python/cpython
synced 2026-09-27 05:18:16 +03:00
gh-103357: Add logging.Formatter defaults support to logging.config fileConfig and dictConfig (GH-103359)
This commit is contained in:
parent
449bf2a76b
commit
8f54302ab4
4 changed files with 137 additions and 5 deletions
|
|
@ -253,6 +253,7 @@ otherwise, the context is used to determine what to instantiate.
|
|||
* ``datefmt``
|
||||
* ``style``
|
||||
* ``validate`` (since version >=3.8)
|
||||
* ``defaults`` (since version >=3.12)
|
||||
|
||||
An optional ``class`` key indicates the name of the formatter's
|
||||
class (as a dotted module and class name). The instantiation
|
||||
|
|
@ -953,16 +954,22 @@ Sections which specify formatter configuration are typified by the following.
|
|||
.. code-block:: ini
|
||||
|
||||
[formatter_form01]
|
||||
format=F1 %(asctime)s %(levelname)s %(message)s
|
||||
format=F1 %(asctime)s %(levelname)s %(message)s %(customfield)s
|
||||
datefmt=
|
||||
style=%
|
||||
validate=True
|
||||
defaults={'customfield': 'defaultvalue'}
|
||||
class=logging.Formatter
|
||||
|
||||
The arguments for the formatter configuration are the same as the keys
|
||||
in the dictionary schema :ref:`formatters section
|
||||
<logging-config-dictschema-formatters>`.
|
||||
|
||||
The ``defaults`` entry, when :ref:`evaluated <func-eval>` in the context of
|
||||
the ``logging`` package's namespace, is a dictionary of default values for
|
||||
custom formatting fields. If not provided, it defaults to ``None``.
|
||||
|
||||
|
||||
.. note::
|
||||
|
||||
Due to the use of :func:`eval` as described above, there are
|
||||
|
|
|
|||
|
|
@ -114,11 +114,18 @@ def _create_formatters(cp):
|
|||
fs = cp.get(sectname, "format", raw=True, fallback=None)
|
||||
dfs = cp.get(sectname, "datefmt", raw=True, fallback=None)
|
||||
stl = cp.get(sectname, "style", raw=True, fallback='%')
|
||||
defaults = cp.get(sectname, "defaults", raw=True, fallback=None)
|
||||
|
||||
c = logging.Formatter
|
||||
class_name = cp[sectname].get("class")
|
||||
if class_name:
|
||||
c = _resolve(class_name)
|
||||
f = c(fs, dfs, stl)
|
||||
|
||||
if defaults is not None:
|
||||
defaults = eval(defaults, vars(logging))
|
||||
f = c(fs, dfs, stl, defaults=defaults)
|
||||
else:
|
||||
f = c(fs, dfs, stl)
|
||||
formatters[form] = f
|
||||
return formatters
|
||||
|
||||
|
|
@ -668,18 +675,27 @@ class DictConfigurator(BaseConfigurator):
|
|||
dfmt = config.get('datefmt', None)
|
||||
style = config.get('style', '%')
|
||||
cname = config.get('class', None)
|
||||
defaults = config.get('defaults', None)
|
||||
|
||||
if not cname:
|
||||
c = logging.Formatter
|
||||
else:
|
||||
c = _resolve(cname)
|
||||
|
||||
kwargs = {}
|
||||
|
||||
# Add defaults only if it exists.
|
||||
# Prevents TypeError in custom formatter callables that do not
|
||||
# accept it.
|
||||
if defaults is not None:
|
||||
kwargs['defaults'] = defaults
|
||||
|
||||
# A TypeError would be raised if "validate" key is passed in with a formatter callable
|
||||
# that does not accept "validate" as a parameter
|
||||
if 'validate' in config: # if user hasn't mentioned it, the default will be fine
|
||||
result = c(fmt, dfmt, style, config['validate'])
|
||||
result = c(fmt, dfmt, style, config['validate'], **kwargs)
|
||||
else:
|
||||
result = c(fmt, dfmt, style)
|
||||
result = c(fmt, dfmt, style, **kwargs)
|
||||
|
||||
return result
|
||||
|
||||
|
|
|
|||
|
|
@ -1524,6 +1524,32 @@ class ConfigFileTest(BaseTest):
|
|||
kwargs={{"encoding": "utf-8"}}
|
||||
"""
|
||||
|
||||
|
||||
config9 = """
|
||||
[loggers]
|
||||
keys=root
|
||||
|
||||
[handlers]
|
||||
keys=hand1
|
||||
|
||||
[formatters]
|
||||
keys=form1
|
||||
|
||||
[logger_root]
|
||||
level=WARNING
|
||||
handlers=hand1
|
||||
|
||||
[handler_hand1]
|
||||
class=StreamHandler
|
||||
level=NOTSET
|
||||
formatter=form1
|
||||
args=(sys.stdout,)
|
||||
|
||||
[formatter_form1]
|
||||
format=%(message)s ++ %(customfield)s
|
||||
defaults={"customfield": "defaultvalue"}
|
||||
"""
|
||||
|
||||
disable_test = """
|
||||
[loggers]
|
||||
keys=root
|
||||
|
|
@ -1687,6 +1713,16 @@ class ConfigFileTest(BaseTest):
|
|||
handler = logging.root.handlers[0]
|
||||
self.addCleanup(closeFileHandler, handler, fn)
|
||||
|
||||
def test_config9_ok(self):
|
||||
self.apply_config(self.config9)
|
||||
formatter = logging.root.handlers[0].formatter
|
||||
result = formatter.format(logging.makeLogRecord({'msg': 'test'}))
|
||||
self.assertEqual(result, 'test ++ defaultvalue')
|
||||
result = formatter.format(logging.makeLogRecord(
|
||||
{'msg': 'test', 'customfield': "customvalue"}))
|
||||
self.assertEqual(result, 'test ++ customvalue')
|
||||
|
||||
|
||||
def test_logger_disabling(self):
|
||||
self.apply_config(self.disable_test)
|
||||
logger = logging.getLogger('some_pristine_logger')
|
||||
|
|
@ -2909,6 +2945,30 @@ class ConfigDictTest(BaseTest):
|
|||
},
|
||||
}
|
||||
|
||||
# config0 but with default values for formatter. Skipped 15, it is defined
|
||||
# in the test code.
|
||||
config16 = {
|
||||
'version': 1,
|
||||
'formatters': {
|
||||
'form1' : {
|
||||
'format' : '%(message)s ++ %(customfield)s',
|
||||
'defaults': {"customfield": "defaultvalue"}
|
||||
},
|
||||
},
|
||||
'handlers' : {
|
||||
'hand1' : {
|
||||
'class' : 'logging.StreamHandler',
|
||||
'formatter' : 'form1',
|
||||
'level' : 'NOTSET',
|
||||
'stream' : 'ext://sys.stdout',
|
||||
},
|
||||
},
|
||||
'root' : {
|
||||
'level' : 'WARNING',
|
||||
'handlers' : ['hand1'],
|
||||
},
|
||||
}
|
||||
|
||||
bad_format = {
|
||||
"version": 1,
|
||||
"formatters": {
|
||||
|
|
@ -3021,7 +3081,7 @@ class ConfigDictTest(BaseTest):
|
|||
}
|
||||
}
|
||||
|
||||
# Configuration with custom function and 'validate' set to False
|
||||
# Configuration with custom function, 'validate' set to False and no defaults
|
||||
custom_formatter_with_function = {
|
||||
'version': 1,
|
||||
'formatters': {
|
||||
|
|
@ -3048,6 +3108,33 @@ class ConfigDictTest(BaseTest):
|
|||
}
|
||||
}
|
||||
|
||||
# Configuration with custom function, and defaults
|
||||
custom_formatter_with_defaults = {
|
||||
'version': 1,
|
||||
'formatters': {
|
||||
'form1': {
|
||||
'()': formatFunc,
|
||||
'format': '%(levelname)s:%(name)s:%(message)s:%(customfield)s',
|
||||
'defaults': {"customfield": "myvalue"}
|
||||
},
|
||||
},
|
||||
'handlers' : {
|
||||
'hand1' : {
|
||||
'class': 'logging.StreamHandler',
|
||||
'formatter': 'form1',
|
||||
'level': 'NOTSET',
|
||||
'stream': 'ext://sys.stdout',
|
||||
},
|
||||
},
|
||||
"loggers": {
|
||||
"my_test_logger_custom_formatter": {
|
||||
"level": "DEBUG",
|
||||
"handlers": ["hand1"],
|
||||
"propagate": "true"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
config_queue_handler = {
|
||||
'version': 1,
|
||||
'handlers' : {
|
||||
|
|
@ -3349,6 +3436,22 @@ class ConfigDictTest(BaseTest):
|
|||
handler = logging.root.handlers[0]
|
||||
self.addCleanup(closeFileHandler, handler, fn)
|
||||
|
||||
def test_config16_ok(self):
|
||||
self.apply_config(self.config16)
|
||||
h = logging._handlers['hand1']
|
||||
|
||||
# Custom value
|
||||
result = h.formatter.format(logging.makeLogRecord(
|
||||
{'msg': 'Hello', 'customfield': 'customvalue'}))
|
||||
self.assertEqual(result, 'Hello ++ customvalue')
|
||||
|
||||
# Default value
|
||||
result = h.formatter.format(logging.makeLogRecord(
|
||||
{'msg': 'Hello'}))
|
||||
self.assertEqual(result, 'Hello ++ defaultvalue')
|
||||
|
||||
|
||||
|
||||
def setup_via_listener(self, text, verify=None):
|
||||
text = text.encode("utf-8")
|
||||
# Ask for a randomly assigned port (by using port 0)
|
||||
|
|
@ -3516,6 +3619,9 @@ class ConfigDictTest(BaseTest):
|
|||
def test_custom_formatter_function_with_validate(self):
|
||||
self.assertRaises(ValueError, self.apply_config, self.custom_formatter_with_function)
|
||||
|
||||
def test_custom_formatter_function_with_defaults(self):
|
||||
self.assertRaises(ValueError, self.apply_config, self.custom_formatter_with_defaults)
|
||||
|
||||
def test_baseconfig(self):
|
||||
d = {
|
||||
'atuple': (1, 2, 3),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
Added support for :class:`logging.Formatter` ``defaults`` parameter to
|
||||
:func:`logging.config.dictConfig` and :func:`logging.config.fileConfig`.
|
||||
Patch by Bar Harel.
|
||||
Loading…
Add table
Add a link
Reference in a new issue