From 49ceadc6ad218af16a9c423e53751a93125b2c53 Mon Sep 17 00:00:00 2001 From: Ivan Kravets Date: Thu, 28 Nov 2019 16:15:54 +0200 Subject: [PATCH] Move exceptions to their components --- HISTORY.rst | 1 - platformio/commands/debug/client.py | 32 ++--- platformio/commands/debug/command.py | 8 +- platformio/commands/debug/exception.py | 33 +++++ platformio/commands/debug/helpers.py | 5 +- platformio/commands/debug/server.py | 5 +- platformio/commands/device.py | 3 +- .../commands/home/rpc/handlers/project.py | 5 +- platformio/commands/remote.py | 3 +- platformio/commands/run/processor.py | 5 +- platformio/exception.py | 59 +-------- platformio/maintenance.py | 4 +- platformio/managers/core.py | 2 +- platformio/managers/package.py | 25 +--- platformio/managers/platform.py | 11 +- platformio/project/config.py | 13 +- platformio/project/exception.py | 53 ++++++++ platformio/telemetry.py | 123 ++++++++++-------- 18 files changed, 213 insertions(+), 177 deletions(-) create mode 100644 platformio/commands/debug/exception.py create mode 100644 platformio/project/exception.py diff --git a/HISTORY.rst b/HISTORY.rst index 3fded026..290a04f4 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -16,7 +16,6 @@ PlatformIO Core 4.0 * Handle project configuration (monitor, test, and upload options) for PIO Remote commands (`issue #2591 `_) * Warn about broken library manifest when scanning dependencies (`issue #3268 `_) -* Fixed an issue with the broken latest news for PIO Home * Fixed an issue when ``env.BoardConfig()`` does not work for custom boards in extra scripts of libraries (`issue #3264 `_) * Fixed an issue with "start-group/end-group" linker flags on Native development platform (`issue #3282 `_) diff --git a/platformio/commands/debug/client.py b/platformio/commands/debug/client.py index 5650f302..2c6d9724 100644 --- a/platformio/commands/debug/client.py +++ b/platformio/commands/debug/client.py @@ -12,7 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -import json import os import re import signal @@ -26,13 +25,13 @@ from twisted.internet import reactor # pylint: disable=import-error from twisted.internet import stdio # pylint: disable=import-error from twisted.internet import task # pylint: disable=import-error -from platformio import app, exception, fs, proc, util +from platformio import app, fs, proc, telemetry, util from platformio.commands.debug import helpers, initcfgs +from platformio.commands.debug.exception import DebugInvalidOptionsError from platformio.commands.debug.process import BaseProcess from platformio.commands.debug.server import DebugServer from platformio.compat import hashlib_encode_data, is_bytes from platformio.project.helpers import get_project_cache_dir -from platformio.telemetry import MeasurementProtocol LOG_FILE = None @@ -58,6 +57,7 @@ class GDBClient(BaseProcess): # pylint: disable=too-many-instance-attributes self._target_is_run = False self._last_server_activity = 0 self._auto_continue_timer = None + self._errors_buffer = b"" def spawn(self, gdb_path, prog_path): session_hash = gdb_path + prog_path @@ -94,7 +94,7 @@ class GDBClient(BaseProcess): # pylint: disable=too-many-instance-attributes ] args.extend(self.args) if not gdb_path: - raise exception.DebugInvalidOptions("GDB client is not configured") + raise DebugInvalidOptionsError("GDB client is not configured") gdb_data_dir = self._get_data_dir(gdb_path) if gdb_data_dir: args.extend(["--data-directory", gdb_data_dir]) @@ -215,6 +215,9 @@ class GDBClient(BaseProcess): # pylint: disable=too-many-instance-attributes self._handle_error(data) # go to init break automatically if self.INIT_COMPLETED_BANNER.encode() in data: + telemetry.send_event( + "Debug", "Started", telemetry.encode_run_environment(self.env_options) + ) self._auto_continue_timer = task.LoopingCall(self._auto_exec_continue) self._auto_continue_timer.start(0.1) @@ -250,20 +253,19 @@ class GDBClient(BaseProcess): # pylint: disable=too-many-instance-attributes self._target_is_run = True def _handle_error(self, data): + self._errors_buffer += data if self.PIO_SRC_NAME.encode() not in data or b"Error in sourced" not in data: return - configuration = {"debug": self.debug_options, "env": self.env_options} - exd = re.sub(r'\\(?!")', "/", json.dumps(configuration)) - exd = re.sub( - r'"(?:[a-z]\:)?((/[^"/]+)+)"', - lambda m: '"%s"' % join(*m.group(1).split("/")[-2:]), - exd, - re.I | re.M, + + last_erros = self._errors_buffer.decode() + last_erros = " ".join(reversed(last_erros.split("\n"))) + last_erros = re.sub(r'((~|&)"|\\n\"|\\t)', " ", last_erros, flags=re.M) + + err = "%s -> %s" % ( + telemetry.encode_run_environment(self.env_options), + last_erros, ) - mp = MeasurementProtocol() - mp["exd"] = "DebugGDBPioInitError: %s" % exd - mp["exf"] = 1 - mp.send("exception") + telemetry.send_exception("DebugInitError: %s" % err, is_fatal=True) self.transport.loseConnection() def _kill_previous_session(self): diff --git a/platformio/commands/debug/command.py b/platformio/commands/debug/command.py index ab273063..c3c29dd1 100644 --- a/platformio/commands/debug/command.py +++ b/platformio/commands/debug/command.py @@ -23,8 +23,10 @@ import click from platformio import app, exception, fs, proc, util from platformio.commands.debug import helpers +from platformio.commands.debug.exception import DebugInvalidOptionsError from platformio.managers.core import inject_contrib_pysite from platformio.project.config import ProjectConfig +from platformio.project.exception import ProjectEnvsNotAvailableError from platformio.project.helpers import is_platformio_project, load_project_ide_data @@ -70,7 +72,7 @@ def cli(ctx, project_dir, project_conf, environment, verbose, interface, __unpro env_name = environment or helpers.get_default_debug_env(config) env_options = config.items(env=env_name, as_dict=True) if not set(env_options.keys()) >= set(["platform", "board"]): - raise exception.ProjectEnvsNotAvailable() + raise ProjectEnvsNotAvailableError() debug_options = helpers.validate_debug_options(ctx, env_options) assert debug_options @@ -79,7 +81,7 @@ def cli(ctx, project_dir, project_conf, environment, verbose, interface, __unpro configuration = load_project_ide_data(project_dir, env_name) if not configuration: - raise exception.DebugInvalidOptions("Could not load debug configuration") + raise DebugInvalidOptionsError("Could not load debug configuration") if "--version" in __unprocessed: result = proc.exec_command([configuration["gdb_path"], "--version"]) @@ -140,7 +142,7 @@ def cli(ctx, project_dir, project_conf, environment, verbose, interface, __unpro helpers.is_prog_obsolete(configuration["prog_path"]) if not isfile(configuration["prog_path"]): - raise exception.DebugInvalidOptions("Program/firmware is missed") + raise DebugInvalidOptionsError("Program/firmware is missed") # run debugging client inject_contrib_pysite() diff --git a/platformio/commands/debug/exception.py b/platformio/commands/debug/exception.py new file mode 100644 index 00000000..a1269b2f --- /dev/null +++ b/platformio/commands/debug/exception.py @@ -0,0 +1,33 @@ +# Copyright (c) 2014-present PlatformIO +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from platformio.exception import PlatformioException, UserSideException + + +class DebugError(PlatformioException): + pass + + +class DebugSupportError(DebugError, UserSideException): + + MESSAGE = ( + "Currently, PlatformIO does not support debugging for `{0}`.\n" + "Please request support at https://github.com/platformio/" + "platformio-core/issues \nor visit -> https://docs.platformio.org" + "/page/plus/debugging.html" + ) + + +class DebugInvalidOptionsError(DebugError, UserSideException): + pass diff --git a/platformio/commands/debug/helpers.py b/platformio/commands/debug/helpers.py index e8e6c525..5edba11a 100644 --- a/platformio/commands/debug/helpers.py +++ b/platformio/commands/debug/helpers.py @@ -22,6 +22,7 @@ from os.path import isfile from platformio import exception, fs, util from platformio.commands import PlatformioCLI +from platformio.commands.debug.exception import DebugInvalidOptionsError from platformio.commands.platform import platform_install as cmd_platform_install from platformio.commands.run.command import cli as cmd_run from platformio.compat import is_bytes @@ -301,7 +302,5 @@ def reveal_debug_port(env_debug_port, tool_name, tool_settings): debug_port = _look_for_serial_port(tool_settings.get("hwids", [])) if not debug_port: - raise exception.DebugInvalidOptions( - "Please specify `debug_port` for environment" - ) + raise DebugInvalidOptionsError("Please specify `debug_port` for environment") return debug_port diff --git a/platformio/commands/debug/server.py b/platformio/commands/debug/server.py index b0ddb908..3b16b61d 100644 --- a/platformio/commands/debug/server.py +++ b/platformio/commands/debug/server.py @@ -17,7 +17,8 @@ from os.path import isdir, isfile, join from twisted.internet import reactor # pylint: disable=import-error -from platformio import exception, fs, util +from platformio import fs, util +from platformio.commands.debug.exception import DebugInvalidOptionsError from platformio.commands.debug.helpers import escape_gdbmi_stream, is_gdbmi_mode from platformio.commands.debug.process import BaseProcess from platformio.proc import where_is_program @@ -53,7 +54,7 @@ class DebugServer(BaseProcess): if not isfile(server_executable): server_executable = where_is_program(server_executable) if not isfile(server_executable): - raise exception.DebugInvalidOptions( + raise DebugInvalidOptionsError( "\nCould not launch Debug Server '%s'. Please check that it " "is installed and is included in a system PATH\n\n" "See documentation or contact contact@platformio.org:\n" diff --git a/platformio/commands/device.py b/platformio/commands/device.py index 6a547ae8..72c7ee8e 100644 --- a/platformio/commands/device.py +++ b/platformio/commands/device.py @@ -22,6 +22,7 @@ from serial.tools import miniterm from platformio import exception, fs, util from platformio.compat import dump_json_to_unicode from platformio.project.config import ProjectConfig +from platformio.project.exception import NotPlatformIOProjectError @click.group(short_help="Monitor device or list existing") @@ -181,7 +182,7 @@ def device_monitor(**kwargs): # pylint: disable=too-many-branches with fs.cd(kwargs["project_dir"]): project_options = get_project_options(kwargs["environment"]) kwargs = apply_project_monitor_options(kwargs, project_options) - except exception.NotPlatformIOProject: + except NotPlatformIOProjectError: pass if not kwargs["port"]: diff --git a/platformio/commands/home/rpc/handlers/project.py b/platformio/commands/home/rpc/handlers/project.py index 8a58d886..51e47dcb 100644 --- a/platformio/commands/home/rpc/handlers/project.py +++ b/platformio/commands/home/rpc/handlers/project.py @@ -27,6 +27,7 @@ from platformio.compat import PY2, get_filesystem_encoding from platformio.ide.projectgenerator import ProjectGenerator from platformio.managers.platform import PlatformManager from platformio.project.config import ProjectConfig +from platformio.project.exception import ProjectError from platformio.project.helpers import get_project_dir, is_platformio_project from platformio.project.options import get_config_options_schema @@ -113,7 +114,7 @@ class ProjectRPC(object): try: with fs.cd(project_dir): data = _get_project_data() - except exception.PlatformIOProjectException: + except ProjectError: continue for board_id in data.get("boards", []): @@ -158,7 +159,7 @@ class ProjectRPC(object): config = ProjectConfig(os.path.join(project_dir, "platformio.ini")) config.validate(silent=True) project_description = config.get("platformio", "description") - except exception.PlatformIOProjectException: + except ProjectError: continue path_tokens = project_dir.split(os.path.sep) diff --git a/platformio/commands/remote.py b/platformio/commands/remote.py index f4b17ade..0ebbf856 100644 --- a/platformio/commands/remote.py +++ b/platformio/commands/remote.py @@ -23,6 +23,7 @@ import click from platformio import exception, fs from platformio.commands import device from platformio.managers.core import pioplus_call +from platformio.project.exception import NotPlatformIOProjectError # pylint: disable=unused-argument @@ -198,7 +199,7 @@ def device_monitor(ctx, **kwargs): with fs.cd(kwargs["project_dir"]): project_options = device.get_project_options(kwargs["environment"]) kwargs = device.apply_project_monitor_options(kwargs, project_options) - except exception.NotPlatformIOProject: + except NotPlatformIOProjectError: pass kwargs["baud"] = kwargs["baud"] or 9600 diff --git a/platformio/commands/run/processor.py b/platformio/commands/run/processor.py index 3366c1e1..75b09b40 100644 --- a/platformio/commands/run/processor.py +++ b/platformio/commands/run/processor.py @@ -16,6 +16,7 @@ from platformio import exception, telemetry from platformio.commands.platform import platform_install as cmd_platform_install from platformio.commands.test.processor import CTX_META_TEST_RUNNING_NAME from platformio.managers.platform import PlatformFactory +from platformio.project.exception import UndefinedEnvPlatformError # pylint: disable=too-many-instance-attributes @@ -56,12 +57,12 @@ class EnvironmentProcessor(object): def process(self): if "platform" not in self.options: - raise exception.UndefinedEnvPlatform(self.name) + raise UndefinedEnvPlatformError(self.name) build_vars = self.get_build_variables() build_targets = list(self.get_build_targets()) - telemetry.on_run_environment(self.options, build_targets) + telemetry.send_run_environment(self.options, build_targets) # skip monitor target, we call it above if "monitor" in build_targets: diff --git a/platformio/exception.py b/platformio/exception.py index 6e5910f8..cfd357a4 100644 --- a/platformio/exception.py +++ b/platformio/exception.py @@ -152,49 +152,6 @@ class FDSHASumMismatch(PlatformIOPackageException): ) -# -# Project -# - - -class PlatformIOProjectException(PlatformioException): - pass - - -class NotPlatformIOProject(PlatformIOProjectException): - - MESSAGE = ( - "Not a PlatformIO project. `platformio.ini` file has not been " - "found in current working directory ({0}). To initialize new project " - "please use `platformio init` command" - ) - - -class InvalidProjectConf(PlatformIOProjectException): - - MESSAGE = "Invalid '{0}' (project configuration file): '{1}'" - - -class UndefinedEnvPlatform(PlatformIOProjectException): - - MESSAGE = "Please specify platform for '{0}' environment" - - -class ProjectEnvsNotAvailable(PlatformIOProjectException): - - MESSAGE = "Please setup environments in `platformio.ini` file" - - -class UnknownEnvNames(PlatformIOProjectException): - - MESSAGE = "Unknown environment names '{0}'. Valid names are '{1}'" - - -class ProjectOptionValueError(PlatformIOProjectException): - - MESSAGE = "{0} for option `{1}` in section [{2}]" - - # # Library # @@ -319,7 +276,7 @@ class UpgradeError(PlatformioException): """ -class HomeDirPermissionsError(PlatformioException): +class HomeDirPermissionsError(UserSideException): MESSAGE = ( "The directory `{0}` or its parent directory is not owned by the " @@ -338,20 +295,6 @@ class CygwinEnvDetected(PlatformioException): ) -class DebugSupportError(PlatformioException): - - MESSAGE = ( - "Currently, PlatformIO does not support debugging for `{0}`.\n" - "Please request support at https://github.com/platformio/" - "platformio-core/issues \nor visit -> https://docs.platformio.org" - "/page/plus/debugging.html" - ) - - -class DebugInvalidOptions(PlatformioException): - pass - - class TestDirNotExists(PlatformioException): MESSAGE = ( diff --git a/platformio/maintenance.py b/platformio/maintenance.py index 94a9140a..c9b3d742 100644 --- a/platformio/maintenance.py +++ b/platformio/maintenance.py @@ -151,7 +151,7 @@ def after_upgrade(ctx): "PlatformIO has been successfully upgraded to %s!\n" % __version__, fg="green", ) - telemetry.on_event( + telemetry.send_event( category="Auto", action="Upgrade", label="%s > %s" % (last_version, __version__), @@ -315,7 +315,7 @@ def check_internal_updates(ctx, what): ctx.invoke(cmd_lib_update, libraries=outdated_items) click.echo() - telemetry.on_event(category="Auto", action="Update", label=what.title()) + telemetry.send_event(category="Auto", action="Update", label=what.title()) click.echo("*" * terminal_width) click.echo("") diff --git a/platformio/managers/core.py b/platformio/managers/core.py index 7996d02a..2397a34d 100644 --- a/platformio/managers/core.py +++ b/platformio/managers/core.py @@ -26,7 +26,7 @@ from platformio.project.config import ProjectConfig CORE_PACKAGES = { "contrib-piohome": ">=3.1.0-beta.3,<3.2.0", "contrib-pysite": "~2.%d%d.0" % (sys.version_info[0], sys.version_info[1]), - "tool-pioplus": "^2.6.0", + "tool-pioplus": "^2.6.1", "tool-unity": "~1.20403.0", "tool-scons": "~2.20501.7" if PY2 else "~3.30101.0", "tool-cppcheck": "~1.189.0", diff --git a/platformio/managers/package.py b/platformio/managers/package.py index b253ea0e..6df5276d 100644 --- a/platformio/managers/package.py +++ b/platformio/managers/package.py @@ -24,7 +24,7 @@ import click import requests import semantic_version -from platformio import __version__, app, exception, fs, telemetry, util +from platformio import __version__, app, exception, fs, util from platformio.compat import hashlib_encode_data from platformio.downloader import FileDownloader from platformio.lockfile import LockFile @@ -660,7 +660,7 @@ class BasePkgManager(PkgRepoMixin, PkgInstallerMixin): def install( self, name, requirements=None, silent=False, after_update=False, force=False - ): + ): # pylint: disable=unused-argument pkg_dir = None # interprocess lock with LockFile(self.package_dir): @@ -709,13 +709,6 @@ class BasePkgManager(PkgRepoMixin, PkgInstallerMixin): manifest = self.load_manifest(pkg_dir) assert manifest - if not after_update: - telemetry.on_event( - category=self.__class__.__name__, - action="Install", - label=manifest["name"], - ) - click.secho( "{name} @ {version} has been successfully installed!".format( **manifest @@ -725,7 +718,9 @@ class BasePkgManager(PkgRepoMixin, PkgInstallerMixin): return pkg_dir - def uninstall(self, package, requirements=None, after_update=False): + def uninstall( + self, package, requirements=None, after_update=False + ): # pylint: disable=unused-argument # interprocess lock with LockFile(self.package_dir): self.cache_reset() @@ -764,13 +759,6 @@ class BasePkgManager(PkgRepoMixin, PkgInstallerMixin): click.echo("[%s]" % click.style("OK", fg="green")) - if not after_update: - telemetry.on_event( - category=self.__class__.__name__, - action="Uninstall", - label=manifest["name"], - ) - return True def update(self, package, requirements=None, only_check=False): @@ -819,9 +807,6 @@ class BasePkgManager(PkgRepoMixin, PkgInstallerMixin): self.uninstall(pkg_dir, after_update=True) self.install(name, latest, after_update=True) - telemetry.on_event( - category=self.__class__.__name__, action="Update", label=manifest["name"] - ) return True diff --git a/platformio/managers/platform.py b/platformio/managers/platform.py index ec7bd2a2..5d61829e 100644 --- a/platformio/managers/platform.py +++ b/platformio/managers/platform.py @@ -23,7 +23,11 @@ from os.path import basename, dirname, isdir, isfile, join import click import semantic_version -from platformio import __version__, app, exception, fs, proc, util +from platformio import __version__, app, exception, fs, proc, telemetry, util +from platformio.commands.debug.exception import ( + DebugInvalidOptionsError, + DebugSupportError, +) from platformio.compat import PY2, hashlib_encode_data, is_bytes, load_python_module from platformio.managers.core import get_core_package_dir from platformio.managers.package import BasePkgManager, PackageManager @@ -799,11 +803,12 @@ class PlatformBoardConfig(object): if tool_name == "custom": return tool_name if not debug_tools: - raise exception.DebugSupportError(self._manifest["name"]) + telemetry.send_event("Debug", "Request", self.id) + raise DebugSupportError(self._manifest["name"]) if tool_name: if tool_name in debug_tools: return tool_name - raise exception.DebugInvalidOptions( + raise DebugInvalidOptionsError( "Unknown debug tool `%s`. Please use one of `%s` or `custom`" % (tool_name, ", ".join(sorted(list(debug_tools)))) ) diff --git a/platformio/project/config.py b/platformio/project/config.py index 7fdbcdf8..739429f6 100644 --- a/platformio/project/config.py +++ b/platformio/project/config.py @@ -20,8 +20,9 @@ from hashlib import sha1 import click -from platformio import exception, fs +from platformio import fs from platformio.compat import PY2, WINDOWS, hashlib_encode_data +from platformio.project import exception from platformio.project.options import ProjectOptions try: @@ -104,7 +105,7 @@ class ProjectConfigBase(object): try: self._parser.read(path) except ConfigParser.Error as e: - raise exception.InvalidProjectConf(path, str(e)) + raise exception.InvalidProjectConfError(path, str(e)) if not parse_extra: return @@ -273,7 +274,7 @@ class ProjectConfigBase(object): except (ConfigParser.NoSectionError, ConfigParser.NoOptionError): pass # handle value from system environment except ConfigParser.Error as e: - raise exception.InvalidProjectConf(self.path, str(e)) + raise exception.InvalidProjectConfError(self.path, str(e)) option_meta = ProjectOptions.get("%s.%s" % (section.split(":", 1)[0], option)) if not option_meta: @@ -327,14 +328,14 @@ class ProjectConfigBase(object): def validate(self, envs=None, silent=False): if not os.path.isfile(self.path): - raise exception.NotPlatformIOProject(self.path) + raise exception.NotPlatformIOProjectError(self.path) # check envs known = set(self.envs()) if not known: - raise exception.ProjectEnvsNotAvailable() + raise exception.ProjectEnvsNotAvailableError() unknown = set(list(envs or []) + self.default_envs()) - known if unknown: - raise exception.UnknownEnvNames(", ".join(unknown), ", ".join(known)) + raise exception.UnknownEnvNamesError(", ".join(unknown), ", ".join(known)) if not silent: for warning in self.warnings: click.secho("Warning! %s" % warning, fg="yellow") diff --git a/platformio/project/exception.py b/platformio/project/exception.py new file mode 100644 index 00000000..c2d7fd09 --- /dev/null +++ b/platformio/project/exception.py @@ -0,0 +1,53 @@ +# Copyright (c) 2014-present PlatformIO +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from platformio.exception import PlatformioException, UserSideException + + +class ProjectError(PlatformioException): + pass + + +class NotPlatformIOProjectError(ProjectError, UserSideException): + + MESSAGE = ( + "Not a PlatformIO project. `platformio.ini` file has not been " + "found in current working directory ({0}). To initialize new project " + "please use `platformio init` command" + ) + + +class InvalidProjectConfError(ProjectError, UserSideException): + + MESSAGE = "Invalid '{0}' (project configuration file): '{1}'" + + +class UndefinedEnvPlatformError(ProjectError, UserSideException): + + MESSAGE = "Please specify platform for '{0}' environment" + + +class ProjectEnvsNotAvailableError(ProjectError, UserSideException): + + MESSAGE = "Please setup environments in `platformio.ini` file" + + +class UnknownEnvNamesError(ProjectError, UserSideException): + + MESSAGE = "Unknown environment names '{0}'. Valid names are '{1}'" + + +class ProjectOptionValueError(ProjectError, UserSideException): + + MESSAGE = "{0} for option `{1}` in section [{2}]" diff --git a/platformio/telemetry.py b/platformio/telemetry.py index 4a55f968..f765e06d 100644 --- a/platformio/telemetry.py +++ b/platformio/telemetry.py @@ -13,13 +13,12 @@ # limitations under the License. import atexit +import os import platform import re import sys import threading from collections import deque -from os import getenv, sep -from os.path import join from time import sleep, time from traceback import format_exc @@ -99,8 +98,8 @@ class MeasurementProtocol(TelemetryBase): dpdata.append("PlatformIO/%s" % __version__) if app.get_session_var("caller_id"): dpdata.append("Caller/%s" % app.get_session_var("caller_id")) - if getenv("PLATFORMIO_IDE"): - dpdata.append("IDE/%s" % getenv("PLATFORMIO_IDE")) + if os.getenv("PLATFORMIO_IDE"): + dpdata.append("IDE/%s" % os.getenv("PLATFORMIO_IDE")) self["an"] = " ".join(dpdata) def _prefill_custom_data(self): @@ -179,13 +178,10 @@ class MeasurementProtocol(TelemetryBase): cmd_path.append(sub_cmd) self["screen_name"] = " ".join([p.title() for p in cmd_path]) - @staticmethod - def _ignore_hit(): + def _ignore_hit(self): if not app.get_setting("enable_telemetry"): return True - if app.get_session_var("caller_id") and all( - c in sys.argv for c in ("run", "idedata") - ): + if all(c in sys.argv for c in ("run", "idedata")) or self["ea"] == "Idedata": return True return False @@ -296,29 +292,64 @@ def on_command(): measure_ci() +def on_exception(e): + skip_conditions = [ + isinstance(e, cls) + for cls in (IOError, exception.ReturnErrorCode, exception.UserSideException,) + ] + try: + skip_conditions.append("[API] Account: " in str(e)) + except UnicodeEncodeError as ue: + e = ue + if any(skip_conditions): + return + is_fatal = any( + [ + not isinstance(e, exception.PlatformioException), + "Error" in e.__class__.__name__, + ] + ) + description = "%s: %s" % ( + type(e).__name__, + " ".join(reversed(format_exc().split("\n"))) if is_fatal else str(e), + ) + send_exception(description, is_fatal) + + def measure_ci(): event = {"category": "CI", "action": "NoName", "label": None} known_cis = ("TRAVIS", "APPVEYOR", "GITLAB_CI", "CIRCLECI", "SHIPPABLE", "DRONE") for name in known_cis: - if getenv(name, "false").lower() == "true": + if os.getenv(name, "false").lower() == "true": event["action"] = name break - on_event(**event) + send_event(**event) -def on_run_environment(options, targets): - non_sensative_values = ["board", "platform", "framework"] - safe_options = [] - for key, value in sorted(options.items()): - if key in non_sensative_values: - safe_options.append("%s=%s" % (key, value)) - else: - safe_options.append(key) - targets = [t.title() for t in targets or ["run"]] - on_event("Env", " ".join(targets), "&".join(safe_options)) +def encode_run_environment(options): + non_sensative_keys = [ + "platform", + "framework", + "board", + "upload_protocol", + "check_tool", + "debug_tool", + ] + safe_options = [ + "%s=%s" % (k, v) for k, v in sorted(options.items()) if k in non_sensative_keys + ] + return "&".join(safe_options) -def on_event(category, action, label=None, value=None, screen_name=None): +def send_run_environment(options, targets): + send_event( + "Env", + " ".join([t.title() for t in targets or ["run"]]), + encode_run_environment(options), + ) + + +def send_event(category, action, label=None, value=None, screen_name=None): mp = MeasurementProtocol() mp["event_category"] = category[:150] mp["event_action"] = action[:500] @@ -331,43 +362,21 @@ def on_event(category, action, label=None, value=None, screen_name=None): mp.send("event") -def on_exception(e): - def _cleanup_description(text): - text = text.replace("Traceback (most recent call last):", "") - text = re.sub( - r'File "([^"]+)"', - lambda m: join(*m.group(1).split(sep)[-2:]), - text, - flags=re.M, - ) - text = re.sub(r"\s+", " ", text, flags=re.M) - return text.strip() - - skip_conditions = [ - isinstance(e, cls) - for cls in ( - IOError, - exception.ReturnErrorCode, - exception.UserSideException, - exception.PlatformIOProjectException, - ) - ] - try: - skip_conditions.append("[API] Account: " in str(e)) - except UnicodeEncodeError as ue: - e = ue - if any(skip_conditions): - return - is_crash = any( - [ - not isinstance(e, exception.PlatformioException), - "Error" in e.__class__.__name__, - ] +def send_exception(description, is_fatal=False): + # cleanup sensitive information, such as paths + description = description.replace("Traceback (most recent call last):", "") + description = description.replace("\\", "/") + description = re.sub( + r'(^|\s+|")(?:[a-z]\:)?((/[^"/]+)+)(\s+|"|$)', + lambda m: " %s " % os.path.join(*m.group(2).split("/")[-2:]), + description, + re.I | re.M, ) + description = re.sub(r"\s+", " ", description, flags=re.M) + mp = MeasurementProtocol() - description = _cleanup_description(format_exc() if is_crash else str(e)) - mp["exd"] = ("%s: %s" % (type(e).__name__, description))[:2048] - mp["exf"] = 1 if is_crash else 0 + mp["exd"] = description[:8192].strip() + mp["exf"] = 1 if is_fatal else 0 mp.send("exception")