# -*- coding: utf-8 -*- # # Copyright 2013 Google LLC. All Rights Reserved. # # 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. """Error Reporting Handler.""" from __future__ import absolute_import from __future__ import division from __future__ import unicode_literals import functools import sys import traceback from apitools.base.py import exceptions as apitools_exceptions from googlecloudsdk.api_lib.error_reporting import util from googlecloudsdk.api_lib.util import apis as core_apis from googlecloudsdk.calliope import command_loading from googlecloudsdk.command_lib import error_reporting_util from googlecloudsdk.core import config from googlecloudsdk.core import log from googlecloudsdk.core import metrics from googlecloudsdk.core import properties from googlecloudsdk.core.console import console_attr from googlecloudsdk.core.util import platforms def _IsInstallationCorruption(err): """Determines if the error may be from installation corruption. Args: err: Exception err. Returns: bool, True if installation error, False otherwise """ return (isinstance(err, command_loading.CommandLoadFailure) and isinstance(err.root_exception, ImportError)) def _PrintInstallationAction(err, err_string): """Prompts installation error action. Args: err: Exception err. err_string: Exception err string. """ # This usually indicates installation corruption. # We do want to suggest `gcloud components reinstall` here (ex. as opposed # to the similar message in gcloud.py), because there's a good chance it'll # work (rather than a manual reinstall). # Don't suggest `gcloud feedback`, because this is probably an # installation problem. log.error( ( 'gcloud failed to load ({command}): {err_str}\n\nThis usually' ' indicates corruption in your gcloud installation or problems with' ' your Python interpreter.\n\nPlease verify that the following is the' ' path to a working Python {py_major_version}.{py_minor_version}+' ' executable:\n {executable}\nIf it is not, please set the' ' CLOUDSDK_PYTHON environment variable to point to a working Python' ' {py_major_version}.{py_minor_version}+ executable.\n\nIf you are' ' still experiencing problems, please run the following command to' ' reinstall:\n $ gcloud components reinstall\n\nIf that command' ' fails, please reinstall the Google Cloud CLI using the instructions' ' here:\n https://cloud.google.com/sdk/' ).format( command=err.command, err_str=err_string, executable=sys.executable, py_major_version=platforms.PythonVersion.MIN_SUPPORTED_PY3_VERSION[0], py_minor_version=platforms.PythonVersion.MIN_SUPPORTED_PY3_VERSION[1], ) ) ERROR_PROJECT = 'cloud-sdk-user-errors' ERROR_REPORTING_PARAM = 'AIzaSyCUuWyME_r4XylltWNeydEjKSkgXkvpVyU' SERVICE = 'gcloud' CRASH_PROJECT = 'cloud-sdk-crashes' CRASH_REPORTING_PARAM = 'AIzaSyAp4DSI_Z3-mK-B8U0t7GE34n74OWDJmak' def _GetReportingClient(is_crash=True): """Returns a client that uses an API key for Cloud SDK crash reports. Args: is_crash: bool, True use CRASH_REPORTING_PARAM, if False use ERROR_REPORTING_PARAM. Returns: An error reporting client that uses an API key for Cloud SDK crash reports. """ client_class = core_apis.GetClientClass(util.API_NAME, util.API_VERSION) client_instance = client_class(get_credentials=False) if is_crash: client_instance.AddGlobalParam('key', CRASH_REPORTING_PARAM) else: client_instance.AddGlobalParam('key', ERROR_REPORTING_PARAM) return client_instance def ReportError(is_crash): """Report the anonymous crash information to the Error Reporting service. This will report the actively handled exception. Args: is_crash: bool, True if this is a crash, False if it is a user error. """ if (not properties.IsDefaultUniverse() or properties.VALUES.core.disable_usage_reporting.GetBool()): return # traceback prints the exception that is currently being handled stacktrace = traceback.format_exc() stacktrace = error_reporting_util.RemovePrivateInformationFromTraceback( stacktrace) command = properties.VALUES.metrics.command_name.Get() cid = metrics.GetCIDIfMetricsEnabled() client = _GetReportingClient(is_crash) reporter = util.ErrorReporting(client) try: method_config = client.projects_events.GetMethodConfig('Report') request = reporter.GenerateReportRequest( error_message=stacktrace, service=SERVICE, version=config.CLOUD_SDK_VERSION, project=CRASH_PROJECT if is_crash else ERROR_PROJECT, request_url=command, user=cid) http_request = client.projects_events.PrepareHttpRequest( method_config, request) metrics.CustomBeacon(http_request.url, http_request.http_method, http_request.body, http_request.headers) except apitools_exceptions.Error as e: log.file_only_logger.error( 'Unable to report crash stacktrace:\n{0}'.format( console_attr.SafeText(e))) def HandleGcloudCrash(err): """Checks if installation error occurred, then proceeds with Error Reporting. Args: err: Exception err. """ err_string = console_attr.SafeText(err) log.file_only_logger.exception('BEGIN CRASH STACKTRACE') if _IsInstallationCorruption(err): _PrintInstallationAction(err, err_string) else: log.error('gcloud crashed ({0}): {1}'.format( getattr(err, 'error_name', type(err).__name__), err_string)) if 'certificate verify failed' in err_string: log.err.Print( '\ngcloud\'s default CA certificates failed to verify your connection' ', which can happen if you are behind a proxy or firewall.') log.err.Print('To use a custom CA certificates file, please run the ' 'following command:') log.err.Print( ' gcloud config set core/custom_ca_certs_file /path/to/ca_certs') ReportError(is_crash=True) log.err.Print('\nIf you would like to report this issue, please run the ' 'following command:') log.err.Print(' gcloud feedback') log.err.Print('\nTo check gcloud for common problems, please run the ' 'following command:') log.err.Print(' gcloud info --run-diagnostics') def CrashManager(target_function): """Context manager for handling gcloud crashes. Good for wrapping multiprocessing and multithreading target functions. Args: target_function (function): Unit test to decorate. Returns: Decorator function. """ @functools.wraps(target_function) def Wrapper(*args, **kwargs): try: target_function(*args, **kwargs) # pylint:disable=broad-except except Exception as e: # pylint:enable=broad-except HandleGcloudCrash(e) if properties.VALUES.core.print_unhandled_tracebacks.GetBool(): # We want to see the traceback as normally handled by Python raise else: # This is the case for most non-Cloud SDK developers. They shouldn't see # the full stack trace, but just the nice "gcloud crashed" message. sys.exit(1) return Wrapper