Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 62 additions & 35 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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: "[<c>{time:YYYY-MM-DD HH:mm:ss.SSS Z}</c> | <level>{level_short:<5}</level> | <w>{name}:{line}</w>]: <level>{message}</level>"
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: '<n><w>[{request_id}]</w></n> {client_host} {user_id} "<u>{method} {url_path}</u> HTTP/{http_version}" {status_code} {content_length}B {response_time}ms'
http_std_debug_format: '<n>[{request_id}]</n> {client_host} {user_id} "<u>{method} {url_path}</u> HTTP/{http_version}"'
http_std_msg_format: '<n><w>[{request_id}]</w></n> {client_host} {user_id} "<u>{method} {url_path}</u> 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):
Expand All @@ -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()

Expand All @@ -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"}
Expand All @@ -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]
```

---
Expand Down
2 changes: 1 addition & 1 deletion beans_logging/__init__.py
Original file line number Diff line number Diff line change
@@ -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__
57 changes: 29 additions & 28 deletions beans_logging/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
Expand All @@ -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 <class 'LoggerConfigPM'>. 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 <class 'LoggerConfigPM'>. 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.
Expand All @@ -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,
Expand All @@ -79,16 +79,15 @@ 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()

if auto_config_file:
self._load_config_file()

if config:
self.update_config(config=config)

if auto_load:
self.load()

Expand Down Expand Up @@ -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:
Expand All @@ -166,18 +165,20 @@ def update_config(self, config: Union[LoggerConfigPM, dict]):
Exception: Failed to load `config` argument into <class 'LoggerConfigPM'>.
"""

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 <class 'LoggerConfigPM'>."
)
raise
except Exception:
logger.critical(
"Failed to load `config` argument into <class 'LoggerConfigPM'>."
)
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."""
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand All @@ -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 <dict>!."
Expand Down
File renamed without changes.
25 changes: 14 additions & 11 deletions beans_logging/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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
Expand Down
8 changes: 4 additions & 4 deletions beans_logging/fastapi/__init__.py
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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:
Expand All @@ -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
Loading