diff --git a/README.md b/README.md
index 9585b57..3a0ee53 100644
--- a/README.md
+++ b/README.md
@@ -290,7 +290,7 @@ if logger_loader.config.extra.http_json_enabled:
)
```
-[**`app.py`**](https://github.com/bybatkhuu/module.python-logging/blob/main/examples/advanced/app.py):
+[**`main.py`**](https://github.com/bybatkhuu/module.python-logging/blob/main/examples/advanced/main.py):
```python
from typing import Union
@@ -338,28 +338,28 @@ cd ./examples/advanced
# Install python dependencies for examples:
pip install -r ./requirements.txt
-uvicorn app:app --host=0.0.0.0 --port=8000
+uvicorn main:app --host=0.0.0.0 --port=8000
```
**Output**:
```txt
-[2023-09-01 12:37:38.569 +09:00 | TRACE | beans_logging._base:499]: Intercepted modules: ['watchfiles.watcher', 'asyncio', 'watchfiles', 'concurrent', 'dotenv', 'concurrent.futures', 'fastapi', 'dotenv.main', 'uvicorn', 'watchfiles.main']; Muted modules: ['uvicorn.error', 'uvicorn.access'];
-[2023-09-01 12:37:38.579 +09:00 | INFO | uvicorn.server:76]: Started server process [22599]
-[2023-09-01 12:37:38.579 +09:00 | INFO | uvicorn.lifespan.on:46]: Waiting for application startup.
-[2023-09-01 12:37:38.579 +09:00 | INFO | app:21]: Preparing to startup...
-[2023-09-01 12:37:38.580 +09:00 | OK | app:22]: Finished preparation to startup.
-[2023-09-01 12:37:38.580 +09:00 | INFO | app:23]: API version: 0.0.1-000000
-[2023-09-01 12:37:38.580 +09:00 | INFO | uvicorn.lifespan.on:60]: Application startup complete.
-[2023-09-01 12:37:38.582 +09:00 | INFO | uvicorn.server:218]: Uvicorn running on http://0.0.0.0:9000 (Press CTRL+C to quit)
-[2023-09-01 12:37:48.487 +09:00 | DEBUG | anyio._backends._asyncio:807]: [0b9f972939054a58ba10e7a39a12bd21] 127.0.0.1 - "GET / HTTP/1.1"
-[2023-09-01 12:37:48.488 +09:00 | OK | anyio._backends._asyncio:807]: [0b9f972939054a58ba10e7a39a12bd21] 127.0.0.1 - "GET / HTTP/1.1" 200 17B 0.5ms
-^C[2023-09-01 12:37:51.845 +09:00 | INFO | uvicorn.server:264]: Shutting down
-[2023-09-01 12:37:51.949 +09:00 | INFO | uvicorn.lifespan.on:65]: Waiting for application shutdown.
-[2023-09-01 12:37:51.951 +09:00 | INFO | app:26]: Praparing to shutdown...
-[2023-09-01 12:37:51.952 +09:00 | OK | app:27]: Finished preparation to shutdown.
-[2023-09-01 12:37:51.952 +09:00 | INFO | uvicorn.lifespan.on:76]: Application shutdown complete.
-[2023-09-01 12:37:51.953 +09:00 | INFO | uvicorn.server:86]: Finished server process [22599]
+[2023-09-01 14:55:11.724 +09:00 | TRACE | beans_logging._base:576]: Intercepted modules: ['watchfiles.watcher', 'dotenv', 'asyncio', 'dotenv.main', 'watchfiles.main', 'concurrent.futures', 'uvicorn', 'fastapi', 'concurrent', 'watchfiles']; Muted modules: ['uvicorn.access', 'uvicorn.error'];
+[2023-09-01 14:55:11.740 +09:00 | INFO | uvicorn.server:76]: Started server process [17146]
+[2023-09-01 14:55:11.740 +09:00 | INFO | uvicorn.lifespan.on:46]: Waiting for application startup.
+[2023-09-01 14:55:11.741 +09:00 | INFO | main:21]: Preparing to startup...
+[2023-09-01 14:55:11.741 +09:00 | OK | main:22]: Finished preparation to startup.
+[2023-09-01 14:55:11.741 +09:00 | INFO | main:23]: API version: 0.0.1-000000
+[2023-09-01 14:55:11.741 +09:00 | INFO | uvicorn.lifespan.on:60]: Application startup complete.
+[2023-09-01 14:55:11.745 +09:00 | INFO | uvicorn.server:218]: Uvicorn running on http://0.0.0.0:9000 (Press CTRL+C to quit)
+[2023-09-01 14:55:17.417 +09:00 | DEBUG | anyio._backends._asyncio:833]: [f635ebbc3f2348db9dcff681be1bd52a] 127.0.0.1 - "GET / HTTP/1.1"
+[2023-09-01 14:55:17.418 +09:00 | OK | anyio._backends._asyncio:833]: [f635ebbc3f2348db9dcff681be1bd52a] 127.0.0.1 - "GET / HTTP/1.1" 200 17B 0.7ms
+^C[2023-09-01 14:55:18.729 +09:00 | INFO | uvicorn.server:264]: Shutting down
+[2023-09-01 14:55:18.831 +09:00 | INFO | uvicorn.lifespan.on:65]: Waiting for application shutdown.
+[2023-09-01 14:55:18.834 +09:00 | INFO | main:26]: Praparing to shutdown...
+[2023-09-01 14:55:18.835 +09:00 | OK | main:27]: Finished preparation to shutdown.
+[2023-09-01 14:55:18.837 +09:00 | INFO | uvicorn.lifespan.on:76]: Application shutdown complete.
+[2023-09-01 14:55:18.837 +09:00 | INFO | uvicorn.server:86]: Finished server process [17146]
```
---
diff --git a/beans_logging/__init__.py b/beans_logging/__init__.py
index bad4689..7bd9b5c 100644
--- a/beans_logging/__init__.py
+++ b/beans_logging/__init__.py
@@ -2,5 +2,5 @@
from ._base import Logger, logger, LoggerLoader
from .schemas import LoggerConfigPM
-from ._const import WarnEnum
+from ._consts import WarnEnum
from .__version__ import __version__
diff --git a/beans_logging/_base.py b/beans_logging/_base.py
index 5373d74..cbbcf04 100644
--- a/beans_logging/_base.py
+++ b/beans_logging/_base.py
@@ -11,7 +11,12 @@
import yaml
from loguru import logger
from loguru._logger import Logger
-from pydantic import validate_call
+import pydantic
+
+if "2.0.0" <= pydantic.__version__:
+ from pydantic import validate_call
+else:
+ from pydantic import validate_arguments as validate_call
## Internal modules
from ._utils import create_dir, deep_merge
@@ -168,7 +173,11 @@ def update_config(self, config: Union[LoggerConfigPM, Dict[str, Any]]):
"""
if isinstance(config, dict):
- _config_dict = self.config.model_dump()
+ if "2.0.0" <= pydantic.__version__:
+ _config_dict = self.config.model_dump()
+ else:
+ _config_dict = self.config.dict()
+
_merged_dict = deep_merge(_config_dict, config)
try:
self.config = LoggerConfigPM(**_merged_dict)
@@ -219,7 +228,11 @@ def _load_config_file(self):
return
_new_config_dict = _new_config_dict["logger"]
- _config_dict = self.config.model_dump()
+ if "2.0.0" <= pydantic.__version__:
+ _config_dict = self.config.model_dump()
+ else:
+ _config_dict = self.config.dict()
+
_merged_dict = deep_merge(_config_dict, _new_config_dict)
self.config = LoggerConfigPM(**_merged_dict)
except Exception:
@@ -240,7 +253,11 @@ def _load_config_file(self):
return
_new_config_dict = _new_config_dict["logger"]
- _config_dict = self.config.model_dump()
+ if "2.0.0" <= pydantic.__version__:
+ _config_dict = self.config.model_dump()
+ else:
+ _config_dict = self.config.dict()
+
_merged_dict = deep_merge(_config_dict, _new_config_dict)
self.config = LoggerConfigPM(**_merged_dict)
except Exception:
@@ -252,7 +269,9 @@ def _load_config_file(self):
# try:
# import toml
- # with open(self.config_file_path, "r", encoding="utf-8") as _config_file:
+ # with open(
+ # self.config_file_path, "r", encoding="utf-8"
+ # ) as _config_file:
# _new_config_dict = toml.load(_config_file) or {}
# if "logger" not in _new_config_dict:
# logger.warning(
@@ -261,7 +280,11 @@ def _load_config_file(self):
# return
# _new_config_dict = _new_config_dict["logger"]
- # _config_dict = self.config.model_dump()
+ # if "2.0.0" <= pydantic.__version__:
+ # _config_dict = self.config.model_dump()
+ # else:
+ # _config_dict = self.config.dict()
+
# _merged_dict = deep_merge(_config_dict, _new_config_dict)
# self.config = LoggerConfigPM(**_merged_dict)
# except Exception:
diff --git a/beans_logging/_const.py b/beans_logging/_const.py
deleted file mode 100644
index f8745e9..0000000
--- a/beans_logging/_const.py
+++ /dev/null
@@ -1,10 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from enum import Enum
-
-
-class WarnEnum(str, Enum):
- RAISE = "RAISE"
- LOG = "LOG"
- DEBUG = "DEBUG"
- IGNORE = "IGNORE"
diff --git a/beans_logging/_consts.py b/beans_logging/_consts.py
new file mode 100644
index 0000000..20a72c6
--- /dev/null
+++ b/beans_logging/_consts.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+
+from enum import Enum
+
+
+class WarnEnum(str, Enum):
+ ERROR = "ERROR"
+ ALWAYS = "ALWAYS"
+ DEBUG = "DEBUG"
+ IGNORE = "IGNORE"
+
+
+class LogLevelEnum(str, Enum):
+ TRACE = "TRACE"
+ DEBUG = "DEBUG"
+ INFO = "INFO"
+ SUCCESS = "SUCCESS"
+ WARNING = "WARNING"
+ ERROR = "ERROR"
+ CRITICAL = "CRITICAL"
diff --git a/beans_logging/_utils.py b/beans_logging/_utils.py
index 9461522..fc2c17c 100644
--- a/beans_logging/_utils.py
+++ b/beans_logging/_utils.py
@@ -1,13 +1,19 @@
# -*- coding: utf-8 -*-
import os
+import sys
import copy
import errno
from loguru import logger
-from pydantic import validate_call
+import pydantic
-from ._const import WarnEnum
+if "2.0.0" <= pydantic.__version__:
+ from pydantic import validate_call
+else:
+ from pydantic import validate_arguments as validate_call
+
+from ._consts import WarnEnum
@validate_call
@@ -16,13 +22,13 @@ def create_dir(create_dir: str, warn_mode: WarnEnum = WarnEnum.DEBUG):
Args:
create_dir (str, required): Create directory path.
- warn_mode (str, optional): Warning message mode, for example: 'LOG', 'DEBUG', 'QUIET'. Defaults to "QUIET".
+ warn_mode (str, optional): Warning message mode, for example: 'ERROR', 'ALWAYS', 'DEBUG', 'IGNORE'. Defaults to "DEBUG".
"""
if not os.path.isdir(create_dir):
try:
_message = f"Creaing '{create_dir}' directory..."
- if warn_mode == WarnEnum.LOG:
+ if warn_mode == WarnEnum.ALWAYS:
logger.info(_message)
elif warn_mode == WarnEnum.DEBUG:
logger.debug(_message)
@@ -36,7 +42,7 @@ def create_dir(create_dir: str, warn_mode: WarnEnum = WarnEnum.DEBUG):
raise
_message = f"Successfully created '{create_dir}' directory."
- if warn_mode == WarnEnum.LOG:
+ if warn_mode == WarnEnum.ALWAYS:
logger.success(_message)
elif warn_mode == WarnEnum.DEBUG:
logger.debug(_message)
@@ -67,3 +73,28 @@ def deep_merge(dict1: dict, dict2: dict) -> dict:
_merged[_key] = copy.deepcopy(_val)
return _merged
+
+
+def get_default_logs_dir() -> str:
+ """Return default logs directory path (current working directory + 'logs').
+
+ Returns:
+ str: Default logs directory path.
+ """
+
+ return os.path.join(os.getcwd(), "logs")
+
+
+def get_app_name() -> str:
+ """Return application name (sys.argv[0]).
+
+ Returns:
+ str: Application name.
+ """
+
+ return (
+ os.path.splitext(os.path.basename(sys.argv[0]))[0]
+ .strip()
+ .replace(" ", "-")
+ .lower()
+ )
diff --git a/beans_logging/fastapi/_handlers.py b/beans_logging/fastapi/_handlers.py
index a8eae50..36c58da 100644
--- a/beans_logging/fastapi/_handlers.py
+++ b/beans_logging/fastapi/_handlers.py
@@ -2,7 +2,12 @@
from typing import Union, Callable
-from pydantic import validate_call
+import pydantic
+
+if "2.0.0" <= pydantic.__version__:
+ from pydantic import validate_call
+else:
+ from pydantic import validate_arguments as validate_call
from beans_logging import LoggerLoader
diff --git a/beans_logging/schemas/__init__.py b/beans_logging/schemas/__init__.py
new file mode 100644
index 0000000..4b15a18
--- /dev/null
+++ b/beans_logging/schemas/__init__.py
@@ -0,0 +1,8 @@
+# -*- coding: utf-8 -*-
+
+import pydantic
+
+if "2.0.0" <= pydantic.__version__:
+ from .config import LoggerConfigPM
+else:
+ from .config_v1 import LoggerConfigPM
diff --git a/beans_logging/schemas.py b/beans_logging/schemas/config.py
similarity index 86%
rename from beans_logging/schemas.py
rename to beans_logging/schemas/config.py
index 89cc9fb..e751a81 100644
--- a/beans_logging/schemas.py
+++ b/beans_logging/schemas/config.py
@@ -1,9 +1,6 @@
# -*- coding: utf-8 -*-
-import os
-import sys
import datetime
-from enum import Enum
from typing import List
from pydantic import (
@@ -15,34 +12,14 @@
ConfigDict,
)
-
-def _get_logs_dir() -> str:
- return os.path.join(os.getcwd(), "logs")
-
-
-def _get_app_name() -> str:
- return (
- os.path.splitext(os.path.basename(sys.argv[0]))[0]
- .strip()
- .replace(" ", "-")
- .lower()
- )
+from .._consts import LogLevelEnum
+from .._utils import get_default_logs_dir, get_app_name
class ExtraBaseModel(BaseModel):
model_config = ConfigDict(extra="allow")
-class LevelEnum(str, Enum):
- TRACE = "TRACE"
- DEBUG = "DEBUG"
- INFO = "INFO"
- SUCCESS = "SUCCESS"
- WARNING = "WARNING"
- ERROR = "ERROR"
- CRITICAL = "CRITICAL"
-
-
class StdHandlerPM(ExtraBaseModel):
enabled: bool = Field(default=True)
@@ -104,7 +81,7 @@ def _check_log_path(self) -> "JsonHandlersPM":
class FilePM(ExtraBaseModel):
logs_dir: constr(strip_whitespace=True) = Field(
- default_factory=_get_logs_dir, min_length=2, max_length=1023
+ default_factory=get_default_logs_dir, min_length=2, max_length=1023
)
rotate_size: int = Field(
default=10_000_000, ge=1_000, lt=1_000_000_000 # 10MB = 10 * 1000 * 1000
@@ -143,11 +120,11 @@ class ExtraPM(ExtraBaseModel):
class LoggerConfigPM(ExtraBaseModel):
app_name: constr(strip_whitespace=True) = Field(
- default_factory=_get_app_name,
+ default_factory=get_app_name,
min_length=1,
max_length=127,
)
- level: LevelEnum = Field(default=LevelEnum.INFO)
+ level: LogLevelEnum = Field(default=LogLevelEnum.INFO)
use_backtrace: bool = Field(default=True)
use_diagnose: bool = Field(default=False)
stream: StreamPM = Field(default_factory=StreamPM)
diff --git a/beans_logging/schemas/config_v1.py b/beans_logging/schemas/config_v1.py
new file mode 100644
index 0000000..0b8a907
--- /dev/null
+++ b/beans_logging/schemas/config_v1.py
@@ -0,0 +1,128 @@
+# -*- coding: utf-8 -*-
+
+import datetime
+from typing import List
+
+from pydantic import BaseModel, constr, Field, validator, root_validator
+
+from .._consts import LogLevelEnum
+from .._utils import get_default_logs_dir, get_app_name
+
+
+class ExtraBaseModel(BaseModel):
+ class Config:
+ extra = "allow"
+
+
+class StdHandlerPM(ExtraBaseModel):
+ enabled: bool = Field(default=True)
+
+
+class StreamPM(ExtraBaseModel):
+ use_color: bool = Field(default=True)
+ use_icon: bool = Field(default=False)
+ format_str: constr(strip_whitespace=True) = Field(
+ default="[{time:YYYY-MM-DD HH:mm:ss.SSS Z} | {level_short:<5} | {name}:{line}]: {message}",
+ min_length=3,
+ max_length=511,
+ )
+ std_handler: StdHandlerPM = Field(default_factory=StdHandlerPM)
+
+
+class LogHandlersPM(ExtraBaseModel):
+ enabled: bool = Field(default=False)
+ format_str: constr(strip_whitespace=True) = Field(
+ default="[{time:YYYY-MM-DD HH:mm:ss.SSS Z} | {level_short:<5} | {name}:{line}]: {message}",
+ min_length=4,
+ max_length=511,
+ )
+ log_path: constr(strip_whitespace=True) = Field(
+ default="{app_name}.std.all.log", min_length=4, max_length=1023
+ )
+ err_path: constr(strip_whitespace=True) = Field(
+ default="{app_name}.std.err.log", min_length=4, max_length=1023
+ )
+
+ @root_validator
+ def _check_log_path(cls, values):
+ _log_path, _err_path = values.get("log_path"), values.get("err_path")
+ if _log_path == _err_path:
+ raise ValueError(
+ f"`log_path` and `err_path` attributes are same: '{_log_path}', must be different!"
+ )
+
+ return values
+
+
+class JsonHandlersPM(ExtraBaseModel):
+ enabled: bool = Field(default=False)
+ use_custom: bool = Field(default=False)
+ log_path: constr(strip_whitespace=True) = Field(
+ default="{app_name}.json.all.log", min_length=4, max_length=1023
+ )
+ err_path: constr(strip_whitespace=True) = Field(
+ default="{app_name}.json.err.log", min_length=4, max_length=1023
+ )
+
+ @root_validator
+ def _check_log_path(cls, values):
+ _log_path, _err_path = values.get("log_path"), values.get("err_path")
+ if _log_path == _err_path:
+ raise ValueError(
+ f"`log_path` and `err_path` attributes are same: '{_log_path}', must be different!"
+ )
+
+ return values
+
+
+class FilePM(ExtraBaseModel):
+ logs_dir: constr(strip_whitespace=True) = Field(
+ default_factory=get_default_logs_dir, min_length=2, max_length=1023
+ )
+ rotate_size: int = Field(
+ default=10_000_000, ge=1_000, lt=1_000_000_000 # 10MB = 10 * 1000 * 1000
+ )
+ rotate_time: datetime.time = Field(datetime.time(0, 0, 0))
+ backup_count: int = Field(default=90, ge=1)
+ encoding: constr(strip_whitespace=True) = Field(
+ default="utf8", min_length=2, max_length=31
+ )
+ log_handlers: LogHandlersPM = Field(default_factory=LogHandlersPM)
+ json_handlers: JsonHandlersPM = Field(default_factory=JsonHandlersPM)
+
+ @validator("rotate_time", pre=True, always=True)
+ def _check_rotate_time(cls, val):
+ if val and isinstance(val, str):
+ val = datetime.time.fromisoformat(val)
+ return val
+
+
+class AutoLoadPM(ExtraBaseModel):
+ enabled: bool = Field(default=True)
+ only_base: bool = Field(default=False)
+ ignore_modules: List[str] = Field(default=[])
+
+
+class InterceptPM(ExtraBaseModel):
+ auto_load: AutoLoadPM = Field(default_factory=AutoLoadPM)
+ include_modules: List[str] = Field(default=[])
+ mute_modules: List[str] = Field(default=[])
+
+
+class ExtraPM(ExtraBaseModel):
+ pass
+
+
+class LoggerConfigPM(ExtraBaseModel):
+ app_name: constr(strip_whitespace=True) = Field(
+ default_factory=get_app_name,
+ min_length=1,
+ max_length=127,
+ )
+ level: LogLevelEnum = Field(default=LogLevelEnum.INFO)
+ use_backtrace: bool = Field(default=True)
+ use_diagnose: bool = Field(default=False)
+ stream: StreamPM = Field(default_factory=StreamPM)
+ file: FilePM = Field(default_factory=FilePM)
+ intercept: InterceptPM = Field(default_factory=InterceptPM)
+ extra: ExtraPM = Field(default_factory=ExtraPM)
diff --git a/examples/advanced/app.py b/examples/advanced/main.py
similarity index 100%
rename from examples/advanced/app.py
rename to examples/advanced/main.py
diff --git a/examples/advanced/requirements.txt b/examples/advanced/requirements.txt
index 34a1bf0..7a46392 100644
--- a/examples/advanced/requirements.txt
+++ b/examples/advanced/requirements.txt
@@ -1,5 +1,3 @@
-exceptiongroup>=1.1.3,<2.0.0
-packaging>=23.1
-certifi>=2023.7.22
-fastapi[all]>=0.101.1,<1.0.0
+fastapi>=0.99.1,<1.0.0
+uvicorn[standard]>=0.23.0,<1.0.0
gunicorn>=21.2.0,<22.0.0
diff --git a/requirements.txt b/requirements.txt
index 0d2a4cf..ea1ff8e 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,3 +1,3 @@
PyYAML>=6.0.1,<7.0
-pydantic>=2.1.1,<3.0.0
+pydantic>=1.10.0,!=2.0.0,!=2.0.1,!=2.1.0,<3.0.0
loguru>=0.7.2,<1.0.0
diff --git a/setup.py b/setup.py
index f15685f..03f00af 100644
--- a/setup.py
+++ b/setup.py
@@ -37,7 +37,7 @@
python_requires=">=3.8",
install_requires=[
"PyYAML>=6.0,<7.0",
- "pydantic>=2.1.1,<3.0.0",
+ "pydantic>=1.10.0,!=2.0.0,!=2.0.1,!=2.1.0,<3.0.0",
"loguru>=0.7.2,<1.0.0",
],
classifiers=[