# Story 10.5: Simplified File Sink Configuration

**Epic**: Epic 10 - Developer Experience & Ergonomics Improvements
**Status**: Complete
**Priority**: High (Phase 2: API Improvements)
**Estimated Complexity**: Low (2-3 days)

---

## User Story

**As a** developer,
**I want** to configure file rotation with human-readable strings and minimal boilerplate,
**So that** I don't need to import configs, calculate bytes, or understand the full Settings model for basic file logging.

---

## Business Value

### Current Pain Points:

1. Configuring file rotation requires importing `RotatingFileSinkConfig`, `Path`
2. Must calculate bytes manually (10485760 instead of "10 MB")
3. Settings use `directory` + `filename_prefix` instead of simple path
4. Minimum 8-12 lines of imports and config for basic file logging
5. No convenience functions for common sink types

### After This Story:

**Before (Current)**:

```python
from fapilog import get_logger
from fapilog.plugins.sinks.rotating_file import RotatingFileSink, RotatingFileSinkConfig
from pathlib import Path

config = RotatingFileSinkConfig(
    directory=Path("logs"),
    filename_prefix="app",
    max_bytes=10485760,  # Need to calculate bytes
    max_files=7,
    compress_rotated=True,
)
sink = RotatingFileSink(config)
logger = get_logger(sinks=[sink])
```

**After (Story 10.5)**:

```python
from fapilog import get_logger
from fapilog.sinks import rotating_file

logger = get_logger(sinks=[
    rotating_file("logs/app.log", rotation="10 MB", retention=7, compression=True)
])
```

**Or using Settings with human-readable strings**:

```python
from fapilog import get_logger, Settings

settings = Settings()
settings.file.directory = "logs"
settings.file.max_bytes = "10 MB"  # Story 10.4 parser
settings.file.max_files = 7
settings.file.compress_rotated = True

logger = get_logger(settings=settings)
```

**Lines Reduced**: 12 → 4 (67% reduction)
**Cognitive Load**: High (imports, config objects, Path) → Low (simple function call)
**Discovery**: Hidden in plugins → Top-level import `fapilog.sinks`

---

## Scope

### In Scope:

- Convenience function `rotating_file()` for easy sink creation
- Apply Story 10.4 parsers to `RotatingFileSettings` fields (already planned)
- Top-level module `fapilog.sinks` with common sink factories
- Support simple path strings (parse to directory + prefix internally)
- Human-readable rotation/retention via Story 10.4 parsers

### Out of Scope (Deferred):

- Runtime sink addition/removal (`add_file()`, `remove_sink()`) - **Not needed**
- Time-based rotation - **Story 10.6**
- Other sink convenience functions (`webhook()`, `cloudwatch()`) - **Future stories**
- Sink introspection/management - **Not needed for logging library**

### Dependencies:

- **Story 10.4**: Human-Readable Config Strings (provides parsers)
- **Existing**: `get_logger(sinks=[...])` parameter (already works)
- **Existing**: RotatingFileSink implementation

---

## API Design Decision

### Decision 1: Convenience Function Location

**Options Considered:**

**Option A: Top-level module `fapilog.sinks`**

```python
from fapilog.sinks import rotating_file
logger = get_logger(sinks=[rotating_file("app.log")])
```

**Option B: Nested in plugins**

```python
from fapilog.plugins.sinks import rotating_file
logger = get_logger(sinks=[rotating_file("app.log")])
```

**Option C: Direct in fapilog**

```python
from fapilog import get_logger, rotating_file
logger = get_logger(sinks=[rotating_file("app.log")])
```

**Decision**: **Option A** (`fapilog.sinks` module)

**Rationale**:

1. **Discoverability**: `fapilog.sinks` is intuitive and IDE-friendly
2. **Namespace separation**: Keeps main `fapilog` namespace clean
3. **Future expansion**: Easy to add `webhook()`, `cloudwatch()`, etc.
4. **Not too deep**: One level (`fapilog.sinks`), not buried in `plugins.sinks`
5. **Matches pattern**: Similar to `fapilog.core`, `fapilog.plugins`

**Trade-offs**:

- ✅ Clean, discoverable import
- ✅ Room for future sink factories
- ⚠️ New module to create (but simple)

---

### Decision 2: Path Parameter - Simple String vs Directory+Prefix

**Options Considered:**

**Option A: Accept simple path, parse internally**

```python
rotating_file("logs/app.log", rotation="10 MB")
# Internally: directory="logs", filename_prefix="app"
# Rotated files: logs/app-2025-01-11T12:00:00.log
```

**Option B: Require directory + prefix parameters**

```python
rotating_file(directory="logs", prefix="app", rotation="10 MB")
# Rotated files: logs/app-2025-01-11T12:00:00.log
```

**Option C: Accept both path and directory+prefix**

```python
rotating_file(path="logs/app.log", rotation="10 MB")  # OR
rotating_file(directory="logs", prefix="app", rotation="10 MB")
```

**Decision**: **Option A** (simple path, parse internally)

**Rationale**:

1. **Best DX**: `"logs/app.log"` is most intuitive
2. **Matches loguru**: Loguru uses simple paths
3. **Simpler API**: Single parameter vs two
4. **Current behavior**: Rotated files already use timestamps (not `.1`, `.2`)
5. **Parse is trivial**: `Path(path).parent` and `Path(path).stem`

**Implementation**:

```python
from pathlib import Path

def rotating_file(path, rotation=None, retention=None, compression=False):
    path_obj = Path(path)
    return RotatingFileSink(RotatingFileSinkConfig(
        directory=path_obj.parent,
        filename_prefix=path_obj.stem,  # "app" from "app.log"
        max_bytes=_parse_size(rotation) if rotation else 10*1024*1024,
        max_files=retention,
        compress_rotated=compression,
    ))
```

**Trade-offs**:

- ✅ Best DX (simplest to use)
- ✅ Matches user expectations
- ⚠️ Rotated files are `app-<timestamp>.log`, not `app.log.1` (but this is already current behavior)

---

### Decision 3: Rotation/Retention Parameters - Leverage Story 10.4

**Options Considered:**

**Option A: Parse in convenience function**

```python
def rotating_file(path, rotation=None, retention=None, ...):
    max_bytes = _parse_size(rotation) if rotation else None
    # Pass to RotatingFileSinkConfig
```

**Option B: Update RotatingFileSettings to use Story 10.4 types**

```python
class RotatingFileSettings(BaseModel):
    max_bytes: OptionalSizeField = Field(...)  # Accepts "10 MB"
    max_files: int | None = Field(...)
```

**Decision**: **Both** - Update Settings (Option B) AND provide convenience function (Option A)

**Rationale**:

1. **Settings benefit**: Users configuring via Settings/env vars get human-readable strings
2. **Convenience function**: Also benefits from parsers, keeps API simple
3. **Consistency**: Both paths support same human-readable format
4. **Story 10.4 integration**: Leverages parsers already being created

**Trade-offs**:

- ✅ Consistent experience across Settings and convenience functions
- ✅ Maximal benefit from Story 10.4 work
- ⚠️ Need to update RotatingFileSettings schema (but simple, already planned in 10.4)

---

### Decision 4: Scope - File Sink Only or Multiple Sinks?

**Options Considered:**

**Option A: File sink only (Story 10.5)**

```python
from fapilog.sinks import rotating_file
```

**Option B: Multiple sinks (file, webhook, stdout, etc.)**

```python
from fapilog.sinks import rotating_file, webhook, stdout_json
```

**Decision**: **Option A** (file sink only for Story 10.5)

**Rationale**:

1. **Focus**: Story 10.5 is about file rotation DX
2. **Incremental**: Can add other sinks in future stories
3. **Validation**: Prove pattern works before expanding
4. **Other sinks simpler**: stdout, webhook don't need rotation complexity
5. **Fast shipping**: Smaller scope, ships faster

**Future Stories**:

- Story 10.7: Add `webhook()`, `cloudwatch()`, `postgres()` factories
- Each can be added independently as needed

**Trade-offs**:

- ✅ Small, focused scope
- ✅ Ships fast, validates pattern
- ⚠️ Only solves file logging initially (but that's the main pain point)

---

## Implementation Guide

### Files to Create/Modify

#### 1. `src/fapilog/sinks/__init__.py` (CREATE)

```python
"""Convenience functions for creating common sinks with minimal boilerplate."""

from __future__ import annotations

from pathlib import Path
from typing import Literal

from ..plugins.sinks.rotating_file import RotatingFileSink, RotatingFileSinkConfig
from ..core.types import _parse_size  # From Story 10.4


def rotating_file(
    path: str,
    *,
    rotation: str | int | None = None,
    retention: int | None = None,
    compression: bool = False,
    mode: Literal["json", "text"] = "json",
) -> RotatingFileSink:
    """
    Create a rotating file sink with human-readable configuration.

    Args:
        path: File path (e.g., "logs/app.log"). Parent directory must exist.
        rotation: Size-based rotation. Examples: "10 MB", "50MB", 10485760 (bytes).
                 None = 10 MB default.
        retention: Number of rotated files to keep (default: unlimited).
                  Examples: 7 keeps 7 most recent files.
        compression: If True, compress rotated files with gzip.
        mode: Output format - "json" (default) or "text".

    Returns:
        RotatingFileSink instance ready to pass to get_logger(sinks=[...])

    Example:
        >>> from fapilog import get_logger
        >>> from fapilog.sinks import rotating_file
        >>>
        >>> logger = get_logger(sinks=[
        ...     rotating_file("logs/app.log", rotation="10 MB", retention=7)
        ... ])
        >>> logger.info("Logging to file")

    Example with multiple sinks and level routing:
        >>> from fapilog import get_logger, Settings
        >>> from fapilog.sinks import rotating_file
        >>> from fapilog.core.settings import RoutingRule
        >>>
        >>> settings = Settings()
        >>> settings.sink_routing.enabled = True
        >>> settings.sink_routing.rules = [
        ...     RoutingRule(levels=["ERROR", "CRITICAL"], sinks=["rotating_file"]),
        ... ]
        >>>
        >>> logger = get_logger(
        ...     settings=settings,
        ...     sinks=[rotating_file("errors.log", rotation="50 MB")]
        ... )
    """
    # Parse path to directory + prefix
    path_obj = Path(path)
    directory = path_obj.parent
    filename_prefix = path_obj.stem  # "app" from "app.log"

    # Parse rotation size (default to 10 MB if None)
    if rotation is None:
        max_bytes = 10 * 1024 * 1024  # 10 MB default
    else:
        max_bytes = _parse_size(rotation)

    # Create config
    config = RotatingFileSinkConfig(
        directory=directory,
        filename_prefix=filename_prefix,
        mode=mode,
        max_bytes=max_bytes,
        max_files=retention,
        compress_rotated=compression,
    )

    return RotatingFileSink(config)


__all__ = ["rotating_file"]
```

#### 2. `src/fapilog/core/settings.py` (MODIFY)

Update `RotatingFileSettings` to use Story 10.4 parsers:

```python
from .types import OptionalSizeField  # From Story 10.4


class RotatingFileSettings(BaseModel):
    """Per-plugin configuration for RotatingFileSink."""

    directory: str | None = Field(
        default=None, description="Log directory for rotating file sink"
    )
    filename_prefix: str = Field(default="fapilog", description="Filename prefix")
    mode: Literal["json", "text"] = Field(
        default="json", description="Output format: json or text"
    )
    max_bytes: OptionalSizeField = Field(  # CHANGED: Use Story 10.4 parser
        default=10 * 1024 * 1024,
        description="Max bytes before rotation. Accepts '10 MB' or 10485760.",
    )
    interval_seconds: int | None = Field(
        default=None, description="Rotation interval in seconds (optional)"
    )
    max_files: int | None = Field(
        default=None, description="Max number of rotated files to keep"
    )
    max_total_bytes: int | None = Field(
        default=None, description="Max total bytes across all rotated files"
    )
    compress_rotated: bool = Field(
        default=False, description="Compress rotated log files with gzip"
    )
```

#### 3. `src/fapilog/__init__.py` (MODIFY)

Add `sinks` to public exports:

```python
from . import sinks  # NEW

__all__ = [
    "get_logger",
    "get_async_logger",
    "runtime",
    "runtime_async",
    "Settings",
    "DrainResult",
    "LogEvent",
    "sinks",  # NEW
    "__version__",
    "VERSION",
]
```

#### 4. `docs/user-guide/rotating-file-sink.md` (UPDATE)

Add convenience function examples:

````markdown
# Rotating File Sink

## Quick Start (Convenience Function)

```python
from fapilog import get_logger
from fapilog.sinks import rotating_file

# Simple file logging
logger = get_logger(sinks=[rotating_file("logs/app.log")])

# With rotation and retention
logger = get_logger(sinks=[
    rotating_file(
        "logs/app.log",
        rotation="10 MB",
        retention=7,
        compression=True,
    )
])
```
````

## Environment Variables (Human-Readable)

```bash
export FAPILOG_FILE__DIRECTORY=logs
export FAPILOG_FILE__MAX_BYTES="10 MB"  # Human-readable!
export FAPILOG_FILE__MAX_FILES=5
export FAPILOG_FILE__COMPRESS_ROTATED=true
```

## Programmatic Settings

```python
from fapilog import get_logger, Settings

settings = Settings()
settings.file.directory = "logs"
settings.file.max_bytes = "10 MB"  # Story 10.4 parser
settings.file.max_files = 7
settings.file.compress_rotated = True

logger = get_logger(settings=settings)
```

## Multiple Sinks with Level Routing

```python
from fapilog import get_logger, Settings
from fapilog.sinks import rotating_file
from fapilog.core.settings import RoutingRule

settings = Settings()
settings.sink_routing.enabled = True
settings.sink_routing.rules = [
    RoutingRule(levels=["ERROR", "CRITICAL"], sinks=["rotating_file"]),
    RoutingRule(levels=["DEBUG", "INFO", "WARNING"], sinks=["rotating_file"]),
]

logger = get_logger(
    settings=settings,
    sinks=[
        rotating_file("logs/all.log", rotation="50 MB"),
        rotating_file("logs/errors.log", rotation="10 MB"),
    ]
)

logger.debug("Debug message")  # → logs/all.log
logger.error("Error message")  # → logs/errors.log AND logs/all.log (if overlap=True)
```

````

---

## Test Specification

### Unit Tests (`tests/unit/test_sinks_module.py`)

```python
import pytest
from pathlib import Path
from fapilog.sinks import rotating_file
from fapilog.plugins.sinks.rotating_file import RotatingFileSink


class TestRotatingFileFactory:
    """Test rotating_file() convenience function."""

    def test_rotating_file_minimal(self):
        """Test rotating_file with just path."""
        sink = rotating_file("logs/app.log")

        assert isinstance(sink, RotatingFileSink)
        assert sink._cfg.directory == Path("logs")
        assert sink._cfg.filename_prefix == "app"
        assert sink._cfg.max_bytes == 10 * 1024 * 1024  # 10 MB default
        assert sink._cfg.max_files is None
        assert sink._cfg.compress_rotated is False

    def test_rotating_file_with_rotation_string(self):
        """Test rotation with human-readable string."""
        sink = rotating_file("logs/app.log", rotation="50 MB")

        assert sink._cfg.max_bytes == 50 * 1024 * 1024

    def test_rotating_file_with_rotation_int(self):
        """Test rotation with integer bytes."""
        sink = rotating_file("logs/app.log", rotation=1024)

        assert sink._cfg.max_bytes == 1024

    def test_rotating_file_with_retention(self):
        """Test file retention."""
        sink = rotating_file("logs/app.log", retention=5)

        assert sink._cfg.max_files == 5

    def test_rotating_file_with_compression(self):
        """Test compression option."""
        sink = rotating_file("logs/app.log", compression=True)

        assert sink._cfg.compress_rotated is True

    def test_rotating_file_mode_json(self):
        """Test JSON mode (default)."""
        sink = rotating_file("logs/app.log")

        assert sink._cfg.mode == "json"

    def test_rotating_file_mode_text(self):
        """Test text mode."""
        sink = rotating_file("logs/app.log", mode="text")

        assert sink._cfg.mode == "text"

    def test_rotating_file_nested_path(self):
        """Test nested directory path."""
        sink = rotating_file("logs/api/requests.log")

        assert sink._cfg.directory == Path("logs/api")
        assert sink._cfg.filename_prefix == "requests"

    def test_rotating_file_full_config(self):
        """Test with all parameters."""
        sink = rotating_file(
            "logs/app.log",
            rotation="100 MB",
            retention=10,
            compression=True,
            mode="text",
        )

        assert sink._cfg.directory == Path("logs")
        assert sink._cfg.filename_prefix == "app"
        assert sink._cfg.max_bytes == 100 * 1024 * 1024
        assert sink._cfg.max_files == 10
        assert sink._cfg.compress_rotated is True
        assert sink._cfg.mode == "text"


class TestRotatingFileSettingsHumanReadable:
    """Test RotatingFileSettings with Story 10.4 parsers."""

    def test_max_bytes_string(self):
        """Test max_bytes accepts human-readable string."""
        from fapilog.core.settings import RotatingFileSettings

        settings = RotatingFileSettings(max_bytes="50 MB")

        assert settings.max_bytes == 50 * 1024 * 1024

    def test_max_bytes_int(self):
        """Test max_bytes accepts integer."""
        from fapilog.core.settings import RotatingFileSettings

        settings = RotatingFileSettings(max_bytes=1024)

        assert settings.max_bytes == 1024

    def test_max_bytes_env_var(self):
        """Test max_bytes from environment variable."""
        import os
        from fapilog import Settings

        os.environ["FAPILOG_FILE__MAX_BYTES"] = "10 MB"
        settings = Settings()

        assert settings.file.max_bytes == 10 * 1024 * 1024
````

### Integration Tests (`tests/integration/test_rotating_file_integration.py`)

```python
import pytest
from pathlib import Path
from fapilog import get_logger
from fapilog.sinks import rotating_file


class TestRotatingFileIntegration:
    """Integration tests for rotating_file() convenience function."""

    def test_get_logger_with_rotating_file(self, tmp_path):
        """Test get_logger with rotating_file sink."""
        log_path = tmp_path / "app.log"
        logger = get_logger(sinks=[rotating_file(str(log_path))])

        logger.info("Test message")

        # File should exist (but may have timestamp suffix)
        log_files = list(tmp_path.glob("app*.log"))
        assert len(log_files) > 0
        assert "Test message" in log_files[0].read_text()

    def test_multiple_sinks_with_routing(self, tmp_path):
        """Test multiple file sinks with level routing."""
        from fapilog import Settings
        from fapilog.core.settings import RoutingRule

        all_log = tmp_path / "all.log"
        errors_log = tmp_path / "errors.log"

        settings = Settings()
        settings.sink_routing.enabled = True
        settings.sink_routing.overlap = False  # First match wins
        settings.sink_routing.rules = [
            RoutingRule(levels=["ERROR", "CRITICAL"], sinks=["rotating_file"]),
        ]
        settings.sink_routing.fallback_sinks = ["rotating_file"]

        logger = get_logger(
            settings=settings,
            sinks=[
                rotating_file(str(all_log)),
                rotating_file(str(errors_log)),
            ],
        )

        logger.info("Info message")
        logger.error("Error message")

        # Check messages in correct files
        # (Actual routing logic depends on sink naming - this is example structure)
        assert len(list(tmp_path.glob("*.log"))) >= 2

    def test_rotation_triggers_correctly(self, tmp_path):
        """Test that rotation triggers at size limit."""
        log_path = tmp_path / "app.log"
        logger = get_logger(sinks=[
            rotating_file(str(log_path), rotation="1 KB", retention=3)
        ])

        # Write enough to trigger rotation
        for i in range(50):
            logger.info("x" * 100)  # 100+ byte messages

        # Should have rotated files
        log_files = list(tmp_path.glob("app*"))
        assert len(log_files) > 1  # Original + rotated
        assert len(log_files) <= 4  # Retention = 3 + current

    def test_compression_creates_gz_files(self, tmp_path):
        """Test compression creates .gz files."""
        log_path = tmp_path / "app.log"
        logger = get_logger(sinks=[
            rotating_file(str(log_path), rotation="500 bytes", compression=True)
        ])

        # Trigger rotation
        for i in range(20):
            logger.info("x" * 50)

        # Should have .gz files
        gz_files = list(tmp_path.glob("*.gz"))
        assert len(gz_files) > 0
```

---

## Definition of Done

### Code Complete:

- [ ] `fapilog.sinks` module created with `rotating_file()` function
- [ ] `RotatingFileSettings` updated to use `OptionalSizeField` from Story 10.4
- [ ] `fapilog.sinks` exported in `__init__.py`
- [ ] Docstrings with examples for `rotating_file()`

### Tests Complete:

- [ ] Unit tests for `rotating_file()` factory (10+ test cases)
- [ ] Unit tests for Settings with human-readable strings
- [ ] Integration tests with `get_logger(sinks=[...])`
- [ ] Integration tests for rotation/retention/compression
- [ ] Integration tests with sink routing

### Documentation Complete:

- [ ] `rotating-file-sink.md` updated with convenience function examples
- [ ] API reference for `fapilog.sinks.rotating_file()`
- [ ] Examples showing Settings with human-readable strings
- [ ] Migration guide from current verbose pattern

### Quality Gates:

- [ ] Type hints pass mypy strict mode
- [ ] Code coverage ≥ 90% for new code
- [ ] No regressions in existing tests
- [ ] Backward compatibility 100% (convenience function is additive)

### User Acceptance:

- [ ] Can create file sink in 1 import: `from fapilog.sinks import rotating_file`
- [ ] Can use human-readable strings: `rotation="10 MB"`
- [ ] Settings accept human-readable strings via env vars
- [ ] Multiple sinks work with level routing
- [ ] Clear examples in documentation

---

## Examples

### Example 1: Basic File Logging

```python
from fapilog import get_logger
from fapilog.sinks import rotating_file

logger = get_logger(sinks=[rotating_file("logs/app.log")])

logger.info("Application started")
logger.error("An error occurred", exc_info=True)
```

### Example 2: Size-Based Rotation with Retention

```python
from fapilog import get_logger
from fapilog.sinks import rotating_file

logger = get_logger(sinks=[
    rotating_file(
        "logs/app.log",
        rotation="10 MB",
        retention=7,
        compression=True,
    )
])

for i in range(10000):
    logger.info(f"Processing item {i}", item_id=i)
```

### Example 3: Multiple Sinks with Different Levels

```python
from fapilog import get_logger, Settings
from fapilog.sinks import rotating_file
from fapilog.core.settings import RoutingRule

# Configure routing
settings = Settings()
settings.sink_routing.enabled = True
settings.sink_routing.rules = [
    RoutingRule(levels=["ERROR", "CRITICAL"], sinks=["rotating_file"]),
    RoutingRule(levels=["DEBUG", "INFO", "WARNING"], sinks=["rotating_file"]),
]

# Create sinks
logger = get_logger(
    settings=settings,
    sinks=[
        rotating_file("logs/all.log", rotation="50 MB"),
        rotating_file("logs/errors.log", rotation="10 MB", retention=30),
    ]
)

logger.debug("Debug info")      # → logs/all.log
logger.info("Info message")     # → logs/all.log
logger.error("Error occurred")  # → logs/errors.log (and logs/all.log if overlap=True)
```

### Example 4: Settings with Human-Readable Strings

```python
from fapilog import get_logger, Settings

settings = Settings()
settings.file.directory = "logs"
settings.file.filename_prefix = "api"
settings.file.max_bytes = "100 MB"  # Human-readable!
settings.file.max_files = 10
settings.file.compress_rotated = True
settings.file.mode = "json"

logger = get_logger(settings=settings)
logger.info("Using Settings with human-readable config")
```

### Example 5: Environment Variables

```bash
export FAPILOG_FILE__DIRECTORY=logs
export FAPILOG_FILE__FILENAME_PREFIX=app
export FAPILOG_FILE__MAX_BYTES="50 MB"
export FAPILOG_FILE__MAX_FILES=7
export FAPILOG_FILE__COMPRESS_ROTATED=true
```

```python
from fapilog import get_logger

# Automatically picks up env vars
logger = get_logger()
logger.info("Configured via environment variables")
```

---

## Non-Goals

1. Runtime sink addition/removal (`add_file()`, `remove_sink()`) - **Not needed for logging library**
2. Time-based rotation ("daily", "hourly") - **Story 10.6**
3. Other sink convenience functions (`webhook()`, `cloudwatch()`) - **Future stories**
4. Sink introspection/management - **Not needed**
5. Mutable logger configuration after creation - **Keep sinks immutable**

---

## Dependencies

### Story Dependencies:

- **Story 10.4**: Human-Readable Config Strings (provides `_parse_size()` parser)

### External Dependencies:

- None (uses existing fapilog infrastructure)

### Internal Dependencies:

- `RotatingFileSink` implementation (exists)
- `get_logger(sinks=[...])` parameter (exists)
- `Settings` validation (exists)

---

## Risks & Mitigations

### Risk 1: Path Parsing Confusion

**Risk**: Users expect `app.log.1`, but get `app-<timestamp>.log`
**Likelihood**: Medium
**Impact**: Low (documentation issue)
**Mitigation**: Clear documentation showing rotated filename format

### Risk 2: Import Confusion

**Risk**: Users import from wrong location (`fapilog.plugins.sinks` vs `fapilog.sinks`)
**Likelihood**: Low
**Impact**: Low
**Mitigation**: Clear examples, top-level export, IDE autocomplete

### Risk 3: Settings Schema Change

**Risk**: Changing `max_bytes` to `OptionalSizeField` breaks existing code
**Likelihood**: Very Low
**Impact**: Low
**Mitigation**: Union type accepts both string and int, fully backward compatible

---

## Migration Path

### From Current Verbose Pattern:

**Before**:

```python
from fapilog import get_logger
from fapilog.plugins.sinks.rotating_file import RotatingFileSink, RotatingFileSinkConfig
from pathlib import Path

config = RotatingFileSinkConfig(
    directory=Path("logs"),
    filename_prefix="app",
    max_bytes=10485760,
    max_files=7,
    compress_rotated=True,
)
sink = RotatingFileSink(config)
logger = get_logger(sinks=[sink])
```

**After**:

```python
from fapilog import get_logger
from fapilog.sinks import rotating_file

logger = get_logger(sinks=[
    rotating_file("logs/app.log", rotation="10 MB", retention=7, compression=True)
])
```

**Migration Strategy**:

1. Old pattern continues to work (100% backward compatible)
2. New convenience function is purely additive
3. Documentation shows both patterns with recommendation for new projects
4. Settings-based config also benefits from Story 10.4 parsers

---

## Future Enhancements (Out of Scope)

1. **Story 10.6**: Time-based rotation and advanced retention
2. **Story 10.7**: Other sink factories (`webhook()`, `cloudwatch()`, `postgres()`)
3. **Story 10.8**: Preset configurations (may use these factories)
4. **Documentation**: Best practices guide for sink selection

---

## Success Metrics

### Before Story 10.5:

```python
# 12 lines with imports and config
from fapilog import get_logger
from fapilog.plugins.sinks.rotating_file import RotatingFileSink, RotatingFileSinkConfig
from pathlib import Path

config = RotatingFileSinkConfig(
    directory=Path("logs"),
    filename_prefix="app",
    max_bytes=10485760,
    max_files=7,
    compress_rotated=True,
)
sink = RotatingFileSink(config)
logger = get_logger(sinks=[sink])
```

### After Story 10.5:

```python
# 4 lines
from fapilog import get_logger
from fapilog.sinks import rotating_file

logger = get_logger(sinks=[
    rotating_file("logs/app.log", rotation="10 MB", retention=7, compression=True)
])
```

**Metrics**:

- ✅ Lines of code: 12 → 4 (67% reduction)
- ✅ Imports: 3 lines → 2 lines
- ✅ Cognitive load: High (Path, config objects) → Low (simple function)
- ✅ IDE discovery: `fapilog.sinks` autocomplete
- ✅ Human-readable config: "10 MB" instead of 10485760
- ✅ No architectural changes: Leverages existing `get_logger(sinks=[...])`

---

## Conclusion

Story 10.5 achieves the same DX improvement as the original proposal (runtime sink management) but with **much simpler implementation**:

- **Leverages existing**: Uses `get_logger(sinks=[...])` parameter that already works
- **No architectural changes**: Sinks remain immutable after logger creation
- **Simple factory function**: Just a convenience wrapper around existing `RotatingFileSink`
- **Story 10.4 integration**: Settings benefit from human-readable parsers
- **Best-in-class logging library**: Focused on logging, not configuration management

The convenience function provides 67% code reduction with zero architectural risk, shipping in 2-3 days vs 5-7 days for runtime sink management.
