Skip to content

Commit 11de510

Browse files
committed
Replace database IDs with human-readable identifiers in log messages
Use org/project slugs and base32 build IDs instead of internal integer IDs in structured log keys for better operator traceability.
1 parent 0699e1c commit 11de510

File tree

7 files changed

+56
-21
lines changed

7 files changed

+56
-21
lines changed

src/docverse/dependencies/auth.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ async def __call__( # noqa: D102
9494
auth_service = context.factory.create_authorization_service()
9595
auth_result = await auth_service.require_role(
9696
org_id=org.id,
97+
org_slug=org_slug,
9798
username=username,
9899
groups=groups,
99100
minimum_role=self._min_role,

src/docverse/services/authorization.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ async def resolve_role(
3232
self,
3333
*,
3434
org_id: int,
35+
org_slug: str,
3536
username: str,
3637
groups: list[str],
3738
) -> AuthorizationResult | None:
@@ -40,15 +41,15 @@ async def resolve_role(
4041
self._logger.debug(
4142
"Super admin access granted via config",
4243
username=username,
43-
org_id=org_id,
44+
org=org_slug,
4445
)
4546
return AuthorizationResult(
4647
role=OrgRole.admin, basis=AuthBasis.super_admin
4748
)
4849
self._logger.debug(
4950
"User is not a super admin",
5051
username=username,
51-
org_id=org_id,
52+
org=org_slug,
5253
)
5354
result = await self._membership_store.resolve_role(
5455
org_id=org_id, username=username, groups=groups
@@ -66,6 +67,7 @@ async def require_role(
6667
self,
6768
*,
6869
org_id: int,
70+
org_slug: str,
6971
username: str,
7072
groups: list[str],
7173
minimum_role: OrgRole,
@@ -83,15 +85,18 @@ async def require_role(
8385
If the user does not have the required role.
8486
"""
8587
auth_result = await self.resolve_role(
86-
org_id=org_id, username=username, groups=groups
88+
org_id=org_id,
89+
org_slug=org_slug,
90+
username=username,
91+
groups=groups,
8792
)
8893
if auth_result is None or (
8994
ROLE_RANK[auth_result.role] < ROLE_RANK[minimum_role]
9095
):
9196
self._logger.warning(
9297
"Permission denied",
9398
username=username,
94-
org_id=org_id,
99+
org=org_slug,
95100
required=minimum_role.value,
96101
actual=auth_result.role.value if auth_result else None,
97102
)

src/docverse/services/build.py

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from safir.database import CountedPaginatedList
77

88
from docverse.client.models import BuildCreate, BuildStatus, JobKind
9-
from docverse.domain.base32id import validate_base32_id
9+
from docverse.domain.base32id import serialize_base32_id, validate_base32_id
1010
from docverse.domain.build import Build
1111
from docverse.domain.project import Project
1212
from docverse.domain.queue import QueueJob
@@ -88,8 +88,9 @@ async def create(
8888
)
8989
self._logger.info(
9090
"Created build",
91-
build_id=build.id,
92-
project_id=project.id,
91+
build=serialize_base32_id(build.public_id),
92+
org=org_slug,
93+
project=project_slug,
9394
git_ref=data.git_ref,
9495
)
9596
return build
@@ -121,15 +122,20 @@ async def signal_upload_complete(
121122
)
122123
self._logger.info(
123124
"Build upload complete, transitioning to processing",
124-
build_id=build.id,
125+
build=build_id,
126+
org=org_slug,
127+
project=project_slug,
125128
)
126129

127130
backend_job_id = await self._queue_backend.enqueue(
128131
"build_processing",
129132
{
130133
"org_id": project.org_id,
134+
"org_slug": org_slug,
131135
"project_id": project.id,
136+
"project_slug": project_slug,
132137
"build_id": build.id,
138+
"build_public_id": serialize_base32_id(build.public_id),
133139
},
134140
)
135141
queue_job = await self._queue_job_store.create(
@@ -178,15 +184,20 @@ async def complete(self, *, build_id: int) -> Build:
178184
build = await self._store.transition_status(
179185
build_id=build_id, new_status=BuildStatus.completed
180186
)
181-
self._logger.info("Build completed", build_id=build_id)
187+
self._logger.info(
188+
"Build completed",
189+
build=serialize_base32_id(build.public_id),
190+
)
182191
return build
183192

184193
async def fail(self, *, build_id: int) -> Build:
185194
"""Mark a build as failed."""
186195
build = await self._store.transition_status(
187196
build_id=build_id, new_status=BuildStatus.failed
188197
)
189-
self._logger.info("Build failed", build_id=build_id)
198+
self._logger.info(
199+
"Build failed", build=serialize_base32_id(build.public_id)
200+
)
190201
return build
191202

192203
async def soft_delete(
@@ -214,4 +225,9 @@ async def soft_delete(
214225
if not deleted:
215226
msg = f"Build {build_id!r} not found"
216227
raise NotFoundError(msg)
217-
self._logger.info("Soft-deleted build", build_id=build.id)
228+
self._logger.info(
229+
"Soft-deleted build",
230+
build=build_id,
231+
org=org_slug,
232+
project=project_slug,
233+
)

src/docverse/services/edition.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,8 @@ async def create(
6969
self._logger.info(
7070
"Created edition",
7171
slug=data.slug,
72-
project_id=project_id,
72+
org=org_slug,
73+
project=project_slug,
7374
)
7475
return edition
7576

@@ -138,7 +139,9 @@ async def update(
138139
if edition is None:
139140
msg = f"Edition {slug!r} not found"
140141
raise NotFoundError(msg)
141-
self._logger.info("Updated edition", slug=slug, project_id=project_id)
142+
self._logger.info(
143+
"Updated edition", slug=slug, org=org_slug, project=project_slug
144+
)
142145
return edition
143146

144147
async def set_current_build(
@@ -173,5 +176,8 @@ async def soft_delete(
173176
msg = f"Edition {slug!r} not found"
174177
raise NotFoundError(msg)
175178
self._logger.info(
176-
"Soft-deleted edition", slug=slug, project_id=project_id
179+
"Soft-deleted edition",
180+
slug=slug,
181+
org=org_slug,
182+
project=project_slug,
177183
)

src/docverse/services/project.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ async def create(self, *, org_slug: str, data: ProjectCreate) -> Project:
4848
msg = f"Project with slug {data.slug!r} already exists"
4949
raise ConflictError(msg)
5050
project = await self._store.create(org_id=org_id, data=data)
51-
self._logger.info("Created project", slug=data.slug, org_id=org_id)
51+
self._logger.info("Created project", slug=data.slug, org=org_slug)
5252
return project
5353

5454
async def get_by_slug(self, *, org_slug: str, slug: str) -> Project:
@@ -106,7 +106,7 @@ async def update(
106106
if project is None:
107107
msg = f"Project {slug!r} not found"
108108
raise NotFoundError(msg)
109-
self._logger.info("Updated project", slug=slug, org_id=org_id)
109+
self._logger.info("Updated project", slug=slug, org=org_slug)
110110
return project
111111

112112
async def soft_delete(self, *, org_slug: str, slug: str) -> None:
@@ -122,4 +122,4 @@ async def soft_delete(self, *, org_slug: str, slug: str) -> None:
122122
if not deleted:
123123
msg = f"Project {slug!r} not found"
124124
raise NotFoundError(msg)
125-
self._logger.info("Soft-deleted project", slug=slug, org_id=org_id)
125+
self._logger.info("Soft-deleted project", slug=slug, org=org_slug)

src/docverse/worker/functions/build_processing.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,14 @@ async def build_processing(
4848
"""
4949
logger = structlog.get_logger("docverse.worker.build_processing")
5050
org_id: int = payload["org_id"]
51-
project_id: int = payload["project_id"]
51+
org_slug: str = payload["org_slug"]
52+
project_slug: str = payload["project_slug"]
5253
build_id: int = payload["build_id"]
54+
build_public_id: str = payload["build_public_id"]
5355
logger = logger.bind(
54-
org_id=org_id,
55-
project_id=project_id,
56-
build_id=build_id,
56+
org=org_slug,
57+
project=project_slug,
58+
build=build_public_id,
5759
)
5860

5961
encryptor: CredentialEncryptor = ctx["encryptor"]

tests/services/authorization_test.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ async def test_require_role_sufficient(
5353
service = AuthorizationService(membership_store=store, logger=logger)
5454
result = await service.require_role(
5555
org_id=org_id,
56+
org_slug="auth-org",
5657
username="alice",
5758
groups=[],
5859
minimum_role=OrgRole.reader,
@@ -81,6 +82,7 @@ async def test_require_role_insufficient(
8182
with pytest.raises(PermissionDeniedError):
8283
await service.require_role(
8384
org_id=org_id,
85+
org_slug="auth-org",
8486
username="bob",
8587
groups=[],
8688
minimum_role=OrgRole.admin,
@@ -99,6 +101,7 @@ async def test_require_role_no_membership(
99101
with pytest.raises(PermissionDeniedError):
100102
await service.require_role(
101103
org_id=org_id,
104+
org_slug="auth-org",
102105
username="nobody",
103106
groups=[],
104107
minimum_role=OrgRole.reader,
@@ -121,6 +124,7 @@ async def test_superadmin_username_grants_admin(
121124
)
122125
result = await service.require_role(
123126
org_id=org_id,
127+
org_slug="auth-org",
124128
username="superadmin",
125129
groups=[],
126130
minimum_role=OrgRole.admin,
@@ -153,6 +157,7 @@ async def test_superadmin_username_overrides_lower_role(
153157
)
154158
result = await service.require_role(
155159
org_id=org_id,
160+
org_slug="auth-org",
156161
username="sa-reader",
157162
groups=[],
158163
minimum_role=OrgRole.admin,

0 commit comments

Comments
 (0)