Coverage for src/app.py: 0%

58 statements  

« prev     ^ index     » next       coverage.py v7.11.0, created at 2025-12-20 11:53 +0000

1import logging 

2import signal 

3import sys 

4from os import environ 

5from tempfile import TemporaryDirectory 

6 

7from couchers.i18n.i18n import get_translations 

8 

9# these two lines need to be at the top of the file before we span child processes 

10# this temp dir will be destroyed when prometheus_multiproc_dir is destroyed, aka at the end of the program 

11prometheus_multiproc_dir = TemporaryDirectory() 

12environ["PROMETHEUS_MULTIPROC_DIR"] = prometheus_multiproc_dir.name 

13# ruff: noqa: E402 

14 

15import sentry_sdk 

16from sentry_sdk.integrations import argv, atexit, dedupe, modules, stdlib, threading 

17from sentry_sdk.integrations import logging as sentry_logging 

18from sqlalchemy.sql import text 

19 

20from couchers.config import check_config, config 

21from couchers.db import apply_migrations, session_scope 

22from couchers.experimentation import setup_experimentation 

23from couchers.jobs.worker import start_jobs_scheduler, start_jobs_worker 

24from couchers.metrics import create_prometheus_server 

25from couchers.server import create_main_server, create_media_server 

26from couchers.tracing import setup_tracing 

27from dummy_data import add_dummy_data 

28 

29check_config(config) 

30 

31logging.basicConfig( 

32 format="[%(process)5d:%(thread)20d] %(asctime)s: %(name)s:%(lineno)d: %(message)s", level=logging.INFO 

33) 

34logger = logging.getLogger(__name__) 

35 

36if config["SENTRY_ENABLED"]: 

37 # Sends exception tracebacks to Sentry, a cloud service for collecting exceptions 

38 sentry_sdk.init( 

39 config["SENTRY_URL"], 

40 traces_sample_rate=0.0, 

41 environment=config["COOKIE_DOMAIN"], 

42 release=config["VERSION"], 

43 default_integrations=False, 

44 integrations=[ 

45 # we need to manually list out the integrations, there is no other way of disabling the global excepthook integration 

46 # we want to disable that because it seems to be picking up already handled gRPC errors (e.g. grpc.StatusCode.NOT_FOUND) 

47 argv.ArgvIntegration(), 

48 atexit.AtexitIntegration(), 

49 dedupe.DedupeIntegration(), 

50 sentry_logging.LoggingIntegration(), 

51 modules.ModulesIntegration(), 

52 stdlib.StdlibIntegration(), 

53 threading.ThreadingIntegration(), 

54 ], 

55 ) 

56 

57# used to export metrics 

58create_prometheus_server(8000) 

59 

60 

61def log_unhandled_exception(exc_type, exc_value, exc_traceback): 

62 """Make sure that any unhandled exceptions will write to the logs""" 

63 if issubclass(exc_type, KeyboardInterrupt): 

64 # call the default excepthook saved at __excepthook__ 

65 sys.__excepthook__(exc_type, exc_value, exc_traceback) 

66 return 

67 logger.critical("Unhandled exception", exc_info=(exc_type, exc_value, exc_traceback)) 

68 

69 

70sys.excepthook = log_unhandled_exception 

71 

72logger.info("Checking DB connection") 

73 

74with session_scope() as session: 

75 res = session.execute(text("SELECT 42;")) 

76 if list(res) != [(42,)]: 

77 raise Exception("Failed to connect to DB") 

78 

79logger.info("Running DB migrations") 

80 

81apply_migrations() 

82 

83get_translations() 

84 

85if config["ADD_DUMMY_DATA"]: 

86 add_dummy_data() 

87 

88logger.info("Starting") 

89 

90if config["ROLE"] in ["scheduler", "all"]: 

91 scheduler = start_jobs_scheduler() 

92 

93if config["ROLE"] in ["worker", "all"]: 

94 for _ in range(config["BACKGROUND_WORKER_COUNT"]): 

95 start_jobs_worker() 

96 

97setup_tracing() 

98 

99# Initialize experimentation framework for feature flags in the main process. 

100# IMPORTANT: This MUST be called AFTER worker processes are spawned (above). 

101# The underlying SDK uses internal threading that doesn't survive fork(). 

102# Worker processes initialize their own instance in _run_forever(). 

103setup_experimentation() 

104 

105if config["ROLE"] in ["api", "all"]: 

106 server = create_main_server(port=1751) 

107 server.start() 

108 media_server = create_media_server(port=1753) 

109 media_server.start() 

110 logger.info("Serving on 1751 (secure) and 1753 (media)") 

111 

112logger.info("App waiting for signal...") 

113 

114signal.pause()