Skip to content

Commit c373cb4

Browse files
committed
feat(logger): backport to py3.7
1 parent 63bb741 commit c373cb4

File tree

2 files changed

+63
-8
lines changed

2 files changed

+63
-8
lines changed

aws_lambda_powertools/logging/logger.py

Lines changed: 60 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import functools
22
import inspect
3+
import io
34
import logging
45
import os
56
import random
67
import sys
8+
import traceback
79
from typing import IO, Any, Callable, Dict, Iterable, Mapping, Optional, TypeVar, Union
810

911
import jmespath
@@ -235,6 +237,9 @@ def _init_logger(self, **kwargs):
235237
self._logger.addHandler(self.logger_handler)
236238
self.structure_logs(**kwargs)
237239

240+
# Maintenance: We can drop this upon Py3.7 EOL. It's a backport for "location" key to work
241+
self._logger.findCaller = self.findCaller
242+
238243
# Pytest Live Log feature duplicates log records for colored output
239244
# but we explicitly add a filter for log deduplication.
240245
# This flag disables this protection when you explicit want logs to be duplicated (#262)
@@ -369,9 +374,10 @@ def info(
369374
extra: Optional[Mapping[str, object]] = None,
370375
**kwargs,
371376
):
372-
# NOTE: We need to solve stack frame location for Python <3.8
373377
extra = extra or {}
374378
extra = {**extra, **kwargs}
379+
380+
# Maintenance: We can drop this upon Py3.7 EOL. It's a backport for "location" key to work
375381
if sys.version_info < (3, 8):
376382
return self._logger.info(msg, *args, exc_info=exc_info, stack_info=stack_info, extra=extra)
377383
return self._logger.info(
@@ -388,9 +394,10 @@ def error(
388394
extra: Optional[Mapping[str, object]] = None,
389395
**kwargs,
390396
):
391-
# NOTE: We need to solve stack frame location for Python <3.8
392397
extra = extra or {}
393398
extra = {**extra, **kwargs}
399+
400+
# Maintenance: We can drop this upon Py3.7 EOL. It's a backport for "location" key to work
394401
if sys.version_info < (3, 8):
395402
return self._logger.error(msg, *args, exc_info=exc_info, stack_info=stack_info, extra=extra)
396403
return self._logger.error(
@@ -407,9 +414,10 @@ def exception(
407414
extra: Optional[Mapping[str, object]] = None,
408415
**kwargs,
409416
):
410-
# NOTE: We need to solve stack frame location for Python <3.8
411417
extra = extra or {}
412418
extra = {**extra, **kwargs}
419+
420+
# Maintenance: We can drop this upon Py3.7 EOL. It's a backport for "location" key to work
413421
if sys.version_info < (3, 8):
414422
return self._logger.exception(msg, *args, exc_info=exc_info, stack_info=stack_info, extra=extra)
415423
return self._logger.exception(
@@ -426,9 +434,10 @@ def critical(
426434
extra: Optional[Mapping[str, object]] = None,
427435
**kwargs,
428436
):
429-
# NOTE: We need to solve stack frame location for Python <3.8
430437
extra = extra or {}
431438
extra = {**extra, **kwargs}
439+
440+
# Maintenance: We can drop this upon Py3.7 EOL. It's a backport for "location" key to work
432441
if sys.version_info < (3, 8):
433442
return self._logger.critical(msg, *args, exc_info=exc_info, stack_info=stack_info, extra=extra)
434443
return self._logger.critical(
@@ -445,9 +454,10 @@ def warning(
445454
extra: Optional[Mapping[str, object]] = None,
446455
**kwargs,
447456
):
448-
# NOTE: We need to solve stack frame location for Python <3.8
449457
extra = extra or {}
450458
extra = {**extra, **kwargs}
459+
460+
# Maintenance: We can drop this upon Py3.7 EOL. It's a backport for "location" key to work
451461
if sys.version_info < (3, 8):
452462
return self._logger.warning(msg, *args, exc_info=exc_info, stack_info=stack_info, extra=extra)
453463
return self._logger.warning(
@@ -557,6 +567,41 @@ def _get_caller_filename():
557567
caller_frame = frame.f_back.f_back.f_back
558568
return caller_frame.f_globals["__name__"]
559569

570+
# Maintenance: We can drop this upon Py3.7 EOL. It's a backport for "location" key to work
571+
def findCaller(self, stack_info=False, stacklevel=2): # pragma: no cover
572+
"""
573+
Find the stack frame of the caller so that we can note the source
574+
file name, line number and function name.
575+
"""
576+
f = logging.currentframe() # noqa: VNE001
577+
# On some versions of IronPython, currentframe() returns None if
578+
# IronPython isn't run with -X:Frames.
579+
if f is None:
580+
return "(unknown file)", 0, "(unknown function)", None
581+
while stacklevel > 0:
582+
next_f = f.f_back
583+
if next_f is None:
584+
## We've got options here.
585+
## If we want to use the last (deepest) frame:
586+
break
587+
## If we want to mimic the warnings module:
588+
# return ("sys", 1, "(unknown function)", None) # noqa: E800
589+
## If we want to be pedantic: # noqa: E800
590+
# raise ValueError("call stack is not deep enough") # noqa: E800
591+
f = next_f # noqa: VNE001
592+
if not _is_internal_frame(f):
593+
stacklevel -= 1
594+
co = f.f_code
595+
sinfo = None
596+
if stack_info:
597+
with io.StringIO() as sio:
598+
sio.write("Stack (most recent call last):\n")
599+
traceback.print_stack(f, file=sio)
600+
sinfo = sio.getvalue()
601+
if sinfo[-1] == "\n":
602+
sinfo = sinfo[:-1]
603+
return co.co_filename, f.f_lineno, co.co_name, sinfo
604+
560605

561606
def set_package_logger(
562607
level: Union[str, int] = logging.DEBUG,
@@ -595,3 +640,13 @@ def set_package_logger(
595640
handler = logging.StreamHandler(stream)
596641
handler.setFormatter(formatter)
597642
logger.addHandler(handler)
643+
644+
645+
# Maintenance: We can drop this upon Py3.7 EOL. It's a backport for "location" key to work
646+
# The following is based on warnings._is_internal_frame. It makes sure that
647+
# frames of the import mechanism are skipped when logging at module level and
648+
# using a stacklevel value greater than one.
649+
def _is_internal_frame(frame): # pragma: no cover
650+
"""Signal whether the frame is a CPython or logging module internal."""
651+
filename = os.path.normcase(frame.f_code.co_filename)
652+
return filename == logging._srcfile or ("importlib" in filename and "_bootstrap" in filename)

tests/functional/test_logger.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -415,14 +415,14 @@ def test_logger_extra_kwargs(stdout, service_name):
415415
assert "request_id" not in no_extra_fields_log
416416

417417

418-
def test_logger_kwargs_no_extra(stdout, service_name):
418+
def test_logger_arbitrary_fields_as_kwargs(stdout, service_name):
419419
# GIVEN Logger is initialized
420420
logger = Logger(service=service_name, stream=stdout)
421421

422-
# WHEN `request_id` is an extra field in a log message to the existing structured log
422+
# WHEN `request_id` is an arbitrary field in a log message to the existing structured log
423423
fields = {"request_id": "blah"}
424424

425-
logger.info("with extra fields", **fields)
425+
logger.info("with arbitrary fields", **fields)
426426
logger.info("without extra fields")
427427

428428
extra_fields_log, no_extra_fields_log = capture_multiple_logging_statements_output(stdout)

0 commit comments

Comments
 (0)