Skip to content

Commit aeef40e

Browse files
committed
ai(rules[AGENTS]): 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 AGENTS.md - Define doctest_/sphinx_ prefixed key vocabulary - Document lazy formatting, stacklevel patterns - Specify log levels, message style, exception logging rules - Add testing guidance (caplog.records over caplog.text)
1 parent 6995b93 commit aeef40e

1 file changed

Lines changed: 80 additions & 0 deletions

File tree

AGENTS.md

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,86 @@ True
262262
3. Keep doctests simple and focused on demonstrating usage
263263
4. Add blank lines between test sections for improved readability
264264

265+
### Logging Standards
266+
267+
These rules guide future logging changes; existing code may not yet conform.
268+
269+
#### Logger setup
270+
271+
- Use `logging.getLogger(__name__)` in every module
272+
- Add `NullHandler` in library `__init__.py` files
273+
- Never configure handlers, levels, or formatters in library code — that's the application's job
274+
275+
#### Structured context via `extra`
276+
277+
Pass structured data on every log call where useful for filtering, searching, or test assertions.
278+
279+
**Core keys** (stable, scalar, safe at any log level):
280+
281+
| Key | Type | Context |
282+
|-----|------|---------|
283+
| `doctest_source_file` | `str` | doctest source path (.rst, .md, .py) |
284+
| `doctest_block_type` | `str` | block type (doctest_block, code fence) |
285+
| `sphinx_extension` | `str` | Sphinx extension name |
286+
287+
Treat established keys as compatibility-sensitive — downstream users may build dashboards and alerts on them. Change deliberately.
288+
289+
#### Key naming rules
290+
291+
- `snake_case`, not dotted; project-specific prefixes (`doctest_`, `sphinx_`)
292+
- Prefer stable scalars; avoid ad-hoc objects
293+
294+
#### Lazy formatting
295+
296+
`logger.debug("msg %s", val)` not f-strings. Two rationales:
297+
- Deferred string interpolation: skipped entirely when level is filtered
298+
- Aggregator message template grouping: `"Running %s"` is one signature grouped ×10,000; f-strings make each line unique
299+
300+
When computing `val` itself is expensive, guard with `if logger.isEnabledFor(logging.DEBUG)`.
301+
302+
#### stacklevel for wrappers
303+
304+
Increment for each wrapper layer so `%(filename)s:%(lineno)d` and OTel `code.filepath` point to the real caller. Verify whenever call depth changes.
305+
306+
#### Log levels
307+
308+
| Level | Use for | Examples |
309+
|-------|---------|----------|
310+
| `DEBUG` | Internal mechanics | Doctest parsing, node traversal steps |
311+
| `INFO` | Lifecycle, user-visible operations | Extension loaded, document processed |
312+
| `WARNING` | Recoverable issues, deprecation | Deprecated directive, missing optional dependency |
313+
| `ERROR` | Failures that stop an operation | Parse error, invalid configuration |
314+
315+
#### Message style
316+
317+
- Lowercase, past tense for events: `"extension loaded"`, `"parse error"`
318+
- No trailing punctuation
319+
- Keep messages short; put details in `extra`, not the message string
320+
321+
#### Exception logging
322+
323+
- Use `logger.exception()` only inside `except` blocks when you are **not** re-raising
324+
- Use `logger.error(..., exc_info=True)` when you need the traceback outside an `except` block
325+
- 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
326+
327+
#### Testing logs
328+
329+
Assert on `caplog.records` attributes, not string matching on `caplog.text`:
330+
- Scope capture: `caplog.at_level(logging.DEBUG, logger="doctest_docutils")`
331+
- Filter records rather than index by position: `[r for r in caplog.records if hasattr(r, "doctest_source_file")]`
332+
- Assert on schema: `record.sphinx_extension == "doctest_docutils"` not `"doctest_docutils" in caplog.text`
333+
- `caplog.record_tuples` cannot access extra fields — always use `caplog.records`
334+
335+
#### Avoid
336+
337+
- f-strings/`.format()` in log calls
338+
- Unguarded logging in hot loops (guard with `isEnabledFor()`)
339+
- Catch-log-reraise without adding new context
340+
- `print()` for diagnostics
341+
- Logging secret env var values (log key names only)
342+
- Non-scalar ad-hoc objects in `extra`
343+
- Requiring custom `extra` fields in format strings without safe defaults (missing keys raise `KeyError`)
344+
265345
### Git Commit Standards
266346

267347
See `.cursor/rules/git-commits.mdc` for detailed commit message standards.

0 commit comments

Comments
 (0)