Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ jobs:
- name: Ruff format check
run: uv run ruff format --check .

- name: RAG001 — logging policy check
run: uv run python scripts/check_logging.py

- name: Mypy (strict)
run: uv run mypy packages/ apps/gateway/

Expand Down
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

| File | Description |
|------|-------------|
| [logging-policy.md](guides/logging-policy.md) | RAG001 policy: structured-logging requirement, allowlist, and how to extend it |
| [ragctl-quickstart.md](guides/ragctl-quickstart.md) | Five-minute tour of the `ragctl` CLI |

## adr/
Expand Down
64 changes: 64 additions & 0 deletions docs/guides/logging-policy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Logging policy (RAG001)

## Overview

Every log record emitted by AgentContextOS must conform to the 7-field JSON
schema enforced by `rag_observability.logging`. To guarantee this, the project
bans direct use of `logging.getLogger()` in application code — a pre-commit
hook (`scripts/check_logging.py`) called **RAG001** fails the build if it is
called outside an explicit allowlist.

## Usage

Use the structured logger in all new code:

```python
from rag_observability.logging import get_logger

_log = get_logger(__name__)
_log.info("event.name", extra={"rag_tenant_id": tenant_id, ...})
```

`rag_core.logging` re-exports the same symbols as a backwards-compatibility
shim — either import path is fine.

Run the check manually:

```bash
task lint:logging # via Taskfile
uv run python scripts/check_logging.py # direct
```

The hook also runs automatically through pre-commit on every staged commit,
and via the `lint-test` job in [.github/workflows/ci.yml](../../.github/workflows/ci.yml)
on every push and pull request — a non-zero exit fails the build on
ubuntu-22.04, macos-14, and windows-latest.

## Internals

The check scans every tracked `.py` file under the repo root with the regex
`\blogging\.getLogger\s*\(` and reports each match as a `RAG001` violation.
Files in `_SKIP_DIRS` (`.venv`, `__pycache__`, `.git`, `.mypy_cache`,
`.ruff_cache`, `node_modules`) are skipped wholesale. Files in `_ALLOWLIST`
are permitted to call `logging.getLogger()` directly.

### Allowlist

| Path | Reason |
|------|--------|
| `packages/observability/src/rag_observability/logging.py` | The structured-logger implementation itself. |
| `packages/core/src/rag_core/logging.py` | Backwards-compat shim that re-exports the observability logger. |
| `packages/core/src/rag_core/audit.py` | Cannot import the structured logger without creating a circular import: `rag_observability` depends on `rag_core.types`, so any path from `rag_core.audit` → `rag_observability` → `rag_core` is a cycle. |
| `tests/logs/conftest.py` | Test fixtures that intentionally manipulate the `rag` logger hierarchy to assert schema conformance. |
| `scripts/check_logging.py` | This script — its pattern string contains the banned call as a literal. |

## Extension points

If you need to add a new allowlisted file, prefer fixing the underlying cause
instead — a circular import almost always points to a layering mistake, and
test scaffolding can usually live under `tests/logs/conftest.py`. When a new
entry is genuinely required:

1. Add the path to `_ALLOWLIST` in [scripts/check_logging.py](../../scripts/check_logging.py).
2. Add a one-line comment above the entry explaining why the exception is necessary.
3. Update the allowlist table above in the same PR.
4 changes: 4 additions & 0 deletions scripts/check_logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@
Path("packages/observability/src/rag_observability/logging.py"),
# Shim re-export — delegates to rag_observability.logging.
Path("packages/core/src/rag_core/logging.py"),
# rag_core.audit cannot import rag_observability.logging (or the
# rag_core.logging shim that re-exports it) because rag_observability
# depends on rag_core.types — doing so would create a circular import.
Path("packages/core/src/rag_core/audit.py"),
# Test infrastructure that intentionally manipulates the rag logger hierarchy.
Path("tests/logs/conftest.py"),
# This script — pattern string contains the banned call as a literal.
Expand Down
Loading