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.
@@ -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