diff --git a/README.md b/README.md
index 1744cf6..96e3c2a 100644
--- a/README.md
+++ b/README.md
@@ -218,22 +218,45 @@ ZeroDivisionError: division by zero
logger:
app_name: "fastapi-app"
level: "TRACE"
+ use_diagnose: false
+ stream:
+ use_color: true
+ use_icon: false
+ format_str: "[{time:YYYY-MM-DD HH:mm:ss.SSS Z} | {level_short:<5} | {name}:{line}]: {message}"
+ std_handler:
+ enabled: true
file:
+ logs_dir: "./logs"
+ rotate_size: 10000000 # 10MB
+ rotate_time: "00:00:00"
+ backup_count: 90
log_handlers:
enabled: true
+ format_str: "[{time:YYYY-MM-DD HH:mm:ss.SSS Z} | {level_short:<5} | {name}:{line}]: {message}"
+ log_path: "{app_name}.std.all.log"
+ err_path: "{app_name}.std.err.log"
json_handlers:
enabled: true
+ use_custom: false
log_path: "json/{app_name}.json.all.log"
err_path: "json/{app_name}.json.err.log"
intercept:
+ auto_load:
+ enabled: true
+ only_base: false
+ ignore_modules: []
+ include_modules: []
mute_modules: ["uvicorn.access", "uvicorn.error"]
extra:
- http_std_msg_format: '[{request_id}] {client_host} {user_id} "{method} {url_path} HTTP/{http_version}" {status_code} {content_length}B {response_time}ms'
http_std_debug_format: '[{request_id}] {client_host} {user_id} "{method} {url_path} HTTP/{http_version}"'
+ http_std_msg_format: '[{request_id}] {client_host} {user_id} "{method} {url_path} HTTP/{http_version}" {status_code} {content_length}B {response_time}ms'
+ http_file_enabled: true
+ http_file_msg_format: '{client_host} {request_id} {user_id} [{datetime}] "{method} {url_path} HTTP/{http_version}" {status_code} {content_length} "{h_referer}" "{h_user_agent}" {response_time}'
http_log_path: "http/{app_name}.http.access.log"
http_err_path: "http/{app_name}.http.err.log"
- http_json_path: "http.json/{app_name}.json.http.access.log"
- http_json_err_path: "http.json/{app_name}.json.http.err.log"
+ http_json_enabled: true
+ http_json_path: "json.http/{app_name}.json.http.access.log"
+ http_json_err_path: "json.http/{app_name}.json.http.err.log"
```
[**`.env`**](https://github.com/bybatkhuu/module.python-logging/blob/main/examples/advanced/.env):
@@ -246,32 +269,37 @@ DEBUG=true
[**`logger.py`**](https://github.com/bybatkhuu/module.python-logging/blob/main/examples/advanced/logger.py):
```python
-from beans_logging import LoggerLoader, Logger
-from beans_logging.fastapi import add_file_http_handler, add_file_json_http_handler
+from beans_logging import Logger, LoggerLoader
+from beans_logging.fastapi import add_http_file_handler, add_http_file_json_handler
logger_loader = LoggerLoader()
logger: Logger = logger_loader.load()
-add_file_http_handler(
- logger_loader=logger_loader,
- log_path=logger_loader.config.extra.http_log_path,
- err_path=logger_loader.config.extra.http_err_path,
-)
-add_file_json_http_handler(
- logger_loader=logger_loader,
- log_path=logger_loader.config.extra.http_json_path,
- err_path=logger_loader.config.extra.http_json_err_path,
-)
+if logger_loader.config.extra.http_file_enabled:
+ add_http_file_handler(
+ logger_loader=logger_loader,
+ log_path=logger_loader.config.extra.http_log_path,
+ err_path=logger_loader.config.extra.http_err_path,
+ )
+
+if logger_loader.config.extra.http_json_enabled:
+ add_http_file_json_handler(
+ logger_loader=logger_loader,
+ log_path=logger_loader.config.extra.http_json_path,
+ err_path=logger_loader.config.extra.http_json_err_path,
+ )
```
[**`app.py`**](https://github.com/bybatkhuu/module.python-logging/blob/main/examples/advanced/app.py):
```python
+from typing import Union
from contextlib import asynccontextmanager
from dotenv import load_dotenv
-from fastapi import FastAPI
+from fastapi import FastAPI, HTTPException
+from fastapi.responses import RedirectResponse
load_dotenv()
@@ -291,16 +319,15 @@ async def lifespan(app: FastAPI):
logger.info("Praparing to shutdown...")
logger.success("Finished preparation to shutdown.")
-
app = FastAPI(lifespan=lifespan, version=__version__)
app.add_middleware(
HttpAccessLogMiddleware,
has_proxy_headers=True,
- msg_format=logger_loader.config.extra.http_std_msg_format,
debug_format=logger_loader.config.extra.http_std_debug_format,
+ msg_format=logger_loader.config.extra.http_std_msg_format,
+ file_msg_format=logger_loader.config.extra.http_file_msg_format,
)
-
@app.get("/")
def root():
return {"Hello": "World"}
@@ -319,22 +346,22 @@ uvicorn app:app --host=0.0.0.0 --port=8000
**Output**:
```txt
-[2023-09-01 19:04:44.342 +09:00 | TRACE | beans_logging._base:478]: Intercepted modules: ['watchfiles.watcher', 'asyncio', 'uvicorn', 'watchfiles', 'dotenv.main', 'watchfiles.main', 'fastapi', 'concurrent.futures', 'dotenv', 'concurrent']; Muted modules: ['uvicorn.access', 'uvicorn.error'];
-[2023-09-01 19:04:44.360 +09:00 | INFO | uvicorn.server:76]: Started server process [50837]
-[2023-09-01 19:04:44.360 +09:00 | INFO | uvicorn.lifespan.on:46]: Waiting for application startup.
-[2023-09-01 19:04:44.360 +09:00 | INFO | app:21]: Preparing to startup...
-[2023-09-01 19:04:44.361 +09:00 | OK | app:22]: Finished preparation to startup.
-[2023-09-01 19:04:44.361 +09:00 | INFO | app:23]: API version: 0.0.1-000000
-[2023-09-01 19:04:44.361 +09:00 | INFO | uvicorn.lifespan.on:60]: Application startup complete.
-[2023-09-01 19:04:44.363 +09:00 | INFO | uvicorn.server:218]: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
-[2023-09-01 19:04:46.594 +09:00 | DEBUG | beans_logging.fastapi._middleware:158]: [bcf605800a6d432d8d5021cfea8efd5f] 192.168.1.10 - "GET / HTTP/1.1"
-[2023-09-01 19:04:46.596 +09:00 | OK | beans_logging.fastapi._middleware:221]: [bcf605800a6d432d8d5021cfea8efd5f] 192.168.1.10 - "GET / HTTP/1.1" 200 17B 1.1ms
-^C[2023-09-01 19:04:48.017 +09:00 | INFO | uvicorn.server:264]: Shutting down
-[2023-09-01 19:04:48.121 +09:00 | INFO | uvicorn.lifespan.on:65]: Waiting for application shutdown.
-[2023-09-01 19:04:48.125 +09:00 | INFO | app:26]: Praparing to shutdown...
-[2023-09-01 19:04:48.126 +09:00 | OK | app:27]: Finished preparation to shutdown.
-[2023-09-01 19:04:48.127 +09:00 | INFO | uvicorn.lifespan.on:76]: Application shutdown complete.
-[2023-09-01 19:04:48.128 +09:00 | INFO | uvicorn.server:86]: Finished server process [50837]
+[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]
```
---
diff --git a/beans_logging/__init__.py b/beans_logging/__init__.py
index cc839be..81c7894 100644
--- a/beans_logging/__init__.py
+++ b/beans_logging/__init__.py
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
-from .schema import LoggerConfigPM
+from .schemas import LoggerConfigPM
from ._base import Logger, logger, LoggerLoader
from .__version__ import __version__
diff --git a/beans_logging/_base.py b/beans_logging/_base.py
index 564261a..c364126 100644
--- a/beans_logging/_base.py
+++ b/beans_logging/_base.py
@@ -5,7 +5,7 @@
import copy
import json
import logging
-from typing import Union
+from typing import Union, Dict, Any
## Third-party libraries
import yaml
@@ -15,12 +15,12 @@
## Internal modules
from ._utils import create_dir, deep_merge
-from ._handler import InterceptHandler
+from ._handlers import InterceptHandler
from .rotation import RotationChecker
-from .schema import LoggerConfigPM
-from .sink import std_sink
-from .format import json_format
-from .filter import (
+from .schemas import LoggerConfigPM
+from .sinks import std_sink
+from .formats import json_format
+from .filters import (
use_all_filter,
use_std_filter,
use_file_filter,
@@ -36,9 +36,9 @@ class LoggerLoader:
Attributes:
_CONFIG_FILE_PATH (str ): Default logger config file path. Defaults to '${PWD}/configs/logger.yml'.
- handlers_map (dict ): Registered logger handlers map as dictionary. Defaults to None.
- config (LoggerConfigPM): Logger config as . Defaults to None.
- config_file_path (str ): Logger config file path. Defaults to `LoggerLoader._CONFIG_FILE_PATH`.
+ handlers_map (dict ): Registered logger handlers map as dictionary. Defaults to None.
+ config (LoggerConfigPM): Logger config as . Defaults to None.
+ config_file_path (str ): Logger config file path. Defaults to `LoggerLoader._CONFIG_FILE_PATH`.
Methods:
load() : Load logger handlers based on logger config.
@@ -61,7 +61,7 @@ class LoggerLoader:
@validate_call
def __init__(
self,
- config: Union[LoggerConfigPM, dict, None] = None,
+ config: Union[LoggerConfigPM, Dict[str, Any], None] = None,
config_file_path: str = _CONFIG_FILE_PATH,
auto_config_file: bool = True,
auto_load: bool = False,
@@ -79,6 +79,8 @@ def __init__(
self.handlers_map = {"default": 0}
self.config = LoggerConfigPM()
+ if config:
+ self.update_config(config=config)
self.config_file_path = config_file_path
self._load_env_vars()
@@ -86,9 +88,6 @@ def __init__(
if auto_config_file:
self._load_config_file()
- if config:
- self.update_config(config=config)
-
if auto_load:
self.load()
@@ -156,7 +155,7 @@ def remove_handler(
self.handlers_map.clear()
@validate_call
- def update_config(self, config: Union[LoggerConfigPM, dict]):
+ def update_config(self, config: Union[LoggerConfigPM, Dict[str, Any]]):
"""Update logger config with new config.
Args:
@@ -166,18 +165,20 @@ def update_config(self, config: Union[LoggerConfigPM, dict]):
Exception: Failed to load `config` argument into .
"""
- try:
- if isinstance(config, dict):
- _config_dict = self.config.model_dump()
- _merged_dict = deep_merge(_config_dict, config)
+ if isinstance(config, dict):
+ _config_dict = self.config.model_dump()
+ _merged_dict = deep_merge(_config_dict, config)
+ print(_merged_dict)
+ try:
self.config = LoggerConfigPM(**_merged_dict)
- elif isinstance(config, LoggerConfigPM):
- self.config = config
- except Exception:
- logger.critical(
- "Failed to load `config` argument into ."
- )
- raise
+ except Exception:
+ logger.critical(
+ "Failed to load `config` argument into ."
+ )
+ raise
+
+ elif isinstance(config, LoggerConfigPM):
+ self.config = config
def _load_env_vars(self):
"""Load 'BEANS_LOGGING_CONFIG_PATH' environment variable for logger config file path."""
@@ -436,7 +437,7 @@ def add_custom_handler(self, handler_name: str, **kwargs) -> int:
_log_path = _log_path.format(app_name=self.config.app_name)
_logs_dir, _ = os.path.split(_log_path)
- create_dir(create_dir=_logs_dir)
+ create_dir(create_dir=_logs_dir, warn_mode="DEBUG")
kwargs["sink"] = _log_path
if "format" not in kwargs:
@@ -516,7 +517,7 @@ def _load_intercept_handlers(self):
### ATTRIBUTES ###
## handlers_map ##
@property
- def handlers_map(self) -> Union[dict, None]:
+ def handlers_map(self) -> Dict[str, int]:
try:
return self.__handlers_map
except AttributeError:
@@ -525,7 +526,7 @@ def handlers_map(self) -> Union[dict, None]:
return self.__handlers_map
@handlers_map.setter
- def handlers_map(self, handlers_map: dict):
+ def handlers_map(self, handlers_map: Dict[str, int]):
if not isinstance(handlers_map, dict):
raise TypeError(
f"`handlers_map` attribute type {type(handlers_map)} is invalid, must be !."
diff --git a/beans_logging/_handler.py b/beans_logging/_handlers.py
similarity index 100%
rename from beans_logging/_handler.py
rename to beans_logging/_handlers.py
diff --git a/beans_logging/_utils.py b/beans_logging/_utils.py
index 75c1cee..21d37f7 100644
--- a/beans_logging/_utils.py
+++ b/beans_logging/_utils.py
@@ -13,20 +13,22 @@
@validate_call
-def create_dir(create_dir: str, quiet: bool = True):
+def create_dir(create_dir: str, warn_mode: str = "DEBUG"):
"""Create directory if `create_dir` doesn't exist.
Args:
- create_dir (str , required): Create directory path.
- quiet (bool, optional): If True, don't log anything unless debug is enabled. Defaults to True.
+ create_dir (str, required): Create directory path.
+ warn_mode (str, optional): Warning message mode, for example: 'LOG', 'DEBUG', 'QUIET'. Defaults to "QUIET".
"""
+ warn_mode = warn_mode.strip().upper()
if not os.path.isdir(create_dir):
try:
- if quiet:
- logger.debug(f"Creaing '{create_dir}' directory...")
- else:
- logger.info(f"Creaing '{create_dir}' directory...")
+ _message = f"Creaing '{create_dir}' directory..."
+ if warn_mode == "LOG":
+ logger.info(_message)
+ elif warn_mode == "DEBUG":
+ logger.debug(_message)
os.makedirs(create_dir)
except OSError as err:
@@ -36,10 +38,11 @@ def create_dir(create_dir: str, quiet: bool = True):
logger.error(f"Failed to create '{create_dir}' directory!")
raise
- if quiet:
- logger.debug(f"Successfully created '{create_dir}' directory.")
- else:
- logger.success(f"Successfully created '{create_dir}' directory.")
+ _message = f"Successfully created '{create_dir}' directory."
+ if warn_mode == "LOG":
+ logger.success(_message)
+ elif warn_mode == "DEBUG":
+ logger.debug(_message)
@validate_call
diff --git a/beans_logging/fastapi/__init__.py b/beans_logging/fastapi/__init__.py
index 3682d42..8ecb7e2 100644
--- a/beans_logging/fastapi/__init__.py
+++ b/beans_logging/fastapi/__init__.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
-from ._middleware import HttpAccessLogMiddleware
-from ._handler import add_file_http_handler, add_file_json_http_handler
-from ._filter import use_http_filter
-from ._format import file_http_format, file_json_http_format
+from ._middlewares import HttpAccessLogMiddleware
+from ._handlers import add_http_file_handler, add_http_file_json_handler
+from ._filters import use_http_filter
+from ._formats import http_file_format, http_file_json_format
diff --git a/beans_logging/fastapi/_filter.py b/beans_logging/fastapi/_filters.py
similarity index 74%
rename from beans_logging/fastapi/_filter.py
rename to beans_logging/fastapi/_filters.py
index 8375962..42f9c3d 100644
--- a/beans_logging/fastapi/_filter.py
+++ b/beans_logging/fastapi/_filters.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
-from beans_logging.filter import use_all_filter
+from beans_logging.filters import use_all_filter
def use_http_filter(record: dict) -> bool:
@@ -16,7 +16,7 @@ def use_http_filter(record: dict) -> bool:
if not use_all_filter(record):
return False
- if "http_info" in record["extra"]:
- return True
+ if "http_info" not in record["extra"]:
+ return False
- return False
+ return True
diff --git a/beans_logging/fastapi/_format.py b/beans_logging/fastapi/_formats.py
similarity index 71%
rename from beans_logging/fastapi/_format.py
rename to beans_logging/fastapi/_formats.py
index 28fa748..b8c8b01 100644
--- a/beans_logging/fastapi/_format.py
+++ b/beans_logging/fastapi/_formats.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
-def file_http_format(record: dict) -> str:
+def http_file_format(record: dict) -> str:
"""Http access log file format.
Args:
@@ -11,6 +11,8 @@ def file_http_format(record: dict) -> str:
str: Format for http access log record.
"""
+ _MSG_FORMAT = '{client_host} {request_id} {user_id} [{datetime}] "{method} {url_path} HTTP/{http_version}" {status_code} {content_length} "{h_referer}" "{h_user_agent}" {response_time}'
+
if "http_info" not in record["extra"]:
return ""
@@ -20,14 +22,17 @@ def file_http_format(record: dict) -> str:
_http_info["datetime"] = record["time"].isoformat()
record["extra"]["http_info"] = _http_info
- _msg_format = '{client_host} {request_id} {user_id} [{datetime}] "{method} {url_path} HTTP/{http_version}" {status_code} {content_length} "{h_referer}" "{h_user_agent}" {response_time}'
+ _msg_format = _MSG_FORMAT
+ if "http_file_msg_format" in record["extra"]:
+ _msg_format = record["extra"]["http_file_msg_format"]
+
_msg = _msg_format.format(**_http_info)
record["http_message"] = _msg
return "{http_message}\n"
-def file_json_http_format(record: dict) -> str:
+def http_file_json_format(record: dict) -> str:
"""Http access json log file format.
Args:
diff --git a/beans_logging/fastapi/_handler.py b/beans_logging/fastapi/_handlers.py
similarity index 80%
rename from beans_logging/fastapi/_handler.py
rename to beans_logging/fastapi/_handlers.py
index f30d45c..2fc9fb4 100644
--- a/beans_logging/fastapi/_handler.py
+++ b/beans_logging/fastapi/_handlers.py
@@ -4,12 +4,12 @@
from beans_logging import LoggerLoader
-from ._filter import use_http_filter
-from ._format import file_http_format, file_json_http_format
+from ._filters import use_http_filter
+from ._formats import http_file_format, http_file_json_format
@validate_call(config=dict(arbitrary_types_allowed=True))
-def add_file_http_handler(
+def add_http_file_handler(
logger_loader: LoggerLoader,
log_path: str = "http/{app_name}.http.access.log",
err_path: str = "http/{app_name}.http.err.log",
@@ -26,7 +26,7 @@ def add_file_http_handler(
handler_name="FILE.HTTP",
sink=log_path,
filter=use_http_filter,
- format=file_http_format,
+ format=http_file_format,
)
logger_loader.add_custom_handler(
@@ -34,15 +34,15 @@ def add_file_http_handler(
sink=err_path,
level="WARNING",
filter=use_http_filter,
- format=file_http_format,
+ format=http_file_format,
)
@validate_call(config=dict(arbitrary_types_allowed=True))
-def add_file_json_http_handler(
+def add_http_file_json_handler(
logger_loader: LoggerLoader,
- log_path: str = "http.json/{app_name}.json.http.access.log",
- err_path: str = "http.json/{app_name}.json.http.err.log",
+ log_path: str = "json.http/{app_name}.json.http.access.log",
+ err_path: str = "json.http/{app_name}.json.http.err.log",
):
"""Add http access json log file and json error file handler.
@@ -56,7 +56,7 @@ def add_file_json_http_handler(
handler_name="FILE.JSON.HTTP",
sink=log_path,
filter=use_http_filter,
- format=file_json_http_format,
+ format=http_file_json_format,
)
logger_loader.add_custom_handler(
@@ -64,5 +64,5 @@ def add_file_json_http_handler(
sink=err_path,
level="WARNING",
filter=use_http_filter,
- format=file_json_http_format,
+ format=http_file_json_format,
)
diff --git a/beans_logging/fastapi/_middleware.py b/beans_logging/fastapi/_middlewares.py
similarity index 83%
rename from beans_logging/fastapi/_middleware.py
rename to beans_logging/fastapi/_middlewares.py
index ecfc2a8..2f686d2 100644
--- a/beans_logging/fastapi/_middleware.py
+++ b/beans_logging/fastapi/_middlewares.py
@@ -18,25 +18,36 @@ class HttpAccessLogMiddleware(BaseHTTPMiddleware):
BaseHTTPMiddleware: Base HTTP middleware class from starlette.
Attributes:
+ _DEBUG_FORMAT (str ): Default http access log debug message format. Defaults to '[{request_id}] {client_host} {user_id} "{method} {url_path} HTTP/{http_version}"'.
+ _MSG_FORMAT (str ): Default http access log message format. Defaults to '[{request_id}] {client_host} {user_id} "{method} {url_path} HTTP/{http_version}" {status_code} {content_length}B {response_time}ms'.
+ _FILE_MSG_FORMAT (str ): Default http access log file message format. Defaults to '{client_host} {request_id} {user_id} [{datetime}] "{method} {url_path} HTTP/{http_version}" {status_code} {content_length} "{h_referer}" "{h_user_agent}" {response_time}'.
+
has_proxy_headers (bool): If True, use proxy headers to get http request info. Defaults to False.
has_cf_headers (bool): If True, use cloudflare headers to get http request info. Defaults to False.
- msg_format (str ): Http access log message format. Defaults to '[{request_id}] {client_host} {user_id} "{method} {url_path} HTTP/{http_version}" {status_code} {content_length}B {response_time}ms'.
- debug_format (str ): Http access log debug message format. Defaults to '[{request_id}] {client_host} {user_id} "{method} {url_path} HTTP/{http_version}"'.
+ debug_format (str ): Http access log debug message format. Defaults to `HttpAccessLogMiddleware._DEBUG_FORMAT`.
+ msg_format (str ): Http access log message format. Defaults to `HttpAccessLogMiddleware._MSG_FORMAT`.
+ file_msg_format (str ): Http access log file message format. Defaults to `HttpAccessLogMiddleware._FILE_MSG_FORMAT`.
"""
+ _DEBUG_FORMAT = '[{request_id}] {client_host} {user_id} "{method} {url_path} HTTP/{http_version}"'
+ _MSG_FORMAT = '[{request_id}] {client_host} {user_id} "{method} {url_path} HTTP/{http_version}" {status_code} {content_length}B {response_time}ms'
+ _FILE_MSG_FORMAT = '{client_host} {request_id} {user_id} [{datetime}] "{method} {url_path} HTTP/{http_version}" {status_code} {content_length} "{h_referer}" "{h_user_agent}" {response_time}'
+
def __init__(
self,
app,
has_proxy_headers: bool = False,
has_cf_headers: bool = False,
- msg_format: str = '[{request_id}] {client_host} {user_id} "{method} {url_path} HTTP/{http_version}" {status_code} {content_length}B {response_time}ms',
- debug_format: str = '[{request_id}] {client_host} {user_id} "{method} {url_path} HTTP/{http_version}"',
+ debug_format: str = _DEBUG_FORMAT,
+ msg_format: str = _MSG_FORMAT,
+ file_msg_format: str = _FILE_MSG_FORMAT,
):
super().__init__(app)
self.has_proxy_headers = has_proxy_headers
self.has_cf_headers = has_cf_headers
- self.msg_format = msg_format
self.debug_format = debug_format
+ self.msg_format = msg_format
+ self.file_msg_format = file_msg_format
async def dispatch(self, request: Request, call_next) -> Response:
_logger = logger.opt(colors=True, record=True)
@@ -157,7 +168,10 @@ async def dispatch(self, request: Request, call_next) -> Response:
_debug_msg = self.debug_format.format(**_http_info)
# _logger.debug(_debug_msg)
- await run_in_threadpool(_logger.debug, _debug_msg)
+ await run_in_threadpool(
+ _logger.debug,
+ _debug_msg,
+ )
_start_time = time.time()
response = await call_next(request)
@@ -221,6 +235,13 @@ async def dispatch(self, request: Request, call_next) -> Response:
_msg = _msg_format.format(**_http_info)
# _logger.bind(http_info=_http_info).log(_LEVEL, _msg)
- await run_in_threadpool(_logger.bind(http_info=_http_info).log, _LEVEL, _msg)
+ await run_in_threadpool(
+ _logger.bind(
+ http_info=_http_info,
+ http_file_msg_format=self.file_msg_format,
+ ).log,
+ _LEVEL,
+ _msg,
+ )
return response
diff --git a/beans_logging/filter.py b/beans_logging/filters.py
similarity index 100%
rename from beans_logging/filter.py
rename to beans_logging/filters.py
diff --git a/beans_logging/format.py b/beans_logging/formats.py
similarity index 100%
rename from beans_logging/format.py
rename to beans_logging/formats.py
diff --git a/beans_logging/schema.py b/beans_logging/schemas.py
similarity index 66%
rename from beans_logging/schema.py
rename to beans_logging/schemas.py
index f77777c..9127ea1 100644
--- a/beans_logging/schema.py
+++ b/beans_logging/schemas.py
@@ -16,6 +16,10 @@
)
+class ExtraBaseModel(BaseModel):
+ model_config = ConfigDict(extra="allow")
+
+
class LevelEnum(str, Enum):
TRACE = "TRACE"
DEBUG = "DEBUG"
@@ -26,35 +30,33 @@ class LevelEnum(str, Enum):
CRITICAL = "CRITICAL"
-class StdHandlerPM(BaseModel):
+class StdHandlerPM(ExtraBaseModel):
enabled: bool = Field(default=True)
- model_config = ConfigDict(extra="allow")
-
-class StreamPM(BaseModel):
+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=9,
+ min_length=3,
+ max_length=511,
)
std_handler: StdHandlerPM = Field(default=StdHandlerPM())
- model_config = ConfigDict(extra="allow")
-
-class LogHandlersPM(BaseModel):
+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=9,
+ min_length=4,
+ max_length=511,
)
log_path: constr(strip_whitespace=True) = Field(
- default="{app_name}.std.all.log", min_length=5, max_length=255
+ default="{app_name}.std.all.log", min_length=4, max_length=4095
)
err_path: constr(strip_whitespace=True) = Field(
- default="{app_name}.std.err.log", min_length=5, max_length=255
+ default="{app_name}.std.err.log", min_length=4, max_length=4095
)
@model_validator(mode="after")
@@ -66,17 +68,15 @@ def _check_log_path(self) -> "LogHandlersPM":
return self
- model_config = ConfigDict(extra="allow")
-
-class JsonHandlersPM(BaseModel):
+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=5, max_length=255
+ default="{app_name}.json.all.log", min_length=4, max_length=4095
)
err_path: constr(strip_whitespace=True) = Field(
- default="{app_name}.json.err.log", min_length=5, max_length=255
+ default="{app_name}.json.err.log", min_length=4, max_length=4095
)
@model_validator(mode="after")
@@ -88,12 +88,10 @@ def _check_log_path(self) -> "JsonHandlersPM":
return self
- model_config = ConfigDict(extra="allow")
-
-class FilePM(BaseModel):
+class FilePM(ExtraBaseModel):
logs_dir: str = Field(
- default=os.path.join(os.getcwd(), "logs"), min_length=2, max_length=4096
+ default=os.path.join(os.getcwd(), "logs"), min_length=2, max_length=4095
)
rotate_size: int = Field(
default=10_000_000, ge=1_000, lt=1_000_000_000 # 10MB = 10 * 1000 * 1000
@@ -113,30 +111,24 @@ def _check_rotate_time(cls, val):
val = datetime.time.fromisoformat(val)
return val
- model_config = ConfigDict(extra="allow")
-
-class AutoLoadPM(BaseModel):
+class AutoLoadPM(ExtraBaseModel):
enabled: bool = Field(default=True)
only_base: bool = Field(default=False)
ignore_modules: List[str] = Field(default=[])
- model_config = ConfigDict(extra="allow")
-
-class InterceptPM(BaseModel):
+class InterceptPM(ExtraBaseModel):
auto_load: AutoLoadPM = Field(default=AutoLoadPM())
include_modules: List[str] = Field(default=[])
mute_modules: List[str] = Field(default=[])
- model_config = ConfigDict(extra="allow")
-
-class ExtraPM(BaseModel):
- model_config = ConfigDict(extra="allow")
+class ExtraPM(ExtraBaseModel):
+ pass
-class LoggerConfigPM(BaseModel):
+class LoggerConfigPM(ExtraBaseModel):
app_name: constr(strip_whitespace=True) = Field(
default=os.path.splitext(os.path.basename(sys.argv[0]))[0]
.strip()
@@ -153,28 +145,26 @@ class LoggerConfigPM(BaseModel):
intercept: InterceptPM = Field(default=InterceptPM())
extra: ExtraPM = Field(default=ExtraPM())
- @model_validator(mode="after")
- def _check_log_path(self) -> "LoggerConfigPM":
- if "{app_name}" in self.file.log_handlers.log_path:
- self.file.log_handlers.log_path = self.file.log_handlers.log_path.format(
- app_name=self.app_name
- )
-
- if "{app_name}" in self.file.log_handlers.err_path:
- self.file.log_handlers.err_path = self.file.log_handlers.err_path.format(
- app_name=self.app_name
- )
-
- if "{app_name}" in self.file.json_handlers.log_path:
- self.file.json_handlers.log_path = self.file.json_handlers.log_path.format(
- app_name=self.app_name
- )
-
- if "{app_name}" in self.file.json_handlers.err_path:
- self.file.json_handlers.err_path = self.file.json_handlers.err_path.format(
- app_name=self.app_name
- )
-
- return self
-
- model_config = ConfigDict(extra="allow")
+ # @model_validator(mode="after")
+ # def _check_log_path(self) -> "LoggerConfigPM":
+ # if "{app_name}" in self.file.log_handlers.log_path:
+ # self.file.log_handlers.log_path = self.file.log_handlers.log_path.format(
+ # app_name=self.app_name
+ # )
+
+ # if "{app_name}" in self.file.log_handlers.err_path:
+ # self.file.log_handlers.err_path = self.file.log_handlers.err_path.format(
+ # app_name=self.app_name
+ # )
+
+ # if "{app_name}" in self.file.json_handlers.log_path:
+ # self.file.json_handlers.log_path = self.file.json_handlers.log_path.format(
+ # app_name=self.app_name
+ # )
+
+ # if "{app_name}" in self.file.json_handlers.err_path:
+ # self.file.json_handlers.err_path = self.file.json_handlers.err_path.format(
+ # app_name=self.app_name
+ # )
+
+ # return self
diff --git a/beans_logging/sink.py b/beans_logging/sinks.py
similarity index 100%
rename from beans_logging/sink.py
rename to beans_logging/sinks.py
diff --git a/examples/advanced/app.py b/examples/advanced/app.py
index 3e65811..e9269d8 100755
--- a/examples/advanced/app.py
+++ b/examples/advanced/app.py
@@ -31,8 +31,9 @@ async def lifespan(app: FastAPI):
app.add_middleware(
HttpAccessLogMiddleware,
has_proxy_headers=True,
- msg_format=logger_loader.config.extra.http_std_msg_format,
debug_format=logger_loader.config.extra.http_std_debug_format,
+ msg_format=logger_loader.config.extra.http_std_msg_format,
+ file_msg_format=logger_loader.config.extra.http_file_msg_format,
)
diff --git a/examples/advanced/configs/logger.yml b/examples/advanced/configs/logger.yml
index e53bcaf..40b94fb 100644
--- a/examples/advanced/configs/logger.yml
+++ b/examples/advanced/configs/logger.yml
@@ -1,19 +1,42 @@
logger:
app_name: "fastapi-app"
level: "TRACE"
+ use_diagnose: false
+ stream:
+ use_color: true
+ use_icon: false
+ format_str: "[{time:YYYY-MM-DD HH:mm:ss.SSS Z} | {level_short:<5} | {name}:{line}]: {message}"
+ std_handler:
+ enabled: true
file:
+ logs_dir: "./logs"
+ rotate_size: 10000000 # 10MB
+ rotate_time: "00:00:00"
+ backup_count: 90
log_handlers:
enabled: true
+ format_str: "[{time:YYYY-MM-DD HH:mm:ss.SSS Z} | {level_short:<5} | {name}:{line}]: {message}"
+ log_path: "{app_name}.std.all.log"
+ err_path: "{app_name}.std.err.log"
json_handlers:
enabled: true
+ use_custom: false
log_path: "json/{app_name}.json.all.log"
err_path: "json/{app_name}.json.err.log"
intercept:
+ auto_load:
+ enabled: true
+ only_base: false
+ ignore_modules: []
+ include_modules: []
mute_modules: ["uvicorn.access", "uvicorn.error"]
extra:
- http_std_msg_format: '[{request_id}] {client_host} {user_id} "{method} {url_path} HTTP/{http_version}" {status_code} {content_length}B {response_time}ms'
http_std_debug_format: '[{request_id}] {client_host} {user_id} "{method} {url_path} HTTP/{http_version}"'
+ http_std_msg_format: '[{request_id}] {client_host} {user_id} "{method} {url_path} HTTP/{http_version}" {status_code} {content_length}B {response_time}ms'
+ http_file_enabled: true
+ http_file_msg_format: '{client_host} {request_id} {user_id} [{datetime}] "{method} {url_path} HTTP/{http_version}" {status_code} {content_length} "{h_referer}" "{h_user_agent}" {response_time}'
http_log_path: "http/{app_name}.http.access.log"
http_err_path: "http/{app_name}.http.err.log"
- http_json_path: "http.json/{app_name}.json.http.access.log"
- http_json_err_path: "http.json/{app_name}.json.http.err.log"
+ http_json_enabled: true
+ http_json_path: "json.http/{app_name}.json.http.access.log"
+ http_json_err_path: "json.http/{app_name}.json.http.err.log"
diff --git a/examples/advanced/logger.py b/examples/advanced/logger.py
index a12f75c..a5662a3 100644
--- a/examples/advanced/logger.py
+++ b/examples/advanced/logger.py
@@ -1,19 +1,22 @@
# -*- coding: utf-8 -*-
from beans_logging import Logger, LoggerLoader
-from beans_logging.fastapi import add_file_http_handler, add_file_json_http_handler
+from beans_logging.fastapi import add_http_file_handler, add_http_file_json_handler
logger_loader = LoggerLoader()
logger: Logger = logger_loader.load()
-add_file_http_handler(
- logger_loader=logger_loader,
- log_path=logger_loader.config.extra.http_log_path,
- err_path=logger_loader.config.extra.http_err_path,
-)
-add_file_json_http_handler(
- logger_loader=logger_loader,
- log_path=logger_loader.config.extra.http_json_path,
- err_path=logger_loader.config.extra.http_json_err_path,
-)
+if logger_loader.config.extra.http_file_enabled:
+ add_http_file_handler(
+ logger_loader=logger_loader,
+ log_path=logger_loader.config.extra.http_log_path,
+ err_path=logger_loader.config.extra.http_err_path,
+ )
+
+if logger_loader.config.extra.http_json_enabled:
+ add_http_file_json_handler(
+ logger_loader=logger_loader,
+ log_path=logger_loader.config.extra.http_json_path,
+ err_path=logger_loader.config.extra.http_json_err_path,
+ )
diff --git a/tests/test_beans_logging.py b/tests/test_beans_logging.py
index 3b94faa..cf7b5c2 100644
--- a/tests/test_beans_logging.py
+++ b/tests/test_beans_logging.py
@@ -23,7 +23,7 @@ def logger():
del logger
-def test_init(logger, logger_loader):
+def test_init(logger: Logger, logger_loader: LoggerLoader):
logger.info("Testing initialization of 'LoggerLoader'...")
assert isinstance(logger_loader, LoggerLoader)
@@ -34,7 +34,7 @@ def test_init(logger, logger_loader):
logger.success("Done: Initialization of 'LoggerLoader'.\n")
-def test_load(logger, logger_loader):
+def test_load(logger: Logger, logger_loader: LoggerLoader):
logger.info("Testing 'load' method of 'LoggerLoader'...")
logger_loader.update_config(config={"level": "TRACE"})
@@ -53,7 +53,7 @@ def test_load(logger, logger_loader):
logger.success("Done: 'load' method.\n")
-def test_methods(logger):
+def test_methods(logger: Logger):
logger.info("Testing 'logger' methods...")
logger.trace("Tracing...")