Skip to content

Commit 8f54302

Browse files
authored
gh-103357: Add logging.Formatter defaults support to logging.config fileConfig and dictConfig (GH-103359)
1 parent 449bf2a commit 8f54302

File tree

4 files changed

+137
-5
lines changed

4 files changed

+137
-5
lines changed

Doc/library/logging.config.rst

+8-1
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,7 @@ otherwise, the context is used to determine what to instantiate.
253253
* ``datefmt``
254254
* ``style``
255255
* ``validate`` (since version >=3.8)
256+
* ``defaults`` (since version >=3.12)
256257

257258
An optional ``class`` key indicates the name of the formatter's
258259
class (as a dotted module and class name). The instantiation
@@ -953,16 +954,22 @@ Sections which specify formatter configuration are typified by the following.
953954
.. code-block:: ini
954955
955956
[formatter_form01]
956-
format=F1 %(asctime)s %(levelname)s %(message)s
957+
format=F1 %(asctime)s %(levelname)s %(message)s %(customfield)s
957958
datefmt=
958959
style=%
959960
validate=True
961+
defaults={'customfield': 'defaultvalue'}
960962
class=logging.Formatter
961963
962964
The arguments for the formatter configuration are the same as the keys
963965
in the dictionary schema :ref:`formatters section
964966
<logging-config-dictschema-formatters>`.
965967

968+
The ``defaults`` entry, when :ref:`evaluated <func-eval>` in the context of
969+
the ``logging`` package's namespace, is a dictionary of default values for
970+
custom formatting fields. If not provided, it defaults to ``None``.
971+
972+
966973
.. note::
967974

968975
Due to the use of :func:`eval` as described above, there are

Lib/logging/config.py

+19-3
Original file line numberDiff line numberDiff line change
@@ -114,11 +114,18 @@ def _create_formatters(cp):
114114
fs = cp.get(sectname, "format", raw=True, fallback=None)
115115
dfs = cp.get(sectname, "datefmt", raw=True, fallback=None)
116116
stl = cp.get(sectname, "style", raw=True, fallback='%')
117+
defaults = cp.get(sectname, "defaults", raw=True, fallback=None)
118+
117119
c = logging.Formatter
118120
class_name = cp[sectname].get("class")
119121
if class_name:
120122
c = _resolve(class_name)
121-
f = c(fs, dfs, stl)
123+
124+
if defaults is not None:
125+
defaults = eval(defaults, vars(logging))
126+
f = c(fs, dfs, stl, defaults=defaults)
127+
else:
128+
f = c(fs, dfs, stl)
122129
formatters[form] = f
123130
return formatters
124131

@@ -668,18 +675,27 @@ def configure_formatter(self, config):
668675
dfmt = config.get('datefmt', None)
669676
style = config.get('style', '%')
670677
cname = config.get('class', None)
678+
defaults = config.get('defaults', None)
671679

672680
if not cname:
673681
c = logging.Formatter
674682
else:
675683
c = _resolve(cname)
676684

685+
kwargs = {}
686+
687+
# Add defaults only if it exists.
688+
# Prevents TypeError in custom formatter callables that do not
689+
# accept it.
690+
if defaults is not None:
691+
kwargs['defaults'] = defaults
692+
677693
# A TypeError would be raised if "validate" key is passed in with a formatter callable
678694
# that does not accept "validate" as a parameter
679695
if 'validate' in config: # if user hasn't mentioned it, the default will be fine
680-
result = c(fmt, dfmt, style, config['validate'])
696+
result = c(fmt, dfmt, style, config['validate'], **kwargs)
681697
else:
682-
result = c(fmt, dfmt, style)
698+
result = c(fmt, dfmt, style, **kwargs)
683699

684700
return result
685701

Lib/test/test_logging.py

+107-1
Original file line numberDiff line numberDiff line change
@@ -1524,6 +1524,32 @@ class ConfigFileTest(BaseTest):
15241524
kwargs={{"encoding": "utf-8"}}
15251525
"""
15261526

1527+
1528+
config9 = """
1529+
[loggers]
1530+
keys=root
1531+
1532+
[handlers]
1533+
keys=hand1
1534+
1535+
[formatters]
1536+
keys=form1
1537+
1538+
[logger_root]
1539+
level=WARNING
1540+
handlers=hand1
1541+
1542+
[handler_hand1]
1543+
class=StreamHandler
1544+
level=NOTSET
1545+
formatter=form1
1546+
args=(sys.stdout,)
1547+
1548+
[formatter_form1]
1549+
format=%(message)s ++ %(customfield)s
1550+
defaults={"customfield": "defaultvalue"}
1551+
"""
1552+
15271553
disable_test = """
15281554
[loggers]
15291555
keys=root
@@ -1687,6 +1713,16 @@ def test_config8_ok(self):
16871713
handler = logging.root.handlers[0]
16881714
self.addCleanup(closeFileHandler, handler, fn)
16891715

1716+
def test_config9_ok(self):
1717+
self.apply_config(self.config9)
1718+
formatter = logging.root.handlers[0].formatter
1719+
result = formatter.format(logging.makeLogRecord({'msg': 'test'}))
1720+
self.assertEqual(result, 'test ++ defaultvalue')
1721+
result = formatter.format(logging.makeLogRecord(
1722+
{'msg': 'test', 'customfield': "customvalue"}))
1723+
self.assertEqual(result, 'test ++ customvalue')
1724+
1725+
16901726
def test_logger_disabling(self):
16911727
self.apply_config(self.disable_test)
16921728
logger = logging.getLogger('some_pristine_logger')
@@ -2909,6 +2945,30 @@ class ConfigDictTest(BaseTest):
29092945
},
29102946
}
29112947

2948+
# config0 but with default values for formatter. Skipped 15, it is defined
2949+
# in the test code.
2950+
config16 = {
2951+
'version': 1,
2952+
'formatters': {
2953+
'form1' : {
2954+
'format' : '%(message)s ++ %(customfield)s',
2955+
'defaults': {"customfield": "defaultvalue"}
2956+
},
2957+
},
2958+
'handlers' : {
2959+
'hand1' : {
2960+
'class' : 'logging.StreamHandler',
2961+
'formatter' : 'form1',
2962+
'level' : 'NOTSET',
2963+
'stream' : 'ext://sys.stdout',
2964+
},
2965+
},
2966+
'root' : {
2967+
'level' : 'WARNING',
2968+
'handlers' : ['hand1'],
2969+
},
2970+
}
2971+
29122972
bad_format = {
29132973
"version": 1,
29142974
"formatters": {
@@ -3021,7 +3081,7 @@ class ConfigDictTest(BaseTest):
30213081
}
30223082
}
30233083

3024-
# Configuration with custom function and 'validate' set to False
3084+
# Configuration with custom function, 'validate' set to False and no defaults
30253085
custom_formatter_with_function = {
30263086
'version': 1,
30273087
'formatters': {
@@ -3048,6 +3108,33 @@ class ConfigDictTest(BaseTest):
30483108
}
30493109
}
30503110

3111+
# Configuration with custom function, and defaults
3112+
custom_formatter_with_defaults = {
3113+
'version': 1,
3114+
'formatters': {
3115+
'form1': {
3116+
'()': formatFunc,
3117+
'format': '%(levelname)s:%(name)s:%(message)s:%(customfield)s',
3118+
'defaults': {"customfield": "myvalue"}
3119+
},
3120+
},
3121+
'handlers' : {
3122+
'hand1' : {
3123+
'class': 'logging.StreamHandler',
3124+
'formatter': 'form1',
3125+
'level': 'NOTSET',
3126+
'stream': 'ext://sys.stdout',
3127+
},
3128+
},
3129+
"loggers": {
3130+
"my_test_logger_custom_formatter": {
3131+
"level": "DEBUG",
3132+
"handlers": ["hand1"],
3133+
"propagate": "true"
3134+
}
3135+
}
3136+
}
3137+
30513138
config_queue_handler = {
30523139
'version': 1,
30533140
'handlers' : {
@@ -3349,6 +3436,22 @@ def test_config15_ok(self):
33493436
handler = logging.root.handlers[0]
33503437
self.addCleanup(closeFileHandler, handler, fn)
33513438

3439+
def test_config16_ok(self):
3440+
self.apply_config(self.config16)
3441+
h = logging._handlers['hand1']
3442+
3443+
# Custom value
3444+
result = h.formatter.format(logging.makeLogRecord(
3445+
{'msg': 'Hello', 'customfield': 'customvalue'}))
3446+
self.assertEqual(result, 'Hello ++ customvalue')
3447+
3448+
# Default value
3449+
result = h.formatter.format(logging.makeLogRecord(
3450+
{'msg': 'Hello'}))
3451+
self.assertEqual(result, 'Hello ++ defaultvalue')
3452+
3453+
3454+
33523455
def setup_via_listener(self, text, verify=None):
33533456
text = text.encode("utf-8")
33543457
# Ask for a randomly assigned port (by using port 0)
@@ -3516,6 +3619,9 @@ def test_custom_formatter_class_with_validate3(self):
35163619
def test_custom_formatter_function_with_validate(self):
35173620
self.assertRaises(ValueError, self.apply_config, self.custom_formatter_with_function)
35183621

3622+
def test_custom_formatter_function_with_defaults(self):
3623+
self.assertRaises(ValueError, self.apply_config, self.custom_formatter_with_defaults)
3624+
35193625
def test_baseconfig(self):
35203626
d = {
35213627
'atuple': (1, 2, 3),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Added support for :class:`logging.Formatter` ``defaults`` parameter to
2+
:func:`logging.config.dictConfig` and :func:`logging.config.fileConfig`.
3+
Patch by Bar Harel.

0 commit comments

Comments
 (0)