Skip to content

Commit 48eaca0

Browse files
authored
feat(Option): Logfire 🔥 (#89)
1 parent a3b7528 commit 48eaca0

11 files changed

Lines changed: 83 additions & 7 deletions

File tree

‎docker-compose.yaml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ services:
1313
- "5432:5432"
1414
restart: always
1515
healthcheck:
16-
test: pg_isready -U forge
16+
test: pg_isready -U postgres
1717
interval: 2s
1818
timeout: 3s
1919
retries: 40

‎fastapi_forge/example-projects/game_zone.yaml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ project:
77
use_rabbitmq: true
88
use_taskiq: true
99
use_prometheus: true
10+
use_logfire: true
1011

1112
custom_enums:
1213
- name: UserRole

‎fastapi_forge/frontend/panels/project_config_panel.py‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,13 +206,18 @@ def _build(self) -> None:
206206
)
207207

208208
with ui.column().classes("w-full gap-2"):
209-
ui.label("Metrics").classes("text-lg font-bold")
209+
ui.label("Observability").classes("text-lg font-bold")
210210
self.use_prometheus = (
211211
ui.checkbox("Prometheus", value=state.use_prometheus)
212212
.classes("w-full")
213213
.bind_value_from(state, "use_prometheus")
214214
.tooltip("Collect and query metrics with Prometheus")
215215
)
216+
self.use_logfire = (
217+
ui.checkbox("Logfire", value=state.use_logfire)
218+
.classes("w-full")
219+
.bind_value_from(state, "use_logfire")
220+
)
216221

217222
with ui.column().classes("w-full gap-2"):
218223
ui.label("Object Storage").classes("text-lg font-bold")
@@ -247,6 +252,7 @@ def _bind_state_to_ui(self) -> None:
247252
)
248253
self.use_taskiq.bind_value_to(state, "use_taskiq")
249254
self.use_prometheus.bind_value_to(state, "use_prometheus")
255+
self.use_logfire.bind_value_to(state, "use_logfire")
250256

251257
def _update_taskiq_state(self, *_) -> None:
252258
"""Enable or disable Taskiq based on Redis and RabbitMQ."""
@@ -422,6 +428,7 @@ async def _create_project(self) -> None:
422428
state.use_rabbitmq = self.use_rabbitmq.value
423429
state.use_taskiq = self.use_taskiq.value
424430
state.use_prometheus = self.use_prometheus.value
431+
state.use_logfire = self.use_logfire.value
425432

426433
project_spec = state.get_project_spec()
427434
await build_fastapi_project(project_spec)

‎fastapi_forge/frontend/state.py‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ class ProjectState(BaseModel):
5555
use_rabbitmq: bool = False
5656
use_taskiq: bool = False
5757
use_prometheus: bool = False
58+
use_logfire: bool = False
5859

5960
def get_render_manager(self) -> RenderManager:
6061
"""Get the render manager for the current project."""
@@ -88,6 +89,7 @@ def initialize_from_project(self, project: ProjectSpec) -> None:
8889
self.use_rabbitmq = project.use_rabbitmq
8990
self.use_taskiq = project.use_taskiq
9091
self.use_prometheus = project.use_prometheus
92+
self.use_logfire = project.use_logfire
9193
self.models = project.models.copy()
9294
self.custom_enums = project.custom_enums.copy()
9395

@@ -203,6 +205,7 @@ def get_project_spec(self) -> ProjectSpec:
203205
use_rabbitmq=self.use_rabbitmq,
204206
use_taskiq=self.use_taskiq,
205207
use_prometheus=self.use_prometheus,
208+
use_logfire=self.use_logfire,
206209
models=self.models,
207210
custom_enums=self.custom_enums,
208211
)

‎fastapi_forge/schemas.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,7 @@ class ProjectSpec(_Base):
414414
use_rabbitmq: bool = False
415415
use_taskiq: bool = False
416416
use_prometheus: bool = False
417+
use_logfire: bool = False
417418
models: list[Model] = []
418419
custom_enums: list[CustomEnum] = []
419420

‎fastapi_forge/template/cookiecutter.json‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@
2323
"use_prometheus": {
2424
"default": true
2525
},
26+
"use_logfire": {
27+
"default": true
28+
},
2629
"models": {
2730
"models": []
2831
},

‎fastapi_forge/template/{{cookiecutter.project_name}}/.env.example‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,8 @@
3232
{% endif %}
3333
{%- if cookiecutter.use_prometheus %}
3434
{{ cookiecutter.project_name|upper|replace('-', '_') }}_PROMETHEUS_ENABLED=True
35-
{% endif %}
35+
{% endif %}
36+
{%- if cookiecutter.use_logfire %}
37+
{{ cookiecutter.project_name|upper|replace('-', '_') }}_LOGFIRE_ENABLED=True
38+
{{ cookiecutter.project_name|upper|replace('-', '_') }}_LOGFIRE_WRITE_TOKEN=""
39+
{% endif %}

‎fastapi_forge/template/{{cookiecutter.project_name}}/forge-config.yaml‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ paths:
3939
paths:
4040
- observability/prometheus
4141

42+
use_logfire:
43+
enabled: {{cookiecutter.use_logfire | lower}}
44+
paths:
45+
- observability/logfire
46+
4247
constants:
4348
requires_all:
4449
- use_builtin_auth
@@ -49,6 +54,7 @@ paths:
4954
observability:
5055
requires_all:
5156
- use_prometheus
57+
- use_logfire
5258
paths:
5359
- observability
5460

‎fastapi_forge/template/{{cookiecutter.project_name}}/pyproject.toml‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ dependencies = [
4747
{%- if cookiecutter.use_prometheus -%}
4848
"prometheus-fastapi-instrumentator>=7.1.0",
4949
{%- endif %}
50+
{%- if cookiecutter.use_logfire -%}
51+
"logfire[aio-pika,asyncpg,fastapi,httpx,sqlalchemy,system-metrics]>=3.16.0",
52+
{%- endif %}
5053
]
5154

5255
[tool.pytest.ini_options]

‎fastapi_forge/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/middleware.py‎

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,50 @@
44
{% if cookiecutter.use_prometheus %}
55
from prometheus_fastapi_instrumentator import Instrumentator
66
{% endif %}
7+
{% if cookiecutter.use_logfire %}
8+
import logfire
9+
from loguru import logger
10+
{% endif %}
711

812
def _add_cors_middleware(app: FastAPI) -> None:
913
"""Add CORS Middleware."""
1014
app.add_middleware(CORSMiddleware, allow_origins=["*"])
1115
{% if cookiecutter.use_prometheus %}
1216
def _add_prometheus_middleware(app: FastAPI) -> None:
1317
"""Add Prometheus Middleware."""
14-
if settings.prometheus.enabled:
15-
instrumenter = Instrumentator().instrument(app)
16-
instrumenter.expose(app)
18+
if not settings.prometheus.enabled:
19+
return
20+
instrumenter = Instrumentator().instrument(app)
21+
instrumenter.expose(app)
22+
{% endif %}
23+
{% if cookiecutter.use_logfire %}
24+
def _add_logfire_middleware(app: FastAPI) -> None:
25+
"""Add Logfire Middleware."""
26+
if not settings.logfire.enabled:
27+
return
28+
if not settings.logfire.write_token:
29+
logger.warning(
30+
"Logfire is enabled but no write token is provided. "
31+
"Skipping Logfire middleware."
32+
)
33+
return
34+
logfire.configure(
35+
token=settings.logfire.write_token.get_secret_value(),
36+
environment=settings.env,
37+
send_to_logfire="if-token-present",
38+
service_name="game_zone",
39+
)
40+
logfire.instrument_fastapi(app, capture_headers=True)
41+
logfire.instrument_asyncpg()
42+
logfire.instrument_system_metrics()
43+
logger.configure(handlers=[logfire.loguru_handler()])
1744
{% endif %}
1845
def add_middleware(app: FastAPI) -> None:
1946
"""Add all middlewares."""
2047
_add_cors_middleware(app)
2148
{%- if cookiecutter.use_prometheus %}
2249
_add_prometheus_middleware(app)
50+
{% endif %}
51+
{%- if cookiecutter.use_logfire %}
52+
_add_logfire_middleware(app)
2353
{% endif %}

0 commit comments

Comments
 (0)