def _get_environment_payload(self): payload = { "version": get_octoprint_version_string(), "os": self._environment["os"]["id"], "bits": self._environment["os"]["bits"], "python": self._environment["python"]["version"], "pip": self._environment["python"]["pip"], "cores": self._environment["hardware"]["cores"], "freq": self._environment["hardware"]["freq"], "ram": self._environment["hardware"]["ram"], } if ("plugins" in self._environment and "pi_support" in self._environment["plugins"]): payload["pi_model"] = self._environment["plugins"]["pi_support"][ "model"] if "octopi_version" in self._environment["plugins"]["pi_support"]: payload["octopi_version"] = self._environment["plugins"][ "pi_support"]["octopi_version"] if "octopiuptodate_build" in self._environment["plugins"][ "pi_support"]: payload["octopiuptodate_build"] = self._environment["plugins"][ "pi_support"]["octopiuptodate_build"] return payload
def _do_track(self, event, body=False, **kwargs): if not self._connectivity_checker.online: return if not self._settings.get_boolean(["enabled"]): return unique_id = self._settings.get(["unique_id"]) if not unique_id: return server = self._settings.get(["server"]) url = server.format(id=unique_id, event=event) # Don't print the URL or UUID! That would expose the UUID in forums/tickets # if pasted. It's okay for the user to know their uuid, but it shouldn't be shared. headers = { "User-Agent": "OctoPrint/{}".format(get_octoprint_version_string()) } try: params = urlencode(kwargs, doseq=True).replace("+", "%20") if body: requests.post(url, data=params, timeout=3.1, headers=headers) else: requests.get(url, params=params, timeout=3.1, headers=headers) self._logger.info("Sent tracking event {}, payload: {!r}".format( event, kwargs)) except Exception: if self._logger.isEnabledFor(logging.DEBUG): self._logger.exception( "Error while sending event to anonymous usage tracking") else: pass
def _do_track(self, event, **kwargs): if not self._connectivity_checker.online: return if not self._settings.get_boolean([b"enabled"]): return unique_id = self._settings.get([b"unique_id"]) if not unique_id: return server = self._settings.get([b"server"]) url = server.format(id=unique_id, event=event) # Don't print the URL or UUID! That would expose the UUID in forums/tickets # if pasted. It's okay for the user to know their uuid, but it shouldn't be shared. headers = {"User-Agent": "OctoPrint/{}".format(get_octoprint_version_string())} try: params = urlencode(kwargs, doseq=True).replace("+", "%20") requests.get(url, params=params, timeout=3.1, headers=headers) self._logger.info("Sent tracking event {}, payload: {!r}".format(event, kwargs)) except: if self._logger.isEnabledFor(logging.DEBUG): self._logger.exception("Error while sending event to anonymous usage tracking".format(url)) else: pass
def on_after_startup(self): import socket import platform import time self.octoprint_info.info({ 'octoprint_version': get_octoprint_version_string(), 'host': socket.gethostname(), 'platform': platform.system(), 'app_start': str(int(time.time())), }) pass
def view(): return jsonify( plugins=self._get_plugins(), repository=dict(available=self._repository_available, plugins=self._repository_plugins), os=get_os(), octoprint=get_octoprint_version_string(), pip=dict(available=self._pip_caller.available, version=self._pip_caller.version_string, install_dir=self._pip_caller.install_dir, use_user=self._pip_caller.use_user, virtual_env=self._pip_caller.virtual_env, additional_args=self._settings.get(["pip_args"]), python=sys.executable), safe_mode=safe_mode, online=self._connectivity_checker.online)
def view(): return jsonify(plugins=self._get_plugins(), repository=dict( available=self._repository_available, plugins=self._repository_plugins ), os=get_os(), octoprint=get_octoprint_version_string(), pip=dict( available=self._pip_caller.available, version=self._pip_caller.version_string, install_dir=self._pip_caller.install_dir, use_user=self._pip_caller.use_user, virtual_env=self._pip_caller.virtual_env, additional_args=self._settings.get(["pip_args"]), python=sys.executable ), safe_mode=safe_mode, online=self._connectivity_checker.online)
def _track_startup(self): if not self._settings.get_boolean(["events", "startup"]): return payload = dict(version=get_octoprint_version_string(), os=self._environment[b"os"][b"id"], python=self._environment[b"python"][b"version"], pip=self._environment[b"python"][b"pip"], cores=self._environment[b"hardware"][b"cores"], freq=self._environment[b"hardware"][b"freq"], ram=self._environment[b"hardware"][b"ram"]) if b"plugins" in self._environment and b"pi_support" in self._environment[b"plugins"]: payload[b"pi_model"] = self._environment[b"plugins"][b"pi_support"][b"model"] if b"octopi_version" in self._environment[b"plugins"][b"pi_support"]: payload[b"octopi_version"] = self._environment[b"plugins"][b"pi_support"][b"octopi_version"] self._track("startup", **payload)
def _get_environment_payload(self): payload = dict(version=get_octoprint_version_string(), os=self._environment["os"]["id"], python=self._environment["python"]["version"], pip=self._environment["python"]["pip"], cores=self._environment["hardware"]["cores"], freq=self._environment["hardware"]["freq"], ram=self._environment["hardware"]["ram"]) if "plugins" in self._environment and "pi_support" in self._environment[ "plugins"]: payload["pi_model"] = self._environment["plugins"]["pi_support"][ "model"] if "octopi_version" in self._environment["plugins"]["pi_support"]: payload["octopi_version"] = self._environment["plugins"][ "pi_support"]["octopi_version"] return payload
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 _create_backup(cls, name, exclude=None, settings=None, plugin_manager=None, datafolder=None, on_backup_start=None, on_backup_done=None, on_backup_error=None): exclude_by_default = ( "generated", "logs", "watched", ) try: if exclude is None: exclude = [] if not isinstance(exclude, list): exclude = list(exclude) if "timelapse" in exclude: exclude.append("timelapse_tmp") configfile = settings._configfile basedir = settings._basedir temporary_path = os.path.join(datafolder, ".{}".format(name)) final_path = os.path.join(datafolder, name) own_folder = datafolder defaults = [os.path.join(basedir, "config.yaml"),] + \ [os.path.join(basedir, folder) for folder in default_settings["folder"].keys()] # check how much many bytes we are about to backup size = os.stat(configfile).st_size for folder in default_settings["folder"].keys(): if folder in exclude or folder in exclude_by_default: continue size += cls._get_disk_size( settings.global_get_basefolder(folder), ignored=[ own_folder, ]) size += cls._get_disk_size(basedir, ignored=defaults + [ own_folder, ]) # since we can't know the compression ratio beforehand, we assume we need the same amount of space if not cls._free_space(os.path.dirname(temporary_path), size): raise InsufficientSpace() compression = zipfile.ZIP_DEFLATED if zlib else zipfile.ZIP_STORED if callable(on_backup_start): on_backup_start(name, temporary_path, exclude) with zipfile.ZipFile(temporary_path, mode="w", compression=compression, allowZip64=True) as zip: def add_to_zip(source, target, ignored=None): if ignored is None: ignored = [] if source in ignored: return if os.path.isdir(source): for entry in scandir(source): add_to_zip(entry.path, os.path.join(target, entry.name), ignored=ignored) elif os.path.isfile(source): zip.write(source, arcname=target) # add metadata metadata = dict(version=get_octoprint_version_string(), excludes=exclude) zip.writestr("metadata.json", json.dumps(metadata)) # backup current config file add_to_zip(configfile, "basedir/config.yaml", ignored=[ own_folder, ]) # backup configured folder paths for folder in default_settings["folder"].keys(): if folder in exclude or folder in exclude_by_default: continue add_to_zip(settings.global_get_basefolder(folder), "basedir/" + folder.replace("_", "/"), ignored=[ own_folder, ]) # backup anything else that might be lying around in our basedir add_to_zip(basedir, "basedir", ignored=defaults + [ own_folder, ]) # add list of installed plugins plugins = [] plugin_folder = settings.global_get_basefolder("plugins") for key, plugin in plugin_manager.plugins.items(): if plugin.bundled or ( isinstance(plugin.origin, FolderOrigin) and plugin.origin.folder == plugin_folder): # ignore anything bundled or from the plugins folder we already include in the backup continue plugins.append( dict(key=plugin.key, name=plugin.name, url=plugin.url)) if len(plugins): zip.writestr("plugin_list.json", json.dumps(plugins)) shutil.move(temporary_path, final_path) if callable(on_backup_done): on_backup_done(name, final_path, exclude) except: if callable(on_backup_error): exc_info = sys.exc_info() try: on_backup_error(name, exc_info) finally: del exc_info raise
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 = dict(plugins=dict(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 not "exc_info" in hint: # we only want exceptions return None handled = True logger = event.get("logger", "") plugin = event.get("extra", dict()).get("plugin", 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): # exception ignored for logger 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 x["mechanism"].get("handled", True) == False, 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 = dict(id=unique_id) logging.getLogger("octoprint.plugins.errortracking").info("Initialized error tracking") _enabled = True
def _create_backup(cls, name, exclude=None, settings=None, plugin_manager=None, datafolder=None, on_backup_start=None, on_backup_done=None, on_backup_error=None): try: if exclude is None: exclude = [] configfile = settings._configfile basedir = settings._basedir temporary_path = os.path.join(datafolder, ".{}".format(name)) final_path = os.path.join(datafolder, name) size = cls._get_disk_size(basedir) if not cls._free_space(os.path.dirname(temporary_path), size): raise InsufficientSpace() own_folder = datafolder defaults = [os.path.join(basedir, "config.yaml"),] + \ [os.path.join(basedir, folder) for folder in default_settings["folder"].keys()] compression = zipfile.ZIP_DEFLATED if zlib else zipfile.ZIP_STORED if callable(on_backup_start): on_backup_start(name, temporary_path, exclude) with zipfile.ZipFile(temporary_path, mode="w", compression=compression, allowZip64=True) as zip: def add_to_zip(source, target, ignored=None): if ignored is None: ignored = [] if source in ignored: return if os.path.isdir(source): for entry in scandir(source): add_to_zip(entry.path, os.path.join(target, entry.name), ignored=ignored) elif os.path.isfile(source): zip.write(source, arcname=target) # add metadata metadata = dict(version=get_octoprint_version_string(), excludes=exclude) zip.writestr("metadata.json", json.dumps(metadata)) # backup current config file add_to_zip(configfile, "basedir/config.yaml", ignored=[own_folder,]) # backup configured folder paths for folder in default_settings["folder"].keys(): if folder in exclude: continue if folder in ("generated", "logs", "watched",): continue add_to_zip(settings.global_get_basefolder(folder), "basedir/" + folder.replace("_", "/"), ignored=[own_folder,]) # backup anything else that might be lying around in our basedir add_to_zip(basedir, "basedir", ignored=defaults + [own_folder, ]) # add list of installed plugins plugins = [] plugin_folder = settings.global_get_basefolder("plugins") for key, plugin in plugin_manager.plugins.items(): if plugin.bundled or (isinstance(plugin.origin, FolderOrigin) and plugin.origin.folder == plugin_folder): # ignore anything bundled or from the plugins folder we already include in the backup continue plugins.append(dict(key=plugin.key, name=plugin.name, url=plugin.url)) if len(plugins): zip.writestr("plugin_list.json", json.dumps(plugins)) os.rename(temporary_path, final_path) if callable(on_backup_done): on_backup_done(name, final_path, exclude) except: if callable(on_backup_error): exc_info = sys.exc_info() try: on_backup_error(name, exc_info) finally: del exc_info raise
def _create_backup( cls, name, exclude=None, settings=None, plugin_manager=None, logger=None, datafolder=None, on_backup_start=None, on_backup_done=None, on_backup_error=None, ): if logger is None: logger = logging.getLogger(__name__) exclude_by_default = ( "generated", "logs", "watched", ) try: if exclude is None: exclude = [] if not isinstance(exclude, list): exclude = list(exclude) if "timelapse" in exclude: exclude.append("timelapse_tmp") current_excludes = list(exclude) additional_excludes = list() plugin_data = settings.global_get_basefolder("data") for plugin, hook in plugin_manager.get_hooks( "octoprint.plugin.backup.additional_excludes").items(): try: additional = hook(current_excludes) if isinstance(additional, list): if "." in additional: current_excludes.append( os.path.join("data", plugin)) additional_excludes.append( os.path.join(plugin_data, plugin)) else: current_excludes += map( lambda x: os.path.join("data", plugin, x), additional) additional_excludes += map( lambda x: os.path.join(plugin_data, plugin, x), additional) except Exception: logger.exception( "Error while retrieving additional excludes " "from plugin {name}".format(**locals()), extra={"plugin": plugin}, ) configfile = settings._configfile basedir = settings._basedir temporary_path = os.path.join(datafolder, ".{}".format(name)) final_path = os.path.join(datafolder, name) own_folder = datafolder defaults = [ os.path.join(basedir, "config.yaml"), ] + [ os.path.join(basedir, folder) for folder in default_settings["folder"].keys() ] # check how many bytes we are about to backup size = os.stat(configfile).st_size for folder in default_settings["folder"].keys(): if folder in exclude or folder in exclude_by_default: continue size += cls._get_disk_size( settings.global_get_basefolder(folder), ignored=[ own_folder, ], ) size += cls._get_disk_size( basedir, ignored=defaults + [ own_folder, ], ) # since we can't know the compression ratio beforehand, we assume we need the same amount of space if not cls._free_space(os.path.dirname(temporary_path), size): raise InsufficientSpace() compression = zipfile.ZIP_DEFLATED if zlib else zipfile.ZIP_STORED if callable(on_backup_start): on_backup_start(name, temporary_path, exclude) with zipfile.ZipFile(temporary_path, mode="w", compression=compression, allowZip64=True) as zip: def add_to_zip(source, target, ignored=None): if ignored is None: ignored = [] if source in ignored: return if os.path.isdir(source): for entry in scandir(source): add_to_zip( entry.path, os.path.join(target, entry.name), ignored=ignored, ) elif os.path.isfile(source): zip.write(source, arcname=target) # add metadata metadata = { "version": get_octoprint_version_string(), "excludes": exclude, } zip.writestr("metadata.json", json.dumps(metadata)) # backup current config file add_to_zip( configfile, "basedir/config.yaml", ignored=[ own_folder, ], ) # backup configured folder paths for folder in default_settings["folder"].keys(): if folder in exclude or folder in exclude_by_default: continue add_to_zip( settings.global_get_basefolder(folder), "basedir/" + folder.replace("_", "/"), ignored=[ own_folder, ] + additional_excludes, ) # backup anything else that might be lying around in our basedir add_to_zip( basedir, "basedir", ignored=defaults + [ own_folder, ] + additional_excludes, ) # add list of installed plugins plugins = [] plugin_folder = settings.global_get_basefolder("plugins") for plugin in plugin_manager.plugins.values(): if plugin.bundled or ( isinstance(plugin.origin, FolderOrigin) and plugin.origin.folder == plugin_folder): # ignore anything bundled or from the plugins folder we already include in the backup continue plugins.append({ "key": plugin.key, "name": plugin.name, "url": plugin.url }) if len(plugins): zip.writestr("plugin_list.json", json.dumps(plugins)) shutil.move(temporary_path, final_path) if callable(on_backup_done): on_backup_done(name, final_path, exclude) except Exception as exc: # noqa: F841 # TODO py3: use the exception, not sys.exc_info() if callable(on_backup_error): exc_info = sys.exc_info() try: on_backup_error(name, exc_info) finally: del exc_info raise