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
8
+ import traceback
7
9
from typing import IO , Any , Callable , Dict , Iterable , Mapping , Optional , TypeVar , Union
8
10
9
11
import jmespath
@@ -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)
@@ -369,9 +374,10 @@ def info(
369
374
extra : Optional [Mapping [str , object ]] = None ,
370
375
** kwargs ,
371
376
):
372
- # NOTE: We need to solve stack frame location for Python <3.8
373
377
extra = extra or {}
374
378
extra = {** extra , ** kwargs }
379
+
380
+ # Maintenance: We can drop this upon Py3.7 EOL. It's a backport for "location" key to work
375
381
if sys .version_info < (3 , 8 ):
376
382
return self ._logger .info (msg , * args , exc_info = exc_info , stack_info = stack_info , extra = extra )
377
383
return self ._logger .info (
@@ -388,9 +394,10 @@ def error(
388
394
extra : Optional [Mapping [str , object ]] = None ,
389
395
** kwargs ,
390
396
):
391
- # NOTE: We need to solve stack frame location for Python <3.8
392
397
extra = extra or {}
393
398
extra = {** extra , ** kwargs }
399
+
400
+ # Maintenance: We can drop this upon Py3.7 EOL. It's a backport for "location" key to work
394
401
if sys .version_info < (3 , 8 ):
395
402
return self ._logger .error (msg , * args , exc_info = exc_info , stack_info = stack_info , extra = extra )
396
403
return self ._logger .error (
@@ -407,9 +414,10 @@ def exception(
407
414
extra : Optional [Mapping [str , object ]] = None ,
408
415
** kwargs ,
409
416
):
410
- # NOTE: We need to solve stack frame location for Python <3.8
411
417
extra = extra or {}
412
418
extra = {** extra , ** kwargs }
419
+
420
+ # Maintenance: We can drop this upon Py3.7 EOL. It's a backport for "location" key to work
413
421
if sys .version_info < (3 , 8 ):
414
422
return self ._logger .exception (msg , * args , exc_info = exc_info , stack_info = stack_info , extra = extra )
415
423
return self ._logger .exception (
@@ -426,9 +434,10 @@ def critical(
426
434
extra : Optional [Mapping [str , object ]] = None ,
427
435
** kwargs ,
428
436
):
429
- # NOTE: We need to solve stack frame location for Python <3.8
430
437
extra = extra or {}
431
438
extra = {** extra , ** kwargs }
439
+
440
+ # Maintenance: We can drop this upon Py3.7 EOL. It's a backport for "location" key to work
432
441
if sys .version_info < (3 , 8 ):
433
442
return self ._logger .critical (msg , * args , exc_info = exc_info , stack_info = stack_info , extra = extra )
434
443
return self ._logger .critical (
@@ -445,9 +454,10 @@ def warning(
445
454
extra : Optional [Mapping [str , object ]] = None ,
446
455
** kwargs ,
447
456
):
448
- # NOTE: We need to solve stack frame location for Python <3.8
449
457
extra = extra or {}
450
458
extra = {** extra , ** kwargs }
459
+
460
+ # Maintenance: We can drop this upon Py3.7 EOL. It's a backport for "location" key to work
451
461
if sys .version_info < (3 , 8 ):
452
462
return self ._logger .warning (msg , * args , exc_info = exc_info , stack_info = stack_info , extra = extra )
453
463
return self ._logger .warning (
@@ -557,6 +567,41 @@ def _get_caller_filename():
557
567
caller_frame = frame .f_back .f_back .f_back
558
568
return caller_frame .f_globals ["__name__" ]
559
569
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
+
560
605
561
606
def set_package_logger (
562
607
level : Union [str , int ] = logging .DEBUG ,
@@ -595,3 +640,13 @@ def set_package_logger(
595
640
handler = logging .StreamHandler (stream )
596
641
handler .setFormatter (formatter )
597
642
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 )
0 commit comments