# Story 4.39: Document write_serialized Protocol Extension

**Status:** Draft  
**Priority:** Medium  
**Depends on:** None  
**Effort:** 0.5 days

---

## Problem Statement

The `BaseSink` protocol defines the required `write()` method:

```python
async def write(self, _entry: dict) -> None:
    """Emit a single structured JSON-serializable mapping."""
    ...
```

However, many built-in sinks also implement `write_serialized()`:

```python
async def write_serialized(self, view: SerializedView) -> None:
    """Zero-copy fast path for pre-serialized entries."""
    ...
```

This method is:
- ❌ Not documented in the protocol
- ❌ Not mentioned in plugin authoring docs
- ❌ Not checked by validators
- ❌ Not explained in API reference

### Current Implementation

| Sink | Has `write_serialized` |
|------|----------------------|
| `StdoutJsonSink` | ✅ Yes |
| `RotatingFileSink` | ✅ Yes |
| `HttpSink` | ✅ Yes |
| `WebhookSink` | ✅ Yes |
| `MemoryMappedPersistence` | ❌ No (not a sink) |

### How It's Used

When `serialize_in_flush=True` in settings, the logger:
1. Serializes the entry once
2. Calls `write_serialized()` if available
3. Falls back to `write()` if not

This provides a performance optimization for sinks that can accept pre-serialized data.

---

## Goals

1. Document `write_serialized()` as an optional protocol extension
2. Explain when and why to implement it
3. Add examples showing both methods
4. Update API reference
5. Consider adding to validator (as optional check)

---

## Implementation Plan

### Phase 1: Update Protocol Docstring

**File: `src/fapilog/plugins/sinks/__init__.py`**

Update `BaseSink` docstring:

```python
@runtime_checkable
class BaseSink(Protocol):
    """Authoring contract for sinks that emit finalized log entries.

    Required Methods:
        write(entry: dict) -> None: Primary write method, receives dict events.
    
    Optional Methods:
        write_serialized(view: SerializedView) -> None: Zero-copy fast path.
            When implemented, fapilog may call this instead of write() when
            serialize_in_flush=True. This avoids re-serialization in the sink.
            
            If not implemented, fapilog falls back to write() automatically.
    
    Lifecycle:
        start() and stop() are optional. If implemented, they should be
        idempotent and tolerate repeated calls.
    
    Attributes:
        name: Unique identifier for this sink type (e.g., "stdout_json").
    """

    name: str

    async def start(self) -> None:
        """Initialize resources for the sink."""

    async def stop(self) -> None:
        """Flush and release resources for the sink."""

    async def write(self, _entry: dict) -> None:
        """Emit a single structured JSON-serializable mapping.
        
        This is the primary write method. All sinks MUST implement this.
        """
        ...

    async def health_check(self) -> bool:
        """Return True if the sink is healthy."""
        return True
```

### Phase 2: Add Documentation Section

**File: `docs/plugins/sinks.md`**

Add section:

```markdown
## Optional: write_serialized Fast Path

For performance-critical sinks, implement `write_serialized()` to receive
pre-serialized data:

```python
from fapilog.core.serialization import SerializedView

class MyFastSink:
    name = "my-fast-sink"
    
    async def write(self, entry: dict) -> None:
        """Standard write - receives dict, must serialize."""
        data = json.dumps(entry).encode()
        await self._send(data)
    
    async def write_serialized(self, view: SerializedView) -> None:
        """Fast path - receives pre-serialized bytes."""
        # view.data is a memoryview of the serialized JSON
        await self._send(bytes(view.data))
```

### When to Implement

Implement `write_serialized()` when:

- Your sink writes bytes (file, network socket)
- Re-serialization would be redundant
- Performance is critical

Skip it when:

- Your sink needs to inspect/modify the entry
- The target requires a specific format (not JSON)

### How Fapilog Uses It

When `Settings.core.serialize_in_flush=True`:

1. Entry is serialized once before sink calls
2. If `write_serialized()` exists, it's called with `SerializedView`
3. If not, `write()` is called with the original dict

This is automatic - you don't need to configure anything beyond implementing
the method.

### SerializedView Structure

```python
@dataclass
class SerializedView:
    data: memoryview  # The serialized JSON bytes
    
    def __bytes__(self) -> bytes:
        return bytes(self.data)
```
```

### Phase 3: Update API Reference

**File: `docs/api-reference/plugins/sinks.md`**

Add:

```markdown
## Optional Methods

### write_serialized

```python
async def write_serialized(self, view: SerializedView) -> None
```

Optional zero-copy fast path for pre-serialized entries.

**Parameters:**
- `view` - A `SerializedView` containing the pre-serialized JSON bytes

**When Called:**
- Only when `serialize_in_flush=True` in settings
- Only if the method exists on the sink

**Fallback:**
- If not implemented, fapilog automatically calls `write()` instead
```

### Phase 4: Add Validator Warning (Optional)

**File: `src/fapilog/testing/validators.py`**

Add informational warning for sinks without fast path:

```python
def validate_sink(sink: Any) -> ValidationResult:
    # ... existing validation ...
    
    # Informational: Check for fast path
    if not hasattr(sink, "write_serialized"):
        warnings.append(
            "Sink does not implement write_serialized() fast path. "
            "Consider adding it for better performance with serialize_in_flush=True."
        )
    
    # ... rest ...
```

---

## Acceptance Criteria

- [ ] `BaseSink` docstring documents `write_serialized()`
- [ ] `docs/plugins/sinks.md` explains when/how to implement
- [ ] `docs/api-reference/plugins/sinks.md` includes method reference
- [ ] Example code shows both methods
- [ ] `SerializedView` is documented
- [ ] Validator adds informational warning (optional)

---

## Files Changed

| File | Change |
|------|--------|
| `src/fapilog/plugins/sinks/__init__.py` | Update docstring |
| `docs/plugins/sinks.md` | Add fast path section |
| `docs/api-reference/plugins/sinks.md` | Add method reference |
| `src/fapilog/testing/validators.py` | Add optional warning |

