beans-logging 5.0.0__py3-none-any.whl → 6.0.1__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/__version__.py +1 -3
- beans_logging/_base.py +2 -2
- beans_logging-6.0.1.dist-info/METADATA +419 -0
- beans_logging-6.0.1.dist-info/RECORD +17 -0
- {beans_logging-5.0.0.dist-info → beans_logging-6.0.1.dist-info}/WHEEL +1 -1
- {beans_logging-5.0.0.dist-info → beans_logging-6.0.1.dist-info/licenses}/LICENSE.txt +1 -1
- {beans_logging-5.0.0.dist-info → beans_logging-6.0.1.dist-info}/top_level.txt +0 -1
- beans_logging/fastapi/__init__.py +0 -6
- beans_logging/fastapi/_filters.py +0 -22
- beans_logging/fastapi/_formats.py +0 -62
- beans_logging/fastapi/_handlers.py +0 -79
- beans_logging/fastapi/_middlewares.py +0 -262
- beans_logging-5.0.0.dist-info/METADATA +0 -469
- beans_logging-5.0.0.dist-info/RECORD +0 -25
- tests/__init__.py +0 -1
- tests/conftest.py +0 -16
- tests/test_beans_logging.py +0 -67
|
@@ -1,262 +0,0 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
|
|
3
|
-
import json
|
|
4
|
-
import time
|
|
5
|
-
from uuid import uuid4
|
|
6
|
-
|
|
7
|
-
from fastapi import Request, Response
|
|
8
|
-
from fastapi.concurrency import run_in_threadpool
|
|
9
|
-
from starlette.middleware.base import BaseHTTPMiddleware
|
|
10
|
-
|
|
11
|
-
from beans_logging import logger
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
class HttpAccessLogMiddleware(BaseHTTPMiddleware):
|
|
15
|
-
"""Http access log middleware for FastAPI.
|
|
16
|
-
|
|
17
|
-
Inherits:
|
|
18
|
-
BaseHTTPMiddleware: Base HTTP middleware class from starlette.
|
|
19
|
-
|
|
20
|
-
Attributes:
|
|
21
|
-
_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}"'.
|
|
22
|
-
_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'.
|
|
23
|
-
|
|
24
|
-
has_proxy_headers (bool): If True, use proxy headers to get http request info. Defaults to False.
|
|
25
|
-
has_cf_headers (bool): If True, use cloudflare headers to get http request info. Defaults to False.
|
|
26
|
-
debug_format (str ): Http access log debug message format. Defaults to `HttpAccessLogMiddleware._DEBUG_FORMAT`.
|
|
27
|
-
msg_format (str ): Http access log message format. Defaults to `HttpAccessLogMiddleware._MSG_FORMAT`.
|
|
28
|
-
use_debug_log (bool): If True, use debug log to log http access log. Defaults to True.
|
|
29
|
-
"""
|
|
30
|
-
|
|
31
|
-
_DEBUG_FORMAT = '<n>[{request_id}]</n> {client_host} {user_id} "<u>{method} {url_path}</u> HTTP/{http_version}"'
|
|
32
|
-
_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'
|
|
33
|
-
|
|
34
|
-
def __init__(
|
|
35
|
-
self,
|
|
36
|
-
app,
|
|
37
|
-
has_proxy_headers: bool = False,
|
|
38
|
-
has_cf_headers: bool = False,
|
|
39
|
-
debug_format: str = _DEBUG_FORMAT,
|
|
40
|
-
msg_format: str = _MSG_FORMAT,
|
|
41
|
-
use_debug_log: bool = True,
|
|
42
|
-
):
|
|
43
|
-
super().__init__(app)
|
|
44
|
-
self.has_proxy_headers = has_proxy_headers
|
|
45
|
-
self.has_cf_headers = has_cf_headers
|
|
46
|
-
self.debug_format = debug_format
|
|
47
|
-
self.msg_format = msg_format
|
|
48
|
-
self.use_debug_log = use_debug_log
|
|
49
|
-
|
|
50
|
-
async def dispatch(self, request: Request, call_next) -> Response:
|
|
51
|
-
_logger = logger.opt(colors=True, record=True)
|
|
52
|
-
|
|
53
|
-
_http_info = {}
|
|
54
|
-
_http_info["request_id"] = uuid4().hex
|
|
55
|
-
if "X-Request-ID" in request.headers:
|
|
56
|
-
_http_info["request_id"] = request.headers.get("X-Request-ID")
|
|
57
|
-
elif "X-Correlation-ID" in request.headers:
|
|
58
|
-
_http_info["request_id"] = request.headers.get("X-Correlation-ID")
|
|
59
|
-
|
|
60
|
-
## Set request_id to request state:
|
|
61
|
-
request.state.request_id = _http_info["request_id"]
|
|
62
|
-
|
|
63
|
-
_http_info["client_host"] = request.client.host
|
|
64
|
-
_http_info["request_proto"] = request.url.scheme
|
|
65
|
-
_http_info["request_host"] = (
|
|
66
|
-
request.url.hostname if request.url.hostname else ""
|
|
67
|
-
)
|
|
68
|
-
if (request.url.port != 80) and (request.url.port != 443):
|
|
69
|
-
_http_info[
|
|
70
|
-
"request_host"
|
|
71
|
-
] = f"{_http_info['request_host']}:{request.url.port}"
|
|
72
|
-
|
|
73
|
-
_http_info["request_port"] = request.url.port
|
|
74
|
-
_http_info["http_version"] = request.scope["http_version"]
|
|
75
|
-
|
|
76
|
-
if self.has_proxy_headers:
|
|
77
|
-
if "X-Real-IP" in request.headers:
|
|
78
|
-
_http_info["client_host"] = request.headers.get("X-Real-IP")
|
|
79
|
-
elif "X-Forwarded-For" in request.headers:
|
|
80
|
-
_http_info["client_host"] = request.headers.get(
|
|
81
|
-
"X-Forwarded-For"
|
|
82
|
-
).split(",")[0]
|
|
83
|
-
_http_info["h_x_forwarded_for"] = request.headers.get("X-Forwarded-For")
|
|
84
|
-
|
|
85
|
-
if "X-Forwarded-Proto" in request.headers:
|
|
86
|
-
_http_info["request_proto"] = request.headers.get("X-Forwarded-Proto")
|
|
87
|
-
|
|
88
|
-
if "X-Forwarded-Host" in request.headers:
|
|
89
|
-
_http_info["request_host"] = request.headers.get("X-Forwarded-Host")
|
|
90
|
-
elif "Host" in request.headers:
|
|
91
|
-
_http_info["request_host"] = request.headers.get("Host")
|
|
92
|
-
|
|
93
|
-
if "X-Forwarded-Port" in request.headers:
|
|
94
|
-
try:
|
|
95
|
-
_http_info["request_port"] = int(
|
|
96
|
-
request.headers.get("X-Forwarded-Port")
|
|
97
|
-
)
|
|
98
|
-
except ValueError:
|
|
99
|
-
logger.warning(
|
|
100
|
-
f"`X-Forwarded-Port` header value '{request.headers.get('X-Forwarded-Port')}' is invalid, should be parseable to <int>!"
|
|
101
|
-
)
|
|
102
|
-
|
|
103
|
-
if "Via" in request.headers:
|
|
104
|
-
_http_info["h_via"] = request.headers.get("Via")
|
|
105
|
-
|
|
106
|
-
if self.has_cf_headers:
|
|
107
|
-
if "CF-Connecting-IP" in request.headers:
|
|
108
|
-
_http_info["client_host"] = request.headers.get("CF-Connecting-IP")
|
|
109
|
-
_http_info["h_cf_connecting_ip"] = request.headers.get(
|
|
110
|
-
"CF-Connecting-IP"
|
|
111
|
-
)
|
|
112
|
-
elif "True-Client-IP" in request.headers:
|
|
113
|
-
_http_info["client_host"] = request.headers.get("True-Client-IP")
|
|
114
|
-
_http_info["h_true_client_ip"] = request.headers.get(
|
|
115
|
-
"True-Client-IP"
|
|
116
|
-
)
|
|
117
|
-
|
|
118
|
-
if "CF-IPCountry" in request.headers:
|
|
119
|
-
_http_info["client_country"] = request.headers.get("CF-IPCountry")
|
|
120
|
-
_http_info["h_cf_ipcountry"] = request.headers.get("CF-IPCountry")
|
|
121
|
-
|
|
122
|
-
if "CF-RAY" in request.headers:
|
|
123
|
-
_http_info["h_cf_ray"] = request.headers.get("CF-RAY")
|
|
124
|
-
|
|
125
|
-
if "cf-ipcontinent" in request.headers:
|
|
126
|
-
_http_info["h_cf_ipcontinent"] = request.headers.get(
|
|
127
|
-
"cf-ipcontinent"
|
|
128
|
-
)
|
|
129
|
-
|
|
130
|
-
if "cf-ipcity" in request.headers:
|
|
131
|
-
_http_info["h_cf_ipcity"] = request.headers.get("cf-ipcity")
|
|
132
|
-
|
|
133
|
-
if "cf-iplongitude" in request.headers:
|
|
134
|
-
_http_info["h_cf_iplongitude"] = request.headers.get(
|
|
135
|
-
"cf-iplongitude"
|
|
136
|
-
)
|
|
137
|
-
|
|
138
|
-
if "cf-iplatitude" in request.headers:
|
|
139
|
-
_http_info["h_cf_iplatitude"] = request.headers.get("cf-iplatitude")
|
|
140
|
-
|
|
141
|
-
if "cf-postal-code" in request.headers:
|
|
142
|
-
_http_info["h_cf_postal_code"] = request.headers.get(
|
|
143
|
-
"cf-postal-code"
|
|
144
|
-
)
|
|
145
|
-
|
|
146
|
-
if "cf-timezone" in request.headers:
|
|
147
|
-
_http_info["h_cf_timezone"] = request.headers.get("cf-timezone")
|
|
148
|
-
|
|
149
|
-
_http_info["method"] = request.method
|
|
150
|
-
_http_info["url_path"] = request.url.path
|
|
151
|
-
if "{" in _http_info["url_path"]:
|
|
152
|
-
_http_info["url_path"] = _http_info["url_path"].replace("{", "{{")
|
|
153
|
-
|
|
154
|
-
if "}" in _http_info["url_path"]:
|
|
155
|
-
_http_info["url_path"] = _http_info["url_path"].replace("}", "}}")
|
|
156
|
-
|
|
157
|
-
if request.url.query:
|
|
158
|
-
_http_info["url_path"] = f"{request.url.path}?{request.url.query}"
|
|
159
|
-
|
|
160
|
-
_http_info["url_query_params"] = request.query_params._dict
|
|
161
|
-
_http_info["url_path_params"] = request.path_params
|
|
162
|
-
|
|
163
|
-
_http_info["h_referer"] = request.headers.get("Referer", "-")
|
|
164
|
-
_http_info["h_user_agent"] = request.headers.get("User-Agent", "-")
|
|
165
|
-
_http_info["h_accept"] = request.headers.get("Accept", "")
|
|
166
|
-
_http_info["h_content_type"] = request.headers.get("Content-Type", "")
|
|
167
|
-
|
|
168
|
-
if "Origin" in request.headers:
|
|
169
|
-
_http_info["h_origin"] = request.headers.get("Origin")
|
|
170
|
-
|
|
171
|
-
_http_info["user_id"] = "-"
|
|
172
|
-
if hasattr(request.state, "user_id"):
|
|
173
|
-
_http_info["user_id"] = str(request.state.user_id)
|
|
174
|
-
|
|
175
|
-
## Debug log:
|
|
176
|
-
if self.use_debug_log:
|
|
177
|
-
_debug_msg = self.debug_format.format(**_http_info)
|
|
178
|
-
|
|
179
|
-
# _logger.debug(_debug_msg)
|
|
180
|
-
await run_in_threadpool(
|
|
181
|
-
_logger.debug,
|
|
182
|
-
_debug_msg,
|
|
183
|
-
)
|
|
184
|
-
## Debug log
|
|
185
|
-
|
|
186
|
-
## Set http info to request state:
|
|
187
|
-
request.state.http_info = _http_info
|
|
188
|
-
|
|
189
|
-
_start_time = time.time()
|
|
190
|
-
## Process request:
|
|
191
|
-
response = await call_next(request)
|
|
192
|
-
## Response processed.
|
|
193
|
-
_http_info["response_time"] = round((time.time() - _start_time) * 1000, 1)
|
|
194
|
-
|
|
195
|
-
if "X-Process-Time" in response.headers:
|
|
196
|
-
try:
|
|
197
|
-
_http_info["response_time"] = float(
|
|
198
|
-
response.headers.get("X-Process-Time")
|
|
199
|
-
)
|
|
200
|
-
except ValueError:
|
|
201
|
-
logger.warning(
|
|
202
|
-
f"`X-Process-Time` header value '{response.headers.get('X-Process-Time')}' is invalid, should be parseable to <float>!"
|
|
203
|
-
)
|
|
204
|
-
else:
|
|
205
|
-
response.headers["X-Process-Time"] = str(_http_info["response_time"])
|
|
206
|
-
|
|
207
|
-
if "X-Request-ID" not in response.headers:
|
|
208
|
-
response.headers["X-Request-ID"] = _http_info["request_id"]
|
|
209
|
-
|
|
210
|
-
if hasattr(request.state, "user_id"):
|
|
211
|
-
_http_info["user_id"] = str(request.state.user_id)
|
|
212
|
-
|
|
213
|
-
_http_info["status_code"] = response.status_code
|
|
214
|
-
_http_info["content_length"] = 0
|
|
215
|
-
if "Content-Length" in response.headers:
|
|
216
|
-
try:
|
|
217
|
-
_http_info["content_length"] = int(
|
|
218
|
-
response.headers.get("Content-Length")
|
|
219
|
-
)
|
|
220
|
-
except ValueError:
|
|
221
|
-
logger.warning(
|
|
222
|
-
f"`Content-Length` header value '{response.headers.get('Content-Length')}' is invalid, should be parseable to <int>!"
|
|
223
|
-
)
|
|
224
|
-
|
|
225
|
-
try:
|
|
226
|
-
json.dumps(_http_info)
|
|
227
|
-
except TypeError:
|
|
228
|
-
logger.warning(
|
|
229
|
-
"Can not serialize `http_info` to json string in HttpAccessLogMiddleware!"
|
|
230
|
-
)
|
|
231
|
-
|
|
232
|
-
## Http access log:
|
|
233
|
-
_LEVEL = "INFO"
|
|
234
|
-
_msg_format = self.msg_format
|
|
235
|
-
if _http_info["status_code"] < 200:
|
|
236
|
-
_LEVEL = "DEBUG"
|
|
237
|
-
_msg_format = f'<d>{_msg_format.replace("{status_code}", "<n><b><k>{status_code}</k></b></n>")}</d>'
|
|
238
|
-
elif (200 <= _http_info["status_code"]) and (_http_info["status_code"] < 300):
|
|
239
|
-
_LEVEL = "SUCCESS"
|
|
240
|
-
_msg_format = f'<w>{_msg_format.replace("{status_code}", "<lvl>{status_code}</lvl>")}</w>'
|
|
241
|
-
elif (300 <= _http_info["status_code"]) and (_http_info["status_code"] < 400):
|
|
242
|
-
_LEVEL = "INFO"
|
|
243
|
-
_msg_format = f'<d>{_msg_format.replace("{status_code}", "<n><b><c>{status_code}</c></b></n>")}</d>'
|
|
244
|
-
elif (400 <= _http_info["status_code"]) and (_http_info["status_code"] < 500):
|
|
245
|
-
_LEVEL = "WARNING"
|
|
246
|
-
_msg_format = _msg_format.replace("{status_code}", "<r>{status_code}</r>")
|
|
247
|
-
elif 500 <= _http_info["status_code"]:
|
|
248
|
-
_LEVEL = "ERROR"
|
|
249
|
-
_msg_format = (
|
|
250
|
-
f'{_msg_format.replace("{status_code}", "<n>{status_code}</n>")}'
|
|
251
|
-
)
|
|
252
|
-
|
|
253
|
-
_msg = _msg_format.format(**_http_info)
|
|
254
|
-
# _logger.bind(http_info=_http_info).log(_LEVEL, _msg)
|
|
255
|
-
await run_in_threadpool(
|
|
256
|
-
_logger.bind(http_info=_http_info).log,
|
|
257
|
-
_LEVEL,
|
|
258
|
-
_msg,
|
|
259
|
-
)
|
|
260
|
-
## Http access log
|
|
261
|
-
|
|
262
|
-
return response
|