feat(buffer-handler): add buffering support for external loggers#7994
feat(buffer-handler): add buffering support for external loggers#7994acascell wants to merge 4 commits intoaws-powertools:developfrom
Conversation
|
Thanks a lot for your first contribution! Please check out our contributing guidelines and don't hesitate to ask whatever you need. |
|
Hi @acascell - thanks for the PR and sorry for the delayed review. @leandrodamascena will most likely review this next week once he's back. |
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## develop #7994 +/- ##
========================================
Coverage 96.73% 96.73%
========================================
Files 278 279 +1
Lines 13657 13675 +18
Branches 1086 1087 +1
========================================
+ Hits 13211 13229 +18
Misses 327 327
Partials 119 119 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
leandrodamascena
left a comment
There was a problem hiding this comment.
Hey @acascell thanks a lot for working on this! I ran some tests and I think we need to fix small changes before we merge this. I just left some comments, can you please read them and see if this make sense?
Thanks a lot.
| self.buffer_config = buffer_config | ||
| self.source_logger = source_logger | ||
|
|
||
| def emit(self, record: logging.LogRecord) -> None: |
There was a problem hiding this comment.
I noticed the handler buffers everything regardless of level, but our Logger checks _check_minimum_buffer_log_level before deciding to buffer or emit. So if someone sets buffer_at_verbosity="DEBUG", the Logger emits INFO logs right away - but external loggers through this handler would buffer them instead. That inconsistency could cause logs to be silently lost if the buffer is never flushed.
The fix is to add the same level check:
from aws_lambda_powertools.logging.buffer.functions import _check_minimum_buffer_log_level
def emit(self, record: logging.LogRecord) -> None:
level_name = logging.getLevelName(record.levelno)
# If log level exceeds buffer threshold, emit directly through source logger
if _check_minimum_buffer_log_level(self.buffer_config.buffer_at_verbosity, level_name):
self.source_logger._logger.handle(record)
return
self.source_logger._add_log_record_to_buffer(
level=record.levelno,
msg=record.getMessage(),
args=(),
exc_info=record.exc_info,
stack_info=False,
)| ) | ||
| logger.addHandler(buffer_handler) | ||
| LOGGER.debug(f"Logger {logger} configured with BufferingHandler") | ||
| return # exit earlier and don't add source handlers, would cause double logging |
There was a problem hiding this comment.
When include_buffering=True, the external logger gets no source handlers (the return skips the handler copy loop). This is fine for buffered logs since they flush through the source logger's handler. But if you add the level check from comment 1, logs above the buffer threshold need a handler to emit through - otherwise they go nowhere.
You'd need to also attach the source handlers:
if include_buffering and buffer_config is not None:
buffer_handler = BufferingHandler(
buffer_cache=source_logger._buffer_cache,
buffer_config=buffer_config,
source_logger=source_logger,
)
logger.addHandler(buffer_handler)
for source_handler in source_logger.handlers:
logger.addHandler(source_handler)
returnBut then you'd need the BufferingHandler to suppress the record from propagating to the other handlers when it buffers - otherwise you get double logging. A cleaner approach might be to handle both paths inside emit() itself and keep the early return as-is.



Issue number: closes #7918
Summary
This PR implements log buffering support for external libraries when using AWS Lambda Powertools Logger with buffering enabled.
Changes
New
BufferingHandlerclass (aws_lambda_powertools/logging/buffer/handler.py):logging.Handler_add_log_record_to_buffer()functionEnhanced
copy_config_to_registered_loggers()function (aws_lambda_powertools/logging/utils.py):include_bufferingparameter (default:False)FalseUpdated configuration logic (
-):BufferingHandleroninclude_bufferingflagIncluded unit and functional tests
tests/functional/logger/required_dependencies/test_logger_utils.pytests/unit/logger/required_dependencies/test_logger_buffer_handler.pyUser Experience
Before this change:
When using Logger with buffering enabled, external library logs (botocore, mangum, etc.) were NOT buffered - they appeared immediately in CloudWatch, defeating the purpose of buffering:
Result: External library logs appeared immediately in CloudWatch, while application logs were buffered.
After this change:
External library logs are now buffered alongside application logs when
include_buffering=True:Result: All logs (application + external libraries) are buffered together and flushed as a unit
By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.
Disclaimer: We value your time and bandwidth. As such, any pull requests created on non-triaged issues might not be successful.