beans-logging-fastapi 1.1.1__py3-none-any.whl → 3.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- beans_logging_fastapi/__init__.py +6 -24
- beans_logging_fastapi/__version__.py +1 -3
- beans_logging_fastapi/{_async_log.py → _async.py} +39 -27
- beans_logging_fastapi/_core.py +90 -0
- beans_logging_fastapi/config.py +168 -0
- beans_logging_fastapi/constants.py +12 -0
- beans_logging_fastapi/{_filters.py → filters.py} +9 -4
- beans_logging_fastapi/formats.py +95 -0
- beans_logging_fastapi/{_middlewares.py → middlewares.py} +137 -30
- beans_logging_fastapi-3.0.0.dist-info/METADATA +586 -0
- beans_logging_fastapi-3.0.0.dist-info/RECORD +14 -0
- {beans_logging_fastapi-1.1.1.dist-info → beans_logging_fastapi-3.0.0.dist-info}/WHEEL +1 -1
- beans_logging_fastapi-1.1.1.dist-info/LICENCE.txt → beans_logging_fastapi-3.0.0.dist-info/licenses/LICENSE.txt +1 -1
- {beans_logging_fastapi-1.1.1.dist-info → beans_logging_fastapi-3.0.0.dist-info}/top_level.txt +0 -1
- beans_logging_fastapi/_base.py +0 -100
- beans_logging_fastapi/_formats.py +0 -77
- beans_logging_fastapi/_handlers.py +0 -82
- beans_logging_fastapi-1.1.1.dist-info/METADATA +0 -385
- beans_logging_fastapi-1.1.1.dist-info/RECORD +0 -16
- tests/__init__.py +0 -1
- tests/conftest.py +0 -16
- tests/test_beans_logging_fastapi.py +0 -24
beans_logging_fastapi/_base.py
DELETED
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
|
|
3
|
-
from typing import Dict, Any
|
|
4
|
-
|
|
5
|
-
from fastapi import Request, Response
|
|
6
|
-
from fastapi.concurrency import run_in_threadpool
|
|
7
|
-
from starlette.middleware.base import BaseHTTPMiddleware
|
|
8
|
-
|
|
9
|
-
from beans_logging import logger
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class HttpAccessLogMiddleware(BaseHTTPMiddleware):
|
|
13
|
-
"""Http access log middleware for FastAPI.
|
|
14
|
-
|
|
15
|
-
Inherits:
|
|
16
|
-
BaseHTTPMiddleware: Base HTTP middleware class from starlette.
|
|
17
|
-
|
|
18
|
-
Attributes:
|
|
19
|
-
_DEBUG_FORMAT (str ): Default http access log debug message format. Defaults to '<n>[{request_id}]</n> {client_host} {user_id} "<u>{method} {url_path}</u> HTTP/{http_version}"'.
|
|
20
|
-
_MSG_FORMAT (str ): Default http access log message format. Defaults to '<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'.
|
|
21
|
-
|
|
22
|
-
debug_format (str ): Http access log debug message format. Defaults to `HttpAccessLogMiddleware._DEBUG_FORMAT`.
|
|
23
|
-
msg_format (str ): Http access log message format. Defaults to `HttpAccessLogMiddleware._MSG_FORMAT`.
|
|
24
|
-
use_debug_log (bool): If True, use debug log to log http access log. Defaults to True.
|
|
25
|
-
"""
|
|
26
|
-
|
|
27
|
-
_DEBUG_FORMAT = '<n>[{request_id}]</n> {client_host} {user_id} "<u>{method} {url_path}</u> HTTP/{http_version}"'
|
|
28
|
-
_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'
|
|
29
|
-
|
|
30
|
-
def __init__(
|
|
31
|
-
self,
|
|
32
|
-
app,
|
|
33
|
-
debug_format: str = _DEBUG_FORMAT,
|
|
34
|
-
msg_format: str = _MSG_FORMAT,
|
|
35
|
-
use_debug_log: bool = True,
|
|
36
|
-
):
|
|
37
|
-
super().__init__(app)
|
|
38
|
-
self.debug_format = debug_format
|
|
39
|
-
self.msg_format = msg_format
|
|
40
|
-
self.use_debug_log = use_debug_log
|
|
41
|
-
|
|
42
|
-
async def dispatch(self, request: Request, call_next) -> Response:
|
|
43
|
-
_logger = logger.opt(colors=True, record=True)
|
|
44
|
-
|
|
45
|
-
_http_info: Dict[str, Any] = {}
|
|
46
|
-
if hasattr(request.state, "http_info") and isinstance(
|
|
47
|
-
request.state.http_info, dict
|
|
48
|
-
):
|
|
49
|
-
_http_info: Dict[str, Any] = request.state.http_info
|
|
50
|
-
|
|
51
|
-
## Debug log:
|
|
52
|
-
if self.use_debug_log:
|
|
53
|
-
_debug_msg = self.debug_format.format(**_http_info)
|
|
54
|
-
|
|
55
|
-
# _logger.debug(_debug_msg)
|
|
56
|
-
await run_in_threadpool(
|
|
57
|
-
_logger.debug,
|
|
58
|
-
_debug_msg,
|
|
59
|
-
)
|
|
60
|
-
## Debug log
|
|
61
|
-
|
|
62
|
-
## Process request:
|
|
63
|
-
response: Response = await call_next(request)
|
|
64
|
-
## Response processed.
|
|
65
|
-
|
|
66
|
-
if hasattr(request.state, "http_info") and isinstance(
|
|
67
|
-
request.state.http_info, dict
|
|
68
|
-
):
|
|
69
|
-
_http_info: Dict[str, Any] = request.state.http_info
|
|
70
|
-
|
|
71
|
-
## Http access log:
|
|
72
|
-
_LEVEL = "INFO"
|
|
73
|
-
_msg_format = self.msg_format
|
|
74
|
-
if _http_info["status_code"] < 200:
|
|
75
|
-
_LEVEL = "DEBUG"
|
|
76
|
-
_msg_format = f'<d>{_msg_format.replace("{status_code}", "<n><b><k>{status_code}</k></b></n>")}</d>'
|
|
77
|
-
elif (200 <= _http_info["status_code"]) and (_http_info["status_code"] < 300):
|
|
78
|
-
_LEVEL = "SUCCESS"
|
|
79
|
-
_msg_format = f'<w>{_msg_format.replace("{status_code}", "<lvl>{status_code}</lvl>")}</w>'
|
|
80
|
-
elif (300 <= _http_info["status_code"]) and (_http_info["status_code"] < 400):
|
|
81
|
-
_LEVEL = "INFO"
|
|
82
|
-
_msg_format = f'<d>{_msg_format.replace("{status_code}", "<n><b><c>{status_code}</c></b></n>")}</d>'
|
|
83
|
-
elif (400 <= _http_info["status_code"]) and (_http_info["status_code"] < 500):
|
|
84
|
-
_LEVEL = "WARNING"
|
|
85
|
-
_msg_format = _msg_format.replace("{status_code}", "<r>{status_code}</r>")
|
|
86
|
-
elif 500 <= _http_info["status_code"]:
|
|
87
|
-
_LEVEL = "ERROR"
|
|
88
|
-
_msg_format = (
|
|
89
|
-
f'{_msg_format.replace("{status_code}", "<n>{status_code}</n>")}'
|
|
90
|
-
)
|
|
91
|
-
|
|
92
|
-
_msg = _msg_format.format(**_http_info)
|
|
93
|
-
# _logger.bind(http_info=_http_info).log(_LEVEL, _msg)
|
|
94
|
-
await run_in_threadpool(_logger.bind(http_info=_http_info).log, _LEVEL, _msg)
|
|
95
|
-
## Http access log
|
|
96
|
-
|
|
97
|
-
return response
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
__all__ = ["HttpAccessLogMiddleware"]
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
|
|
3
|
-
from typing import Dict, Any
|
|
4
|
-
from zoneinfo import ZoneInfo
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
def http_file_format(
|
|
8
|
-
record: dict,
|
|
9
|
-
msg_format: str = '{client_host} {request_id} {user_id} [{datetime}] "{method} {url_path} HTTP/{http_version}" {status_code} {content_length} "{h_referer}" "{h_user_agent}" {response_time}',
|
|
10
|
-
tz: str = "localtime",
|
|
11
|
-
) -> str:
|
|
12
|
-
"""Http access log file format.
|
|
13
|
-
|
|
14
|
-
Args:
|
|
15
|
-
record (dict): Log record as dictionary.
|
|
16
|
-
|
|
17
|
-
Returns:
|
|
18
|
-
str: Format for http access log record.
|
|
19
|
-
"""
|
|
20
|
-
|
|
21
|
-
if "http_info" not in record["extra"]:
|
|
22
|
-
return ""
|
|
23
|
-
|
|
24
|
-
if "http_message" not in record:
|
|
25
|
-
_http_info: Dict[str, Any] = record["extra"]["http_info"]
|
|
26
|
-
if "datetime" not in _http_info:
|
|
27
|
-
_dt = record["time"]
|
|
28
|
-
if tz != "localtime":
|
|
29
|
-
if not _dt.tzinfo:
|
|
30
|
-
_dt = _dt.replace(tzinfo=ZoneInfo("UTC"))
|
|
31
|
-
|
|
32
|
-
_dt = _dt.astimezone(ZoneInfo(tz))
|
|
33
|
-
|
|
34
|
-
_http_info["datetime"] = _dt.isoformat(timespec="milliseconds")
|
|
35
|
-
|
|
36
|
-
if "content_length" not in _http_info:
|
|
37
|
-
_http_info["content_length"] = 0
|
|
38
|
-
|
|
39
|
-
if "h_referer" not in _http_info:
|
|
40
|
-
_http_info["h_referer"] = "-"
|
|
41
|
-
|
|
42
|
-
if "h_user_agent" not in _http_info:
|
|
43
|
-
_http_info["h_user_agent"] = "-"
|
|
44
|
-
|
|
45
|
-
if "response_time" not in _http_info:
|
|
46
|
-
_http_info["response_time"] = 0
|
|
47
|
-
|
|
48
|
-
record["extra"]["http_info"] = _http_info
|
|
49
|
-
|
|
50
|
-
_msg = msg_format.format(**_http_info)
|
|
51
|
-
record["http_message"] = _msg
|
|
52
|
-
|
|
53
|
-
return "{http_message}\n"
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
def http_file_json_format(record: dict) -> str:
|
|
57
|
-
"""Http access json log file format.
|
|
58
|
-
|
|
59
|
-
Args:
|
|
60
|
-
record (dict): Log record as dictionary.
|
|
61
|
-
|
|
62
|
-
Returns:
|
|
63
|
-
str: Format for http access json log record.
|
|
64
|
-
"""
|
|
65
|
-
|
|
66
|
-
if "http_info" not in record["extra"]:
|
|
67
|
-
return ""
|
|
68
|
-
|
|
69
|
-
_http_info: Dict[str, Any] = record["extra"]["http_info"]
|
|
70
|
-
if "datetime" not in _http_info:
|
|
71
|
-
_http_info["datetime"] = record["time"].isoformat(timespec="milliseconds")
|
|
72
|
-
record["extra"]["http_info"] = _http_info
|
|
73
|
-
|
|
74
|
-
return "{extra[http_info]}\n"
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
__all__ = ["http_file_format", "http_file_json_format"]
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
|
|
3
|
-
from typing import Union, Callable
|
|
4
|
-
|
|
5
|
-
import pydantic
|
|
6
|
-
|
|
7
|
-
if "2.0.0" <= pydantic.__version__:
|
|
8
|
-
from pydantic import validate_call
|
|
9
|
-
else:
|
|
10
|
-
from pydantic import validate_arguments as validate_call
|
|
11
|
-
|
|
12
|
-
from beans_logging import LoggerLoader
|
|
13
|
-
|
|
14
|
-
from ._filters import use_http_filter
|
|
15
|
-
from ._formats import http_file_format, http_file_json_format
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
@validate_call(config=dict(arbitrary_types_allowed=True))
|
|
19
|
-
def add_http_file_handler(
|
|
20
|
-
logger_loader: LoggerLoader,
|
|
21
|
-
log_path: str = "http/{app_name}.http.access.log",
|
|
22
|
-
err_path: str = "http/{app_name}.http.err.log",
|
|
23
|
-
formatter: Union[Callable, str] = http_file_format,
|
|
24
|
-
):
|
|
25
|
-
"""Add http access log file and error file handler.
|
|
26
|
-
|
|
27
|
-
Args:
|
|
28
|
-
logger_loader (LoggerLoader, required): LoggerLoader instance.
|
|
29
|
-
log_path (str, optional): Log file path. Defaults to "http/{app_name}.http.access.log".
|
|
30
|
-
err_path (str, optional): Error log file path. Defaults to "http/{app_name}.http.err.log".
|
|
31
|
-
formatter (Union[Callable, str], optional): Log formatter. Defaults to `http_file_format` function.
|
|
32
|
-
"""
|
|
33
|
-
|
|
34
|
-
logger_loader.add_custom_handler(
|
|
35
|
-
handler_name="FILE.HTTP",
|
|
36
|
-
sink=log_path,
|
|
37
|
-
filter=use_http_filter,
|
|
38
|
-
format=formatter,
|
|
39
|
-
)
|
|
40
|
-
|
|
41
|
-
logger_loader.add_custom_handler(
|
|
42
|
-
handler_name="FILE.HTTP_ERR",
|
|
43
|
-
sink=err_path,
|
|
44
|
-
level="WARNING",
|
|
45
|
-
filter=use_http_filter,
|
|
46
|
-
format=formatter,
|
|
47
|
-
)
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
@validate_call(config=dict(arbitrary_types_allowed=True))
|
|
51
|
-
def add_http_file_json_handler(
|
|
52
|
-
logger_loader: LoggerLoader,
|
|
53
|
-
log_path: str = "json.http/{app_name}.json.http.access.log",
|
|
54
|
-
err_path: str = "json.http/{app_name}.json.http.err.log",
|
|
55
|
-
formatter: Union[Callable, str] = http_file_json_format,
|
|
56
|
-
):
|
|
57
|
-
"""Add http access json log file and json error file handler.
|
|
58
|
-
|
|
59
|
-
Args:
|
|
60
|
-
logger_loader (LoggerLoader, required): LoggerLoader instance.
|
|
61
|
-
log_path (str, optional): Json log file path. Defaults to "http.json/{app_name}.json.http.access.log".
|
|
62
|
-
err_path (str, optional): Json error log file path. Defaults to "http.json/{app_name}.json.http.err.log".
|
|
63
|
-
formatter (Union[Callable, str], optional): Log formatter. Defaults to `http_file_json_format` function.
|
|
64
|
-
"""
|
|
65
|
-
|
|
66
|
-
logger_loader.add_custom_handler(
|
|
67
|
-
handler_name="FILE.JSON.HTTP",
|
|
68
|
-
sink=log_path,
|
|
69
|
-
filter=use_http_filter,
|
|
70
|
-
format=formatter,
|
|
71
|
-
)
|
|
72
|
-
|
|
73
|
-
logger_loader.add_custom_handler(
|
|
74
|
-
handler_name="FILE.JSON.HTTP_ERR",
|
|
75
|
-
sink=err_path,
|
|
76
|
-
level="WARNING",
|
|
77
|
-
filter=use_http_filter,
|
|
78
|
-
format=formatter,
|
|
79
|
-
)
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
__all__ = ["add_http_file_handler", "add_http_file_json_handler"]
|
|
@@ -1,385 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: beans-logging-fastapi
|
|
3
|
-
Version: 1.1.1
|
|
4
|
-
Summary: 'beans_logging_fastapi' is a middleware for FastAPI to log HTTP access. It is based on 'beans-logging' package.
|
|
5
|
-
Home-page: https://github.com/bybatkhuu/module.fastapi-logging
|
|
6
|
-
Download-URL: https://github.com/bybatkhuu/module.fastapi-logging/archive/v1.1.1.tar.gz
|
|
7
|
-
Author: Batkhuu Byambajav
|
|
8
|
-
Author-email: batkhuu10@gmail.com
|
|
9
|
-
License: MIT
|
|
10
|
-
Keywords: beans_logging_fastapi,fastapi-logging,fastapi-logging-middleware,fastapi-middleware,logging-middleware,middleware,beans-logging,http-access-logging,logging,logger,loguru
|
|
11
|
-
Classifier: Development Status :: 4 - Beta
|
|
12
|
-
Classifier: Intended Audience :: Developers
|
|
13
|
-
Classifier: Topic :: Software Development :: Build Tools
|
|
14
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
-
Requires-Python: >=3.8
|
|
20
|
-
Description-Content-Type: text/markdown
|
|
21
|
-
License-File: LICENCE.txt
|
|
22
|
-
Requires-Dist: fastapi <1.0.0,>=0.99.1
|
|
23
|
-
Requires-Dist: beans-logging <7.0.0,>=6.0.0
|
|
24
|
-
|
|
25
|
-
# beans_logging_fastapi
|
|
26
|
-
|
|
27
|
-
[](https://choosealicense.com/licenses/mit)
|
|
28
|
-
[](https://github.com/bybatkhuu/module.fastapi-logging/actions/workflows/2.build-publish.yml)
|
|
29
|
-
[](https://github.com/bybatkhuu/module.fastapi-logging/releases)
|
|
30
|
-
[](https://pypi.org/project/beans-logging-fastapi)
|
|
31
|
-
[](https://docs.conda.io/en/latest/miniconda.html)
|
|
32
|
-
|
|
33
|
-
`beans_logging_fastapi` is a middleware for FastAPI to log HTTP access.
|
|
34
|
-
|
|
35
|
-
It is based on **'beans-logging'** package.
|
|
36
|
-
|
|
37
|
-
## Features
|
|
38
|
-
|
|
39
|
-
- **Logger** based on **'beans-logging'** package
|
|
40
|
-
- **FastAPI** HTTP access logging **middleware**
|
|
41
|
-
|
|
42
|
-
---
|
|
43
|
-
|
|
44
|
-
## Installation
|
|
45
|
-
|
|
46
|
-
### 1. Prerequisites
|
|
47
|
-
|
|
48
|
-
- **Python (>= v3.8)**
|
|
49
|
-
- **PyPi (>= v23)**
|
|
50
|
-
|
|
51
|
-
### 2. Install beans-logging-fastapi package
|
|
52
|
-
|
|
53
|
-
Choose one of the following methods to install the package **[A ~ F]**:
|
|
54
|
-
|
|
55
|
-
**A.** [**RECOMMENDED**] Install from **PyPi**
|
|
56
|
-
|
|
57
|
-
```sh
|
|
58
|
-
# Install or upgrade package:
|
|
59
|
-
pip install -U beans-logging-fastapi
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
**B.** Install latest version from **GitHub**
|
|
63
|
-
|
|
64
|
-
```sh
|
|
65
|
-
# Install package by git:
|
|
66
|
-
pip install git+https://github.com/bybatkhuu/module.fastapi-logging.git
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
**C.** Install from **pre-built release** files
|
|
70
|
-
|
|
71
|
-
1. Download **`.whl`** or **`.tar.gz`** file from **releases** - <https://github.com/bybatkhuu/module.fastapi-logging/releases>
|
|
72
|
-
2. Install with pip:
|
|
73
|
-
|
|
74
|
-
```sh
|
|
75
|
-
# Install from .whl file:
|
|
76
|
-
pip install ./beans_logging_fastapi-[VERSION]-py3-none-any.whl
|
|
77
|
-
# Or install from .tar.gz file:
|
|
78
|
-
pip install ./beans_logging_fastapi-[VERSION].tar.gz
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
**D.** Install from **source code** by building package
|
|
82
|
-
|
|
83
|
-
```sh
|
|
84
|
-
# Clone repository by git:
|
|
85
|
-
git clone https://github.com/bybatkhuu/module.fastapi-logging.git beans_logging_fastapi
|
|
86
|
-
cd ./beans_logging_fastapi
|
|
87
|
-
|
|
88
|
-
# Install python build tool:
|
|
89
|
-
pip install -U pip build
|
|
90
|
-
|
|
91
|
-
# Build python package:
|
|
92
|
-
python -m build
|
|
93
|
-
|
|
94
|
-
_VERSION=$(./scripts/get-version.sh)
|
|
95
|
-
|
|
96
|
-
# Install from .whl file:
|
|
97
|
-
pip install ./dist/beans_logging_fastapi-${_VERSION}-py3-none-any.whl
|
|
98
|
-
# Or install from .tar.gz file:
|
|
99
|
-
pip install ./dist/beans_logging_fastapi-${_VERSION}.tar.gz
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
**E.** Install with pip editable **development mode** (from source code)
|
|
103
|
-
|
|
104
|
-
```sh
|
|
105
|
-
# Clone repository by git:
|
|
106
|
-
git clone https://github.com/bybatkhuu/module.fastapi-logging.git beans_logging_fastapi
|
|
107
|
-
cd ./beans_logging_fastapi
|
|
108
|
-
|
|
109
|
-
# Install with editable development mode:
|
|
110
|
-
pip install -e .
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
**F.** Manually add to **PYTHONPATH** (not recommended)
|
|
114
|
-
|
|
115
|
-
```sh
|
|
116
|
-
# Clone repository by git:
|
|
117
|
-
git clone https://github.com/bybatkhuu/module.fastapi-logging.git beans_logging_fastapi
|
|
118
|
-
cd ./beans_logging_fastapi
|
|
119
|
-
|
|
120
|
-
# Install python dependencies:
|
|
121
|
-
pip install -r ./requirements.txt
|
|
122
|
-
|
|
123
|
-
# Add current path to PYTHONPATH:
|
|
124
|
-
export PYTHONPATH="${PWD}:${PYTHONPATH}"
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
## Usage/Examples
|
|
128
|
-
|
|
129
|
-
To use `beans_logging_fastapi`:
|
|
130
|
-
|
|
131
|
-
### **FastAPI**
|
|
132
|
-
|
|
133
|
-
[**`configs/logger.yml`**](https://github.com/bybatkhuu/module.fastapi-logging/blob/main/examples/configs/logger.yml):
|
|
134
|
-
|
|
135
|
-
```yaml
|
|
136
|
-
logger:
|
|
137
|
-
app_name: "fastapi-app"
|
|
138
|
-
level: "TRACE"
|
|
139
|
-
use_diagnose: false
|
|
140
|
-
stream:
|
|
141
|
-
use_color: true
|
|
142
|
-
use_icon: false
|
|
143
|
-
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>"
|
|
144
|
-
std_handler:
|
|
145
|
-
enabled: true
|
|
146
|
-
file:
|
|
147
|
-
logs_dir: "./logs"
|
|
148
|
-
rotate_size: 10000000 # 10MB
|
|
149
|
-
rotate_time: "00:00:00"
|
|
150
|
-
backup_count: 90
|
|
151
|
-
log_handlers:
|
|
152
|
-
enabled: true
|
|
153
|
-
format_str: "[{time:YYYY-MM-DD HH:mm:ss.SSS Z} | {level_short:<5} | {name}:{line}]: {message}"
|
|
154
|
-
log_path: "{app_name}.std.all.log"
|
|
155
|
-
err_path: "{app_name}.std.err.log"
|
|
156
|
-
json_handlers:
|
|
157
|
-
enabled: true
|
|
158
|
-
use_custom: false
|
|
159
|
-
log_path: "json/{app_name}.json.all.log"
|
|
160
|
-
err_path: "json/{app_name}.json.err.log"
|
|
161
|
-
intercept:
|
|
162
|
-
auto_load:
|
|
163
|
-
enabled: true
|
|
164
|
-
only_base: false
|
|
165
|
-
ignore_modules: []
|
|
166
|
-
include_modules: []
|
|
167
|
-
mute_modules: ["uvicorn.access"]
|
|
168
|
-
extra:
|
|
169
|
-
http_std_debug_format: '<n>[{request_id}]</n> {client_host} {user_id} "<u>{method} {url_path}</u> HTTP/{http_version}"'
|
|
170
|
-
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'
|
|
171
|
-
http_file_enabled: true
|
|
172
|
-
http_file_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}'
|
|
173
|
-
http_file_tz: "localtime"
|
|
174
|
-
http_log_path: "http/{app_name}.http.access.log"
|
|
175
|
-
http_err_path: "http/{app_name}.http.err.log"
|
|
176
|
-
http_json_enabled: true
|
|
177
|
-
http_json_path: "json.http/{app_name}.json.http.access.log"
|
|
178
|
-
http_json_err_path: "json.http/{app_name}.json.http.err.log"
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
[**`.env`**](https://github.com/bybatkhuu/module.fastapi-logging/blob/main/examples/.env):
|
|
182
|
-
|
|
183
|
-
```sh
|
|
184
|
-
ENV=development
|
|
185
|
-
DEBUG=true
|
|
186
|
-
|
|
187
|
-
BEANS_LOGGING_DISABLE_DEFAULT=false
|
|
188
|
-
BEANS_LOGGING_CONFIG_PATH="./configs/logger.yml"
|
|
189
|
-
BEANS_LOGGING_LOGS_DIR="./logs"
|
|
190
|
-
```
|
|
191
|
-
|
|
192
|
-
[**`logger.py`**](https://github.com/bybatkhuu/module.fastapi-logging/blob/main/examples/logger.py):
|
|
193
|
-
|
|
194
|
-
```python
|
|
195
|
-
from beans_logging import Logger, LoggerLoader
|
|
196
|
-
from beans_logging_fastapi import (
|
|
197
|
-
add_http_file_handler,
|
|
198
|
-
add_http_file_json_handler,
|
|
199
|
-
http_file_format,
|
|
200
|
-
)
|
|
201
|
-
|
|
202
|
-
logger_loader = LoggerLoader()
|
|
203
|
-
logger: Logger = logger_loader.load()
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
def _http_file_format(record: dict) -> str:
|
|
207
|
-
_format = http_file_format(
|
|
208
|
-
record=record,
|
|
209
|
-
msg_format=logger_loader.config.extra.http_file_format,
|
|
210
|
-
tz=logger_loader.config.extra.http_file_tz,
|
|
211
|
-
)
|
|
212
|
-
return _format
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
if logger_loader.config.extra.http_file_enabled:
|
|
216
|
-
add_http_file_handler(
|
|
217
|
-
logger_loader=logger_loader,
|
|
218
|
-
log_path=logger_loader.config.extra.http_log_path,
|
|
219
|
-
err_path=logger_loader.config.extra.http_err_path,
|
|
220
|
-
formatter=_http_file_format,
|
|
221
|
-
)
|
|
222
|
-
|
|
223
|
-
if logger_loader.config.extra.http_json_enabled:
|
|
224
|
-
add_http_file_json_handler(
|
|
225
|
-
logger_loader=logger_loader,
|
|
226
|
-
log_path=logger_loader.config.extra.http_json_path,
|
|
227
|
-
err_path=logger_loader.config.extra.http_json_err_path,
|
|
228
|
-
)
|
|
229
|
-
```
|
|
230
|
-
|
|
231
|
-
[**`main.py`**](https://github.com/bybatkhuu/module.fastapi-logging/blob/main/examples/main.py):
|
|
232
|
-
|
|
233
|
-
```python
|
|
234
|
-
from typing import Union
|
|
235
|
-
from contextlib import asynccontextmanager
|
|
236
|
-
|
|
237
|
-
import uvicorn
|
|
238
|
-
from dotenv import load_dotenv
|
|
239
|
-
from fastapi import FastAPI, HTTPException
|
|
240
|
-
from fastapi.responses import RedirectResponse
|
|
241
|
-
|
|
242
|
-
load_dotenv()
|
|
243
|
-
|
|
244
|
-
from beans_logging_fastapi import (
|
|
245
|
-
HttpAccessLogMiddleware,
|
|
246
|
-
RequestHTTPInfoMiddleware,
|
|
247
|
-
ResponseHTTPInfoMiddleware,
|
|
248
|
-
)
|
|
249
|
-
|
|
250
|
-
from logger import logger, logger_loader
|
|
251
|
-
from __version__ import __version__
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
@asynccontextmanager
|
|
255
|
-
async def lifespan(app: FastAPI):
|
|
256
|
-
logger.info("Preparing to startup...")
|
|
257
|
-
logger.success("Finished preparation to startup.")
|
|
258
|
-
logger.info(f"API version: {__version__}")
|
|
259
|
-
|
|
260
|
-
yield
|
|
261
|
-
logger.info("Praparing to shutdown...")
|
|
262
|
-
logger.success("Finished preparation to shutdown.")
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
app = FastAPI(lifespan=lifespan, version=__version__)
|
|
266
|
-
|
|
267
|
-
app.add_middleware(ResponseHTTPInfoMiddleware)
|
|
268
|
-
app.add_middleware(
|
|
269
|
-
HttpAccessLogMiddleware,
|
|
270
|
-
debug_format=logger_loader.config.extra.http_std_debug_format,
|
|
271
|
-
msg_format=logger_loader.config.extra.http_std_msg_format,
|
|
272
|
-
)
|
|
273
|
-
app.add_middleware(
|
|
274
|
-
RequestHTTPInfoMiddleware, has_proxy_headers=True, has_cf_headers=True
|
|
275
|
-
)
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
@app.get("/")
|
|
279
|
-
def root():
|
|
280
|
-
return {"Hello": "World"}
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
@app.get("/items/{item_id}")
|
|
284
|
-
def read_item(item_id: int, q: Union[str, None] = None):
|
|
285
|
-
return {"item_id": item_id, "q": q}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
@app.get("/continue", status_code=100)
|
|
289
|
-
def get_continue():
|
|
290
|
-
return {}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
@app.get("/redirect")
|
|
294
|
-
def redirect():
|
|
295
|
-
return RedirectResponse("/")
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
@app.get("/error")
|
|
299
|
-
def error():
|
|
300
|
-
raise HTTPException(status_code=500)
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
if __name__ == "__main__":
|
|
304
|
-
uvicorn.run(
|
|
305
|
-
app="main:app",
|
|
306
|
-
host="0.0.0.0",
|
|
307
|
-
port=8000,
|
|
308
|
-
access_log=False,
|
|
309
|
-
server_header=False,
|
|
310
|
-
proxy_headers=True,
|
|
311
|
-
forwarded_allow_ips="*",
|
|
312
|
-
)
|
|
313
|
-
```
|
|
314
|
-
|
|
315
|
-
Run the [**`examples`**](https://github.com/bybatkhuu/module.fastapi-logging/tree/main/examples):
|
|
316
|
-
|
|
317
|
-
```sh
|
|
318
|
-
cd ./examples
|
|
319
|
-
# Install python dependencies for examples:
|
|
320
|
-
pip install -r ./requirements.txt
|
|
321
|
-
|
|
322
|
-
uvicorn main:app --host=0.0.0.0 --port=8000
|
|
323
|
-
```
|
|
324
|
-
|
|
325
|
-
**Output**:
|
|
326
|
-
|
|
327
|
-
```txt
|
|
328
|
-
[2023-10-31 12:38:46.733 +09:00 | TRACE | beans_logging._base:578]: Intercepted modules: ['concurrent', 'concurrent.futures', 'watchfiles.watcher', 'dotenv.main', 'watchfiles.main', 'watchfiles', 'asyncio', 'fastapi', 'uvicorn.error', 'uvicorn', 'dotenv']; Muted modules: ['uvicorn.access'];
|
|
329
|
-
[2023-10-31 12:38:46.749 +09:00 | INFO | uvicorn.server:76]: Started server process [56216]
|
|
330
|
-
[2023-10-31 12:38:46.749 +09:00 | INFO | uvicorn.lifespan.on:46]: Waiting for application startup.
|
|
331
|
-
[2023-10-31 12:38:46.750 +09:00 | INFO | main:26]: Preparing to startup...
|
|
332
|
-
[2023-10-31 12:38:46.750 +09:00 | OK | main:27]: Finished preparation to startup.
|
|
333
|
-
[2023-10-31 12:38:46.750 +09:00 | INFO | main:28]: API version: 0.0.0-000000
|
|
334
|
-
[2023-10-31 12:38:46.750 +09:00 | INFO | uvicorn.lifespan.on:60]: Application startup complete.
|
|
335
|
-
[2023-10-31 12:38:46.752 +09:00 | INFO | uvicorn.server:218]: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
|
|
336
|
-
[2023-10-31 12:38:48.701 +09:00 | DEBUG | anyio._backends._asyncio:807]: [c3d76377250947c29559cb103a6d2c56] 127.0.0.1 - "GET / HTTP/1.1"
|
|
337
|
-
[2023-10-31 12:38:48.703 +09:00 | OK | anyio._backends._asyncio:807]: [c3d76377250947c29559cb103a6d2c56] 127.0.0.1 - "GET / HTTP/1.1" 200 17B 0.8ms
|
|
338
|
-
^C[2023-10-31 12:38:49.793 +09:00 | INFO | uvicorn.server:264]: Shutting down
|
|
339
|
-
[2023-10-31 12:38:49.903 +09:00 | INFO | uvicorn.lifespan.on:65]: Waiting for application shutdown.
|
|
340
|
-
[2023-10-31 12:38:49.903 +09:00 | INFO | main:31]: Praparing to shutdown...
|
|
341
|
-
[2023-10-31 12:38:49.904 +09:00 | OK | main:32]: Finished preparation to shutdown.
|
|
342
|
-
[2023-10-31 12:38:49.904 +09:00 | INFO | uvicorn.lifespan.on:76]: Application shutdown complete.
|
|
343
|
-
[2023-10-31 12:38:49.904 +09:00 | INFO | uvicorn.server:86]: Finished server process [56216]
|
|
344
|
-
```
|
|
345
|
-
|
|
346
|
-
---
|
|
347
|
-
|
|
348
|
-
## Running Tests
|
|
349
|
-
|
|
350
|
-
To run tests, run the following command:
|
|
351
|
-
|
|
352
|
-
```sh
|
|
353
|
-
# Install python test dependencies:
|
|
354
|
-
pip install -r ./requirements.test.txt
|
|
355
|
-
|
|
356
|
-
# Run tests:
|
|
357
|
-
python -m pytest -sv
|
|
358
|
-
```
|
|
359
|
-
|
|
360
|
-
## Environment Variables
|
|
361
|
-
|
|
362
|
-
You can use the following environment variables inside [**`.env.example`**](https://github.com/bybatkhuu/module.fastapi-logging/blob/main/.env.example) file:
|
|
363
|
-
|
|
364
|
-
```sh
|
|
365
|
-
ENV=development
|
|
366
|
-
DEBUG=true
|
|
367
|
-
|
|
368
|
-
BEANS_LOGGING_DISABLE_DEFAULT=false
|
|
369
|
-
BEANS_LOGGING_CONFIG_PATH="./configs/logger.yml"
|
|
370
|
-
BEANS_LOGGING_LOGS_DIR="./logs"
|
|
371
|
-
```
|
|
372
|
-
|
|
373
|
-
## Documentation
|
|
374
|
-
|
|
375
|
-
- [docs](https://github.com/bybatkhuu/module.fastapi-logging/blob/main/docs/README.md)
|
|
376
|
-
- [scripts](https://github.com/bybatkhuu/module.fastapi-logging/blob/main/docs/scripts/README.md)
|
|
377
|
-
|
|
378
|
-
---
|
|
379
|
-
|
|
380
|
-
## References
|
|
381
|
-
|
|
382
|
-
- <https://github.com/bybatkhuu/module.python-logging>
|
|
383
|
-
- <https://github.com/Delgan/loguru>
|
|
384
|
-
- <https://loguru.readthedocs.io/en/stable/api/logger.html>
|
|
385
|
-
- <https://loguru.readthedocs.io/en/stable/resources/recipes.html>
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
beans_logging_fastapi/__init__.py,sha256=BRaF1AtJeVBvYtDyvx9YUBUe7Ez632UAwVW6rUQU72I,874
|
|
2
|
-
beans_logging_fastapi/__version__.py,sha256=vaO-yG68LsdKNrjwKiIqxA34q000eeRpru3EyLGSxsY,47
|
|
3
|
-
beans_logging_fastapi/_async_log.py,sha256=2J-LBW0IQRCtZOfcYHZSUSbzQipWF-MuWbXgd6tqP9Q,3334
|
|
4
|
-
beans_logging_fastapi/_base.py,sha256=ChpSFJ0yXUUdgV9Y-2SyrjPRuGHdYCf5UvMDxnPRAGg,4186
|
|
5
|
-
beans_logging_fastapi/_filters.py,sha256=SmluD4RDMk_wsRJVSpE5jp7AylotYJy2N6aw9AVWwFI,541
|
|
6
|
-
beans_logging_fastapi/_formats.py,sha256=MdS5x5EV7BVWEjVstQCeQn8XUz3qW12ltQS8-rNrCKc,2166
|
|
7
|
-
beans_logging_fastapi/_handlers.py,sha256=eEgQD7ho6dqLzUfoN7CNAq7hD-fd97oeo-HROb6x1xk,2794
|
|
8
|
-
beans_logging_fastapi/_middlewares.py,sha256=h9-turKSGnK__4mLAId3Slno3avz7Hu9lNpdmoepjY0,9278
|
|
9
|
-
tests/__init__.py,sha256=iwhKnzeBJLKxpRVjvzwiRE63_zNpIBfaKLITauVph-0,24
|
|
10
|
-
tests/conftest.py,sha256=ycEL83-UMU-fcXQUZWTCNEPcBOZ38pzhoCPpXpCjIEM,319
|
|
11
|
-
tests/test_beans_logging_fastapi.py,sha256=khxZzk-UZJU4BfM3KLytzKZATnsjsYts0UJqn1UB5cU,489
|
|
12
|
-
beans_logging_fastapi-1.1.1.dist-info/LICENCE.txt,sha256=8jrXqC7FZbke39LPGo_mUFR81CkoUCP_vyefZjlQDOg,1074
|
|
13
|
-
beans_logging_fastapi-1.1.1.dist-info/METADATA,sha256=_Jw2qKu3bDukUEkmJI8goSLgIsoalx5G4D7wqqWrfTE,12502
|
|
14
|
-
beans_logging_fastapi-1.1.1.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
|
15
|
-
beans_logging_fastapi-1.1.1.dist-info/top_level.txt,sha256=i97pAfk2NcT9DA7cPsBmF6_90GzNObQ9UJpZFuXdt1s,28
|
|
16
|
-
beans_logging_fastapi-1.1.1.dist-info/RECORD,,
|
tests/__init__.py
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
tests/conftest.py
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
|
|
3
|
-
import pytest
|
|
4
|
-
|
|
5
|
-
from beans_logging import logger
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
@pytest.fixture(scope="session", autouse=True)
|
|
9
|
-
def setup_and_teardown():
|
|
10
|
-
# Equivalent of setUp
|
|
11
|
-
logger.info("Setting up...")
|
|
12
|
-
|
|
13
|
-
yield # This is where the testing happens!
|
|
14
|
-
|
|
15
|
-
# Equivalent of tearDown
|
|
16
|
-
logger.info("Tearing down!")
|