You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Treat established keys as compatibility-sensitive — downstream users may build dashboards and alerts on them. Change deliberately.
225
+
226
+
#### Key naming rules
227
+
228
+
- `snake_case`, not dotted; `vcs_` prefix
229
+
- Prefer stable scalars; avoid ad-hoc objects
230
+
- Heavy keys (`vcs_stdout`, `vcs_stderr`) are DEBUG-only; consider companion `vcs_stdout_len` fields or hard truncation (e.g. `stdout[:100]`)
231
+
232
+
#### Lazy formatting
233
+
234
+
`logger.debug("msg %s", val)` not f-strings. Two rationales:
235
+
- Deferred string interpolation: skipped entirely when level is filtered
236
+
- Aggregator message template grouping: `"Running %s"`is one signature grouped ×10,000; f-strings make each line unique
237
+
238
+
When computing `val` itself is expensive, guard with `if logger.isEnabledFor(logging.DEBUG)`.
239
+
240
+
#### stacklevel for wrappers
241
+
242
+
Increment for each wrapper layer so `%(filename)s:%(lineno)d` and OTel `code.filepath` point to the real caller. Verify whenever call depth changes.
243
+
244
+
#### LoggerAdapter for persistent context
245
+
246
+
For objects with stable identity (Repository, Remote, Sync), use `LoggerAdapter` to avoid repeating the same `extra` on every call. Lead with the portable pattern (override `process()` to merge); `merge_extra=True` simplifies this on Python 3.13+.
| `ERROR` | Failures that stop an operation | VCS command failed, invalid URL |
256
+
257
+
Config discovery noise belongs in `DEBUG`; only surprising/user-actionable config issues → `WARNING`.
258
+
259
+
#### Message style
260
+
261
+
- Lowercase, past tense for events: `"repository cloned"`, `"vcs command failed"`
262
+
- No trailing punctuation
263
+
- Keep messages short; put details in `extra`, not the message string
264
+
265
+
#### Exception logging
266
+
267
+
- Use `logger.exception()` only inside `except` blocks when you are **not** re-raising
268
+
- Use `logger.error(..., exc_info=True)` when you need the traceback outside an `except` block
269
+
- Avoid `logger.exception()` followed by `raise` — this duplicates the traceback. Either add context via `extra` that would otherwise be lost, or let the exception propagate
270
+
271
+
#### Testing logs
272
+
273
+
Assert on `caplog.records` attributes, not string matching on `caplog.text`:
- Filter records rather than index by position: `[r for r in caplog.records if hasattr(r, "vcs_cmd")]`
276
+
- Assert on schema: `record.vcs_exit_code == 0`not `"exit code 0" in caplog.text`
277
+
- `caplog.record_tuples`cannot access extra fields — always use `caplog.records`
278
+
279
+
#### Avoid
280
+
281
+
- f-strings/`.format()` in log calls
282
+
- Unguarded logging in hot loops (guard with `isEnabledFor()`)
283
+
- Catch-log-reraise without adding new context
284
+
- `print()`for diagnostics
285
+
- Logging secret env var values (log key names only)
286
+
- Non-scalar ad-hoc objects in `extra`
287
+
- Requiring custom `extra` fields in format strings without safe defaults (missing keys raise `KeyError`)
288
+
192
289
### Testing
193
290
194
291
**Use functional tests only**: Write tests as standalone functions (`test_*`), not classes. Avoid `class TestFoo:` groupings - use descriptive function names and file organization instead. This applies to pytest tests, not doctests.
0 commit comments