22import os
33import time
44from enum import Enum
5+ from typing import Optional , Callable , Any , Literal
56
67
78class LogLevel (Enum ):
@@ -14,41 +15,79 @@ class LogLevel(Enum):
1415class Singleton (type ):
1516 _instances = {}
1617
17- def __call__ (cls , * args , ** kwargs ):
18+ def __call__ (cls , * args , ** kwargs ) -> Any :
1819 if cls not in cls ._instances :
19- cls ._instances [cls ] = super (Singleton , cls ).__call__ (
20- * args , ** kwargs
21- )
20+ cls ._instances [cls ] = super ().__call__ (* args , ** kwargs )
2221 return cls ._instances [cls ]
2322
2423
2524class Logger (metaclass = Singleton ):
26- def __init__ (self , log_lvl = LogLevel .INFO ):
25+ def __init__ (self , log_lvl : LogLevel = LogLevel .INFO ) -> None :
2726 self ._log = logging .getLogger ("selenium" )
2827 self ._log .setLevel (LogLevel .DEBUG .value )
29-
30- formatter = logging .Formatter (
31- "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
32- )
3328 self .log_file = self ._create_log_file ()
34- self ._configure_logging (log_lvl , formatter )
29+ self ._initialize_logging (log_lvl )
3530
36- def _create_log_file (self ):
31+ def _create_log_file (self ) -> str :
3732 current_time = time .strftime ("%Y-%m-%d" )
3833 log_directory = os .path .abspath (
39- os .path .join (os .path .dirname (__file__ ), "../tests" , "logs" )
40- )
34+ os .path .join (os .path .dirname (__file__ ), "../tests/logs" ))
4135
42- if not os .path .exists (log_directory ):
43- os .makedirs (log_directory )
36+ try :
37+ os .makedirs (log_directory , exist_ok = True ) # Create directory if it doesn't exist
38+ except Exception as e :
39+ raise RuntimeError (f"Failed to create log directory '{ log_directory } ': { e } " )
4440
4541 return os .path .join (log_directory , f"log_{ current_time } .log" )
4642
47- def _configure_logging (self , log_lvl , formatter ):
43+ def _initialize_logging (self , log_lvl : LogLevel ) -> None :
44+ formatter = logging .Formatter ("%(asctime)s - %(name)s - %(levelname)s - %(message)s" )
4845 fh = logging .FileHandler (self .log_file , mode = "w" )
4946 fh .setFormatter (formatter )
5047 fh .setLevel (log_lvl .value )
5148 self ._log .addHandler (fh )
5249
53- def get_instance (self ):
50+ def get_instance (self ) -> logging . Logger :
5451 return self ._log
52+
53+ def annotate (self , message : str , level : Literal ["info" , "warn" , "debug" , "error" ]) -> None :
54+ """Log a message at the specified level."""
55+ if level == "info" :
56+ self ._log .info (message )
57+ elif level == "warn" :
58+ self ._log .warning (message )
59+ elif level == "debug" :
60+ self ._log .debug (message )
61+ elif level == "error" :
62+ self ._log .error (message )
63+ else :
64+ raise ValueError (f"Invalid log level: { level } " )
65+
66+
67+ def log (level : Literal ["info" , "warn" , "debug" , "error" ] = "info" ) -> Callable :
68+ """Decorator to log the current method's execution.
69+
70+ :param level: Level of the logs, e.g., info, warn, debug, error.
71+ """
72+ logger_instance = Logger () # Get the singleton instance of Logger
73+
74+ def decorator (func : Callable ) -> Callable :
75+ def wrapper (self , * args , ** kwargs ) -> Any :
76+ result = func (self , * args , ** kwargs )
77+ method_name = f" Method :: { func .__name__ } ()"
78+ method_docs = format_method_doc_str (func .__doc__ )
79+
80+ logs = method_docs + method_name if method_docs else f"Executed { func .__name__ } ()"
81+ logger_instance .annotate (logs , level )
82+ return result
83+
84+ return wrapper
85+
86+ return decorator
87+
88+
89+ def format_method_doc_str (doc_str : Optional [str ]) -> Optional [str ]:
90+ """Add a dot to the docs string if it doesn't exist."""
91+ if doc_str and not doc_str .endswith ('.' ):
92+ return doc_str + "."
93+ return doc_str
0 commit comments