-
-
Notifications
You must be signed in to change notification settings - Fork 34.5k
gh-92592: Allow logging filters to return a LogRecord. (GH-92591) #92591
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
3aeeba6
1e4d27f
b526a35
f1ea183
19561d3
14e704d
4d34540
a69e76a
67453f6
b1e1084
f72c594
225329f
735d78a
5e54c77
f52e404
46e2f7f
7a0f470
643551d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -359,6 +359,9 @@ def __repr__(self): | |
| return '<LogRecord: %s, %s, %s, %s, "%s">'%(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 | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Change sentences to
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thank you that does sound better |
||
| 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): | ||
| """ | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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( | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use |
||
| 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) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't this now repetition which isn't needed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's worth differentiating between the return values of filters and the return value of the
Filterer.filterfunction. The main difference is that filters may return a truthy value that is not a log record butFilterer.filterwill now always return a log record (or falsy). I reworded it a bit to make these two different paragraphs.