Skip to content

Commit f87164e

Browse files
committed
ai(claude[rules]): Add logging standard conventions
why: Establish structured logging conventions, enabling OTel interop, pytest assertions on extra fields, and aggregator-friendly message templates. what: - Add Logging Standards section to CLAUDE.md - Define vcs_ prefixed key vocabulary (core + heavy/optional) - Document lazy formatting, stacklevel, LoggerAdapter patterns - Specify log levels, message style, exception logging rules - Add testing guidance (caplog.records over caplog.text)
1 parent 17e0585 commit f87164e

1 file changed

Lines changed: 96 additions & 0 deletions

File tree

AGENTS.md

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,102 @@ type
255255
'Cloning into ...'
256256
```
257257

258+
### Logging Standards
259+
260+
These rules guide future logging changes; existing code may not yet conform.
261+
262+
#### Logger setup
263+
264+
- Use `logging.getLogger(__name__)` in every module
265+
- Add `NullHandler` in library `__init__.py` files
266+
- Never configure handlers, levels, or formatters in library code — that's the application's job
267+
268+
#### Structured context via `extra`
269+
270+
Pass structured data on every log call where useful for filtering, searching, or test assertions.
271+
272+
**Core keys** (stable, scalar, safe at any log level):
273+
274+
| Key | Type | Context |
275+
|-----|------|---------|
276+
| `vcs_cmd` | `str` | VCS command line |
277+
| `vcs_type` | `str` | VCS type (git, svn, hg) |
278+
| `vcs_url` | `str` | repository URL |
279+
| `vcs_exit_code` | `int` | VCS process exit code |
280+
| `vcs_repo_path` | `str` | local repository path |
281+
282+
**Heavy/optional keys** (DEBUG only, potentially large):
283+
284+
| Key | Type | Context |
285+
|-----|------|---------|
286+
| `vcs_stdout` | `list[str]` | VCS stdout lines (truncate or cap; `%(vcs_stdout)s` produces repr) |
287+
| `vcs_stderr` | `list[str]` | VCS stderr lines (same caveats) |
288+
289+
Treat established keys as compatibility-sensitive — downstream users may build dashboards and alerts on them. Change deliberately.
290+
291+
#### Key naming rules
292+
293+
- `snake_case`, not dotted; `vcs_` prefix
294+
- Prefer stable scalars; avoid ad-hoc objects
295+
- Heavy keys (`vcs_stdout`, `vcs_stderr`) are DEBUG-only; consider companion `vcs_stdout_len` fields or hard truncation (e.g. `stdout[:100]`)
296+
297+
#### Lazy formatting
298+
299+
`logger.debug("msg %s", val)` not f-strings. Two rationales:
300+
- Deferred string interpolation: skipped entirely when level is filtered
301+
- Aggregator message template grouping: `"Running %s"` is one signature grouped ×10,000; f-strings make each line unique
302+
303+
When computing `val` itself is expensive, guard with `if logger.isEnabledFor(logging.DEBUG)`.
304+
305+
#### stacklevel for wrappers
306+
307+
Increment for each wrapper layer so `%(filename)s:%(lineno)d` and OTel `code.filepath` point to the real caller. Verify whenever call depth changes.
308+
309+
#### LoggerAdapter for persistent context
310+
311+
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+.
312+
313+
#### Log levels
314+
315+
| Level | Use for | Examples |
316+
|-------|---------|----------|
317+
| `DEBUG` | Internal mechanics, VCS I/O | VCS command + stdout, URL parsing steps |
318+
| `INFO` | Repository lifecycle, user-visible operations | Repository cloned, sync completed |
319+
| `WARNING` | Recoverable issues, deprecation, user-actionable config | Deprecated VCS option, unrecognized remote |
320+
| `ERROR` | Failures that stop an operation | VCS command failed, invalid URL |
321+
322+
Config discovery noise belongs in `DEBUG`; only surprising/user-actionable config issues → `WARNING`.
323+
324+
#### Message style
325+
326+
- Lowercase, past tense for events: `"repository cloned"`, `"vcs command failed"`
327+
- No trailing punctuation
328+
- Keep messages short; put details in `extra`, not the message string
329+
330+
#### Exception logging
331+
332+
- Use `logger.exception()` only inside `except` blocks when you are **not** re-raising
333+
- Use `logger.error(..., exc_info=True)` when you need the traceback outside an `except` block
334+
- 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
335+
336+
#### Testing logs
337+
338+
Assert on `caplog.records` attributes, not string matching on `caplog.text`:
339+
- Scope capture: `caplog.at_level(logging.DEBUG, logger="libvcs.cmd")`
340+
- Filter records rather than index by position: `[r for r in caplog.records if hasattr(r, "vcs_cmd")]`
341+
- Assert on schema: `record.vcs_exit_code == 0` not `"exit code 0" in caplog.text`
342+
- `caplog.record_tuples` cannot access extra fields — always use `caplog.records`
343+
344+
#### Avoid
345+
346+
- f-strings/`.format()` in log calls
347+
- Unguarded logging in hot loops (guard with `isEnabledFor()`)
348+
- Catch-log-reraise without adding new context
349+
- `print()` for diagnostics
350+
- Logging secret env var values (log key names only)
351+
- Non-scalar ad-hoc objects in `extra`
352+
- Requiring custom `extra` fields in format strings without safe defaults (missing keys raise `KeyError`)
353+
258354
### Git Commit Standards
259355

260356
Format commit messages as:

0 commit comments

Comments
 (0)