• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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