# Story 6.3: Extract Shared Plugin Stop Logic

## Status: Complete

## Summary

Extract the `_stop_enrichers_and_redactors` method from both `SyncLoggerFacade` and `AsyncLoggerFacade` into a shared location, eliminating ~50 lines of duplicated code.

## Motivation

The complexity audit identified that `_stop_enrichers_and_redactors` is nearly identical in both logger facades:

**SyncLoggerFacade (lines 234-305):** 72 lines
**AsyncLoggerFacade (lines 1027-1095):** 69 lines

Both methods:

1. Stop processors in reverse order
2. Stop filters in reverse order
3. Stop redactors in reverse order
4. Stop enrichers in reverse order
5. Emit diagnostics on failure
6. Contain errors (never propagate)

The only difference is method signature (`async def` in both cases — sync facade also uses async).

## Acceptance Criteria

- [ ] Single implementation of plugin stop logic exists
- [ ] Both facades use the shared implementation
- [ ] Stop order preserved (reverse order: processors → filters → redactors → enrichers)
- [ ] Error containment preserved (failures don't propagate)
- [ ] Diagnostics emission preserved
- [ ] All existing tests pass
- [ ] ~50 lines of duplication removed

## Technical Approach

### Option A: Move to `worker.py`

Add a standalone async function to `worker.py`:

```python
async def stop_plugins(
    processors: list[Any],
    filters: list[Any],
    redactors: list[Any],
    enrichers: list[Any],
) -> None:
    """Stop all plugin types in reverse order, containing errors."""
    for plugin_type, plugins in [
        ("processor", processors),
        ("filter", filters),
        ("redactor", redactors),
        ("enricher", enrichers),
    ]:
        for plugin in reversed(plugins):
            try:
                if hasattr(plugin, "stop"):
                    await plugin.stop()
            except Exception as exc:
                try:
                    warn(
                        plugin_type,
                        "plugin stop failed",
                        plugin=getattr(plugin, "name", type(plugin).__name__),
                        error=str(exc),
                    )
                except Exception:
                    pass
```

Update both facades to call:

```python
await stop_plugins(
    self._processors,
    self._filters,
    self._redactors,
    self._enrichers,
)
```

### Option B: Mixin class (for Story 6.8)

If Story 6.8 (Unify Logger Facades) proceeds first, this becomes part of the base class extraction.

## Recommended Approach

Proceed with Option A as a quick win. If Story 6.8 later unifies facades, the function in `worker.py` remains valid and can be called from the unified base.

## Files Changed

- `src/fapilog/core/worker.py` — Add `stop_plugins` function (+25 lines)
- `src/fapilog/core/logger.py` — Replace duplicated methods with calls (-100 lines, +10 lines)

## Estimated Effort

1-2 hours

## Risk Assessment

**Risk Level: Low**

- Behavioral preservation is easy to verify
- Tests cover shutdown/drain paths
- Function is async so works with both facades
- Fallback: revert and keep duplication

## Rollback Plan

Revert commit; duplication is annoying but functional.

## Dependencies

- None (can proceed independently)
- May be superseded by Story 6.8 if that runs first
