Coverage for app / backend / src / app.py: 0%
66 statements
« prev ^ index » next coverage.py v7.13.2, created at 2026-02-03 06:18 +0000
« prev ^ index » next coverage.py v7.13.2, created at 2026-02-03 06:18 +0000
1import logging
2import signal
3import sys
4from os import environ
5from tempfile import TemporaryDirectory
6from types import TracebackType
8# these two lines need to be at the top of the file before we span child processes
9# this temp dir will be destroyed when prometheus_multiproc_dir is destroyed, aka at the end of the program.
10# Also note that this should only be done in the main process.
11if __name__ == "__main__":
12 prometheus_multiproc_dir = TemporaryDirectory()
13 environ["PROMETHEUS_MULTIPROC_DIR"] = prometheus_multiproc_dir.name
14# ruff: noqa: E402
16import sentry_sdk
17from sentry_sdk.integrations import excepthook
18from sqlalchemy.sql import text
20from couchers.config import check_config, config
21from couchers.db import apply_migrations, session_scope
22from couchers.experimentation import setup_experimentation
23from couchers.i18n.localize import get_main_i18next
24from couchers.jobs.worker import start_jobs_scheduler, start_jobs_worker
25from couchers.metrics import create_prometheus_server
26from couchers.server import create_main_server, create_media_server
27from couchers.tracing import setup_tracing
28from dummy_data import add_dummy_data
30check_config(config)
32logging.basicConfig(
33 format="[%(process)5d:%(thread)20d] %(asctime)s: %(name)s:%(lineno)d: %(message)s", level=logging.INFO
34)
35logger = logging.getLogger(__name__)
38def log_unhandled_exception(
39 exc_type: type[BaseException],
40 exc_value: BaseException,
41 exc_traceback: TracebackType | None,
42) -> None:
43 """Make sure that any unhandled exceptions will write to the logs"""
44 if issubclass(exc_type, KeyboardInterrupt):
45 # call the default excepthook saved at __excepthook__
46 sys.__excepthook__(exc_type, exc_value, exc_traceback)
47 return
48 logger.critical("Unhandled exception", exc_info=(exc_type, exc_value, exc_traceback))
51def common_init() -> None:
52 sys.excepthook = log_unhandled_exception
54 if config["SENTRY_ENABLED"]:
55 # Sends exception tracebacks to Sentry, a cloud service for collecting exceptions
56 sentry_sdk.init(
57 config["SENTRY_URL"],
58 traces_sample_rate=0.0,
59 environment=config["COOKIE_DOMAIN"],
60 release=config["VERSION"],
61 # The global excepthook picks up already handled gRPC errors (e.g. grpc.StatusCode.NOT_FOUND)
62 disabled_integrations=[
63 excepthook.ExcepthookIntegration(),
64 ],
65 )
67 logger.info("Checking DB connection")
68 with session_scope() as session:
69 res = session.execute(text("SELECT 42;"))
70 if list(res) != [(42,)]:
71 raise Exception("Failed to connect to DB")
74def main() -> None:
75 # used to export metrics
76 create_prometheus_server(8000)
78 logger.info("Running DB migrations")
80 apply_migrations()
82 get_main_i18next() # Force eager loading of translations
84 if config["ADD_DUMMY_DATA"]:
85 add_dummy_data()
87 logger.info("Starting")
89 if config["ROLE"] in ["scheduler", "all"]:
90 start_jobs_scheduler()
92 if config["ROLE"] in ["worker", "all"]:
93 for _ in range(config["BACKGROUND_WORKER_COUNT"]):
94 start_jobs_worker()
96 setup_tracing()
98 # Initialize the experimentation framework for feature flags in the main process.
99 # IMPORTANT: This MUST be called AFTER worker processes are spawned (above).
100 # The underlying SDK uses internal threading that doesn't survive fork().
101 # Worker processes initialize their own instance in _run_forever().
102 setup_experimentation()
104 if config["ROLE"] in ["api", "all"]:
105 server = create_main_server(port=1751)
106 server.start()
107 media_server = create_media_server(port=1753)
108 media_server.start()
109 logger.info("Serving on 1751 (secure) and 1753 (media)")
111 logger.info("App waiting for signal...")
113 signal.pause()
116if __name__ == "__main__":
117 common_init()
118 main()
119elif __name__ == "__mp_main__": # processes created via multiprocessing
120 common_init()