From 3aeeba6ed13eda4b895d8a531cf320768f5dc030 Mon Sep 17 00:00:00 2001 From: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> Date: Mon, 9 May 2022 21:45:24 -0500 Subject: [PATCH 01/14] allow logging filters to return a LogRecord --- Lib/logging/__init__.py | 38 ++++++++++++++++++++++++++++++-------- Lib/test/test_logging.py | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 8 deletions(-) diff --git a/Lib/logging/__init__.py b/Lib/logging/__init__.py index e49e0d02a80cf0..467fe702442bb0 100644 --- a/Lib/logging/__init__.py +++ b/Lib/logging/__init__.py @@ -359,6 +359,9 @@ def __repr__(self): return ''%(self.name, self.levelno, self.pathname, self.lineno, self.msg) + def __bool__(self): + return True + def getMessage(self): """ Return the message for this LogRecord. @@ -811,23 +814,34 @@ def filter(self, record): Determine if a record is loggable by consulting all the filters. The default is to allow the record to be logged; any filter can veto - this and the record is then dropped. Returns a zero value if a record - is to be dropped, else non-zero. + this by returning a falsy value and the record is then dropped and + this method returns a falsy value. + Filters can return a log record, which case that log record + is used to call the next filter. + If filters return a truthy value that is not a log record the + next filter is called with the existing log record. + + If none of the filters return falsy values, this method returns + a log record. .. versionchanged:: 3.2 Allow filters to be just callables. + + .. versionchanged:: 3.12 + Allow filters to return a LogRecord instead of + modifying it in place. """ - rv = True for f in self.filters: if hasattr(f, 'filter'): result = f.filter(record) else: result = f(record) # assume callable - will raise if not if not result: - rv = False - break - return rv + return False + if isinstance(result, LogRecord): + record = result + return record #--------------------------------------------------------------------------- # Handler classes and functions @@ -966,6 +980,8 @@ def handle(self, record): emission. """ rv = self.filter(record) + if isinstance(rv, LogRecord): + record = rv if rv: self.acquire() try: @@ -1634,8 +1650,14 @@ def handle(self, record): This method is used for unpickled records received from a socket, as well as those created locally. Logger-level filtering is applied. """ - if (not self.disabled) and self.filter(record): - self.callHandlers(record) + if self.disabled: + return + maybe_record = self.filter(record) + if not maybe_record: + return + if isinstance(maybe_record, LogRecord): + record = maybe_record + self.callHandlers(record) def addHandler(self, hdlr): """ diff --git a/Lib/test/test_logging.py b/Lib/test/test_logging.py index 4f3315161cf20f..86f2dc93f941f2 100644 --- a/Lib/test/test_logging.py +++ b/Lib/test/test_logging.py @@ -466,6 +466,40 @@ def log_at_all_levels(self, logger): for lvl in LEVEL_RANGE: logger.log(lvl, self.next_message()) + def test_handler_filter_replaces_record(self): + def replace_message(record: logging.LogRecord): + return logging.LogRecord( + name=record.name, + level=record.levelno, + pathname=record.pathname, + lineno=record.lineno, + msg="new message!", + exc_info=record.exc_info, + args=(), + ) + + # Set up a logging hierarchy such that "child" and it's handler + # (and thus `replace_message()`) always get called before + # propagating up to "parent". + # Then we can confirm that `replace_message()` was able to + # replace the log record without having a side effect on + # other loggers or handlers. + parent = logging.getLogger("parent") + child = logging.getLogger("parent.child") + stream_1 = io.StringIO() + stream_2 = io.StringIO() + handler_1 = logging.StreamHandler(stream_1) + handler_2 = logging.StreamHandler(stream_2) + handler_2.addFilter(replace_message) + parent.addHandler(handler_1) + child.addHandler(handler_2) + + child.info("original message") + handler_1.flush() + handler_2.flush() + self.assertEqual(stream_1.getvalue(), "original message\n") + self.assertEqual(stream_2.getvalue(), "new message!\n") + def test_logger_filter(self): # Filter at logger level. self.root_logger.setLevel(VERBOSE) From 1e4d27f779a15a3a48f50c025ae88c6bbbcff94f Mon Sep 17 00:00:00 2001 From: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> Date: Mon, 9 May 2022 21:48:16 -0500 Subject: [PATCH 02/14] edit docstring --- Lib/logging/__init__.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Lib/logging/__init__.py b/Lib/logging/__init__.py index 467fe702442bb0..6fc22cd8138820 100644 --- a/Lib/logging/__init__.py +++ b/Lib/logging/__init__.py @@ -976,8 +976,10 @@ def handle(self, record): Emission depends on filters which may have been added to the handler. Wrap the actual emission of the record with acquisition/release of - the I/O thread lock. Returns whether the filter passed the record for - emission. + the I/O thread lock. + + Returns an instance of the log record that was emitted + if it passed all filters, otherwise a falsy value is returned. """ rv = self.filter(record) if isinstance(rv, LogRecord): From b526a35ffe06124bed4f48044e4176506c29bade Mon Sep 17 00:00:00 2001 From: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> Date: Mon, 9 May 2022 21:54:48 -0500 Subject: [PATCH 03/14] remove unecessary __bool__ impl --- Lib/logging/__init__.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/Lib/logging/__init__.py b/Lib/logging/__init__.py index 6fc22cd8138820..c76bed13435237 100644 --- a/Lib/logging/__init__.py +++ b/Lib/logging/__init__.py @@ -359,9 +359,6 @@ def __repr__(self): return ''%(self.name, self.levelno, self.pathname, self.lineno, self.msg) - def __bool__(self): - return True - def getMessage(self): """ Return the message for this LogRecord. From f1ea183f6f98871687084440ede535a628681be8 Mon Sep 17 00:00:00 2001 From: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> Date: Mon, 9 May 2022 22:18:11 -0500 Subject: [PATCH 04/14] edit docs --- Doc/howto/logging-cookbook.rst | 35 ++++++++++++++++++++++++++++++++++ Doc/library/logging.rst | 11 +++++++++-- 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/Doc/howto/logging-cookbook.rst b/Doc/howto/logging-cookbook.rst index f0d944940fc28f..0723a42ad67208 100644 --- a/Doc/howto/logging-cookbook.rst +++ b/Doc/howto/logging-cookbook.rst @@ -713,6 +713,41 @@ which, when run, produces something like: 2010-09-06 22:38:15,301 d.e.f DEBUG IP: 123.231.231.123 User: fred A message at DEBUG level with 2 parameters 2010-09-06 22:38:15,301 d.e.f INFO IP: 123.231.231.123 User: fred A message at INFO level with 2 parameters +Imparting contextual information for specific Handlers +------------------------------------------------------ + +Each :class:`~Handler` has it's own chain of Filters. +If you want to add contextual information to a :class:`LogRecord` without leaking +this contextual information to other handlers, you can use a filter that returns +a new :class:`~LogRecord` instead of modifying it in-place: + +script:: + + import logging + + def filter(record: logging.LogRecord): + record = logging.LogRecord( + name=record.name, + level=record.levelno, + pathname=record.pathname, + lineno=record.lineno, + msg=record.msg, + exc_info=record.exc_info, + args=(), + ) + record.user = 'jim' + return record + + if __name__ == '__main__': + logger = logging.getLogger() + logger.setLevel(logging.INFO) + handler = logging.StreamHandler() + formatter = logging.Formatter('%(message)s from %(user)-8s') + handler.setFormatter(formatter) + handler.addFilter(filter) + logger.addHandler(handler) + + logger.info('A log message') .. _multiple-processes: diff --git a/Doc/library/logging.rst b/Doc/library/logging.rst index ea6494f219ae76..19577e2d6bfe63 100644 --- a/Doc/library/logging.rst +++ b/Doc/library/logging.rst @@ -663,8 +663,9 @@ empty string, all events are passed. .. method:: filter(record) Is the specified record to be logged? Returns zero for no, nonzero for - yes. If deemed appropriate, the record may be modified in-place by this - method. + yes. Filters can also modify log records in-place or return a completely + different log-record which will replace the original log record without + modifying it. Note that filters attached to handlers are consulted before an event is emitted by the handler, whereas filters attached to loggers are consulted @@ -686,6 +687,12 @@ which has a ``filter`` method with the same semantics. parameter. The returned value should conform to that returned by :meth:`~Filter.filter`. +.. versionchanged:: 3.12 + You can now return a :class:`LogRecord` instance from filters to replace + the log record without modifying it in place. This allows filters installed + on a :class:`Handler` to modify the log record before it is emitted without + having side effects on other handlers. + Although filters are used primarily to filter records based on more sophisticated criteria than levels, they get to see every record which is processed by the handler or logger they're attached to: this can be useful if From 19561d39e8f6b28f17851a33d700887b6a1e35d2 Mon Sep 17 00:00:00 2001 From: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> Date: Mon, 9 May 2022 22:27:52 -0500 Subject: [PATCH 05/14] Add docs and blurb --- Doc/howto/logging-cookbook.rst | 4 ++-- .../Library/2022-05-09-22-27-11.gh-issue-92591.V7RCk2.rst | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2022-05-09-22-27-11.gh-issue-92591.V7RCk2.rst diff --git a/Doc/howto/logging-cookbook.rst b/Doc/howto/logging-cookbook.rst index 0723a42ad67208..7e309c516a9faf 100644 --- a/Doc/howto/logging-cookbook.rst +++ b/Doc/howto/logging-cookbook.rst @@ -713,8 +713,8 @@ which, when run, produces something like: 2010-09-06 22:38:15,301 d.e.f DEBUG IP: 123.231.231.123 User: fred A message at DEBUG level with 2 parameters 2010-09-06 22:38:15,301 d.e.f INFO IP: 123.231.231.123 User: fred A message at INFO level with 2 parameters -Imparting contextual information for specific Handlers ------------------------------------------------------- +Imparting contextual in Handlers +--------------------------------- Each :class:`~Handler` has it's own chain of Filters. If you want to add contextual information to a :class:`LogRecord` without leaking diff --git a/Misc/NEWS.d/next/Library/2022-05-09-22-27-11.gh-issue-92591.V7RCk2.rst b/Misc/NEWS.d/next/Library/2022-05-09-22-27-11.gh-issue-92591.V7RCk2.rst new file mode 100644 index 00000000000000..6bec5bcb0199ec --- /dev/null +++ b/Misc/NEWS.d/next/Library/2022-05-09-22-27-11.gh-issue-92591.V7RCk2.rst @@ -0,0 +1,3 @@ +Let :mod:`logging` Filters to return a :class:`logging.LogRecord` instance +so that :class:`logging.Handler`s can enrich records without side effects on +other Handlers. From 4d34540e6a6a309929a59aac8d885599b04ca84a Mon Sep 17 00:00:00 2001 From: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> Date: Mon, 9 May 2022 22:36:27 -0500 Subject: [PATCH 06/14] fix rst --- .../next/Library/2022-05-09-22-27-11.gh-issue-92591.V7RCk2.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2022-05-09-22-27-11.gh-issue-92591.V7RCk2.rst b/Misc/NEWS.d/next/Library/2022-05-09-22-27-11.gh-issue-92591.V7RCk2.rst index 6bec5bcb0199ec..9ad25b3b78d351 100644 --- a/Misc/NEWS.d/next/Library/2022-05-09-22-27-11.gh-issue-92591.V7RCk2.rst +++ b/Misc/NEWS.d/next/Library/2022-05-09-22-27-11.gh-issue-92591.V7RCk2.rst @@ -1,3 +1,3 @@ Let :mod:`logging` Filters to return a :class:`logging.LogRecord` instance -so that :class:`logging.Handler`s can enrich records without side effects on +so that :class:`logging.Handler`\ s can enrich records without side effects on other Handlers. From a69e76a474c59adc7c7b0c02d615df762a304fab Mon Sep 17 00:00:00 2001 From: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> Date: Mon, 9 May 2022 22:40:22 -0500 Subject: [PATCH 07/14] touch up wording --- Doc/library/logging.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/library/logging.rst b/Doc/library/logging.rst index 8de64ba31dd588..5f783c7c6a7ba4 100644 --- a/Doc/library/logging.rst +++ b/Doc/library/logging.rst @@ -664,8 +664,8 @@ empty string, all events are passed. Is the specified record to be logged? Returns zero for no, nonzero for yes. Filters can also modify log records in-place or return a completely - different log-record which will replace the original log record without - modifying it. + different record instance which will replace the original + log record without modifying it. Note that filters attached to handlers are consulted before an event is emitted by the handler, whereas filters attached to loggers are consulted From 67453f60c38b433f14c8e06b170b7fdc6b5b905b Mon Sep 17 00:00:00 2001 From: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> Date: Mon, 9 May 2022 22:50:02 -0500 Subject: [PATCH 08/14] add test for filters on loggers --- Lib/test/test_logging.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/Lib/test/test_logging.py b/Lib/test/test_logging.py index 9747ac6e10e1a3..4f7c4a2fa77f96 100644 --- a/Lib/test/test_logging.py +++ b/Lib/test/test_logging.py @@ -500,6 +500,31 @@ def replace_message(record: logging.LogRecord): handler_2.flush() self.assertEqual(stream_1.getvalue(), "original message\n") self.assertEqual(stream_2.getvalue(), "new message!\n") + + def test_logging_filter_replaces_record(self): + records = set() + + class RecordingFilter(logging.Filter): + def filter(self, record: logging.LogRecord): + records.add(id(record)) + return logging.LogRecord( + name=record.name, + level=record.levelno, + pathname=record.pathname, + lineno=record.lineno, + msg=record.msg, + exc_info=record.exc_info, + args=(), + ) + + logger = logging.getLogger("logger") + logger.setLevel(logging.INFO) + logger.addFilter(RecordingFilter()) + logger.addFilter(RecordingFilter()) + + logger.info("msg") + + self.assertEqual(3, len(records)) def test_logger_filter(self): # Filter at logger level. From b1e10848c981048383fee953efe679fa36035e04 Mon Sep 17 00:00:00 2001 From: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> Date: Mon, 9 May 2022 23:56:19 -0500 Subject: [PATCH 09/14] push straggler change --- Lib/test/test_logging.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_logging.py b/Lib/test/test_logging.py index 4f7c4a2fa77f96..b526d534ae817a 100644 --- a/Lib/test/test_logging.py +++ b/Lib/test/test_logging.py @@ -524,7 +524,7 @@ def filter(self, record: logging.LogRecord): logger.info("msg") - self.assertEqual(3, len(records)) + self.assertEqual(2, len(records)) def test_logger_filter(self): # Filter at logger level. From 225329fcb4d192a07490177a078e753e2f32ac14 Mon Sep 17 00:00:00 2001 From: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> Date: Tue, 10 May 2022 09:18:05 -0500 Subject: [PATCH 10/14] pr feedback --- Doc/howto/logging-cookbook.rst | 19 +++++----------- Doc/library/logging.rst | 6 ++--- Lib/logging/__init__.py | 14 +++++++----- Lib/test/test_logging.py | 22 ++++--------------- ...2-05-09-22-27-11.gh-issue-92591.V7RCk2.rst | 6 ++--- 5 files changed, 24 insertions(+), 43 deletions(-) diff --git a/Doc/howto/logging-cookbook.rst b/Doc/howto/logging-cookbook.rst index 8cb23494c1c334..d4f046d988f7fd 100644 --- a/Doc/howto/logging-cookbook.rst +++ b/Doc/howto/logging-cookbook.rst @@ -713,28 +713,21 @@ which, when run, produces something like: 2010-09-06 22:38:15,301 d.e.f DEBUG IP: 123.231.231.123 User: fred A message at DEBUG level with 2 parameters 2010-09-06 22:38:15,301 d.e.f INFO IP: 123.231.231.123 User: fred A message at INFO level with 2 parameters -Imparting contextual in Handlers ---------------------------------- +Imparting contextual information in handlers +-------------------------------------------- -Each :class:`~Handler` has it's own chain of Filters. +Each :class:`~Handler` has its own chain of filters. If you want to add contextual information to a :class:`LogRecord` without leaking -this contextual information to other handlers, you can use a filter that returns +it to other handlers, you can use a filter that returns a new :class:`~LogRecord` instead of modifying it in-place: script:: + import copy import logging def filter(record: logging.LogRecord): - record = logging.LogRecord( - name=record.name, - level=record.levelno, - pathname=record.pathname, - lineno=record.lineno, - msg=record.msg, - exc_info=record.exc_info, - args=(), - ) + record = copy.copy(record) record.user = 'jim' return record diff --git a/Doc/library/logging.rst b/Doc/library/logging.rst index 5f783c7c6a7ba4..426378b07d0b93 100644 --- a/Doc/library/logging.rst +++ b/Doc/library/logging.rst @@ -662,8 +662,8 @@ empty string, all events are passed. .. method:: filter(record) - Is the specified record to be logged? Returns zero for no, nonzero for - yes. Filters can also modify log records in-place or return a completely + Is the specified record to be logged? Returns falsy for no, truthy for + yes. Filters can either modify log records in-place or return a completely different record instance which will replace the original log record without modifying it. @@ -689,7 +689,7 @@ which has a ``filter`` method with the same semantics. .. versionchanged:: 3.12 You can now return a :class:`LogRecord` instance from filters to replace - the log record without modifying it in place. This allows filters installed + the log record without modifying it in place. This allows filters attached to on a :class:`Handler` to modify the log record before it is emitted without having side effects on other handlers. diff --git a/Lib/logging/__init__.py b/Lib/logging/__init__.py index 98328ecf2ea952..836258e3d54362 100644 --- a/Lib/logging/__init__.py +++ b/Lib/logging/__init__.py @@ -817,15 +817,17 @@ def filter(self, record): Determine if a record is loggable by consulting all the filters. The default is to allow the record to be logged; any filter can veto - this by returning a falsy value and the record is then dropped and - this method returns a falsy value. - Filters can return a log record, which case that log record - is used to call the next filter. - If filters return a truthy value that is not a log record the - next filter is called with the existing log record. + this by returning a falsy value. + If a filter attached to a handler returns a log record instance, + then that instance is used in place of the original log record in + any further processing of the event by that handler. + If a filter returns any other truthy value, the original log record + is used in any further processing of the event by that handler. If none of the filters return falsy values, this method returns a log record. + If any of the filters return a falsy value, this method returns + a falsy value. .. versionchanged:: 3.2 diff --git a/Lib/test/test_logging.py b/Lib/test/test_logging.py index b526d534ae817a..343cdbcbed2a87 100644 --- a/Lib/test/test_logging.py +++ b/Lib/test/test_logging.py @@ -469,15 +469,9 @@ def log_at_all_levels(self, logger): def test_handler_filter_replaces_record(self): def replace_message(record: logging.LogRecord): - return logging.LogRecord( - name=record.name, - level=record.levelno, - pathname=record.pathname, - lineno=record.lineno, - msg="new message!", - exc_info=record.exc_info, - args=(), - ) + record = copy.copy(record) + record.msg = "new message!" + return record # Set up a logging hierarchy such that "child" and it's handler # (and thus `replace_message()`) always get called before @@ -507,15 +501,7 @@ def test_logging_filter_replaces_record(self): class RecordingFilter(logging.Filter): def filter(self, record: logging.LogRecord): records.add(id(record)) - return logging.LogRecord( - name=record.name, - level=record.levelno, - pathname=record.pathname, - lineno=record.lineno, - msg=record.msg, - exc_info=record.exc_info, - args=(), - ) + return copy.copy(record) logger = logging.getLogger("logger") logger.setLevel(logging.INFO) diff --git a/Misc/NEWS.d/next/Library/2022-05-09-22-27-11.gh-issue-92591.V7RCk2.rst b/Misc/NEWS.d/next/Library/2022-05-09-22-27-11.gh-issue-92591.V7RCk2.rst index 9ad25b3b78d351..3f6528621e3518 100644 --- a/Misc/NEWS.d/next/Library/2022-05-09-22-27-11.gh-issue-92591.V7RCk2.rst +++ b/Misc/NEWS.d/next/Library/2022-05-09-22-27-11.gh-issue-92591.V7RCk2.rst @@ -1,3 +1,3 @@ -Let :mod:`logging` Filters to return a :class:`logging.LogRecord` instance -so that :class:`logging.Handler`\ s can enrich records without side effects on -other Handlers. +Let :mod:`logging` filters to return a :class:`logging.LogRecord` instance +so that filters attached to :class:`logging.Handler`\ s can enrich records without +side effects on other handlers. From f52e4041e99d3603bdb7e6ff8dffe8b19841fd7e Mon Sep 17 00:00:00 2001 From: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> Date: Tue, 10 May 2022 13:26:42 -0500 Subject: [PATCH 11/14] fix whitespace --- Lib/logging/__init__.py | 4 ++-- Lib/test/test_logging.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/logging/__init__.py b/Lib/logging/__init__.py index 836258e3d54362..af42a4477e734f 100644 --- a/Lib/logging/__init__.py +++ b/Lib/logging/__init__.py @@ -832,7 +832,7 @@ def filter(self, record): .. versionchanged:: 3.2 Allow filters to be just callables. - + .. versionchanged:: 3.12 Allow filters to return a LogRecord instead of modifying it in place. @@ -982,7 +982,7 @@ def handle(self, record): Emission depends on filters which may have been added to the handler. Wrap the actual emission of the record with acquisition/release of the I/O thread lock. - + Returns an instance of the log record that was emitted if it passed all filters, otherwise a falsy value is returned. """ diff --git a/Lib/test/test_logging.py b/Lib/test/test_logging.py index 343cdbcbed2a87..46cf55c20339e9 100644 --- a/Lib/test/test_logging.py +++ b/Lib/test/test_logging.py @@ -494,7 +494,7 @@ def replace_message(record: logging.LogRecord): handler_2.flush() self.assertEqual(stream_1.getvalue(), "original message\n") self.assertEqual(stream_2.getvalue(), "new message!\n") - + def test_logging_filter_replaces_record(self): records = set() From 46e2f7fbeeae2c124980c58efb453836eaa214d5 Mon Sep 17 00:00:00 2001 From: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> Date: Tue, 10 May 2022 16:02:18 -0500 Subject: [PATCH 12/14] fix docs as per pr feedback --- Doc/library/logging.rst | 6 +++--- .../Library/2022-05-09-22-27-11.gh-issue-92591.V7RCk2.rst | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Doc/library/logging.rst b/Doc/library/logging.rst index 426378b07d0b93..0b3a53e5059fbf 100644 --- a/Doc/library/logging.rst +++ b/Doc/library/logging.rst @@ -665,7 +665,7 @@ empty string, all events are passed. Is the specified record to be logged? Returns falsy for no, truthy for yes. Filters can either modify log records in-place or return a completely different record instance which will replace the original - log record without modifying it. + log record in any future processing of the event. Note that filters attached to handlers are consulted before an event is emitted by the handler, whereas filters attached to loggers are consulted @@ -689,8 +689,8 @@ which has a ``filter`` method with the same semantics. .. versionchanged:: 3.12 You can now return a :class:`LogRecord` instance from filters to replace - the log record without modifying it in place. This allows filters attached to - on a :class:`Handler` to modify the log record before it is emitted without + the log record rather than modifying it in place. This allows filters attached to + a :class:`Handler` to modify the log record before it is emitted without having side effects on other handlers. Although filters are used primarily to filter records based on more diff --git a/Misc/NEWS.d/next/Library/2022-05-09-22-27-11.gh-issue-92591.V7RCk2.rst b/Misc/NEWS.d/next/Library/2022-05-09-22-27-11.gh-issue-92591.V7RCk2.rst index 3f6528621e3518..cd9b598d1dbca1 100644 --- a/Misc/NEWS.d/next/Library/2022-05-09-22-27-11.gh-issue-92591.V7RCk2.rst +++ b/Misc/NEWS.d/next/Library/2022-05-09-22-27-11.gh-issue-92591.V7RCk2.rst @@ -1,3 +1,3 @@ -Let :mod:`logging` filters to return a :class:`logging.LogRecord` instance +Allow :mod:`logging` filters to return a :class:`logging.LogRecord` instance so that filters attached to :class:`logging.Handler`\ s can enrich records without side effects on other handlers. From 7a0f4708e3a396b35d222314aa83286041cc0972 Mon Sep 17 00:00:00 2001 From: Vinay Sajip Date: Tue, 7 Jun 2022 16:21:26 +0100 Subject: [PATCH 13/14] Update logging-cookbook.rst --- Doc/howto/logging-cookbook.rst | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Doc/howto/logging-cookbook.rst b/Doc/howto/logging-cookbook.rst index d4f046d988f7fd..7e45d25ceb48ff 100644 --- a/Doc/howto/logging-cookbook.rst +++ b/Doc/howto/logging-cookbook.rst @@ -719,9 +719,7 @@ Imparting contextual information in handlers Each :class:`~Handler` has its own chain of filters. If you want to add contextual information to a :class:`LogRecord` without leaking it to other handlers, you can use a filter that returns -a new :class:`~LogRecord` instead of modifying it in-place: - -script:: +a new :class:`~LogRecord` instead of modifying it in-place, as shown in the following script:: import copy import logging From 643551d9c502c458caf3c99a0fae0cfc79be65cf Mon Sep 17 00:00:00 2001 From: Vinay Sajip Date: Tue, 7 Jun 2022 16:23:31 +0100 Subject: [PATCH 14/14] Update logging.rst --- Doc/library/logging.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/logging.rst b/Doc/library/logging.rst index 0b3a53e5059fbf..c148da08f57b17 100644 --- a/Doc/library/logging.rst +++ b/Doc/library/logging.rst @@ -690,7 +690,7 @@ which has a ``filter`` method with the same semantics. .. versionchanged:: 3.12 You can now return a :class:`LogRecord` instance from filters to replace the log record rather than modifying it in place. This allows filters attached to - a :class:`Handler` to modify the log record before it is emitted without + a :class:`Handler` to modify the log record before it is emitted, without having side effects on other handlers. Although filters are used primarily to filter records based on more