|
1 | 1 | import functools
|
2 | 2 | import inspect
|
| 3 | +import io |
3 | 4 | import logging
|
4 | 5 | import os
|
5 | 6 | import random
|
6 | 7 | import sys
|
7 |
| -from typing import IO, Any, Callable, Dict, Iterable, Optional, TypeVar, Union |
| 8 | +import traceback |
| 9 | +from typing import IO, Any, Callable, Dict, Iterable, Mapping, Optional, TypeVar, Union |
8 | 10 |
|
9 | 11 | import jmespath
|
10 | 12 |
|
@@ -235,6 +237,9 @@ def _init_logger(self, **kwargs):
|
235 | 237 | self._logger.addHandler(self.logger_handler)
|
236 | 238 | self.structure_logs(**kwargs)
|
237 | 239 |
|
| 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 | + |
238 | 243 | # Pytest Live Log feature duplicates log records for colored output
|
239 | 244 | # but we explicitly add a filter for log deduplication.
|
240 | 245 | # This flag disables this protection when you explicit want logs to be duplicated (#262)
|
@@ -359,6 +364,126 @@ def decorate(event, context, *args, **kwargs):
|
359 | 364 |
|
360 | 365 | return decorate
|
361 | 366 |
|
| 367 | + def info( |
| 368 | + self, |
| 369 | + msg: object, |
| 370 | + *args, |
| 371 | + exc_info=None, |
| 372 | + stack_info: bool = False, |
| 373 | + stacklevel: int = 2, |
| 374 | + extra: Optional[Mapping[str, object]] = None, |
| 375 | + **kwargs, |
| 376 | + ): |
| 377 | + extra = extra or {} |
| 378 | + extra = {**extra, **kwargs} |
| 379 | + |
| 380 | + # Maintenance: We can drop this upon Py3.7 EOL. It's a backport for "location" key to work |
| 381 | + if sys.version_info < (3, 8): # pragma: no cover |
| 382 | + return self._logger.info(msg, *args, exc_info=exc_info, stack_info=stack_info, extra=extra) |
| 383 | + return self._logger.info( |
| 384 | + msg, *args, exc_info=exc_info, stack_info=stack_info, stacklevel=stacklevel, extra=extra |
| 385 | + ) |
| 386 | + |
| 387 | + def error( |
| 388 | + self, |
| 389 | + msg: object, |
| 390 | + *args, |
| 391 | + exc_info=None, |
| 392 | + stack_info: bool = False, |
| 393 | + stacklevel: int = 2, |
| 394 | + extra: Optional[Mapping[str, object]] = None, |
| 395 | + **kwargs, |
| 396 | + ): |
| 397 | + extra = extra or {} |
| 398 | + extra = {**extra, **kwargs} |
| 399 | + |
| 400 | + # Maintenance: We can drop this upon Py3.7 EOL. It's a backport for "location" key to work |
| 401 | + if sys.version_info < (3, 8): # pragma: no cover |
| 402 | + return self._logger.error(msg, *args, exc_info=exc_info, stack_info=stack_info, extra=extra) |
| 403 | + return self._logger.error( |
| 404 | + msg, *args, exc_info=exc_info, stack_info=stack_info, stacklevel=stacklevel, extra=extra |
| 405 | + ) |
| 406 | + |
| 407 | + def exception( |
| 408 | + self, |
| 409 | + msg: object, |
| 410 | + *args, |
| 411 | + exc_info=True, |
| 412 | + stack_info: bool = False, |
| 413 | + stacklevel: int = 2, |
| 414 | + extra: Optional[Mapping[str, object]] = None, |
| 415 | + **kwargs, |
| 416 | + ): |
| 417 | + extra = extra or {} |
| 418 | + extra = {**extra, **kwargs} |
| 419 | + |
| 420 | + # Maintenance: We can drop this upon Py3.7 EOL. It's a backport for "location" key to work |
| 421 | + if sys.version_info < (3, 8): # pragma: no cover |
| 422 | + return self._logger.exception(msg, *args, exc_info=exc_info, stack_info=stack_info, extra=extra) |
| 423 | + return self._logger.exception( |
| 424 | + msg, *args, exc_info=exc_info, stack_info=stack_info, stacklevel=stacklevel, extra=extra |
| 425 | + ) |
| 426 | + |
| 427 | + def critical( |
| 428 | + self, |
| 429 | + msg: object, |
| 430 | + *args, |
| 431 | + exc_info=None, |
| 432 | + stack_info: bool = False, |
| 433 | + stacklevel: int = 2, |
| 434 | + extra: Optional[Mapping[str, object]] = None, |
| 435 | + **kwargs, |
| 436 | + ): |
| 437 | + extra = extra or {} |
| 438 | + extra = {**extra, **kwargs} |
| 439 | + |
| 440 | + # Maintenance: We can drop this upon Py3.7 EOL. It's a backport for "location" key to work |
| 441 | + if sys.version_info < (3, 8): # pragma: no cover |
| 442 | + return self._logger.critical(msg, *args, exc_info=exc_info, stack_info=stack_info, extra=extra) |
| 443 | + return self._logger.critical( |
| 444 | + msg, *args, exc_info=exc_info, stack_info=stack_info, stacklevel=stacklevel, extra=extra |
| 445 | + ) |
| 446 | + |
| 447 | + def warning( |
| 448 | + self, |
| 449 | + msg: object, |
| 450 | + *args, |
| 451 | + exc_info=None, |
| 452 | + stack_info: bool = False, |
| 453 | + stacklevel: int = 2, |
| 454 | + extra: Optional[Mapping[str, object]] = None, |
| 455 | + **kwargs, |
| 456 | + ): |
| 457 | + extra = extra or {} |
| 458 | + extra = {**extra, **kwargs} |
| 459 | + |
| 460 | + # Maintenance: We can drop this upon Py3.7 EOL. It's a backport for "location" key to work |
| 461 | + if sys.version_info < (3, 8): # pragma: no cover |
| 462 | + return self._logger.warning(msg, *args, exc_info=exc_info, stack_info=stack_info, extra=extra) |
| 463 | + return self._logger.warning( |
| 464 | + msg, *args, exc_info=exc_info, stack_info=stack_info, stacklevel=stacklevel, extra=extra |
| 465 | + ) |
| 466 | + |
| 467 | + def debug( |
| 468 | + self, |
| 469 | + msg: object, |
| 470 | + *args, |
| 471 | + exc_info=None, |
| 472 | + stack_info: bool = False, |
| 473 | + stacklevel: int = 2, |
| 474 | + extra: Optional[Mapping[str, object]] = None, |
| 475 | + **kwargs, |
| 476 | + ): |
| 477 | + extra = extra or {} |
| 478 | + extra = {**extra, **kwargs} |
| 479 | + |
| 480 | + # Maintenance: We can drop this upon Py3.7 EOL. It's a backport for "location" key to work |
| 481 | + if sys.version_info < (3, 8): # pragma: no cover |
| 482 | + return self._logger.debug(msg, *args, exc_info=exc_info, stack_info=stack_info, extra=extra) |
| 483 | + return self._logger.debug( |
| 484 | + msg, *args, exc_info=exc_info, stack_info=stack_info, stacklevel=stacklevel, extra=extra |
| 485 | + ) |
| 486 | + |
362 | 487 | def append_keys(self, **additional_keys):
|
363 | 488 | self.registered_formatter.append_keys(**additional_keys)
|
364 | 489 |
|
@@ -462,6 +587,41 @@ def _get_caller_filename():
|
462 | 587 | caller_frame = frame.f_back.f_back.f_back
|
463 | 588 | return caller_frame.f_globals["__name__"]
|
464 | 589 |
|
| 590 | + # Maintenance: We can drop this upon Py3.7 EOL. It's a backport for "location" key to work |
| 591 | + def findCaller(self, stack_info=False, stacklevel=2): # pragma: no cover |
| 592 | + """ |
| 593 | + Find the stack frame of the caller so that we can note the source |
| 594 | + file name, line number and function name. |
| 595 | + """ |
| 596 | + f = logging.currentframe() # noqa: VNE001 |
| 597 | + # On some versions of IronPython, currentframe() returns None if |
| 598 | + # IronPython isn't run with -X:Frames. |
| 599 | + if f is None: |
| 600 | + return "(unknown file)", 0, "(unknown function)", None |
| 601 | + while stacklevel > 0: |
| 602 | + next_f = f.f_back |
| 603 | + if next_f is None: |
| 604 | + ## We've got options here. |
| 605 | + ## If we want to use the last (deepest) frame: |
| 606 | + break |
| 607 | + ## If we want to mimic the warnings module: |
| 608 | + # return ("sys", 1, "(unknown function)", None) # noqa: E800 |
| 609 | + ## If we want to be pedantic: # noqa: E800 |
| 610 | + # raise ValueError("call stack is not deep enough") # noqa: E800 |
| 611 | + f = next_f # noqa: VNE001 |
| 612 | + if not _is_internal_frame(f): |
| 613 | + stacklevel -= 1 |
| 614 | + co = f.f_code |
| 615 | + sinfo = None |
| 616 | + if stack_info: |
| 617 | + with io.StringIO() as sio: |
| 618 | + sio.write("Stack (most recent call last):\n") |
| 619 | + traceback.print_stack(f, file=sio) |
| 620 | + sinfo = sio.getvalue() |
| 621 | + if sinfo[-1] == "\n": |
| 622 | + sinfo = sinfo[:-1] |
| 623 | + return co.co_filename, f.f_lineno, co.co_name, sinfo |
| 624 | + |
465 | 625 |
|
466 | 626 | def set_package_logger(
|
467 | 627 | level: Union[str, int] = logging.DEBUG,
|
@@ -500,3 +660,13 @@ def set_package_logger(
|
500 | 660 | handler = logging.StreamHandler(stream)
|
501 | 661 | handler.setFormatter(formatter)
|
502 | 662 | logger.addHandler(handler)
|
| 663 | + |
| 664 | + |
| 665 | +# Maintenance: We can drop this upon Py3.7 EOL. It's a backport for "location" key to work |
| 666 | +# The following is based on warnings._is_internal_frame. It makes sure that |
| 667 | +# frames of the import mechanism are skipped when logging at module level and |
| 668 | +# using a stacklevel value greater than one. |
| 669 | +def _is_internal_frame(frame): # pragma: no cover |
| 670 | + """Signal whether the frame is a CPython or logging module internal.""" |
| 671 | + filename = os.path.normcase(frame.f_code.co_filename) |
| 672 | + return filename == logging._srcfile or ("importlib" in filename and "_bootstrap" in filename) |
0 commit comments