File size: 3,309 Bytes
bb1c643
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b43bf80
bb1c643
 
 
 
 
b43bf80
bb1c643
 
 
 
 
b43bf80
 
bb1c643
b43bf80
 
bb1c643
 
 
 
 
 
b43bf80
 
 
bb1c643
 
 
 
b43bf80
bb1c643
 
 
 
 
 
 
b43bf80
bb1c643
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b43bf80
bb1c643
 
 
 
 
 
 
 
 
 
b43bf80
bb1c643
 
 
 
 
 
b43bf80
bb1c643
 
 
b43bf80
bb1c643
 
 
 
b43bf80
 
 
bb1c643
 
 
 
b43bf80
bb1c643
b43bf80
bb1c643
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
import functools
import logging
import sys
import os
from dotenv import load_dotenv
from uvicorn.logging import ColourizedFormatter
from typing import Any, Callable

load_dotenv()


LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO").upper()

# Mapping of string levels to logging constants
LOG_LEVEL_MAPPING = {
    "DEBUG": logging.DEBUG,
    "INFO": logging.INFO,
    "WARNING": logging.WARNING,
    "ERROR": logging.ERROR,
    "CRITICAL": logging.CRITICAL,
}

# Set the actual logging level
LOG_LEVEL = LOG_LEVEL_MAPPING.get(LOG_LEVEL, logging.INFO)  # Default to INFO if invalid


# Custom colorized formatter to apply colors specifically to log levels
class CustomColourizedFormatter(ColourizedFormatter):
    def format(self, record: logging.LogRecord) -> str:
        # Define color mappings for different log levels
        level_color_map = {
            "DEBUG": "\033[34m",  # Blue
            "INFO": "\033[32m",  # Green
            "WARNING": "\033[33m",  # Yellow
            "ERROR": "\033[31m",  # Red
            "CRITICAL": "\033[41m",  # Red background
        }

        # Reset color
        reset = "\033[0m"

        # Apply color to the log level name
        record.levelname = (
            f"{level_color_map.get(record.levelname, '')}{record.levelname}{reset}"
        )

        # Format the log message using the parent class's format method
        return super().format(record)


def get_logger(name: str) -> logging.Logger:
    """Creates a logger object that reads its log level from .env.

    Args:
        name (str): name given to the logger

    Returns:
        logging.Logger: logger object to be used for logging
    """
    # Create logger
    logger = logging.getLogger(name)
    logger.setLevel(LOG_LEVEL)  # Set level based on .env file

    # Prevent adding multiple handlers if already exists
    if not logger.hasHandlers():
        # Create console handler
        ch = logging.StreamHandler(sys.stdout)
        ch.setLevel(LOG_LEVEL)  # Set handler level

        # Create a custom formatter with colored log levels
        formatter = CustomColourizedFormatter(
            "{asctime} | {levelname:<8} | {message}",
            style="{",
            datefmt="%Y-%m-%d %H:%M:%S",
            use_colors=True,
        )

        # Add formatter to the handler
        ch.setFormatter(formatter)

        # Add handler to the logger
        logger.addHandler(ch)

    return logger


# Logger decorator implementation
def log_function_call(logger: logging.Logger) -> Callable:
    """A decorator that logs the function calls and results.

    Args:
        logger (logging.Logger): The logger instance to use for logging.

    Returns:
        Callable: A wrapper function that logs the execution details.
    """

    def decorator(func: Callable) -> Callable:
        @functools.wraps(func)
        def wrapper(*args, **kwargs) -> Any:
            # Log the function call with arguments
            logger.debug(
                f"Calling {func.__name__} with args: {args} and kwargs: {kwargs}"
            )
            result = func(*args, **kwargs)
            # Log the function result
            logger.debug(f"{func.__name__} returned {result}")
            return result

        return wrapper

    return decorator


logger = logging.getLogger(__name__)