1# SPDX-License-Identifier: GPL-2.0-only 2# This file is part of Scapy 3# See https://scapy.net/ for more information 4# Copyright (C) Philippe Biondi <phil@secdev.org> 5 6""" 7Logging subsystem and basic exception class. 8""" 9 10############################# 11# Logging subsystem # 12############################# 13 14 15import logging 16import traceback 17import time 18 19from scapy.consts import WINDOWS 20 21# Typing imports 22from logging import LogRecord 23from typing import ( 24 Any, 25 Dict, 26 Tuple, 27) 28 29 30class Scapy_Exception(Exception): 31 pass 32 33 34class ScapyInvalidPlatformException(Scapy_Exception): 35 pass 36 37 38class ScapyNoDstMacException(Scapy_Exception): 39 pass 40 41 42class ScapyFreqFilter(logging.Filter): 43 def __init__(self): 44 # type: () -> None 45 logging.Filter.__init__(self) 46 self.warning_table = {} # type: Dict[int, Tuple[float, int]] # noqa: E501 47 48 def filter(self, record): 49 # type: (LogRecord) -> bool 50 from scapy.config import conf 51 # Levels below INFO are not covered 52 if record.levelno <= logging.INFO: 53 return True 54 wt = conf.warning_threshold 55 if wt > 0: 56 stk = traceback.extract_stack() 57 caller = 0 # type: int 58 for _, l, n, _ in stk: 59 if n == 'warning': 60 break 61 caller = l 62 tm, nb = self.warning_table.get(caller, (0, 0)) 63 ltm = time.time() 64 if ltm - tm > wt: 65 tm = ltm 66 nb = 0 67 else: 68 if nb < 2: 69 nb += 1 70 if nb == 2: 71 record.msg = "more " + str(record.msg) 72 else: 73 return False 74 self.warning_table[caller] = (tm, nb) 75 return True 76 77 78class ScapyColoredFormatter(logging.Formatter): 79 """A subclass of logging.Formatter that handles colors.""" 80 levels_colored = { 81 'DEBUG': 'reset', 82 'INFO': 'reset', 83 'WARNING': 'bold+yellow', 84 'ERROR': 'bold+red', 85 'CRITICAL': 'bold+white+bg_red' 86 } 87 88 def format(self, record): 89 # type: (LogRecord) -> str 90 message = super(ScapyColoredFormatter, self).format(record) 91 from scapy.config import conf 92 message = conf.color_theme.format( 93 message, 94 self.levels_colored[record.levelname] 95 ) 96 return message 97 98 99if WINDOWS: 100 # colorama is bundled within IPython, but 101 # logging.StreamHandler will be overwritten when called, 102 # so we can't wait for IPython to call it 103 try: 104 import colorama 105 colorama.init() 106 except ImportError: 107 pass 108 109# get Scapy's master logger 110log_scapy = logging.getLogger("scapy") 111log_scapy.propagate = False 112# override the level if not already set 113if log_scapy.level == logging.NOTSET: 114 log_scapy.setLevel(logging.WARNING) 115# add a custom handler controlled by Scapy's config 116_handler = logging.StreamHandler() 117_handler.setFormatter( 118 ScapyColoredFormatter( 119 "%(levelname)s: %(message)s", 120 ) 121) 122log_scapy.addHandler(_handler) 123# logs at runtime 124log_runtime = logging.getLogger("scapy.runtime") 125log_runtime.addFilter(ScapyFreqFilter()) 126# logs in interactive functions 127log_interactive = logging.getLogger("scapy.interactive") 128log_interactive.setLevel(logging.DEBUG) 129# logs when loading Scapy 130log_loading = logging.getLogger("scapy.loading") 131 132 133def warning(x, *args, **kargs): 134 # type: (str, *Any, **Any) -> None 135 """ 136 Prints a warning during runtime. 137 """ 138 log_runtime.warning(x, *args, **kargs) 139