Skip to content

Commit 27b1ce9

Browse files
Adding more logic
1 parent e39229a commit 27b1ce9

File tree

3 files changed

+158
-34
lines changed

3 files changed

+158
-34
lines changed

aws_lambda_powertools/logging/buffer/functions.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,26 @@
22

33
import inspect
44
import time
5-
from typing import Any
5+
from typing import Any, Mapping
66

77

8-
def _create_buffer_record(level: str, msg: object, args: object, **kwargs) -> dict[str, Any]:
8+
def _create_buffer_record(
9+
level: int,
10+
msg: object,
11+
args: object,
12+
extra: Mapping[str, object] | None = None,
13+
) -> dict[str, Any]:
914
caller_frame = inspect.stack()[2]
1015
timestamp = time.time()
1116

1217
return {
13-
"level": level.upper(),
18+
"level": level,
1419
"msg": msg,
1520
"args": args,
1621
"filename": caller_frame.filename,
1722
"line": caller_frame.lineno,
1823
"function": caller_frame.function,
19-
"extra_kwargs": kwargs or {},
24+
"extra": extra,
2025
"timestamp": timestamp,
2126
}
2227

aws_lambda_powertools/logging/logger.py

Lines changed: 133 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -457,18 +457,73 @@ def decorate(event, context, *args, **kwargs):
457457

458458
return decorate
459459

460-
def _add_log_line_to_buffer(self, log_record: dict[str, Any]):
461-
# Initial implementation, will always cache
462-
# Add logic for "empty"
460+
def _create_and_flush_log_record(self, log_line: dict) -> None:
461+
"""
462+
Create and immediately flush a log record to the configured logger.
463+
464+
Parameters
465+
----------
466+
log_line : dict[str, Any]
467+
Dictionary containing log record details with keys:
468+
- 'level': Logging level
469+
- 'filename': Source filename
470+
- 'line': Line number
471+
- 'msg': Log message
472+
- 'function': Source function name
473+
- 'extra': Additional context
474+
- 'timestamp': Original log creation time
475+
476+
Notes
477+
-----
478+
Bypasses standard logging flow by directly creating and handling a log record.
479+
Preserves original timestamp and source information.
480+
"""
481+
record = self._logger.makeRecord(
482+
name=self.name,
483+
level=log_line["level"],
484+
fn=log_line["filename"],
485+
lno=log_line["line"],
486+
msg=log_line["msg"],
487+
args=(),
488+
exc_info=None,
489+
func=log_line["function"],
490+
extra=log_line["extra"],
491+
)
492+
record.created = log_line["timestamp"]
493+
self._logger.handle(record)
494+
495+
def _add_log_record_to_buffer(
496+
self,
497+
level: int,
498+
msg: object,
499+
args: object,
500+
exc_info: logging._ExcInfoType,
501+
stack_info: bool,
502+
extra: Mapping[str, object],
503+
):
463504
tracer_id = os.getenv(constants.XRAY_TRACE_ID_ENV, None)
464505
if tracer_id:
506+
log_record: dict[str, Any] = _create_buffer_record(level=level, msg=msg, args=args, extra=extra)
465507
self._buffer_cache.add(tracer_id, log_record)
466508

467509
def flush_buffer(self):
468-
# Initial logic
510+
"""
511+
Flush all buffered log records associated with current trace ID.
512+
513+
Notes
514+
-----
515+
Retrieves log records for current trace from buffer
516+
Immediately processes and logs each record
517+
Clears buffer after complete processing
518+
519+
Raises
520+
------
521+
Any exceptions from underlying logging or buffer mechanisms
522+
will be propagated to caller
523+
"""
469524
tracer_id = os.getenv(constants.XRAY_TRACE_ID_ENV, None)
470-
for item in self._buffer_cache.get(tracer_id):
471-
self._logger.debug(item["msg"])
525+
for log_line in self._buffer_cache.get(tracer_id):
526+
self._create_and_flush_log_record(log_line)
472527

473528
self._buffer_cache.clear()
474529

@@ -485,7 +540,16 @@ def debug(
485540
extra = extra or {}
486541
extra = {**extra, **kwargs}
487542

488-
# Buffer is not active, flushing
543+
# Logging workflow for logging.debug:
544+
# 1. Buffer is completely disabled - log right away
545+
# 2. DEBUG is the maximum level of buffer, so, can't bypass if enabled
546+
# 3. Store in buffer for potential later processing
547+
548+
# MAINTAINABILITY_DECISION:
549+
# Keeping this implementation to avoid complex code handling.
550+
# Also for clarity over complexity
551+
552+
# Buffer is not active and we need to log immediately
489553
if not self._logger_buffer:
490554
return self._logger.debug(
491555
msg,
@@ -496,9 +560,15 @@ def debug(
496560
extra=extra,
497561
)
498562

499-
log_record = _create_buffer_record(level="DEBUG", msg=msg, args=args, **kwargs)
500-
501-
self._add_log_line_to_buffer(log_record)
563+
# Store record in the buffer
564+
self._add_log_record_to_buffer(
565+
level=logging.DEBUG,
566+
msg=msg,
567+
args=args,
568+
exc_info=exc_info,
569+
stack_info=stack_info,
570+
extra=extra,
571+
)
502572

503573
def info(
504574
self,
@@ -513,7 +583,16 @@ def info(
513583
extra = extra or {}
514584
extra = {**extra, **kwargs}
515585

516-
# Buffer is not active and we need to flush
586+
# Logging workflow for logging.info:
587+
# 1. Buffer is completely disabled - log right away
588+
# 2. Log severity exceeds buffer's minimum threshold - bypass buffering
589+
# 3. If neither condition met, store in buffer for potential later processing
590+
591+
# MAINTAINABILITY_DECISION:
592+
# Keeping this implementation to avoid complex code handling.
593+
# Also for clarity over complexity
594+
595+
# Buffer is not active and we need to log immediately
517596
if not self._logger_buffer:
518597
return self._logger.info(
519598
msg,
@@ -524,7 +603,7 @@ def info(
524603
extra=extra,
525604
)
526605

527-
# Buffer log level is higher than this log level and we need to flush
606+
# Bypass buffer when log severity meets or exceeds configured minimum
528607
if _check_minimum_buffer_log_level(self._logger_buffer.minimum_log_level, "INFO"):
529608
return self._logger.info(
530609
msg,
@@ -535,9 +614,15 @@ def info(
535614
extra=extra,
536615
)
537616

538-
log_record: dict[str, Any] = _create_buffer_record(level="INFO", msg=msg, args=args, **kwargs)
539-
540-
self._add_log_line_to_buffer(log_record)
617+
# Store record in the buffer
618+
self._add_log_record_to_buffer(
619+
level=logging.INFO,
620+
msg=msg,
621+
args=args,
622+
exc_info=exc_info,
623+
stack_info=stack_info,
624+
extra=extra,
625+
)
541626

542627
def warning(
543628
self,
@@ -552,7 +637,16 @@ def warning(
552637
extra = extra or {}
553638
extra = {**extra, **kwargs}
554639

555-
# Buffer is not active and we need to flush
640+
# Logging workflow for logging.warning:
641+
# 1. Buffer is completely disabled - log right away
642+
# 2. Log severity exceeds buffer's minimum threshold - bypass buffering
643+
# 3. If neither condition met, store in buffer for potential later processing
644+
645+
# MAINTAINABILITY_DECISION:
646+
# Keeping this implementation to avoid complex code handling.
647+
# Also for clarity over complexity
648+
649+
# Buffer is not active and we need to log immediately
556650
if not self._logger_buffer:
557651
return self._logger.warning(
558652
msg,
@@ -563,7 +657,7 @@ def warning(
563657
extra=extra,
564658
)
565659

566-
# Buffer log level is higher than this log level and we need to flush
660+
# Bypass buffer when log severity meets or exceeds configured minimum
567661
if _check_minimum_buffer_log_level(self._logger_buffer.minimum_log_level, "WARNING"):
568662
return self._logger.warning(
569663
msg,
@@ -574,9 +668,15 @@ def warning(
574668
extra=extra,
575669
)
576670

577-
log_record = _create_buffer_record(level="WARNING", msg=msg, args=args, **kwargs)
578-
579-
self._add_log_line_to_buffer(log_record)
671+
# Store record in the buffer
672+
self._add_log_record_to_buffer(
673+
level=logging.WARNING,
674+
msg=msg,
675+
args=args,
676+
exc_info=exc_info,
677+
stack_info=stack_info,
678+
extra=extra,
679+
)
580680

581681
def error(
582682
self,
@@ -591,9 +691,11 @@ def error(
591691
extra = extra or {}
592692
extra = {**extra, **kwargs}
593693

594-
# Buffer is active and an error happened
595-
# LoggerBufferConfig flush_on_error is True
596-
# So, we need to flush the buffer
694+
# Workflow: Error Logging with automatic buffer flushing
695+
# 1. Buffer configuration checked for immediate flush
696+
# 2. If auto-flush enabled, trigger complete buffer processing
697+
# 3. Error log is not "bufferable", so ensure error log is immediately available
698+
597699
if self._logger_buffer and self._logger_buffer.flush_on_error:
598700
self.flush_buffer()
599701

@@ -619,9 +721,10 @@ def critical(
619721
extra = extra or {}
620722
extra = {**extra, **kwargs}
621723

622-
# Buffer is active and an error happened
623-
# LoggerBufferConfig flush_on_error is True
624-
# So, we need to flush the buffer
724+
# Workflow: Error Logging with automatic buffer flushing
725+
# 1. Buffer configuration checked for immediate flush
726+
# 2. If auto-flush enabled, trigger complete buffer processing
727+
# 3. Critical log is not "bufferable", so ensure error log is immediately available
625728
if self._logger_buffer and self._logger_buffer.flush_on_error:
626729
self.flush_buffer()
627730

@@ -647,9 +750,10 @@ def exception(
647750
extra = extra or {}
648751
extra = {**extra, **kwargs}
649752

650-
# Buffer is active and an error happened
651-
# LoggerBufferConfig flush_on_error is True
652-
# So, we need to flush the buffer
753+
# Workflow: Error Logging with automatic buffer flushing
754+
# 1. Buffer configuration checked for immediate flush
755+
# 2. If auto-flush enabled, trigger complete buffer processing
756+
# 3. Exception log is not "bufferable", so ensure error log is immediately available
653757
if self._logger_buffer and self._logger_buffer.flush_on_error:
654758
self.flush_buffer()
655759

tests/functional/logger/required_dependencies/test_powertools_logger_buffer.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ def test_logger_buffer_is_never_buffered_with_exception(stdout, service_name):
8282
assert "Received an exception" == log["message"]
8383

8484

85-
def test_logger_buffer_is_never_buffered_with_error_new(stdout, service_name):
85+
def test_logger_buffer_is_never_buffered_with_error(stdout, service_name):
8686
# GIVEN: A logger configured with buffer
8787
logger_buffer_config = LoggerBufferConfig(max_size=10240)
8888
logger = Logger(service=service_name, stream=stdout, logger_buffer=logger_buffer_config)
@@ -143,3 +143,18 @@ def test_logger_buffer_is_not_flushed_when_an_error_happens(stdout, service_name
143143
# THEN: We expect the log record is not buffered
144144
log = capture_logging_output(stdout)
145145
assert "Received an exception" == log["message"]
146+
147+
148+
def test_create_and_flush_logs(stdout, service_name, monkeypatch):
149+
# GIVEN: A logger configured with buffer
150+
monkeypatch.setenv(constants.XRAY_TRACE_ID_ENV, "1234")
151+
logger_buffer_config = LoggerBufferConfig(max_size=10240, minimum_log_level="DEBUG", flush_on_error=True)
152+
logger = Logger(level="DEBUG", service=service_name, stream=stdout, logger_buffer=logger_buffer_config)
153+
154+
logger.debug("this log line will be flushed")
155+
156+
logger.flush_buffer()
157+
158+
# THEN: We expect the log record is not buffered
159+
log = capture_multiple_logging_statements_output(stdout)
160+
assert "this log line will be flushed" == log[0]["message"]

0 commit comments

Comments
 (0)