Răsfoiți Sursa

Add some fixes.

Cixo Develop 1 săptămână în urmă
părinte
comite
0fcc9a6ca6

+ 9 - 0
source/cx_logger/__init__.py

@@ -0,0 +1,9 @@
+from .handler import handler
+from .stdout_handler import stdout_handler
+from .stderr_handler import stderr_handler
+from .file_handler import file_handler
+from .levels import levels
+from .logger import logger
+from .async_logger import async_logger
+from .sync_logger import sync_logger
+from .logs_manager import logs_manager

+ 125 - 0
source/cx_logger/async_logger.py

@@ -0,0 +1,125 @@
+from .levels import levels
+from .handler import handler
+from .logger import logger
+
+class async_logger(logger):
+    """
+    That is logger, which use async methods to save data into handles.
+
+    Methods
+    -------
+    async info(content, *args, **kwargs)
+        That log info level message.
+
+    async warning(content, *args, **kwargs)
+        That log warning level message.
+
+    async error(content, *args, **kwargs)
+        That log error level message.
+
+    async critical(content, *args, **kwargs)
+        That log critical level message.
+
+    async log(level, content, *args, **kwargs)
+        That generally save content to log with given level.
+    """
+
+    async def info(self, content: str, *args, **kwargs) -> None:
+        """
+        That log info level message.
+
+        Parameters
+        ----------
+        content : str
+            Content to store in the log.
+
+        *args, **kwargs
+            When any of that parameters had been given, then format funcion
+            hed been used on the content.
+        """
+
+        await self.log(levels.info, content, *args, **kwargs)
+
+    async def warning(self, content: str, *args, **kwargs) -> None:
+        """
+        That log warning level message.
+
+        Parameters
+        ----------
+        content : str
+            Content to store in the log.
+
+        *args, **kwargs
+            When any of that parameters had been given, then format funcion
+            hed been used on the content.
+        """
+
+        await self.log(levels.warning, content, *args, **kwargs)
+
+    async def error(self, content: str, *args, **kwargs) -> None:
+        """
+        That log error level message.
+
+        Parameters
+        ----------
+        content : str
+            Content to store in the log.
+
+        *args, **kwargs
+            When any of that parameters had been given, then format funcion
+            hed been used on the content.
+        """
+
+        await self.log(levels.error, content, *args, **kwargs)
+
+    async def critical(self, content: str, *args, **kwargs) -> None:
+        """
+        That log critical level message.
+
+        Parameters
+        ----------
+        content : str
+            Content to store in the log.
+
+        *args, **kwargs
+            When any of that parameters had been given, then format funcion
+            hed been used on the content.
+        """
+
+        await self.log(levels.critical, content, *args, **kwargs)
+
+    async def log(self, level: levels, content: str, *args, **kwargs) -> None:
+        """
+        That log message, log level is given in the parameter.
+
+        Parameters
+        ----------
+        level : levels
+            Level of the message to save.
+        
+        content : str
+            Content to store in the log.
+
+        *args, **kwargs
+            When any of that parameters had been given, then format funcion
+            hed been used on the content.
+        """
+        
+        await self._write_to_all(
+            self._get_message(level, content, *args, **kwargs)
+        )
+    
+    async def _write_to_all(self, content: str) -> None:
+        """
+        That write content to all handlers.
+
+        Parameters
+        ----------
+        content : str
+            Content to been writen.
+        """
+
+        for handler in self._get_handlers():
+            await handler.adding(content) 
+
+

+ 67 - 0
source/cx_logger/file_handler.py

@@ -0,0 +1,67 @@
+import pathlib
+import os
+
+from .handler import handler
+
+class file_handler(handler):    
+    """
+    That handler puts log to file given when object was created.
+    """
+
+    def __init__(self, target: pathlib.Path) -> None:
+        """
+        That initialize new object with given file.
+
+        Parameters
+        ----------
+        target : pathlib.Path
+            File to use by handler.
+        """
+
+        super().__init__()
+
+        self.__target = target
+        self.__handler = None
+
+    def add(self, content: str) -> None:
+        """
+        That add new content to the file as new line.
+
+        Parameters
+        ----------
+        content : str
+            Content to add into the file as new line.
+        """
+
+        if not self.is_ready:
+            self.open()
+        
+        self.__handler.write(content + os.linesep)
+
+    @property
+    def is_ready(self) -> bool: 
+        """
+        That check that file handler is ready to use or not.
+        """
+
+        return self.__handler is not None and not self.__handler.closed
+
+    def open(self) -> None:
+        """
+        That open file and save handler to use in the future.
+        """
+
+        if not self.is_ready:
+            self.__handler = self.__target.open("a")
+
+    def clean(self) -> None:
+        """
+        That close file handler if it is open yet. 
+        """
+
+        if not self.is_ready:
+            return
+
+        self.__handler.close()
+
+

+ 67 - 0
source/cx_logger/handler.py

@@ -0,0 +1,67 @@
+import asyncio
+
+class handler:
+    """
+    That is class, which is used to implements new handlers. Handler is 
+    element, which directly store content to log. For example.
+    
+    To implement property handler, implement:
+     * add(str) <- Add new content to log,
+     * open() <- Optional function, create creatr handler for add,
+     * clean() <- Optional function, clean up resources user by handler.
+    """
+
+    def __init__(self) -> None:
+        """
+        That prepare lock for the hndler.
+        """
+
+        self.__lock = asyncio.Lock()
+    
+    def __del__(self) -> None:
+        """
+        That clean up handler when item is removed.
+        """
+
+        self.clean()
+
+    def open(self) -> None:
+        """
+        That register system resources for handler.
+        """
+
+        pass
+
+    async def adding(self, content: str) -> None:
+        """
+        That add new content to the the log as new line. It do that 
+        asynchronically.
+        
+        Parameters
+        ----------
+        content : str
+            Content which must be added to log.
+        """
+
+        async with self.__lock:
+            await asyncio.to_thread(self.add, content)
+
+    def add(self, content: str) -> None:
+        """
+        That add new content to the log as new line . It is virtual 
+        function, and must being overwritten.
+
+        Parameters
+        ----------
+        content : str
+            Content which must be added to log.
+        """
+
+        raise NotImplementedError()
+
+    def clean(self) -> None:
+        """
+        That clean up resources used by handler.
+        """
+
+        pass

+ 21 - 0
source/cx_logger/levels.py

@@ -0,0 +1,21 @@
+import enum
+
+class levels(enum.Enum):
+    """
+    That enum store log levels to use.
+    """
+
+    """ Info about any action. """
+    info = 0
+    
+    """ Simple warning. """
+    warning = 1
+
+    """ Not critical error. """
+    error = 2
+
+    """ Critical error. """
+    critical = 3
+
+
+

+ 129 - 0
source/cx_logger/logger.py

@@ -0,0 +1,129 @@
+import time
+
+from .handler import handler
+from .levels import levels 
+
+class logger:
+    """
+    That class is responsible for managing log handlers, and generating
+    log message. That formats log messages by adding time, date and also
+    level of the message.
+    """
+
+    def __init__(self) -> None:
+        """
+        That initialize handlers set.
+        """
+
+        self.__handlers = set()
+
+    def _get_handlers(self) -> tuple:   
+        """
+        That returns copy of the handlers list.
+        """
+
+        return tuple(self.__handlers)
+
+    def _get_message(
+        self, 
+        level: levels, 
+        content: str, 
+        *args, 
+        **kwargs
+    ) -> str:
+        """
+        That try to format log message. It require level of the message and 
+        also message itself. When it get only level and message itself, that
+        only add message to level and timestamp info. When more parameters
+        had been given that run format function on the first message. It is
+        useable when log message must contain for example IP address or
+        other things like that.
+
+        Parameters
+        ----------
+        level : levels
+            Log level of the message.
+
+        content : str
+            Content of the message to log.
+        
+        *args, **kwargs
+            Optional arguments used when format would be used.
+
+        Returns
+        -------
+        str
+            Result message which would be saved in the logs.
+        """
+
+        if len(args) > 0 or len(kwargs) > 0:
+            content = content.format(*args, **kwargs)
+
+        return ( \
+            self.__level_name(level) + " " + \
+            self.time_stamp + " " + \
+            content \
+        )
+
+    @property
+    def time_stamp(self) -> str:
+        """
+        That return current time as timestamp to use in log message.
+
+        Returns
+        -------
+        str
+            Current time as timestamp.
+        """
+
+        return "(" + time.strftime("%Y-%m-%d %H:%M:%S") + ")"
+
+    def __level_name(self, level: levels) -> str:
+        """
+        That convert level enum value into level stamp.
+
+        Parameters
+        ----------
+        level : levels
+            Level enum to convert.
+
+        Returns
+        -------
+        str
+            Result as string stamp.
+        """
+
+        name = ""
+
+        if level == levels.info:
+            name = "info"
+
+        if level == levels.warning:
+            name = "warning"
+
+        if level == levels.error:
+            name = "error"
+
+        if level == levels.critical:
+            name = "CRITICAL"
+
+        return ("[" + name + "]")
+
+    def use_handler(self, target: handler) -> object:
+        """
+        That add new handler to the handlers set.
+
+        Parameters
+        ----------
+        target : handler
+            New handler to add.
+        
+        Returns
+        -------
+            Self to chain loading.
+        """
+
+        self.__handlers.add(target)
+        return self
+
+

+ 170 - 0
source/cx_logger/logs_manager.py

@@ -0,0 +1,170 @@
+import pathlib
+import time
+import typing
+
+from .file_handler import file_handler
+from .async_logger import async_logger
+from .sync_logger import sync_logger
+from .logger import logger
+
+class logs_manager:
+    def __init__(self, target: pathlib.Path | None = None) -> None:
+        """
+        That create new logs manager. It require directory, where logs would
+        be stored.
+
+        Parameters
+        ----------
+        target : pathlib.Path
+        """
+
+        if target is None:
+            target = pathlib.Path("./logs")
+
+        if not target.is_dir():
+            target.mkdir()
+
+        self.__root = target
+
+    @staticmethod
+    def _ends_with(name: str, ending: str) -> bool:
+        """
+        That check name, and returh True when ends with ending.
+
+        Parameters
+        ----------
+        name : str
+            Name to check.
+
+        ending : str
+            Ending to check that name ends with.
+
+        Returns
+        -------
+        bool   
+            True when name ends with ending.
+        """
+
+        return name[-len(ending):] == ending
+
+    @property
+    def root(self) -> pathlib.Path:
+        """
+        Logs directory.
+        """
+
+        return self.__root
+
+    @property
+    def logs(self) -> tuple:
+        """
+        That return tuple with all logs.
+        """
+
+        return tuple(self.iter_logs())
+
+    def iter_logs(self) -> typing.Iterator[pathlib.Path]:
+        """
+        That generator iterate all logs in the log directory.
+
+        Returns
+        -------
+        typing.Generator[pathlib.Path]
+            Log files.
+        """
+
+        for count in self.__root.iterdir():
+            if self._ends_with(count.name, ".log"):
+                yield self.__root / count
+
+    def search_log(self, name: str | None = None) -> tuple:
+        """
+        That search for log in the logs directory.
+
+        Parameters
+        ----------
+        name : str | None
+            Name to filter logs by. If none, current date.
+
+        Returns
+        -------
+        tuple
+            All logs for given name.
+        """
+
+        if name is None:
+            name = self._base_name
+
+        logs = self.iter_logs()
+        filtered = filter(lambda count: str(count).find(name) != -1, logs)
+
+        return tuple(filtered)
+
+    @property
+    def _base_name(self) -> str:
+        """
+        That return default base name for current date.
+
+        Returns
+        -------
+        str
+            Name for current date.
+        """
+
+        return time.strftime("%Y-%m-%d", time.localtime())
+    
+    def get_new_file(self) -> pathlib.Path:
+        """
+        That generate new log file handler.
+
+        Returns
+        -------
+        pathlib.Path
+            New file handler.
+        """
+
+        base_name = self._base_name
+        name_logs = self.search_log(base_name)
+        name_count = len(name_logs)
+
+        while True:
+            result_name = (
+                base_name + "." + \
+                str(name_count + 1) + ".log" \
+            )
+
+            result_path = self.root / pathlib.Path(result_name)
+
+            if not result_path.exists():
+                return result_path
+            
+            name_count = name_count + 1
+
+    def get_new_handler(self) -> file_handler:
+        """
+        That return handler to the new file.
+
+        Returns
+        -------
+        file_handler
+            That return new handler to the new log file.
+        """
+
+        return file_handler(self.get_new_file())
+
+    def get_logger(self, logger_type : type) -> logger:
+        """
+        That return new logger that use new log file.
+
+        Parameters
+        ----------
+        logger_type : type
+            Select async_logger or sync_logger.
+
+        Returns
+        -------
+        logger
+            New logger that use new log file
+        """
+
+        return logger_type().use_handler(self.get_new_handler()) 

+ 24 - 0
source/cx_logger/stderr_handler.py

@@ -0,0 +1,24 @@
+import sys
+import os
+
+from .handler import handler
+
+class stderr_handler(handler):
+    """
+    That handler simple put logs into stderr.
+    """
+
+    def add(self, content: str) -> None:
+        """
+        That put contewnt as new line in stderr.
+        
+        Parameters
+        ----------
+        content : str
+            Content to write as new line.
+        """
+
+        sys.stderr.write(content + os.linesep)
+
+
+

+ 23 - 0
source/cx_logger/stdout_handler.py

@@ -0,0 +1,23 @@
+import sys
+import os
+
+from .handler import handler
+
+class stdout_handler(handler):
+    """
+    That handler simple put logs into stdout.
+    """
+
+    def add(self, content: str) -> None:
+        """
+        That put content as new line into stdout.
+
+        Parameters
+        ----------
+        content : str
+            Content to write as new line.
+        """
+
+        sys.stdout.write(content + os.linesep)
+
+    

+ 122 - 0
source/cx_logger/sync_logger.py

@@ -0,0 +1,122 @@
+from .levels import levels
+from .handler import handler
+from .logger import logger
+
+class sync_logger(logger):
+    """
+    That is logger which use standard sync mode..
+
+    Methods
+    -------
+    info(content, *args, **kwargs)
+        That log info level message.
+
+    warning(content, *args, **kwargs)
+        That log warning level message.
+
+    error(content, *args, **kwargs)
+        That log error level message.
+
+    critical(content, *args, **kwargs)
+        That log critical level message.
+
+    log(level, content, *args, **kwargs)
+        That generally save content to log with given level.
+    """
+
+
+    def info(self, *args, **kwargs) -> None:
+        """
+        That log info level message.
+
+        Parameters
+        ----------
+        content : str
+            Content to store in the log.
+
+        *args, **kwargs
+            When any of that parameters had been given, then format funcion
+            hed been used on the content.
+        """
+        
+        self.log(levels.info, *args, **kwargs)
+
+    def warning(self, *args, **kwargs) -> None:
+        """
+        That log warning level message.
+
+        Parameters
+        ----------
+        content : str
+            Content to store in the log.
+
+        *args, **kwargs
+            When any of that parameters had been given, then format funcion
+            hed been used on the content.
+        """
+        
+        self.log(levels.warning, *args, **kwargs)
+  
+    def error(self, *args, **kwargs) -> None:
+        """
+        That log error level message.
+
+        Parameters
+        ----------
+        content : str
+            Content to store in the log.
+
+        *args, **kwargs
+            When any of that parameters had been given, then format funcion
+            hed been used on the content.
+        """
+
+        self.log(levels.error, *args, **kwargs)
+    
+    def critical(self, *args, **kwargs) -> None:
+        """
+        That log critical level message.
+
+        Parameters
+        ----------
+        content : str
+            Content to store in the log.
+
+        *args, **kwargs
+            When any of that parameters had been given, then format funcion
+            hed been used on the content.
+        """
+        
+        self.log(levels.critical, *args, **kwargs)
+
+    def log(self, level: levels, *args, **kwargs) -> None:
+        """
+        That log message, log level is given in the parameter.
+
+        Parameters
+        ----------
+        level : levels
+            Level of the message to save.
+        
+        content : str
+            Content to store in the log.
+
+        *args, **kwargs
+            When any of that parameters had been given, then format funcion
+            hed been used on the content.
+        """
+
+        self._write_to_all(self._get_message(level, *args, **kwargs))
+
+    def _write_to_all(self, content: str) -> None: 
+        """
+        That write content to all handlers.
+
+        Parameters
+        ----------
+        content : str
+            Content to been writen.
+        """
+
+        for handler in self._get_handlers():
+            handler.add(content)