"""add log_events — stored log events for diagnostics (E22 S3) Revision ID: 0086_log_events Revises: 0085_users_last_login_at Create Date: 2026-09-17 Written by app/logsink.py's background writer (WARNING and above, request lines), pruned by the log_gc job after `log_retention_days`. Sized against prod on 2026-09-16: ~2k rows on a normal day, ~14k on a YouTube-RSS-outage day before flood control — a few MB over the 7-day window. `user_id` carries no foreign key on purpose (see models.LogEvent). """ import sqlalchemy as sa from alembic import op from sqlalchemy.dialects import postgresql revision: str = "0086_log_events" down_revision: str | None = "0085_users_last_login_at" branch_labels: str | None = None depends_on: str | None = None def upgrade() -> None: op.create_table( "log_events", sa.Column("id", sa.BigInteger(), primary_key=True), sa.Column("ts", sa.DateTime(timezone=True), nullable=False), sa.Column("level", sa.String(10), nullable=False), sa.Column("sev", sa.SmallInteger(), nullable=False), sa.Column("source", sa.String(16), nullable=False), sa.Column("logger", sa.String(128), nullable=False), sa.Column("event", sa.String(64), nullable=True), sa.Column("msg", sa.Text(), nullable=False), sa.Column("attrs", postgresql.JSONB(), nullable=True), sa.Column("exc", sa.Text(), nullable=True), sa.Column("request_id", sa.String(64), nullable=True), sa.Column("user_id", sa.Integer(), nullable=True), sa.Column("debug_session_id", sa.String(64), nullable=True), sa.Column("job_id", sa.Integer(), nullable=True), sa.Column("version", sa.String(64), nullable=True), ) op.create_index("ix_log_events_ts", "log_events", ["ts"]) op.create_index("ix_log_events_request_id", "log_events", ["request_id"]) op.create_index("ix_log_events_user_ts", "log_events", ["user_id", "ts"]) def downgrade() -> None: op.drop_table("log_events")