# ADR-054: Structured Logging Architecture

## Status
Accepted

## Context
StarPunk currently uses print statements and basic logging without structure. For production deployments, we need:
- Consistent log formatting
- Appropriate log levels
- Structured data for parsing
- Correlation IDs for request tracking
- Performance-conscious logging

We need a logging architecture that is simple, follows Python best practices, and provides production-grade observability.

## Decision
Implement structured logging using Python's built-in `logging` module with JSON formatting and contextual information.

### Logging Architecture

```
Application Code
      ↓
Logger Interface → Filters → Formatters → Handlers → Output
      ↑                                              ↓
Context Injection                            (stdout/file)
```

### Log Levels

Following standard Python/syslog levels:

| Level | Value | Usage |
|-------|-------|-------|
| CRITICAL | 50 | System failures requiring immediate attention |
| ERROR | 40 | Errors that need investigation |
| WARNING | 30 | Unexpected conditions that might cause issues |
| INFO | 20 | Normal operation events |
| DEBUG | 10 | Detailed diagnostic information |

### Log Structure

JSON format for production, human-readable for development:

```json
{
  "timestamp": "2025-11-25T10:30:45.123Z",
  "level": "INFO",
  "logger": "starpunk.micropub",
  "message": "Note created",
  "request_id": "a1b2c3d4",
  "user": "alice@example.com",
  "context": {
    "note_id": 123,
    "slug": "my-note",
    "word_count": 42
  },
  "performance": {
    "duration_ms": 45
  }
}
```

### Logger Hierarchy

```
starpunk (root logger)
├── starpunk.auth        # Authentication/authorization
├── starpunk.micropub    # Micropub endpoint
├── starpunk.database    # Database operations
├── starpunk.search      # Search functionality
├── starpunk.web        # Web interface
├── starpunk.rss        # RSS generation
├── starpunk.monitoring # Performance monitoring
└── starpunk.migration  # Database migrations
```

### Implementation Pattern

```python
# starpunk/logging.py
import logging
import json
import sys
from datetime import datetime
from contextvars import ContextVar

# Request context for correlation
request_id: ContextVar[str] = ContextVar('request_id', default='')

class StructuredFormatter(logging.Formatter):
    """JSON formatter for structured logging"""

    def format(self, record):
        log_obj = {
            'timestamp': datetime.utcnow().isoformat() + 'Z',
            'level': record.levelname,
            'logger': record.name,
            'message': record.getMessage(),
            'request_id': request_id.get()
        }

        # Add extra fields
        if hasattr(record, 'context'):
            log_obj['context'] = record.context

        if hasattr(record, 'performance'):
            log_obj['performance'] = record.performance

        # Add exception info if present
        if record.exc_info:
            log_obj['exception'] = self.formatException(record.exc_info)

        return json.dumps(log_obj)

def setup_logging(level='INFO', format_type='json'):
    """Configure logging for the application"""
    root_logger = logging.getLogger('starpunk')
    root_logger.setLevel(level)

    handler = logging.StreamHandler(sys.stdout)

    if format_type == 'json':
        formatter = StructuredFormatter()
    else:
        # Human-readable for development
        formatter = logging.Formatter(
            '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
        )

    handler.setFormatter(formatter)
    root_logger.addHandler(handler)

    return root_logger

# Usage pattern
logger = logging.getLogger('starpunk.micropub')

def create_note(content, user):
    logger.info(
        "Creating note",
        extra={
            'context': {
                'user': user,
                'content_length': len(content)
            }
        }
    )
    # ... implementation
```

### What to Log

#### Always Log (INFO+)
- Authentication attempts (success/failure)
- Note CRUD operations
- Configuration changes
- Startup/shutdown
- External API calls
- Migration execution
- Search queries

#### Error Conditions (ERROR)
- Database connection failures
- Invalid Micropub requests
- Authentication failures
- File system errors
- Configuration errors

#### Warnings (WARNING)
- Slow queries
- High memory usage
- Deprecated feature usage
- Missing optional configuration
- FTS5 unavailability

#### Debug Information (DEBUG)
- SQL queries executed
- Request/response bodies
- Template rendering details
- Cache operations
- Detailed timing data

### What NOT to Log
- Passwords or tokens
- Full note content (unless debug)
- Personal information (PII)
- Request headers with auth
- Database connection strings

### Performance Considerations

1. **Lazy Evaluation**: Use lazy % formatting
   ```python
   logger.debug("Processing note %s", note_id)  # Good
   logger.debug(f"Processing note {note_id}")   # Bad
   ```

2. **Level Checking**: Check before expensive operations
   ```python
   if logger.isEnabledFor(logging.DEBUG):
       logger.debug("Data: %s", expensive_serialization())
   ```

3. **Async Logging**: For high-volume scenarios (future)

4. **Sampling**: For very frequent operations
   ```python
   if random.random() < 0.1:  # Log 10%
       logger.debug("High frequency operation")
   ```

## Rationale

### Why Standard Logging Module?
1. **No Dependencies**: Built into Python
2. **Industry Standard**: Well understood
3. **Flexible**: Handlers, formatters, filters
4. **Battle-tested**: Proven in production
5. **Integration**: Works with existing tools

### Why JSON Format?
1. **Parseable**: Easy for log aggregators
2. **Structured**: Consistent field access
3. **Flexible**: Can add fields without breaking
4. **Standard**: Widely supported

### Why Not Alternatives?

**structlog**:
- Additional dependency
- More complex API
- Overkill for our needs

**loguru**:
- Third-party dependency
- Non-standard API
- Not necessary for our scale

**Print statements**:
- No levels
- No structure
- No filtering
- Not production-ready

## Consequences

### Positive
1. **Production Ready**: Professional logging
2. **Debuggable**: Rich context in logs
3. **Parseable**: Integration with log tools
4. **Performant**: Minimal overhead
5. **Configurable**: Adjust without code changes
6. **Correlatable**: Request tracking via IDs

### Negative
1. **Verbosity**: More code for logging
2. **Learning**: Developers must understand levels
3. **Size**: JSON logs are larger than plain text
4. **Complexity**: More setup than prints

### Mitigations
1. Provide logging utilities/helpers
2. Document logging guidelines
3. Use log rotation for size management
4. Create developer-friendly formatter option

## Alternatives Considered

### 1. Continue with Print Statements
**Pros**: Simplest possible
**Cons**: Not production-ready
**Decision**: Inadequate for production

### 2. Custom Logging Solution
**Pros**: Exactly what we need
**Cons**: Reinventing the wheel
**Decision**: Standard library is sufficient

### 3. External Logging Service
**Pros**: No local storage needed
**Cons**: Privacy, dependency, cost
**Decision**: Conflicts with self-hosted philosophy

### 4. Syslog Integration
**Pros**: Standard Unix logging
**Cons**: Platform-specific, complexity
**Decision**: Can add as handler if needed

## Implementation Notes

### Bootstrap Logging
```python
# Application startup
import logging
from starpunk.logging import setup_logging

# Configure based on environment
if os.environ.get('STARPUNK_ENV') == 'production':
    setup_logging(level='INFO', format_type='json')
else:
    setup_logging(level='DEBUG', format_type='human')
```

### Request Correlation
```python
# Middleware sets request ID
from uuid import uuid4
from contextvars import copy_context

def middleware(request):
    request_id.set(str(uuid4())[:8])
    # Process request in context
    return copy_context().run(handler, request)
```

### Migration Strategy
1. Phase 1: Add logging module, keep prints
2. Phase 2: Convert prints to logger calls
3. Phase 3: Remove print statements
4. Phase 4: Add structured context

## Testing Strategy

1. **Unit Tests**: Mock logger, verify calls
2. **Integration Tests**: Verify log output format
3. **Performance Tests**: Measure logging overhead
4. **Configuration Tests**: Test different levels/formats

## Configuration

Environment variables:
- `STARPUNK_LOG_LEVEL`: DEBUG|INFO|WARNING|ERROR|CRITICAL
- `STARPUNK_LOG_FORMAT`: json|human
- `STARPUNK_LOG_FILE`: Path to log file (optional)
- `STARPUNK_LOG_ROTATION`: Enable rotation (optional)

## Security Considerations

1. Never log sensitive data
2. Sanitize user input in logs
3. Rate limit log output
4. Monitor for log injection attacks
5. Secure log file permissions

## References

- [Python Logging HOWTO](https://docs.python.org/3/howto/logging.html)
- [The Twelve-Factor App - Logs](https://12factor.net/logs)
- [OWASP Logging Guide](https://cheatsheetseries.owasp.org/cheatsheets/Logging_Cheat_Sheet.html)
- [JSON Logging Best Practices](https://www.loggly.com/use-cases/json-logging-best-practices/)

## Document History

- 2025-11-25: Initial draft for v1.1.1 release planning