Welcome to mirror list, hosted at ThFree Co, Russian Federation.

__init__.py « errortracking « plugins « octoprint « src - github.com/OctoPrint/OctoPrint.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 67ffa2a231b8ca646bde25c58b2b4b7795ed3e99 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
__license__ = "GNU Affero General Public License http://www.gnu.org/licenses/agpl.html"
__copyright__ = "Copyright (C) 2019 The OctoPrint Project - Released under terms of the AGPLv3 License"

import errno
import logging

import requests.exceptions
import serial
import tornado.websocket
from flask import jsonify
from flask_babel import gettext

import octoprint.plugin
from octoprint.util import get_fully_qualified_classname as fqcn  # noqa: F401
from octoprint.util.version import (
    get_octoprint_version_string,
    is_released_octoprint_version,
)

SENTRY_URL_SERVER = (
    "https://f4265899b96044f3a31e6414c315a0d1@o118517.ingest.sentry.io/1373987"
)
SENTRY_URL_COREUI = (
    "https://2f33808cb0cb4027afd005c35eebfeb4@o118517.ingest.sentry.io/1374096"
)

SETTINGS_DEFAULTS = {
    "enabled": False,
    "enabled_unreleased": False,
    "unique_id": None,
    "url_server": SENTRY_URL_SERVER,
    "url_coreui": SENTRY_URL_COREUI,
}

IGNORED_EXCEPTIONS = [
    # serial exceptions in octoprint.util.comm
    (
        serial.SerialException,
        lambda exc, logger, plugin, cb: logger == "octoprint.util.comm",
    ),
    # KeyboardInterrupts
    KeyboardInterrupt,
    # IOErrors of any kind due to a full file system
    (
        IOError,
        lambda exc, logger, plugin, cb: exc.errorgetattr(exc, "errno")  # noqa: B009
        and exc.errno in (getattr(errno, "ENOSPC"),),  # noqa: B009
    ),
    # RequestExceptions of any kind
    requests.exceptions.RequestException,
    # Tornado WebSocketErrors of any kind
    tornado.websocket.WebSocketError,
    # Anything triggered by or in third party plugin Astroprint
    (
        Exception,
        lambda exc, logger, plugin, cb: logger.startswith("octoprint.plugins.astroprint")
        or plugin == "astroprint"
        or cb.startswith("octoprint_astroprint."),
    ),
]

try:
    # noinspection PyUnresolvedReferences
    from octoprint.plugins.backup import InsufficientSpace

    # if the backup plugin is enabled, ignore InsufficientSpace errors from it as well
    IGNORED_EXCEPTIONS.append(InsufficientSpace)

    del InsufficientSpace
except ImportError:
    pass


class ErrorTrackingPlugin(
    octoprint.plugin.SettingsPlugin,
    octoprint.plugin.AssetPlugin,
    octoprint.plugin.TemplatePlugin,
    octoprint.plugin.SimpleApiPlugin,
):
    def get_template_configs(self):
        return [
            {
                "type": "settings",
                "name": gettext("Error Tracking"),
                "template": "errortracking_settings.jinja2",
                "custom_bindings": False,
            },
            {"type": "generic", "template": "errortracking_javascripts.jinja2"},
        ]

    def get_template_vars(self):
        enabled = self._settings.get_boolean(["enabled"])
        enabled_unreleased = self._settings.get_boolean(["enabled_unreleased"])

        return {
            "enabled": _is_enabled(enabled, enabled_unreleased),
            "unique_id": self._settings.get(["unique_id"]),
            "url_coreui": self._settings.get(["url_coreui"]),
        }

    def get_assets(self):
        return {"js": ["js/sentry.min.js", "js/errortracking.js"]}

    def get_settings_defaults(self):
        return SETTINGS_DEFAULTS

    def on_settings_save(self, data):
        old_enabled = _is_enabled(
            self._settings.get_boolean(["enabled"]),
            self._settings.get_boolean(["enabled_unreleased"]),
        )

        octoprint.plugin.SettingsPlugin.on_settings_save(self, data)

        enabled = _is_enabled(
            self._settings.get_boolean(["enabled"]),
            self._settings.get_boolean(["enabled_unreleased"]),
        )

        if old_enabled != enabled:
            _enable_errortracking()

    def on_api_get(self, request):
        return jsonify(**self.get_template_vars())


_enabled = False


def _enable_errortracking():
    # this is a bit hackish, but we want to enable error tracking as early in the platform lifecycle as possible
    # and hence can't wait until our implementation is initialized and injected with settings

    from octoprint.settings import settings

    global _enabled

    if _enabled:
        return

    version = get_octoprint_version_string()

    s = settings()
    plugin_defaults = {"plugins": {"errortracking": SETTINGS_DEFAULTS}}

    enabled = s.getBoolean(
        ["plugins", "errortracking", "enabled"], defaults=plugin_defaults
    )
    enabled_unreleased = s.getBoolean(
        ["plugins", "errortracking", "enabled_unreleased"], defaults=plugin_defaults
    )
    url_server = s.get(
        ["plugins", "errortracking", "url_server"], defaults=plugin_defaults
    )
    unique_id = s.get(["plugins", "errortracking", "unique_id"], defaults=plugin_defaults)
    if unique_id is None:
        import uuid

        unique_id = str(uuid.uuid4())
        s.set(
            ["plugins", "errortracking", "unique_id"], unique_id, defaults=plugin_defaults
        )
        s.save()

    if _is_enabled(enabled, enabled_unreleased):
        import sentry_sdk

        from octoprint.plugin import plugin_manager

        def _before_send(event, hint):
            if "exc_info" not in hint:
                # we only want exceptions
                return None

            handled = True
            logger = event.get("logger", "")
            plugin = event.get("extra", {}).get("plugin", None)
            callback = event.get("extra", {}).get("callback", None)

            for ignore in IGNORED_EXCEPTIONS:
                if isinstance(ignore, tuple):
                    ignored_exc, matcher = ignore
                else:
                    ignored_exc = ignore
                    matcher = lambda *args: True

                exc = hint["exc_info"][1]
                if isinstance(exc, ignored_exc) and matcher(
                    exc, logger, plugin, callback
                ):
                    # exception ignored for logger, plugin and/or callback
                    return None

                elif isinstance(ignore, type):
                    if isinstance(hint["exc_info"][1], ignore):
                        # exception ignored
                        return None

            if event.get("exception") and event["exception"].get("values"):
                handled = not any(
                    map(
                        lambda x: x.get("mechanism")
                        and not x["mechanism"].get("handled", True),
                        event["exception"]["values"],
                    )
                )

            if handled:
                # error is handled, restrict further based on logger
                if logger != "" and not (
                    logger.startswith("octoprint.") or logger.startswith("tornado.")
                ):
                    # we only want errors logged by loggers octoprint.* or tornado.*
                    return None

                if logger.startswith("octoprint.plugins."):
                    plugin_id = logger.split(".")[2]
                    plugin_info = plugin_manager().get_plugin_info(plugin_id)
                    if plugin_info is None or not plugin_info.bundled:
                        # we only want our active bundled plugins
                        return None

                if plugin is not None:
                    plugin_info = plugin_manager().get_plugin_info(plugin)
                    if plugin_info is None or not plugin_info.bundled:
                        # we only want our active bundled plugins
                        return None

            return event

        sentry_sdk.init(url_server, release=version, before_send=_before_send)

        with sentry_sdk.configure_scope() as scope:
            scope.user = {"id": unique_id}

        logging.getLogger("octoprint.plugins.errortracking").info(
            "Initialized error tracking"
        )
        _enabled = True


def _is_enabled(enabled, enabled_unreleased):
    return enabled and (enabled_unreleased or is_released_octoprint_version())


def __plugin_enable__():
    _enable_errortracking()


__plugin_name__ = "Error Tracking"
__plugin_author__ = "Gina Häußge"
__plugin_license__ = "AGPLv3"
__plugin_pythoncompat__ = ">=3.7,<4"
__plugin_implementation__ = ErrorTrackingPlugin()