Coverage for app/venv/lib/python3.14/site-packages/weblate/utils/errors.py: 53%

67 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-10-07 07:15 +0000

1# Copyright © Michal Čihař <michal@weblate.org> 

2# 

3# SPDX-License-Identifier: GPL-3.0-or-later 

4from __future__ import annotations 

5 

6import logging 

7import sys 

8from json import JSONDecodeError 

9from typing import TYPE_CHECKING, Literal 

10 

11import sentry_sdk 

12from django.conf import settings 

13from django.utils.translation import get_language 

14from sentry_sdk.integrations.celery import CeleryIntegration 

15from sentry_sdk.integrations.django import DjangoIntegration 

16from sentry_sdk.integrations.logging import ignore_logger 

17from sentry_sdk.integrations.redis import RedisIntegration 

18 

19import weblate.utils.version 

20 

21if TYPE_CHECKING: 21 ↛ 22line 21 didn't jump to line 22 because the condition on line 21 was never true

22 from weblate.auth.models import AuthenticatedHttpRequest 

23 

24ERROR_LOGGER = "weblate.errors" 

25 

26LOGGER = logging.getLogger(ERROR_LOGGER) 

27 

28try: 

29 import rollbar 

30 

31 HAS_ROLLBAR = True 

32except ImportError: 

33 HAS_ROLLBAR = False 

34 

35 

36def report_error( 

37 cause: str, 

38 *, 

39 level: Literal[ 

40 "fatal", "critical", "error", "warning", "info", "debug" 

41 ] = "warning", 

42 skip_sentry: bool = False, 

43 print_tb: bool = False, 

44 extra_log: str | None = None, 

45 project=None, 

46 message: bool = False, 

47) -> None: 

48 """ 

49 Report errors. 

50 

51 This can be used for store exceptions in error reporting solutions as rollbar while 

52 handling error gracefully and giving user cleaner message. 

53 """ 

54 __traceback_hide__ = True # noqa: F841 

55 if HAS_ROLLBAR and hasattr(settings, "ROLLBAR"): 55 ↛ 56line 55 didn't jump to line 56 because the condition on line 55 was never true

56 rollbar.report_exc_info(level=level) 

57 

58 if not skip_sentry and settings.SENTRY_DSN: 58 ↛ 59line 58 didn't jump to line 59 because the condition on line 58 was never true

59 sentry_sdk.set_tag("cause", cause) 

60 if project is not None: 

61 sentry_sdk.set_tag("project", project.slug) 

62 sentry_sdk.set_tag("user.locale", get_language()) 

63 sentry_sdk.set_level(level) 

64 if message: 

65 sentry_sdk.capture_message(cause) 

66 else: 

67 sentry_sdk.capture_exception() 

68 

69 log = getattr(LOGGER, level) 

70 

71 error = sys.exc_info()[1] 

72 

73 # Include JSON document if available. It might be missing 

74 # when the error is raised from requests. 

75 if isinstance(error, JSONDecodeError) and not extra_log and hasattr(error, "doc"): 75 ↛ 76line 75 didn't jump to line 76 because the condition on line 75 was never true

76 extra_log = repr(error.doc) 

77 

78 if error: 78 ↛ 80line 78 didn't jump to line 80 because the condition on line 78 was always true

79 log("%s: %s: %s", cause, error.__class__.__name__, error) 

80 if extra_log: 80 ↛ 81line 80 didn't jump to line 81 because the condition on line 80 was never true

81 if error: 

82 log("%s: %s: %s", cause, error.__class__.__name__, extra_log) 

83 else: 

84 log("%s: %s", cause, extra_log) 

85 if print_tb: 85 ↛ 87line 85 didn't jump to line 87 because the condition on line 85 was never true

86 # This is called from an exception handler 

87 LOGGER.exception(cause) # noqa: LOG004 

88 

89 

90def add_breadcrumb(category: str, message: str, level: str = "info", **data) -> None: 

91 # Add breadcrumb only if settings are already loaded, 

92 # we do not want to force loading settings early 

93 if not settings.configured or not getattr(settings, "SENTRY_DSN", None): 93 ↛ 95line 93 didn't jump to line 95 because the condition on line 93 was always true

94 return 

95 sentry_sdk.add_breadcrumb( 

96 category=category, message=message, level=level, data=data 

97 ) 

98 

99 

100def celery_base_data_hook(request: AuthenticatedHttpRequest, data) -> None: 

101 data["framework"] = "celery" 

102 

103 

104def init_sentry() -> None: 

105 integrations = [ 

106 CeleryIntegration(monitor_beat_tasks=settings.SENTRY_MONITOR_BEAT_TASKS), 

107 DjangoIntegration(), 

108 RedisIntegration(), 

109 ] 

110 

111 sentry_sdk.init( 

112 dsn=settings.SENTRY_DSN, 

113 integrations=integrations, 

114 auto_enabling_integrations=False, 

115 send_default_pii=settings.SENTRY_SEND_PII, 

116 release=weblate.utils.version.GIT_REVISION or weblate.utils.version.TAG_NAME, 

117 environment=settings.SENTRY_ENVIRONMENT, 

118 traces_sample_rate=settings.SENTRY_TRACES_SAMPLE_RATE, 

119 profiles_sample_rate=settings.SENTRY_PROFILES_SAMPLE_RATE, 

120 in_app_include=[ 

121 "weblate", 

122 "wlhosted", 

123 "wllegal", 

124 "weblate_fedora_messaging", 

125 "weblate_language_data", 

126 "translate", 

127 "translation_finder", 

128 ], 

129 attach_stacktrace=True, 

130 _experiments={"max_spans": 2000}, 

131 keep_alive=True, 

132 **settings.SENTRY_EXTRA_ARGS, 

133 ) 

134 # Ignore Weblate logging, those should trigger proper errors 

135 ignore_logger("weblate") 

136 ignore_logger("weblate.*") 

137 

138 

139def init_rollbar() -> None: 

140 rollbar.init(**settings.ROLLBAR) 

141 rollbar.BASE_DATA_HOOK = celery_base_data_hook 

142 LOGGER.info("configured Rollbar error collection") 

143 

144 

145def init_error_collection(celery=False) -> None: 

146 if settings.SENTRY_DSN: 146 ↛ 147line 146 didn't jump to line 147 because the condition on line 146 was never true

147 init_sentry() 

148 

149 if celery and HAS_ROLLBAR and hasattr(settings, "ROLLBAR"): 149 ↛ 150line 149 didn't jump to line 150 because the condition on line 149 was never true

150 init_rollbar()