##// END OF EJS Templates
exception-tracker: store event sending exception for easier event fail debugging.
marcink -
r3007:a41e7b95 default
parent child Browse files
Show More
@@ -1,70 +1,74 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2012-2018 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20 import sys
21 21 import logging
22 22
23 23 from rhodecode.integrations.registry import IntegrationTypeRegistry
24 24 from rhodecode.integrations.types import webhook, slack, hipchat, email, base
25 from rhodecode.lib.exc_tracking import store_exception
26
25 27 log = logging.getLogger(__name__)
26 28
27 29
28 30 # TODO: dan: This is currently global until we figure out what to do about
29 31 # VCS's not having a pyramid context - move it to pyramid app configuration
30 32 # includeme level later to allow per instance integration setup
31 33 integration_type_registry = IntegrationTypeRegistry()
32 34
33 35 integration_type_registry.register_integration_type(
34 36 webhook.WebhookIntegrationType)
35 37 integration_type_registry.register_integration_type(
36 38 slack.SlackIntegrationType)
37 39 integration_type_registry.register_integration_type(
38 40 hipchat.HipchatIntegrationType)
39 41 integration_type_registry.register_integration_type(
40 42 email.EmailIntegrationType)
41 43
42 44
43 45 # dummy EE integration to show users what we have in EE edition
44 46 integration_type_registry.register_integration_type(
45 47 base.EEIntegration('Jira Issues integration', 'jira'))
46 48 integration_type_registry.register_integration_type(
47 49 base.EEIntegration('Redmine Tracker integration', 'redmine'))
48 50 integration_type_registry.register_integration_type(
49 51 base.EEIntegration('Jenkins CI integration', 'jenkins'))
50 52
51 53
52 54 def integrations_event_handler(event):
53 55 """
54 56 Takes an event and passes it to all enabled integrations
55 57 """
56 58 from rhodecode.model.integration import IntegrationModel
57 59
58 60 integration_model = IntegrationModel()
59 61 integrations = integration_model.get_for_event(event)
60 62 for integration in integrations:
61 63 try:
62 64 integration_model.send_event(integration, event)
63 65 except Exception:
66 exc_info = sys.exc_info()
67 store_exception(id(exc_info), exc_info)
64 68 log.exception(
65 69 'failure occurred when sending event %s to integration %s' % (
66 70 event, integration))
67 71
68 72
69 73 def includeme(config):
70 74 config.include('rhodecode.integrations.routes')
@@ -1,146 +1,153 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import os
22 22 import time
23 23 import datetime
24 24 import msgpack
25 25 import logging
26 26 import traceback
27 27 import tempfile
28 28
29 29
30 30 log = logging.getLogger(__name__)
31 31
32 32 # NOTE: Any changes should be synced with exc_tracking at vcsserver.lib.exc_tracking
33 33 global_prefix = 'rhodecode'
34 34
35 35
36 36 def exc_serialize(exc_id, tb, exc_type):
37 37
38 38 data = {
39 39 'version': 'v1',
40 40 'exc_id': exc_id,
41 41 'exc_utc_date': datetime.datetime.utcnow().isoformat(),
42 42 'exc_timestamp': repr(time.time()),
43 43 'exc_message': tb,
44 44 'exc_type': exc_type,
45 45 }
46 46 return msgpack.packb(data), data
47 47
48 48
49 49 def exc_unserialize(tb):
50 50 return msgpack.unpackb(tb)
51 51
52 52
53 53 def get_exc_store():
54 54 """
55 55 Get and create exception store if it's not existing
56 56 """
57 57 exc_store_dir = 'rc_exception_store_v1'
58 58 # fallback
59 59 _exc_store_path = os.path.join(tempfile.gettempdir(), exc_store_dir)
60 60
61 61 exc_store_dir = '' # TODO: need a persistent cross instance store here
62 62 if exc_store_dir:
63 63 _exc_store_path = os.path.join(exc_store_dir, exc_store_dir)
64 64
65 65 _exc_store_path = os.path.abspath(_exc_store_path)
66 66 if not os.path.isdir(_exc_store_path):
67 67 os.makedirs(_exc_store_path)
68 68 log.debug('Initializing exceptions store at %s', _exc_store_path)
69 69 return _exc_store_path
70 70
71 71
72 72 def _store_exception(exc_id, exc_info, prefix):
73 73 exc_type, exc_value, exc_traceback = exc_info
74 74 tb = ''.join(traceback.format_exception(
75 75 exc_type, exc_value, exc_traceback, None))
76 76
77 77 exc_type_name = exc_type.__name__
78 78 exc_store_path = get_exc_store()
79 79 exc_data, org_data = exc_serialize(exc_id, tb, exc_type_name)
80 80 exc_pref_id = '{}_{}_{}'.format(exc_id, prefix, org_data['exc_timestamp'])
81 81 if not os.path.isdir(exc_store_path):
82 82 os.makedirs(exc_store_path)
83 83 stored_exc_path = os.path.join(exc_store_path, exc_pref_id)
84 84 with open(stored_exc_path, 'wb') as f:
85 85 f.write(exc_data)
86 86 log.debug('Stored generated exception %s as: %s', exc_id, stored_exc_path)
87 87
88 88
89 89 def store_exception(exc_id, exc_info, prefix=global_prefix):
90 """
91 Example usage::
92
93 exc_info = sys.exc_info()
94 store_exception(id(exc_info), exc_info)
95 """
96
90 97 try:
91 98 _store_exception(exc_id=exc_id, exc_info=exc_info, prefix=prefix)
92 99 except Exception:
93 100 log.exception('Failed to store exception `%s` information', exc_id)
94 101 # there's no way this can fail, it will crash server badly if it does.
95 102 pass
96 103
97 104
98 105 def _find_exc_file(exc_id, prefix=global_prefix):
99 106 exc_store_path = get_exc_store()
100 107 if prefix:
101 108 exc_id = '{}_{}'.format(exc_id, prefix)
102 109 else:
103 110 # search without a prefix
104 111 exc_id = '{}'.format(exc_id)
105 112
106 113 # we need to search the store for such start pattern as above
107 114 for fname in os.listdir(exc_store_path):
108 115 if fname.startswith(exc_id):
109 116 exc_id = os.path.join(exc_store_path, fname)
110 117 break
111 118 continue
112 119 else:
113 120 exc_id = None
114 121
115 122 return exc_id
116 123
117 124
118 125 def _read_exception(exc_id, prefix):
119 126 exc_id_file_path = _find_exc_file(exc_id=exc_id, prefix=prefix)
120 127 if exc_id_file_path:
121 128 with open(exc_id_file_path, 'rb') as f:
122 129 return exc_unserialize(f.read())
123 130 else:
124 131 log.debug('Exception File `%s` not found', exc_id_file_path)
125 132 return None
126 133
127 134
128 135 def read_exception(exc_id, prefix=global_prefix):
129 136 try:
130 137 return _read_exception(exc_id=exc_id, prefix=prefix)
131 138 except Exception:
132 139 log.exception('Failed to read exception `%s` information', exc_id)
133 140 # there's no way this can fail, it will crash server badly if it does.
134 141 return None
135 142
136 143
137 144 def delete_exception(exc_id, prefix=global_prefix):
138 145 try:
139 146 exc_id_file_path = _find_exc_file(exc_id, prefix=prefix)
140 147 if exc_id_file_path:
141 148 os.remove(exc_id_file_path)
142 149
143 150 except Exception:
144 151 log.exception('Failed to remove exception `%s` information', exc_id)
145 152 # there's no way this can fail, it will crash server badly if it does.
146 153 pass
General Comments 0
You need to be logged in to leave comments. Login now