fastapi-augment 0.1.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.
Files changed (50) hide show
  1. fastapi_augment/__init__.py +24 -0
  2. fastapi_augment/common/__init__.py +61 -0
  3. fastapi_augment/common/constants.py +26 -0
  4. fastapi_augment/common/exception_handlers.py +178 -0
  5. fastapi_augment/common/exceptions.py +162 -0
  6. fastapi_augment/common/utils/__init__.py +5 -0
  7. fastapi_augment/common/utils/strings.py +175 -0
  8. fastapi_augment/config/__init__.py +8 -0
  9. fastapi_augment/config/settings.py +104 -0
  10. fastapi_augment/db/__init__.py +5 -0
  11. fastapi_augment/db/sqlalchemy/__init__.py +20 -0
  12. fastapi_augment/db/sqlalchemy/alembic/__init__.py +5 -0
  13. fastapi_augment/db/sqlalchemy/alembic/env.py +141 -0
  14. fastapi_augment/db/sqlalchemy/base.py +9 -0
  15. fastapi_augment/db/sqlalchemy/crud_base.py +426 -0
  16. fastapi_augment/db/sqlalchemy/engine.py +238 -0
  17. fastapi_augment/db/sqlalchemy/migrate.py +356 -0
  18. fastapi_augment/db/sqlalchemy/mixins/__init__.py +18 -0
  19. fastapi_augment/db/sqlalchemy/mixins/audit.py +61 -0
  20. fastapi_augment/db/sqlalchemy/mixins/soft_delete.py +80 -0
  21. fastapi_augment/db/sqlalchemy/mixins/timestamp.py +48 -0
  22. fastapi_augment/db/sqlalchemy/model_base.py +47 -0
  23. fastapi_augment/db/sqlalchemy/session.py +160 -0
  24. fastapi_augment/factory.py +238 -0
  25. fastapi_augment/health/__init__.py +34 -0
  26. fastapi_augment/health/checker.py +101 -0
  27. fastapi_augment/health/checkers.py +109 -0
  28. fastapi_augment/health/router.py +87 -0
  29. fastapi_augment/lifespan.py +450 -0
  30. fastapi_augment/log/__init__.py +26 -0
  31. fastapi_augment/log/config.py +201 -0
  32. fastapi_augment/log/factory.py +32 -0
  33. fastapi_augment/log/filters.py +23 -0
  34. fastapi_augment/log/handlers.py +81 -0
  35. fastapi_augment/middlewares/__init__.py +20 -0
  36. fastapi_augment/middlewares/base.py +79 -0
  37. fastapi_augment/middlewares/request_id.py +82 -0
  38. fastapi_augment/openapi.py +110 -0
  39. fastapi_augment/py.typed +0 -0
  40. fastapi_augment/schemas/__init__.py +29 -0
  41. fastapi_augment/schemas/base.py +32 -0
  42. fastapi_augment/schemas/pagination.py +46 -0
  43. fastapi_augment/schemas/request.py +28 -0
  44. fastapi_augment/schemas/response.py +139 -0
  45. fastapi_augment/schemas/types.py +11 -0
  46. fastapi_augment-0.1.0.dist-info/METADATA +654 -0
  47. fastapi_augment-0.1.0.dist-info/RECORD +50 -0
  48. fastapi_augment-0.1.0.dist-info/WHEEL +5 -0
  49. fastapi_augment-0.1.0.dist-info/entry_points.txt +2 -0
  50. fastapi_augment-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,87 @@
1
+ """
2
+ @Author : zarkhan
3
+ @CreateDate : 2026/9/6
4
+ @Description: 健康检查路由工厂
5
+ - 提供 create_health_router() 创建可配置的健康检查路由
6
+ - 自动注册 AppChecker,可选注册 DatabaseChecker 和自定义检查器
7
+ """
8
+ from __future__ import annotations
9
+
10
+ from typing import Sequence
11
+
12
+ from fastapi import APIRouter, Request, Response
13
+
14
+ from .checker import BaseChecker, HealthResponse, _worst_status, STATUS_HEALTHY, STATUS_UNHEALTHY
15
+ from .checkers import AppChecker, DatabaseChecker
16
+
17
+
18
+ def create_health_router(
19
+ *,
20
+ path: str = '/health',
21
+ tags: list[str] | None = None,
22
+ include_db_check: bool = True,
23
+ extra_checkers: Sequence[BaseChecker] | None = None,
24
+ ) -> APIRouter:
25
+ """创建健康检查路由。
26
+
27
+ 默认包含 ``AppChecker``(应用状态),可选 ``DatabaseChecker``(数据库连通性),
28
+ 以及任意自定义检查器::
29
+
30
+ from fastapi_augment.health import create_health_router
31
+
32
+ # 最简用法:仅应用状态
33
+ app.include_router(create_health_router(include_db_check=False))
34
+
35
+ # 完整用法:应用 + 数据库 + 自定义检查器
36
+ app.include_router(create_health_router(
37
+ extra_checkers=[RedisChecker()],
38
+ ))
39
+
40
+ Args:
41
+ path: 健康检查端点路径
42
+ tags: OpenAPI 标签
43
+ include_db_check: 是否包含数据库连通性检查(需要 app.state.engine_manager)
44
+ extra_checkers: 额外的自定义检查器列表
45
+
46
+ Returns:
47
+ 配置好的健康检查 APIRouter
48
+ """
49
+ router = APIRouter(tags=tags or ['Health'])
50
+
51
+ # 组装检查器列表
52
+ checkers: list[BaseChecker] = [AppChecker()]
53
+ if include_db_check:
54
+ checkers.append(DatabaseChecker())
55
+ if extra_checkers:
56
+ checkers.extend(extra_checkers)
57
+
58
+ @router.get(path, response_model=HealthResponse)
59
+ async def health_check(request: Request, response: Response) -> HealthResponse:
60
+ """执行所有健康检查并返回聚合结果。
61
+
62
+ 任一检查器返回 unhealthy 时,HTTP 状态码为 503。
63
+ """
64
+ results = []
65
+ for checker in checkers:
66
+ try:
67
+ result = await checker.check(request.app)
68
+ except Exception as e:
69
+ # 检查器自身异常,兜底为 unhealthy
70
+ from .checker import CheckResult
71
+ result = CheckResult(
72
+ name=checker.name,
73
+ status=STATUS_UNHEALTHY,
74
+ details={'error': str(e)},
75
+ )
76
+ results.append(result)
77
+
78
+ # 总体状态取最差
79
+ overall = _worst_status(*(r.status for r in results)) if results else STATUS_HEALTHY
80
+
81
+ # 不健康时返回 503
82
+ if overall == STATUS_UNHEALTHY:
83
+ response.status_code = 503
84
+
85
+ return HealthResponse(status=overall, checks=results)
86
+
87
+ return router
@@ -0,0 +1,450 @@
1
+ """
2
+ @Author : hangu
3
+ @CreateDate : 2026/9/3
4
+ @Description : 应用生命周期钩子管理 (完整版)
5
+ - core_registry 始终在首位执行(基础/核心钩子)
6
+ - 支持多个自定义注册表,通过 app.state.registries 注入
7
+ - 每个注册表独立管理优先级、超时、异常策略
8
+ """
9
+ import asyncio
10
+ from contextlib import asynccontextmanager
11
+ from dataclasses import dataclass
12
+ from inspect import signature, iscoroutinefunction
13
+ from logging import getLogger, Logger
14
+ from typing import (
15
+ runtime_checkable,
16
+ Protocol,
17
+ overload,
18
+ Callable,
19
+ AsyncGenerator,
20
+ Sequence,
21
+ )
22
+
23
+ from fastapi import FastAPI
24
+
25
+ _logger = getLogger(__name__)
26
+
27
+ # -------------------------- 常量定义 --------------------------
28
+ DEFAULT_PRIORITY: int = 50
29
+ STARTUP_ABORT_ON_EXCEPTION: bool = True
30
+ SHUTDOWN_ABORT_ON_EXCEPTION: bool = False
31
+
32
+
33
+ # ---------- 类型定义 ----------
34
+ @runtime_checkable
35
+ class _NoArgHook(Protocol):
36
+ """无参数钩子协议,用于类型检查"""
37
+
38
+ async def __call__(self) -> None: ...
39
+
40
+
41
+ @runtime_checkable
42
+ class _AppArgHook(Protocol):
43
+ """带 FastAPI 应用实例参数的钩子协议"""
44
+
45
+ async def __call__(self, app: FastAPI) -> None: ...
46
+
47
+
48
+ HookFunc = _NoArgHook | _AppArgHook
49
+
50
+
51
+ @dataclass(frozen=True, slots=True)
52
+ class _HookItem:
53
+ """钩子条目,存储单个钩子的元数据"""
54
+ func: HookFunc
55
+ priority: int
56
+ abort_on_exception: bool
57
+ needs_app: bool
58
+ timeout: int | float | None = None
59
+
60
+
61
+ # ---------- 注册表 ----------
62
+ class HookRegistry:
63
+ """生命周期钩子注册表。
64
+
65
+ 每个实例独立管理自己的钩子列表,支持:
66
+ - 注册启动/关闭钩子
67
+ - 优先级控制(启动降序,关闭升序)
68
+ - 超时控制
69
+ - 异常时是否终止执行
70
+ - 装饰器语法
71
+ - 去重(基于函数 id)
72
+
73
+ Attributes:
74
+ _startup_hooks (list): 启动钩子列表
75
+ _shutdown_hooks (list): 关闭钩子列表
76
+ _startup_seen (set): 已注册的启动钩子函数 id 集合
77
+ _shutdown_seen (set): 已注册的关闭钩子函数 id 集合
78
+ _logger: 实例日志器
79
+ """
80
+
81
+ __slots__ = ('_startup_hooks', '_shutdown_hooks', '_startup_seen', '_shutdown_seen', '_logger')
82
+
83
+ def __init__(self, logger: Logger | None = None) -> None:
84
+ """初始化一个新的注册表实例,所有钩子列表为空"""
85
+ self._startup_hooks: list[_HookItem] = []
86
+ self._shutdown_hooks: list[_HookItem] = []
87
+ self._startup_seen: set[int] = set()
88
+ self._shutdown_seen: set[int] = set()
89
+ self._logger = logger or _logger
90
+
91
+ def register_startup(
92
+ self,
93
+ func: HookFunc,
94
+ priority: int = DEFAULT_PRIORITY,
95
+ abort_on_exception: bool = STARTUP_ABORT_ON_EXCEPTION,
96
+ timeout: int | float | None = None,
97
+ ) -> None:
98
+ """注册启动钩子。
99
+
100
+ Args:
101
+ func: 钩子函数
102
+ priority: 优先级(越大越先执行)
103
+ abort_on_exception: 异常时是否终止启动过程
104
+ timeout: 超时秒数(None 表示不限制)
105
+ """
106
+ self._register(
107
+ self._startup_hooks,
108
+ self._startup_seen,
109
+ func,
110
+ priority,
111
+ abort_on_exception,
112
+ timeout,
113
+ reverse_sort=True,
114
+ )
115
+
116
+ def register_shutdown(
117
+ self,
118
+ func: HookFunc,
119
+ priority: int = DEFAULT_PRIORITY,
120
+ abort_on_exception: bool = SHUTDOWN_ABORT_ON_EXCEPTION,
121
+ timeout: int | float | None = None,
122
+ ) -> None:
123
+ """注册关闭钩子。
124
+
125
+ Args:
126
+ func: 钩子函数
127
+ priority: 优先级(越大越先执行)
128
+ abort_on_exception: 异常时是否终止关闭过程
129
+ timeout: 超时秒数(None 表示不限制)
130
+ """
131
+ self._register(
132
+ self._shutdown_hooks,
133
+ self._shutdown_seen,
134
+ func,
135
+ priority,
136
+ abort_on_exception,
137
+ timeout,
138
+ reverse_sort=False,
139
+ )
140
+
141
+ @overload
142
+ def on_startup(self, func: HookFunc, /) -> HookFunc: ...
143
+
144
+ @overload
145
+ def on_startup(
146
+ self,
147
+ *,
148
+ priority: int = DEFAULT_PRIORITY,
149
+ abort_on_exception: bool = STARTUP_ABORT_ON_EXCEPTION,
150
+ timeout: int | float | None = None,
151
+ ) -> Callable[[HookFunc], HookFunc]: ...
152
+
153
+ def on_startup(
154
+ self,
155
+ func: HookFunc | None = None,
156
+ *,
157
+ priority: int = DEFAULT_PRIORITY,
158
+ abort_on_exception: bool = STARTUP_ABORT_ON_EXCEPTION,
159
+ timeout: int | float | None = None,
160
+ ) -> Callable[[HookFunc], HookFunc] | HookFunc:
161
+ """启动钩子装饰器。
162
+
163
+ 可带参数调用:@registry.on_startup(priority=100)
164
+ 或不带参数:@registry.on_startup
165
+
166
+ Args:
167
+ func: 被装饰的函数
168
+ priority: 优先级(越大越先执行)
169
+ abort_on_exception: 异常时是否终止
170
+ timeout: 超时秒数
171
+
172
+ Returns:
173
+ 装饰器或原函数(取决于调用方式)
174
+ """
175
+
176
+ def decorator(fn: HookFunc) -> HookFunc:
177
+ self.register_startup(fn, priority, abort_on_exception, timeout)
178
+ return fn
179
+
180
+ return decorator(func) if func else decorator
181
+
182
+ @overload
183
+ def on_shutdown(self, func: HookFunc, /) -> HookFunc: ...
184
+
185
+ @overload
186
+ def on_shutdown(
187
+ self,
188
+ *,
189
+ priority: int = DEFAULT_PRIORITY,
190
+ abort_on_exception: bool = SHUTDOWN_ABORT_ON_EXCEPTION,
191
+ timeout: int | float | None = None,
192
+ ) -> Callable[[HookFunc], HookFunc]: ...
193
+
194
+ def on_shutdown(
195
+ self,
196
+ func: HookFunc | None = None,
197
+ *,
198
+ priority: int = DEFAULT_PRIORITY,
199
+ abort_on_exception: bool = SHUTDOWN_ABORT_ON_EXCEPTION,
200
+ timeout: int | float | None = None,
201
+ ) -> Callable[[HookFunc], HookFunc] | HookFunc:
202
+ """关闭钩子装饰器。
203
+
204
+ 可带参数调用:@registry.on_shutdown(priority=100)
205
+ 或不带参数:@registry.on_shutdown
206
+
207
+ Args:
208
+ func: 被装饰的函数
209
+ priority: 优先级(越大越先执行)
210
+ abort_on_exception: 异常时是否终止
211
+ timeout: 超时秒数
212
+
213
+ Returns:
214
+ 装饰器或原函数(取决于调用方式)
215
+ """
216
+
217
+ def decorator(fn: HookFunc) -> HookFunc:
218
+ self.register_shutdown(fn, priority, abort_on_exception, timeout)
219
+ return fn
220
+
221
+ return decorator(func) if func else decorator
222
+
223
+ async def run_startup(self, app: FastAPI) -> None:
224
+ """执行所有启动钩子。
225
+
226
+ Args:
227
+ app: FastAPI 应用实例
228
+ """
229
+ if self._startup_hooks:
230
+ self._logger.info(f'正在执行 {len(self._startup_hooks)} 个启动钩子 (注册表 {id(self)})...')
231
+ await self._run_hooks(self._startup_hooks, app)
232
+ self._logger.info('启动钩子执行完成')
233
+
234
+ async def run_shutdown(self, app: FastAPI) -> None:
235
+ """执行所有关闭钩子。
236
+
237
+ Args:
238
+ app: FastAPI 应用实例
239
+ """
240
+ if self._shutdown_hooks:
241
+ self._logger.info(f'正在执行 {len(self._shutdown_hooks)} 个关闭钩子 (注册表 {id(self)})...')
242
+ await self._run_hooks(self._shutdown_hooks, app)
243
+ self._logger.info('关闭钩子执行完成')
244
+
245
+ def clear(self) -> None:
246
+ """清空当前注册表中的所有钩子(包括启动和关闭)。
247
+
248
+ 此方法会清空所有钩子列表和去重集合,用于测试环境隔离。
249
+ """
250
+ self._startup_hooks.clear()
251
+ self._shutdown_hooks.clear()
252
+ self._startup_seen.clear()
253
+ self._shutdown_seen.clear()
254
+
255
+ def list_startup_hooks(self) -> list[str]:
256
+ """返回按执行顺序排列的启动钩子描述列表。
257
+
258
+ Returns:
259
+ 钩子描述字符串列表,顺序即为执行顺序。
260
+ """
261
+ return [self._format_hook(item) for item in self._startup_hooks]
262
+
263
+ def list_shutdown_hooks(self) -> list[str]:
264
+ """返回按执行顺序排列的关闭钩子描述列表。
265
+
266
+ Returns:
267
+ 钩子描述字符串列表,顺序即为执行顺序。
268
+ """
269
+ return [self._format_hook(item) for item in self._shutdown_hooks]
270
+
271
+ def _register(
272
+ self,
273
+ hooks: list[_HookItem],
274
+ seen: set[int],
275
+ func: HookFunc,
276
+ priority: int,
277
+ abort_on_exception: bool,
278
+ timeout: int | float | None,
279
+ reverse_sort: bool,
280
+ ) -> None:
281
+ """通用注册方法,供启动/关闭钩子复用。
282
+
283
+ Args:
284
+ hooks: 目标钩子列表(启动或关闭)
285
+ seen: 已注册函数 id 集合(用于去重)
286
+ func: 待注册的钩子函数
287
+ priority: 执行优先级
288
+ abort_on_exception: 异常时是否终止执行
289
+ timeout: 超时秒数
290
+ reverse_sort: 排序方向(True 降序,False 升序)
291
+ """
292
+ func_id = id(func)
293
+ if func_id in seen:
294
+ self._logger.debug(f'钩子 {func.__name__} 已注册,跳过重复注册')
295
+ return
296
+ seen.add(func_id)
297
+
298
+ # 校验必须是异步函数
299
+ if not iscoroutinefunction(func):
300
+ raise TypeError(f'生命周期钩子 `{func.__name__}` 必须是async异步函数')
301
+
302
+ try:
303
+ sig = signature(func)
304
+ needs_app = len(sig.parameters) > 0
305
+ except ValueError:
306
+ # 无法解析签名,保守认为需要app参数,避免静默错误
307
+ needs_app = True
308
+
309
+ item = _HookItem(func, priority, abort_on_exception, needs_app, timeout)
310
+ hooks.append(item)
311
+ hooks.sort(key=lambda x: x.priority, reverse=reverse_sort)
312
+
313
+ async def _run_hooks(self, hooks: list[_HookItem], app: FastAPI) -> None:
314
+ """按序执行给定的钩子列表。
315
+
316
+ Args:
317
+ hooks: 钩子条目列表
318
+ app: FastAPI 应用实例
319
+
320
+ Raises:
321
+ RuntimeError: 当钩子超时或异常且 abort_on_exception=True 时抛出
322
+ """
323
+ for item in hooks:
324
+ name = item.func.__name__
325
+ timeout: int | float | None = item.timeout
326
+ try:
327
+ coro = item.func(app) if item.needs_app else item.func()
328
+ if timeout is not None:
329
+ await asyncio.wait_for(coro, timeout=timeout)
330
+ else:
331
+ await coro
332
+ except asyncio.TimeoutError:
333
+ self._logger.error(
334
+ f'[生命周期钩子执行超时] {name}: 超过 {timeout}s',
335
+ exc_info=False
336
+ )
337
+ if item.abort_on_exception:
338
+ raise RuntimeError(f'[启动/关闭终止:钩子 {name} 执行超时]')
339
+ except Exception as e:
340
+ self._logger.error(f'[生命周期钩子执行失败] {name}', exc_info=True)
341
+ if item.abort_on_exception:
342
+ raise RuntimeError(f'[启动/关闭终止:钩子 {name} 异常]') from e
343
+
344
+ @staticmethod
345
+ def _format_hook(item: _HookItem) -> str:
346
+ """将单个钩子条目格式化为可读的字符串。
347
+
348
+ Args:
349
+ item: 钩子条目
350
+
351
+ Returns:
352
+ 格式化后的字符串,如 'func_name(priority=50, abort=True, timeout=None)'
353
+ """
354
+ if item.timeout is not None:
355
+ timeout_str = f'{item.timeout}s'
356
+ else:
357
+ timeout_str = 'None'
358
+ return (
359
+ f'{item.func.__name__}(priority={item.priority}, '
360
+ f'abort={item.abort_on_exception}, timeout={timeout_str})'
361
+ )
362
+
363
+
364
+ # ---------- 核心注册表(始终优先执行) ----------
365
+ core_registry = HookRegistry()
366
+
367
+
368
+ # ---------- FastAPI 生命周期 ----------
369
+ @asynccontextmanager
370
+ async def fastapi_lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
371
+ """FastAPI 生命周期管理,强制 core_registry 在首位执行。
372
+
373
+ 从 app.state.registries 读取用户自定义注册表列表(可选),
374
+ 并确保 core_registry 始终在列表最前面,以保证核心钩子优先执行。
375
+
376
+ 启动时,按列表顺序执行每个注册表的 startup 钩子;
377
+ 关闭时,按逆序执行 shutdown 钩子,保证资源释放顺序符合依赖关系。
378
+
379
+ Example:
380
+ app = FastAPI(lifespan=fastapi_lifespan)
381
+ app.state.registries = [db_registry, cache_registry]
382
+ # core_registry 会自动插入到列表首位
383
+
384
+ Args:
385
+ app: FastAPI 应用实例
386
+
387
+ Yields:
388
+ None
389
+
390
+ Raises:
391
+ TypeError: 当 app.state.registries 的类型不是 HookRegistry 或列表时
392
+ """
393
+ registries = getattr(app.state, 'registries', None)
394
+
395
+ # 规范化输入
396
+ if registries is None:
397
+ registries = []
398
+ elif isinstance(registries, HookRegistry):
399
+ registries = [registries]
400
+ elif isinstance(registries, Sequence):
401
+ registries = list(registries)
402
+ else:
403
+ raise TypeError(
404
+ f'app.state.registries 必须为 HookRegistry 或列表,实际为 {type(registries).__name__}'
405
+ )
406
+
407
+ # 过滤掉列表中非HookRegistry实例,防止异常
408
+ registries = [r for r in registries if isinstance(r, HookRegistry)]
409
+
410
+ # 去重(保留首次出现顺序)
411
+ seen = set[int]()
412
+ unique = []
413
+ for reg in registries:
414
+ rid = id(reg)
415
+ if rid not in seen:
416
+ seen.add(rid)
417
+ unique.append(reg)
418
+ registries = unique
419
+
420
+ # ---- 强制 core_registry 在首位 ----
421
+ core_id = id(core_registry)
422
+ if core_id not in seen:
423
+ registries.insert(0, core_registry)
424
+ else:
425
+ # 如果已在列表中,移除原有位置再插入首位(保持其他顺序不变)
426
+ registries = [reg for reg in registries if id(reg) != core_id]
427
+ registries.insert(0, core_registry)
428
+
429
+ try:
430
+ # 执行启动钩子(按列表顺序)
431
+ for reg in registries:
432
+ await reg.run_startup(app)
433
+ yield # 应用运行期间
434
+ finally:
435
+ # 无论启动阶段是否报错,都执行关闭钩子,防止资源泄漏
436
+ for reg in reversed(registries):
437
+ await reg.run_shutdown(app)
438
+
439
+
440
+ # ---------- 测试辅助 ----------
441
+ def clear_hooks(registry: HookRegistry | None = None) -> None:
442
+ """清空注册表钩子,默认清空 core_registry(用于测试隔离)。
443
+
444
+ Args:
445
+ registry: 需要清空的注册表实例;不传默认 core_registry
446
+ """
447
+ if registry is None:
448
+ core_registry.clear()
449
+ else:
450
+ registry.clear()
@@ -0,0 +1,26 @@
1
+ """
2
+ @Author : zarkhan
3
+ @CreateDate : 2026/9/6
4
+ @Description: 日志模块——request_id注入、uvicorn接管、多进程安全轮转、一键配置
5
+ """
6
+ from .config import NORMAL_FORMAT, set_log_format, set_log_level, setup_logger
7
+ from .filters import UvicornNameRewriteFilter
8
+ from .handlers import (
9
+ MonthlyRotatingFileHandler,
10
+ MultiProcessTimedRotatingFileHandler,
11
+ YearlyRotatingFileHandler,
12
+ )
13
+
14
+ __all__ = [
15
+ # config
16
+ 'NORMAL_FORMAT',
17
+ 'setup_logger',
18
+ 'set_log_level',
19
+ 'set_log_format',
20
+ # filters
21
+ 'UvicornNameRewriteFilter',
22
+ # handlers
23
+ 'MultiProcessTimedRotatingFileHandler',
24
+ 'MonthlyRotatingFileHandler',
25
+ 'YearlyRotatingFileHandler',
26
+ ]