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
Copy file name to clipboardExpand all lines: AGENTS.md
+96Lines changed: 96 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -114,6 +114,102 @@ tests/test_cli.py
114
114
- Ruff is the source of truth for lint rules; see `pyproject.toml` for enabled checks (E, F, I, UP, A, B, C4, COM, EM, Q, PTH, SIM, TRY, PERF, RUF, D, FA100).
115
115
- Type checking is strict (`mypy --strict`); favor precise types and avoid `Any` unless necessary.
116
116
117
+
## Logging Standards
118
+
119
+
These rules guide future logging changes; existing code may not yet conform.
120
+
121
+
### Logger setup
122
+
123
+
- Use `logging.getLogger(__name__)` in every module
124
+
- Add `NullHandler` in library `__init__.py` files
125
+
- Never configure handlers, levels, or formatters in library code — that's the application's job
126
+
127
+
### Structured context via `extra`
128
+
129
+
Pass structured data on every log call where useful for filtering, searching, or test assertions.
130
+
131
+
**Core keys** (stable, scalar, safe at any log level):
Treat established keys as compatibility-sensitive — downstream users may build dashboards and alerts on them. Change deliberately.
149
+
150
+
### Key naming rules
151
+
152
+
-`snake_case`, not dotted; `vcs_` prefix
153
+
- Prefer stable scalars; avoid ad-hoc objects
154
+
- Heavy keys (`vcs_stdout`, `vcs_stderr`) are DEBUG-only; consider companion `vcs_stdout_len` fields or hard truncation (e.g. `stdout[:100]`)
155
+
156
+
### Lazy formatting
157
+
158
+
`logger.debug("msg %s", val)` not f-strings. Two rationales:
159
+
- Deferred string interpolation: skipped entirely when level is filtered
160
+
- Aggregator message template grouping: `"Running %s"` is one signature grouped ×10,000; f-strings make each line unique
161
+
162
+
When computing `val` itself is expensive, guard with `if logger.isEnabledFor(logging.DEBUG)`.
163
+
164
+
### stacklevel for wrappers
165
+
166
+
Increment for each wrapper layer so `%(filename)s:%(lineno)d` and OTel `code.filepath` point to the real caller. Verify whenever call depth changes.
167
+
168
+
### LoggerAdapter for persistent context
169
+
170
+
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 |
180
+
181
+
Config discovery noise belongs in `DEBUG`; only surprising/user-actionable config issues → `WARNING`.
182
+
183
+
### Message style
184
+
185
+
- Lowercase, past tense for events: `"repository cloned"`, `"vcs command failed"`
186
+
- No trailing punctuation
187
+
- Keep messages short; put details in `extra`, not the message string
188
+
189
+
### Exception logging
190
+
191
+
- Use `logger.exception()` only inside `except` blocks when you are **not** re-raising
192
+
- Use `logger.error(..., exc_info=True)` when you need the traceback outside an `except` block
193
+
- 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
194
+
195
+
### Testing logs
196
+
197
+
Assert on `caplog.records` attributes, not string matching on `caplog.text`:
0 commit comments