Coverage for app / backend / src / couchers / models / logging.py: 100%
45 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-19 14:14 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-19 14:14 +0000
1import enum
2from datetime import datetime
3from typing import Any
5from sqlalchemy import BigInteger, Boolean, DateTime, Enum, Float, Index, String, func
6from sqlalchemy import LargeBinary as Binary
7from sqlalchemy.dialects.postgresql import JSONB
8from sqlalchemy.orm import Mapped, mapped_column
9from sqlalchemy.sql import expression
11from couchers.config import config
12from couchers.models.base import Base
15class EventSource(enum.Enum):
16 backend = enum.auto()
17 frontend = enum.auto()
20class APICall(Base, kw_only=True):
21 """
22 API call logs
23 """
25 __tablename__ = "api_calls"
26 __table_args__ = {"schema": "logging"}
28 id: Mapped[int] = mapped_column(BigInteger, primary_key=True, init=False)
30 # whether the call was made using an api key or session cookies
31 is_api_key: Mapped[bool] = mapped_column(Boolean, server_default=expression.false())
33 # backend version (normally e.g. develop-31469e3), allows us to figure out which proto definitions were used
34 # note that `default` is a python side default, not hardcoded into DB schema
35 version: Mapped[str] = mapped_column(String, default=config["VERSION"])
37 # approximate time of the call
38 time: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), init=False)
40 # the method call name, e.g. "/org.couchers.api.core.API/ListFriends"
41 method: Mapped[str] = mapped_column(String)
43 # gRPC status code name, e.g. FAILED_PRECONDITION, None if success
44 status_code: Mapped[str | None] = mapped_column(String, default=None)
46 # handler duration (excluding serialization, etc)
47 duration: Mapped[float] = mapped_column(Float)
49 # user_id of caller, None means not logged in
50 user_id: Mapped[int | None] = mapped_column(BigInteger, default=None)
52 # sanitized request bytes
53 request: Mapped[bytes | None] = mapped_column(Binary, default=None)
55 # sanitized response bytes
56 response: Mapped[bytes | None] = mapped_column(Binary, default=None)
58 # whether response bytes have been truncated
59 response_truncated: Mapped[bool] = mapped_column(Boolean, server_default=expression.false())
61 # the exception traceback, if any
62 traceback: Mapped[str | None] = mapped_column(String, default=None)
64 # human readable perf report
65 perf_report: Mapped[str | None] = mapped_column(String, default=None)
67 # details of the browser, if available
68 ip_address: Mapped[str | None] = mapped_column(String, default=None)
69 user_agent: Mapped[str | None] = mapped_column(String, default=None)
71 sofa: Mapped[str | None] = mapped_column(String, default=None)
74class EventLog(Base, kw_only=True):
75 """
76 Analytics event log for tracking user behavior and business metrics.
78 Append-only table for ELT extraction. Do not query this table for user-facing features.
79 """
81 __tablename__ = "event_log"
83 id: Mapped[int] = mapped_column(BigInteger, primary_key=True, init=False)
85 # when the row was inserted into the DB
86 created: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), init=False)
88 # when the event actually happened (same as created for backend; may differ for frontend events)
89 occurred: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), init=False)
91 # backend/frontend version
92 version: Mapped[str] = mapped_column(String, default=config["VERSION"])
94 # sofa, null for background/system events
95 sofa: Mapped[str | None] = mapped_column(String, default=None)
97 # hierarchical event type, e.g. "host_request.sent", "account.login"
98 event_type: Mapped[str] = mapped_column(String)
100 # user who triggered the event, nullable for system events
101 user_id: Mapped[int | None] = mapped_column(BigInteger, default=None)
103 # flexible event-specific properties
104 properties: Mapped[dict[str, Any]] = mapped_column(JSONB)
106 # numeric value (duration, count, etc.)
107 value: Mapped[float] = mapped_column(Float, server_default="1.0", default=1.0)
109 # where the event originated
110 source: Mapped[EventSource] = mapped_column(Enum(EventSource))
112 __table_args__ = (
113 Index("ix_logging_event_log_created", "created"),
114 Index("ix_logging_event_log_event_type_created", "event_type", "created"),
115 Index("ix_logging_event_log_user_id_created", "user_id", "created"),
116 {"schema": "logging"},
117 )