# format-config-extension

## Context

Stogger hardcodes all timestamps to ISO 8601 with microsecond precision. The existing `SimpleFormatSettings` dataclass is dead config — unreachable from pyproject.toml or public API, with documented values that were never implemented. The broader config layer uses manual `_load_config()` parsing without validation. This spec addresses both: timestamp format configuration and a proper attrs-based config layer.

## Decisions

### config-layer-attrs

#### Context

`StoggerConfig` uses a manual `__init__` with `_load_config()` that parses TOML, merges kwargs, and extracts ~20 fields via `config.get(key, default)`. No validation, no type safety. `SimpleFormatSettings` is a separate dataclass that is never constructed from config — always defaults.

#### Decision

Migrate `StoggerConfig` to `attrs` classes with proper converters and validators. `FormatConfig` is a nested attrs class for `[tool.stogger.format]`, following the existing `[tool.stogger.ast]` precedent. `SimpleFormatSettings` is deleted — its living fields migrate to `FormatConfig`.

#### Alternatives

a. Only new `FormatConfig` as attrs, rest stays manual — rejected, half-cleaned state.
b. Pydantic instead of attrs — rejected, heavy runtime dependency for a 2-dep library.
c. attrs + cattrs — rejected, cattrs is overkill for simple TOML-to-attrs mapping.
d. Keep dataclasses with `__post_init__` validation — rejected, reimplements attrs validators.

#### Consequences

`attrs` becomes a new runtime dependency. Config loading gains type safety and validation. `SimpleFormatSettings` dies. One-time migration effort for `StoggerConfig` (~160 lines).

### format-config-fields

#### Context

`SimpleFormatSettings` has 6 fields. Three are never used from config (`show_logger_brackets`, `show_pid`, `timestamp_format`). Three are functional (`min_level`, `show_code_info`, `pad_event_width`). A new `timestamp_precision` field is needed.

#### Decision

`FormatConfig` (attrs class) contains:

| Field | Type | Default | Origin |
|---|---|---|---|
| `timestamp_precision` | `str` | `"iso_seconds"` | New |
| `min_level` | `str` | `"info"` | From SimpleFormatSettings |
| `show_code_info` | `bool` | `False` | From SimpleFormatSettings |
| `pad_event_width` | `int` | `30` | From SimpleFormatSettings |

Dead fields (`show_logger_brackets`, `show_pid`, `timestamp_format`) are not migrated.

#### Alternatives

a. Only `timestamp_precision` in FormatConfig, keep SimpleFormatSettings — rejected, defeats cleanup purpose.
b. All 6 SimpleFormatSettings fields migrated — rejected, includes dead fields.
c. No FormatConfig, fields live in StoggerConfig flat — rejected, loses sub-section structure.

#### Consequences

`ConsoleFileRenderer` receives `FormatConfig` instead of `SimpleFormatSettings`. Four fields total. Config TOML uses `[tool.stogger.format]` section.

### timestamp-precision-values

#### Context

structlog's `TimeStamper` accepts `fmt="iso"` (with µs), arbitrary strftime strings, or `fmt=None` (unix float). The feature needs four format variants with zero validation on invalid input (GIGO).

#### Decision

Four supported values:

| Value | TimeStamper fmt | Output example | Description |
|---|---|---|---|
| `"iso"` | `"iso"` | `2026-05-02T12:34:56.123456Z` | Full precision with µs |
| `"iso_seconds"` | `"%Y-%m-%dT%H:%M:%SZ"` | `2026-05-02T12:34:56Z` | Seconds only |
| `"iso_no_z"` | `"%Y-%m-%dT%H:%M:%S"` | `2026-05-02T12:34:56` | Seconds, no Z suffix |
| `"relative"` | `"iso"` (kept for pipeline) | `+2.341s` | Renderer computes delta from process start |

Default changes from `"iso"` to `"iso_seconds"` — breaking change, no deprecation. Invalid values pass through to structlog/strftime (GIGO).

#### Alternatives

a. Free-form strftime with "iso" shortcut — rejected, ugly in TOML, validation complexity.
b. Two values only (full/seconds) — rejected, drops iso_no_z and relative.
c. Validation with error on invalid value — rejected, user wants GIGO.

#### Consequences

Breaking change: existing log output loses microseconds unless user opts into `"iso"`. Three ISO variants handled by TimeStamper fmt parameter. Relative handled by renderer.

### pipeline-approach

#### Context

Three separate `TimeStamper` call sites exist (factory.py:41, core.py:507, core.py:582), all hardcoded to `fmt="iso"`. The renderer's `_format_timestamp` only strips Z for `"iso_no_z"`, passes everything else through.

#### Decision

ISO variants (`iso`, `iso_seconds`, `iso_no_z`): `TimeStamper` receives the appropriate `fmt` parameter. Renderer wraps in ANSI colors, no further transformation needed.

Relative format: `TimeStamper` keeps `fmt="iso"` (unchanged). Renderer computes `time.time() - format_config._process_start` and formats as `+X.XXXs`. The `FormatConfig` stores `_process_start: float = time.time()` at instantiation.

#### Alternatives

a. TimeStamper always `fmt=None` (unix float), custom processor formats everything — rejected, reimplements TimeStamper.
b. TimeStamper unchanged, renderer strips µs/Z from ISO strings — rejected, wasteful string manipulation.
c. Custom TimestampProcessor replaces TimeStamper — rejected, maintains own timestamp logic.

#### Consequences

Minimal pipeline change for ISO variants (just fmt parameter). Relative needs renderer change and process-start tracking. No new processors added.

### call-site-unification

#### Context

Three TimeStamper instantiations with inconsistent parameters: `init_logging()` uses `utc=False`, the other two use `utc=True`. One omits `key="timestamp"`.

#### Decision

Central `build_timestamp_processor(config)` function creates `TimeStamper` with the correct `fmt` from `config.format.timestamp_precision`. All three call sites use this function. All sites normalized to `utc=True`.

#### Alternatives

a. Config passed to each site, each creates own TimeStamper — rejected, code duplication.
b. Only fix factory.py and init_early_logging, leave init_logging — rejected, leaves inconsistency.
c. Keep all three hardcoded, renderer handles formatting — rejected, renderer becomes complex.

#### Consequences

Single source of truth for timestamp configuration. `init_logging()` gains `utc=True` (was `utc=False`). Three code paths reduced to one factory function.

### test-strategy

#### Context

Zero tests exist for timestamp config flowing from TOML through to renderer. Existing tests cover `ConsoleFileRenderer` with direct `SimpleFormatSettings` construction.

#### Decision

TDD approach. Spec-validation tests in `tests/impl_spec/test_format_config_extension.py` (xfail, garbage-collected after passing). Permanent decision tests in `tests/test_core.py` and `tests/test_config.py`. Tests cover:

- TOML `[tool.stogger.format]` loading → FormatConfig field values
- Each `timestamp_precision` value produces correct TimeStamper fmt
- Renderer output for all four format values
- Relative format shows elapsed time
- GIGO: invalid value produces output without crash
- Default is `"iso_seconds"` when no config present

#### Alternatives

a. Tests-after — rejected, no spec validation traceability.
b. Only extend existing tests — rejected, no config-flow coverage.
c. No automated tests — rejected, config pipeline is critical path.

#### Consequences

Full test coverage for config flow. Bidirectional traceability: ADR references test file paths. Spec-validation tests verify design decisions before implementation.

## Verified By

<!-- Tests will be added after implementation -->
