33import time
44from enum import Enum
55from typing import Optional , Callable , Any , Literal
6+ from threading import Lock
67
78
89class LogLevel (Enum ):
@@ -14,23 +15,23 @@ class LogLevel(Enum):
1415
1516class Singleton (type ):
1617 _instances = {}
18+ _lock = Lock () # Ensure thread safety
1719
1820 def __call__ (cls , * args , ** kwargs ) -> Any :
19- if cls not in cls ._instances :
20- cls ._instances [cls ] = super ().__call__ (* args , ** kwargs )
21+ with cls ._lock :
22+ if cls not in cls ._instances :
23+ cls ._instances [cls ] = super ().__call__ (* args , ** kwargs )
2124 return cls ._instances [cls ]
2225
2326
2427class Logger (metaclass = Singleton ):
25- def __init__ (self ,
26- log_lvl : LogLevel = LogLevel .INFO ,
27- log_target : Literal ["console" , "file" , "both" ] = "console" ) -> None :
28- """Logger
29- :param log_lvl: LogLevel
30- :param log_target: Target for logs (store to file, display to console or both(file and console))
31- """
28+ def __init__ (
29+ self ,
30+ log_lvl : LogLevel = LogLevel .INFO ,
31+ log_target : Literal ["console" , "file" , "both" ] = "console" ,
32+ ) -> None :
3233 self ._log = logging .getLogger ("selenium" )
33- self ._log .setLevel (LogLevel . INFO .value )
34+ self ._log .setLevel (log_lvl .value )
3435 self .log_file = self ._create_log_file ()
3536 self .log_target = log_target
3637 self ._initialize_logging (log_lvl )
@@ -42,9 +43,7 @@ def _create_log_file(self) -> str:
4243 )
4344
4445 try :
45- os .makedirs (
46- log_directory , exist_ok = True
47- ) # Create directory if it doesn't exist
46+ os .makedirs (log_directory , exist_ok = True )
4847 except Exception as e :
4948 raise RuntimeError (
5049 f"Failed to create log directory '{ log_directory } ': { e } "
@@ -56,14 +55,12 @@ def _initialize_logging(self, log_lvl: LogLevel) -> None:
5655 formatter = logging .Formatter (
5756 "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
5857 )
59- # Lot to file or both file and console
6058 if self .log_target in ("file" , "both" ):
61- fh = logging .FileHandler (self .log_file , mode = "w " )
59+ fh = logging .FileHandler (self .log_file , mode = "a " )
6260 fh .setFormatter (formatter )
6361 fh .setLevel (log_lvl .value )
6462 self ._log .addHandler (fh )
6563
66- # Log to console or both file and console
6764 if self .log_target in ("console" , "both" ):
6865 ch = logging .StreamHandler ()
6966 ch .setFormatter (formatter )
@@ -73,61 +70,30 @@ def _initialize_logging(self, log_lvl: LogLevel) -> None:
7370 def get_instance (self ) -> logging .Logger :
7471 return self ._log
7572
76- def annotate (
77- self , message : str , level : Literal ["info" , "warn" , "debug" , "error" ]
78- ) -> None :
79- """Log a message at the specified level."""
80- if level == "info" :
73+ def annotate (self , message : str , level : LogLevel ) -> None :
74+ if level == LogLevel .INFO :
8175 self ._log .info (message )
82- elif level == "warn" :
76+ elif level == LogLevel . WARNING :
8377 self ._log .warning (message )
84- elif level == "debug" :
78+ elif level == LogLevel . DEBUG :
8579 self ._log .debug (message )
86- elif level == "error" :
80+ elif level == LogLevel . ERROR :
8781 self ._log .error (message )
8882 else :
8983 raise ValueError (f"Invalid log level: { level } " )
9084
9185
92- def log (
93- data : Optional [str ] = None ,
94- level : Literal ["info" , "warn" , "debug" , "error" ] = "info" ,
95- ) -> Callable :
96- """Decorator to log the current method's execution.
97-
98- :param data: Custom log message to use if no docstring is provided.
99- :param level: Level of the logs, e.g., info, warn, debug, error.
100- """
86+ def log (data : Optional [str ] = None , level : LogLevel = LogLevel .INFO ) -> Callable :
10187 logger_instance = Logger () # Get the singleton instance of Logger
10288
10389 def decorator (func : Callable ) -> Callable :
10490 def wrapper (self , * args , ** kwargs ) -> Any :
105- # Get the method's docstring
10691 method_docs = format_method_doc_str (func .__doc__ )
107-
108- if method_docs is None and data is None :
109- raise ValueError (
110- f"No documentation available for :: { func .__name__ } "
111- )
112-
113- # Construct the parameter string for logging
114- params_str = ", " .join (repr (arg ) for arg in args )
115- kwargs_str = ", " .join (f"{ k } ={ v !r} " for k , v in kwargs .items ())
116- all_params_str = ", " .join (filter (None , [params_str , kwargs_str ]))
117-
118- logs = (
119- method_docs
120- + f" Method :: { func .__name__ } ()"
121- + f" with parameters: { all_params_str } "
122- if method_docs
123- else data
124- + f" Method :: { func .__name__ } ()"
125- + f" with parameters: { all_params_str } "
92+ log_message = (
93+ (method_docs or data or "" )
94+ + f" Method :: { func .__name__ } () with parameters: { ', ' .join (map (str , args ))} "
12695 )
127-
128- logger_instance .annotate (logs , level )
129-
130- # Call the original method, passing *args and **kwargs
96+ logger_instance .annotate (log_message , level )
13197 return func (self , * args , ** kwargs )
13298
13399 return wrapper
@@ -136,7 +102,6 @@ def wrapper(self, *args, **kwargs) -> Any:
136102
137103
138104def format_method_doc_str (doc_str : Optional [str ]) -> Optional [str ]:
139- """Add a dot to the docs string if it doesn't exist."""
140105 if doc_str and not doc_str .endswith ("." ):
141106 return doc_str + "."
142107 return doc_str
0 commit comments