feat: Implement v1.1.1 Phase 1 - Core Infrastructure

Phase 1 of v1.1.1 "Polish" release focusing on production readiness.
Implements logging, connection pooling, validation, and error handling.

Following specs in docs/design/v1.1.1/developer-qa.md and ADRs 052-055.

**Structured Logging** (Q3, ADR-054)
- RotatingFileHandler (10MB files, keep 10)
- Correlation IDs for request tracing
- All print statements replaced with logging
- Context-aware correlation IDs (init/request)
- Logs written to data/logs/starpunk.log

**Database Connection Pooling** (Q2, ADR-053)
- Connection pool with configurable size (default: 5)
- Request-scoped connections via Flask g object
- Pool statistics for monitoring
- WAL mode enabled for concurrency
- Backward compatible get_db() signature

**Configuration Validation** (Q14, ADR-052)
- Validates presence and type of all config values
- Fail-fast startup with clear error messages
- LOG_LEVEL enum validation
- Type checking for strings, integers, paths
- Non-zero exit status on errors

**Centralized Error Handling** (Q4, ADR-055)
- Moved handlers to starpunk/errors.py
- Micropub spec-compliant JSON errors
- HTML templates for browser requests
- All errors logged with correlation IDs
- MicropubError exception class

**Database Module Reorganization**
- Moved database.py to database/ package
- Separated init.py, pool.py, schema.py
- Maintains backward compatibility
- Cleaner separation of concerns

**Testing**
- 580 tests passing
- 1 pre-existing flaky test noted
- No breaking changes to public API

**Documentation**
- CHANGELOG.md updated with v1.1.1 entry
- Version bumped to 1.1.1
- Implementation report in docs/reports/

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-25 13:56:30 -07:00
parent f62d3c5382
commit 93d2398c1d
9 changed files with 1011 additions and 92 deletions

View File

@@ -4,12 +4,20 @@ Creates and configures the Flask application
"""
import logging
from flask import Flask
from logging.handlers import RotatingFileHandler
from pathlib import Path
from flask import Flask, g
import uuid
def configure_logging(app):
"""
Configure application logging based on LOG_LEVEL
Configure application logging with RotatingFileHandler and structured logging
Per ADR-054 and developer Q&A Q3:
- Uses RotatingFileHandler (10MB files, keep 10)
- Supports correlation IDs for request tracking
- Uses Flask's app.logger for all logging
Args:
app: Flask application instance
@@ -19,12 +27,24 @@ def configure_logging(app):
# Set Flask logger level
app.logger.setLevel(getattr(logging, log_level, logging.INFO))
# Configure handler with detailed format for DEBUG
handler = logging.StreamHandler()
# Configure console handler
console_handler = logging.StreamHandler()
# Configure file handler with rotation (10MB per file, keep 10 files)
log_dir = app.config.get("DATA_PATH", Path("./data")) / "logs"
log_dir.mkdir(parents=True, exist_ok=True)
log_file = log_dir / "starpunk.log"
file_handler = RotatingFileHandler(
log_file,
maxBytes=10 * 1024 * 1024, # 10MB
backupCount=10
)
# Format with correlation ID support
if log_level == "DEBUG":
formatter = logging.Formatter(
"[%(asctime)s] %(levelname)s - %(name)s: %(message)s",
"[%(asctime)s] %(levelname)s - %(name)s [%(correlation_id)s]: %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)
@@ -41,14 +61,48 @@ def configure_logging(app):
)
else:
formatter = logging.Formatter(
"[%(asctime)s] %(levelname)s: %(message)s", datefmt="%Y-%m-%d %H:%M:%S"
"[%(asctime)s] %(levelname)s [%(correlation_id)s]: %(message)s",
datefmt="%Y-%m-%d %H:%M:%S"
)
handler.setFormatter(formatter)
console_handler.setFormatter(formatter)
file_handler.setFormatter(formatter)
# Remove existing handlers and add our configured handler
# Remove existing handlers and add our configured handlers
app.logger.handlers.clear()
app.logger.addHandler(handler)
app.logger.addHandler(console_handler)
app.logger.addHandler(file_handler)
# Add filter to inject correlation ID
# This filter will be added to ALL loggers to ensure consistency
class CorrelationIdFilter(logging.Filter):
def filter(self, record):
# Get correlation ID from Flask's g object, or use fallback
# Handle case where we're outside of request context
if not hasattr(record, 'correlation_id'):
try:
from flask import has_request_context
if has_request_context():
record.correlation_id = getattr(g, 'correlation_id', 'no-request')
else:
record.correlation_id = 'init'
except (RuntimeError, AttributeError):
record.correlation_id = 'init'
return True
# Apply filter to Flask's app logger
correlation_filter = CorrelationIdFilter()
app.logger.addFilter(correlation_filter)
# Also apply to the root logger to catch all logging calls
root_logger = logging.getLogger()
root_logger.addFilter(correlation_filter)
def add_correlation_id():
"""Generate and store correlation ID for the current request"""
if not hasattr(g, 'correlation_id'):
g.correlation_id = str(uuid.uuid4())
def create_app(config=None):
@@ -71,11 +125,14 @@ def create_app(config=None):
# Configure logging
configure_logging(app)
# Initialize database
from starpunk.database import init_db
# Initialize database schema
from starpunk.database import init_db, init_pool
init_db(app)
# Initialize connection pool
init_pool(app)
# Initialize FTS index if needed
from pathlib import Path
from starpunk.search import has_fts_table, rebuild_fts_index
@@ -106,24 +163,16 @@ def create_app(config=None):
register_routes(app)
# Error handlers
@app.errorhandler(404)
def not_found(error):
from flask import render_template, request
# Request middleware - Add correlation ID to each request
@app.before_request
def before_request():
"""Add correlation ID to request context for tracing"""
add_correlation_id()
# Return HTML for browser requests, JSON for API requests
if request.path.startswith("/api/"):
return {"error": "Not found"}, 404
return render_template("404.html"), 404
# Register centralized error handlers
from starpunk.errors import register_error_handlers
@app.errorhandler(500)
def server_error(error):
from flask import render_template, request
# Return HTML for browser requests, JSON for API requests
if request.path.startswith("/api/"):
return {"error": "Internal server error"}, 500
return render_template("500.html"), 500
register_error_handlers(app)
# Health check endpoint for containers and monitoring
@app.route("/health")
@@ -178,5 +227,5 @@ def create_app(config=None):
# Package version (Semantic Versioning 2.0.0)
# See docs/standards/versioning-strategy.md for details
__version__ = "1.1.0"
__version_info__ = (1, 1, 0)
__version__ = "1.1.1"
__version_info__ = (1, 1, 1)