Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork33.7k
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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Uh oh!
There was an error while loading.Please reload this page.
Changes from1 commit
3aeeba61e4d27fb526a35f1ea18319561d314e704d4d34540a69e76a67453f6b1e1084f72c594225329f735d78a5e54c77f52e40446e2f7f7a0f470643551dFile filter
Filter by extension
Conversations
Uh oh!
There was an error while loading.Please reload this page.
Jump to
Uh oh!
There was an error while loading.Please reload this page.
Diff view
Diff view
- Loading branch information
Uh oh!
There was an error while loading.Please reload this page.
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 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. | ||
| """ | ||
| 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: | ||
| 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 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( | ||
| ||
| 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) | ||