Skip to content

Commit df1233c

Browse files
Feature/refactor middlewares (#1115)
* refactor middlewares * update from comments * update tests * log request after processing * update changelog * update logger name
1 parent a82e297 commit df1233c

File tree

4 files changed

+309
-85
lines changed

4 files changed

+309
-85
lines changed

CHANGES.md

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,80 @@
1212

1313
* add point value query on right-click to map viewer (@hrodmn, https://github.com/developmentseed/titiler/pull/1100)
1414

15+
* refactor middlewares to use python's dataclasses
16+
17+
* update `LoggerMiddleware` output format and options **breaking change**
18+
19+
```python
20+
from fastapi import FastAPI
21+
22+
from titiler.core.middlewares import LoggerMiddleware
23+
24+
# before
25+
app = FastAPI()
26+
app.add_middlewares(LoggerMiddleware, querystrings=True, headers=True)
27+
28+
# now
29+
app = FastAPI()
30+
app.add_middlewares(
31+
LoggerMiddleware,
32+
# custom Logger
33+
logger=logging.getLogger("mytiler.requests"), # default to logging.getLogger("titiler.requests")
34+
)
35+
```
36+
37+
Note: logger needs then to be `configured` at runtime. e.g :
38+
39+
```python
40+
from logging import config
41+
config.dictConfig(
42+
{
43+
"version": 1,
44+
"disable_existing_loggers": False,
45+
"formatters": {
46+
"detailed": {
47+
"format": "%(asctime)s - %(levelname)s - %(name)s - %(message)s"
48+
},
49+
"request": {
50+
"format": (
51+
"%(asctime)s - %(levelname)s - %(name)s - %(message)s "
52+
+ json.dumps(
53+
{
54+
k: f"%({k})s"
55+
for k in [
56+
"method",
57+
"referer",
58+
"origin",
59+
"route",
60+
"path",
61+
"path_params",
62+
"query_params",
63+
"headers",
64+
]
65+
}
66+
)
67+
),
68+
},
69+
},
70+
"handlers": {
71+
"console_request": {
72+
"class": "logging.StreamHandler",
73+
"level": "DEBUG",
74+
"formatter": "request",
75+
"stream": "ext://sys.stdout",
76+
},
77+
},
78+
"loggers": {
79+
"mytiler.requests": {
80+
"level": "INFO",
81+
"handlers": ["console_request"],
82+
"propagate": False,
83+
},
84+
},
85+
}
86+
)
87+
```
88+
1589
### titiler.xarray
1690

1791
* change `get_variable.drop_dim` parameter type from `str` to `List[str]` **breaking change**

src/titiler/application/titiler/application/main.py

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
"""titiler app."""
22

3+
import json
34
import logging
5+
from logging import config as log_config
46

57
import jinja2
68
import rasterio
@@ -201,9 +203,68 @@ def validate_access_token(access_token: str = Security(api_key_query)):
201203
)
202204

203205
if api_settings.debug:
204-
app.add_middleware(LoggerMiddleware, headers=True, querystrings=True)
206+
app.add_middleware(LoggerMiddleware)
205207
app.add_middleware(TotalTimeMiddleware)
206208

209+
log_config.dictConfig(
210+
{
211+
"version": 1,
212+
"disable_existing_loggers": False,
213+
"formatters": {
214+
"detailed": {
215+
"format": "%(asctime)s - %(levelname)s - %(name)s - %(message)s"
216+
},
217+
"request": {
218+
"format": (
219+
"%(asctime)s - %(levelname)s - %(name)s - %(message)s "
220+
+ json.dumps(
221+
{
222+
k: f"%({k})s"
223+
for k in [
224+
"method",
225+
"referer",
226+
"origin",
227+
"route",
228+
"path",
229+
"path_params",
230+
"query_params",
231+
"headers",
232+
]
233+
}
234+
)
235+
),
236+
},
237+
},
238+
"handlers": {
239+
"console_detailed": {
240+
"class": "logging.StreamHandler",
241+
"level": "WARNING",
242+
"formatter": "detailed",
243+
"stream": "ext://sys.stdout",
244+
},
245+
"console_request": {
246+
"class": "logging.StreamHandler",
247+
"level": "DEBUG",
248+
"formatter": "request",
249+
"stream": "ext://sys.stdout",
250+
},
251+
},
252+
"loggers": {
253+
"titlier": {
254+
"level": "WARNING",
255+
"handlers": ["console_detailed"],
256+
"propagate": False,
257+
},
258+
"titiler-requests": {
259+
"level": "INFO",
260+
"handlers": ["console_request"],
261+
"propagate": False,
262+
},
263+
},
264+
}
265+
)
266+
267+
207268
if api_settings.lower_case_query_parameters:
208269
app.add_middleware(LowerCaseQueryStringMiddleware)
209270

Lines changed: 105 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
"""Test titiler.core.middleware.TotalTimeMiddleware."""
22

3-
from fastapi import FastAPI
3+
import json
4+
import logging
5+
from logging import config
6+
7+
import pytest
8+
from fastapi import FastAPI, Path
49
from starlette.testclient import TestClient
510

611
from titiler.core.middleware import LoggerMiddleware
712

813

9-
def test_timing_middleware_exclude(caplog):
14+
def test_timing_middleware(caplog):
1015
"""Create App."""
1116
app = FastAPI()
1217

@@ -15,18 +20,103 @@ async def route1():
1520
"""route1."""
1621
return "Yo"
1722

18-
app.add_middleware(LoggerMiddleware, querystrings=True, headers=True)
23+
@app.get("/route2/{value}")
24+
async def route2(value: str = Path()):
25+
"""route2."""
26+
return value
27+
28+
@app.get("/route3/{value}")
29+
async def route3(value: str = Path()):
30+
"""route3."""
31+
raise Exception("something went wrong")
32+
33+
app.add_middleware(LoggerMiddleware)
34+
35+
config.dictConfig(
36+
{
37+
"version": 1,
38+
"disable_existing_loggers": False,
39+
"formatters": {
40+
"request": {
41+
"format": (
42+
"%(asctime)s - %(levelname)s - %(name)s - %(message)s "
43+
+ json.dumps(
44+
{
45+
k: f"%({k})s"
46+
for k in [
47+
"method",
48+
"referer",
49+
"origin",
50+
"route",
51+
"path",
52+
"path_params",
53+
"query_params",
54+
"headers",
55+
]
56+
}
57+
)
58+
),
59+
},
60+
},
61+
"handlers": {
62+
"console_request": {
63+
"class": "logging.StreamHandler",
64+
"level": "DEBUG",
65+
"formatter": "request",
66+
"stream": "ext://sys.stdout",
67+
},
68+
},
69+
"loggers": {
70+
"titiler.requests": {
71+
"level": "INFO",
72+
"handlers": ["console_request"],
73+
"propagate": True,
74+
},
75+
},
76+
}
77+
)
1978

2079
with TestClient(app) as client:
21-
caplog.clear()
22-
client.get("/route1")
23-
assert len([rec.message for rec in caplog.records]) == 2
24-
25-
caplog.clear()
26-
client.get("/route1", params={"hey": "yo"})
27-
assert len([rec.message for rec in caplog.records]) == 3
28-
29-
caplog.clear()
30-
client.get("/route1", params={"hey": "yo"}, headers={"accept-encoding": "gzip"})
31-
h = caplog.records[2].message
32-
assert "'accept-encoding': 'gzip'" in h
80+
with caplog.at_level(logging.INFO, logger="titiler.requests"):
81+
caplog.clear()
82+
client.get("/route1")
83+
log = caplog.records[0]
84+
assert log.name == "titiler.requests"
85+
assert log.levelname == "INFO"
86+
assert log.message == "Request received: /route1 GET"
87+
assert hasattr(log, "query_params")
88+
assert log.route == "/route1"
89+
90+
caplog.clear()
91+
client.get("/route1", params={"hey": "yo"})
92+
log = caplog.records[0]
93+
assert log.message == "Request received: /route1 GET"
94+
assert log.query_params == {"hey": "yo"}
95+
96+
caplog.clear()
97+
client.get(
98+
"/route1", params={"hey": "yo"}, headers={"accept-encoding": "gzip"}
99+
)
100+
log = caplog.records[0]
101+
assert log.message == "Request received: /route1 GET"
102+
assert log.query_params == {"hey": "yo"}
103+
assert log.headers["accept-encoding"] == "gzip"
104+
105+
caplog.clear()
106+
client.get("/route2/val")
107+
log = caplog.records[0]
108+
assert log.name == "titiler.requests"
109+
assert log.levelname == "INFO"
110+
assert log.message == "Request received: /route2/val GET"
111+
assert hasattr(log, "query_params")
112+
assert log.route == "/route2/{value}"
113+
114+
caplog.clear()
115+
with pytest.raises(Exception): # noqa: B017
116+
client.get("/route3/val")
117+
log = caplog.records[0]
118+
assert log.name == "titiler.requests"
119+
assert log.levelname == "INFO"
120+
assert log.message == "Request received: /route3/val GET"
121+
assert hasattr(log, "query_params")
122+
assert log.route == "/route3/{value}"

0 commit comments

Comments
 (0)