diff --git a/.github/workflows/functional_tests.yaml b/.github/workflows/functional_tests.yaml index b7035b9ed7..4d0f270fb7 100644 --- a/.github/workflows/functional_tests.yaml +++ b/.github/workflows/functional_tests.yaml @@ -23,13 +23,8 @@ jobs: steps: - name: Clone repository uses: actions/checkout@v3 - - - name: Checkout submodules - shell: bash - run: | - auth_header="$(git config --local --get http.https://github.com/.extraheader)" - git submodule sync --recursive - git -c "http.extraheader=$auth_header" -c protocol.version=2 submodule update --init --force --recursive --depth=1 + with: + submodules: 'true' - name: Install dependecies if: steps.cache-build.outputs.cache-hit != 'true' run: | diff --git a/.github/workflows/gh_pages.yaml b/.github/workflows/gh_pages.yaml index d005d59ce7..6373bb3ae5 100644 --- a/.github/workflows/gh_pages.yaml +++ b/.github/workflows/gh_pages.yaml @@ -81,13 +81,8 @@ jobs: steps: - name: Clone repository uses: actions/checkout@v3 - - - name: Checkout submodules - shell: bash - run: | - auth_header="$(git config --local --get http.https://github.com/.extraheader)" - git submodule sync --recursive - git -c "http.extraheader=$auth_header" -c protocol.version=2 submodule update --init --force --recursive --depth=1 + with: + submodules: 'true' - name: Install Qt shell: bash diff --git a/.github/workflows/linters.yaml b/.github/workflows/linters.yaml index 7a71a4a7cc..efccc49c81 100644 --- a/.github/workflows/linters.yaml +++ b/.github/workflows/linters.yaml @@ -22,13 +22,8 @@ jobs: - name: Clone repository uses: actions/checkout@v3 - - - name: Checkout submodules - shell: bash - run: | - auth_header="$(git config --local --get http.https://github.com/.extraheader)" - git submodule sync --recursive - git -c "http.extraheader=$auth_header" -c protocol.version=2 submodule update --init --force --recursive --depth=1 + with: + submodules: 'true' - name: Set up Python 3 uses: actions/setup-python@v3 diff --git a/.github/workflows/test_unit.yaml b/.github/workflows/test_unit.yaml index b2715135cd..6beef21176 100644 --- a/.github/workflows/test_unit.yaml +++ b/.github/workflows/test_unit.yaml @@ -23,6 +23,8 @@ jobs: steps: - name: Clone repository uses: actions/checkout@v3 + with: + submodules: 'true' - name: Install dependences run: | @@ -32,13 +34,6 @@ jobs: python3 -m pip install aqtinstall aqt install-qt --outputdir /opt linux desktop $QTVERSION gcc_64 -m all - - name: Checkout submodules - shell: bash - run: | - auth_header="$(git config --local --get http.https://github.com/.extraheader)" - git submodule sync --recursive - git -c "http.extraheader=$auth_header" -c protocol.version=2 submodule update --init --force --recursive --depth=1 - - name: Cache grcov id: cache-grcov uses: actions/cache@v3 @@ -96,14 +91,8 @@ jobs: steps: - name: Clone repository uses: actions/checkout@v3 - - - name: Checkout submodules - shell: bash - run: | - auth_header="$(git config --local --get http.https://github.com/.extraheader)" - git submodule sync --recursive - git -c "http.extraheader=$auth_header" -c protocol.version=2 submodule update --init --force --recursive --depth=1 - + with: + submodules: 'true' - name: Install python dependencies shell: bash run: | @@ -177,13 +166,8 @@ jobs: steps: - name: Clone repository uses: actions/checkout@v3 - - - name: Checkout submodules - shell: bash - run: | - auth_header="$(git config --local --get http.https://github.com/.extraheader)" - git submodule sync --recursive - git -c "http.extraheader=$auth_header" -c protocol.version=2 submodule update --init --force --recursive --depth=1 + with: + submodules: 'true' - name: Install Qt shell: bash diff --git a/.github/workflows/wasm.yaml b/.github/workflows/wasm.yaml index dd228296eb..d6f4237890 100644 --- a/.github/workflows/wasm.yaml +++ b/.github/workflows/wasm.yaml @@ -26,13 +26,8 @@ jobs: steps: - name: Clone repository uses: actions/checkout@v3 - - - name: Checkout submodules - shell: bash - run: | - auth_header="$(git config --local --get http.https://github.com/.extraheader)" - git submodule sync --recursive - git -c "http.extraheader=$auth_header" -c protocol.version=2 submodule update --init --force --recursive --depth=1 + with: + submodules: 'true' - name: Install Qt shell: bash diff --git a/.gitignore b/.gitignore index f4aa485127..afc8236697 100644 --- a/.gitignore +++ b/.gitignore @@ -104,6 +104,8 @@ vscode-cpptools/* *.xcodeproj/ MozillaVPN.vcxproj* *.autosave +# Visual Studio stuff. +CMakeSettings.json # Qt Creator CMakeLists.txt.user diff --git a/.gitmodules b/.gitmodules index e153814d57..f0ef8848b9 100644 --- a/.gitmodules +++ b/.gitmodules @@ -29,3 +29,6 @@ path = 3rdparty/glean url = https://github.com/mozilla/glean.git branch = main +[submodule "3rdparty/sentry"] + path = 3rdparty/sentry + url = https://github.com/getsentry/sentry-native/ diff --git a/3rdparty/sentry b/3rdparty/sentry new file mode 160000 index 0000000000..87e67ad783 --- /dev/null +++ b/3rdparty/sentry @@ -0,0 +1 @@ +Subproject commit 87e67ad783a7ec4476b0eb4742bd40fe5a1e2435 diff --git a/LICENSE.md b/LICENSE.md index 4257567750..f0b35884dd 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -56,6 +56,11 @@ the terms of the MIT Public License (*MIT*), a copy of which is available [available here](https://opensource.org/licenses/MIT) and a copy is provided below. +The project relies on [sentry-native](https://github.com/getsentry/sentry-native), under +the terms of the MIT Public License (*MIT*), a copy of which is available +[available here](https://opensource.org/licenses/MIT) and a copy is provided +below. + Finally, this project uses EC25519 and CHACHA-POLY implementations from [HACL\*](https://hacl-star.github.io/), available under the terms of the MIT Public License (*MIT*) and copyright (c) 2016-2020 INRIA, CMU, and Microsoft @@ -1437,3 +1442,28 @@ 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. + + +The MIT License (MIT) - sentry-native +================================= + +Copyright (c) 2019 Sentry (https://sentry.io) and individual contributors. +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/scripts/android/cmake.sh b/scripts/android/cmake.sh index 08aede711d..b0fb4dd48b 100755 --- a/scripts/android/cmake.sh +++ b/scripts/android/cmake.sh @@ -145,6 +145,8 @@ if [[ "$RELEASE" ]]; then -DANDROID_SDK_ROOT=$ANDROID_SDK_ROOT \ -DCMAKE_BUILD_TYPE=Release \ -DADJUST_TOKEN=$ADJUST_SDK_TOKEN \ + -DSENTRY_DSN=$SENTRY_DSN \ + -DSENTRY_ENVELOPE_ENDPOINT=$SENTRY_ENVELOPE_ENDPOINT \ -S . -B .tmp/ else printn Y "Use debug config \n" @@ -154,6 +156,8 @@ else -DANDROID_NDK_ROOT=$ANDROID_NDK_ROOT \ -DANDROID_SDK_ROOT=$ANDROID_SDK_ROOT \ -DCMAKE_BUILD_TYPE=Debug \ + -DSENTRY_DSN=$SENTRY_DSN \ + -DSENTRY_ENVELOPE_ENDPOINT=$SENTRY_ENVELOPE_ENDPOINT \ -S . -B .tmp/ fi diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d947e21ea1..8295383c2d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -32,6 +32,7 @@ target_link_libraries(mozillavpn PUBLIC ${CMAKE_DL_LIBS}) target_link_libraries(mozillavpn PRIVATE glean lottie nebula translations) include(cmake/sources.cmake) +include(cmake/sentry.cmake) if(${BUILD_DUMMY}) set(MVPN_PLATFORM_NAME "dummy") @@ -61,4 +62,4 @@ if(NOT ${CMAKE_SYSTEM_NAME} STREQUAL "Emscripten") endif() endif() -qt_finalize_target(mozillavpn) \ No newline at end of file +qt_finalize_target(mozillavpn) diff --git a/src/cmake/sentry.cmake b/src/cmake/sentry.cmake new file mode 100644 index 0000000000..4d59c2bffe --- /dev/null +++ b/src/cmake/sentry.cmake @@ -0,0 +1,107 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +# This CMAKE File will Integrate Sentry into MVPN + + +# Defines which OS builds can include sentry. Check src/cmake Lists for all values of MVPN_PLATFORM_NAME +set(SENTRY_SUPPORTED_OS "Windows" "Darwin" "Android") +set(EXTERNAL_INSTALL_LOCATION ${CMAKE_BINARY_DIR}/external) +include(ExternalProject) + + + +LIST(FIND SENTRY_SUPPORTED_OS ${CMAKE_SYSTEM_NAME} _SUPPORTED) + +if(NOT SENTRY_DSN OR NOT SENTRY_ENVELOPE_ENDPOINT) +message( "Disabling Sentry, as params are not given") +set( _SUPPORTED -1) +endif() + + + +## Remove support for android 32bit. +## It's currently broken. see: VPN-3332 +if( CMAKE_ANDROID_ARCH STREQUAL "x86" ) + set( _SUPPORTED -1) +elseif( CMAKE_ANDROID_ARCH STREQUAL "arm" ) + set( _SUPPORTED -1) +endif() + +if( ${_SUPPORTED} GREATER -1 ) + message("Building sentry for ${CMAKE_SYSTEM_NAME}") + target_compile_definitions(mozillavpn PRIVATE SENTRY_ENVELOPE_ENDPOINT="${SENTRY_ENVELOPE_ENDPOINT}") + target_compile_definitions(mozillavpn PRIVATE SENTRY_DSN="${SENTRY_DSN}") + target_compile_definitions(mozillavpn PRIVATE SENTRY_ENABLED) + # Sentry support is given + target_sources(mozillavpn PRIVATE + sentry/sentryadapter.cpp + sentry/sentryadapter.h + ) + + # Configure Linking and Compile + if(APPLE) + include(cmake/osxtools.cmake) + # Let sentry.h know we are using a static build + target_compile_definitions(mozillavpn PRIVATE SENTRY_BUILD_STATIC) + # Let mozilla-vpn know we need to provide the upload client + target_compile_definitions(mozillavpn PRIVATE SENTRY_NONE_TRANSPORT) + # Compile Static for apple and link to libsentry.a + target_link_libraries(mozillavpn PUBLIC libsentry.a) + target_link_libraries(mozillavpn PUBLIC breakpad_client.a) + # We are using breakpad as a backend - in process stackwalking is never the best option ... however! + # this is super easy to link against and we do not need another binary shipped with the client. + SET(SENTRY_ARGS -DSENTRY_BACKEND=breakpad -DSENTRY_BUILD_SHARED_LIBS=false -DSENTRY_TRANSPORT=none -DSENTRY_BUILD_TESTS=off -DSENTRY_BUILD_EXAMPLES=off) + endif() + if(WIN32) + # Let sentry.h know we are using a static build + target_compile_definitions(mozillavpn PRIVATE SENTRY_BUILD_STATIC) + # Link against static sentry + breakpad + the stack unwind utils + target_link_libraries(mozillavpn PUBLIC sentry.lib) + target_link_libraries(mozillavpn PUBLIC breakpad_client.lib) + target_link_libraries(mozillavpn PUBLIC dbghelp.lib) + # Windows will use the winhttp transport btw + SET(SENTRY_ARGS -DSENTRY_BUILD_SHARED_LIBS=false -DSENTRY_BACKEND=breakpad -DCMAKE_BUILD_TYPE=Release) + endif() + + if(ANDROID) + # Let mozilla-vpn know we need to provide the upload client + target_compile_definitions(mozillavpn PRIVATE SENTRY_NONE_TRANSPORT) + + target_link_libraries(mozillavpn PUBLIC libsentry.a) + target_link_libraries(mozillavpn PUBLIC libunwindstack.a) + # We can only use inproc as crash backend. + SET(SENTRY_ARGS -DSENTRY_BUILD_SHARED_LIBS=false + -DANDROID_PLATFORM=21 + -DCMAKE_SYSTEM_NAME=Android + -DANDROID_ABI=${ANDROID_ABI} + -DCMAKE_ANDROID_NDK=${ANDROID_NDK_ROOT} + -DCMAKE_TOOLCHAIN_FILE=${ANDROID_NDK_ROOT}/build/cmake/android.toolchain.cmake + -DSENTRY_BACKEND=inproc + ) + endif() + + include(ExternalProject) + ExternalProject_Add(sentry + SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}../../3rdparty/sentry + GIT_REPOSITORY https://github.com/getsentry/sentry-native/ + GIT_TAG 0.5.0 + CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${EXTERNAL_INSTALL_LOCATION} ${SENTRY_ARGS} + ) + + target_include_directories(mozillavpn PUBLIC ${EXTERNAL_INSTALL_LOCATION}/include) + target_link_directories( mozillavpn PUBLIC ${EXTERNAL_INSTALL_LOCATION}/lib) + add_dependencies(mozillavpn sentry) +else() + # Sentry is not supported on this Plattform. + message("Sentry supported OS -> ${SENTRY_SUPPORTED_OS}") + message("Cannot build sentry for ${CMAKE_SYSTEM_NAME}") +endif() + +# Add Sources that will be required anyway +target_sources(mozillavpn PRIVATE + tasks/sentry/tasksentry.cpp + tasks/sentry/tasksentry.h +) \ No newline at end of file diff --git a/src/commands/commandui.cpp b/src/commands/commandui.cpp index b9391116f6..39ef3d85c6 100644 --- a/src/commands/commandui.cpp +++ b/src/commands/commandui.cpp @@ -65,7 +65,6 @@ #endif #ifdef MVPN_WINDOWS -# include "crashreporter/crashclient.h" # include "eventlistener.h" # include "platforms/windows/windowsstartatbootwatcher.h" # include "platforms/windows/windowsappimageprovider.h" @@ -179,9 +178,6 @@ int CommandUI::run(QStringList& tokens) { std::cerr.clear(); } # endif - - CrashClient::instance().start(CommandLineParser::argc(), - CommandLineParser::argv()); #endif #ifdef MVPN_DEBUG diff --git a/src/constants.h b/src/constants.h index 0f9eba9fc6..98c95fd4c2 100644 --- a/src/constants.h +++ b/src/constants.h @@ -85,6 +85,14 @@ CONSTEXPR(uint32_t, controllerPeriodicStateRecorderMsec, 10800000, 60000, 0) #define PRODBETAEXPR(type, functionName, prod, beta) \ inline type functionName() { return inProduction() ? prod : beta; } +#ifdef SENTRY_ENABLED +constexpr const char* SENTRY_DSN_ENDPOINT = SENTRY_DSN; +constexpr const char* SENTRY_ENVELOPE_INGESTION = SENTRY_ENVELOPE_ENDPOINT; +#else +constexpr const char* SENTRY_DSN_ENDPOINT = ""; +constexpr const char* SENTRY_ENVELOPE_INGESTION = ""; +#endif + constexpr const char* API_PRODUCTION_URL = "https://vpn.mozilla.org"; constexpr const char* API_STAGING_URL = "https://stage-vpn.guardian.nonprod.cloudops.mozgcp.net"; diff --git a/src/featureslist.h b/src/featureslist.h index 7786f27d86..d70bd5c3f5 100644 --- a/src/featureslist.h +++ b/src/featureslist.h @@ -240,3 +240,11 @@ FEATURE_SIMPLE(gleanRust, // Feature ID FeatureCallback_true, // Can be flipped off QStringList(), // feature dependencies FeatureCallback_false) + +FEATURE_SIMPLE(sentry, // Feature ID + "Sentry Crash Report SDK", // Feature name + "2.12.0", // released + FeatureCallback_true, // Can be flipped on + FeatureCallback_true, // Can be flipped off + QStringList(), // feature dependencies + FeatureCallback_inStaging) diff --git a/src/mozillavpn.cpp b/src/mozillavpn.cpp index d9fdff8d21..d2bdece29f 100644 --- a/src/mozillavpn.cpp +++ b/src/mozillavpn.cpp @@ -45,6 +45,10 @@ #include "urlopener.h" #include "websocket/websockethandler.h" +#ifdef SENTRY_ENABLED +# include "sentry/sentryadapter.h" +#endif + #ifdef MVPN_IOS # include "platforms/ios/iosdatamigration.h" # include "platforms/ios/iosutils.h" @@ -994,6 +998,9 @@ void MozillaVPN::mainWindowLoaded() { m_gleanTimer.start(Constants::gleanTimeoutMsec()); m_gleanTimer.setSingleShot(false); #endif +#ifdef SENTRY_ENABLED + SentryAdapter::instance()->init(); +#endif } void MozillaVPN::telemetryPolicyCompleted() { @@ -1603,6 +1610,13 @@ void MozillaVPN::exitForUnrecoverableError(const QString& reason) { void MozillaVPN::crashTest() { logger.debug() << "Crashing Application"; + + unsigned char* test = NULL; + test[1000] = 'a'; //<< here it should crash + + // Interestingly this does not cause a "Signal" but a VC runtime exception + // and more interestingly, neither breakpad nor crashpad are catchting this on + // windows... char* text = new char[100]; delete[] text; delete[] text; diff --git a/src/networkrequest.cpp b/src/networkrequest.cpp index cf49d7d484..07cb2ea5ea 100644 --- a/src/networkrequest.cpp +++ b/src/networkrequest.cpp @@ -831,6 +831,19 @@ NetworkRequest* NetworkRequest::createForFxaSessionDestroy( return r; } +// static +NetworkRequest* NetworkRequest::createForSentry(Task* parent, + const QByteArray& envelope) { + NetworkRequest* r = new NetworkRequest(parent, 200, false); + QUrl url(Constants::SENTRY_ENVELOPE_INGESTION); + r->m_request.setUrl(url); + r->m_request.setHeader(QNetworkRequest::ContentTypeHeader, + "application/x-sentry-envelope"); + r->m_request.setRawHeader("dsn", Constants::SENTRY_DSN_ENDPOINT); + r->postRequest(envelope); + return r; +} + NetworkRequest* NetworkRequest::createForProducts(Task* parent) { Q_ASSERT(parent); diff --git a/src/networkrequest.h b/src/networkrequest.h index 3c4f295c16..011c851cd2 100644 --- a/src/networkrequest.h +++ b/src/networkrequest.h @@ -138,6 +138,9 @@ class NetworkRequest final : public QObject { static NetworkRequest* createForFxaSessionDestroy( Task* parent, const QByteArray& sessionToken); + static NetworkRequest* createForSentry(Task* parent, + const QByteArray& envelope); + static NetworkRequest* createForProducts(Task* parent); #ifdef MVPN_IOS diff --git a/src/sentry/sentryadapter.cpp b/src/sentry/sentryadapter.cpp new file mode 100644 index 0000000000..226989a89c --- /dev/null +++ b/src/sentry/sentryadapter.cpp @@ -0,0 +1,152 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "sentryadapter.h" + +#include + +#include + +#include "constants.h" +#include "models/feature.h" +#include "leakdetector.h" +#include "loghandler.h" +#include "logger.h" +#include "settingsholder.h" +#include "tasks/sentry/tasksentry.h" +#include "taskscheduler.h" +#include "mozillavpn.h" + +namespace { +SentryAdapter* s_instance = nullptr; +Logger logger(LOG_MAIN, "Sentry"); + +} // namespace + +SentryAdapter* SentryAdapter::instance() { + if (s_instance == nullptr) { + s_instance = new SentryAdapter(); + } + return s_instance; +} +SentryAdapter::SentryAdapter() { MVPN_COUNT_CTOR(SentryAdapter); } +SentryAdapter::~SentryAdapter() { MVPN_COUNT_DTOR(SentryAdapter); } + +void SentryAdapter::init() { + if (!Feature::get(Feature::Feature_sentry)->isSupported()) { + return; + } + if (QString(Constants::SENTRY_DSN_ENDPOINT).isEmpty() || + QString(Constants::SENTRY_ENVELOPE_INGESTION).isEmpty()) { + logger.error() << "Sentry failed to init, no sentry config present"; + return; + } + + auto vpn = MozillaVPN::instance(); + auto log = LogHandler::instance(); + + QDir dataDir( + QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation)); + QString sentryFolder = dataDir.absoluteFilePath("sentry"); + + connect(vpn, &MozillaVPN::aboutToQuit, this, + &SentryAdapter::onBeforeShutdown); + connect(log, &LogHandler::logEntryAdded, this, + &SentryAdapter::onLoglineAdded); + + sentry_options_t* options = sentry_options_new(); + sentry_options_set_dsn(options, Constants::SENTRY_DSN_ENDPOINT); + sentry_options_set_environment( + options, Constants::inProduction() ? "production" : "stage"); + sentry_options_set_release( + options, Constants::versionString().toLocal8Bit().constData()); + sentry_options_set_database_path(options, + sentryFolder.toLocal8Bit().constData()); + sentry_options_set_on_crash(options, &SentryAdapter::onCrash, NULL); + +#ifdef SENTRY_NONE_TRANSPORT + sentry_transport_t* transport = + sentry_transport_new(&SentryAdapter::transportEnvelope); + sentry_transport_set_state(transport, nullptr); + sentry_options_set_transport(options, transport); +#endif + + // Uncomment the following line for debugging purposes. + // Be warned, it's spammy to stdout. + // sentry_options_set_debug(options, 1); + + if (sentry_init(options) == -1) { + logger.error() << "Sentry failed to init!"; + return; + }; + m_initialized = true; + logger.info() << "Sentry initialized"; +} + +void SentryAdapter::report(const QString& errorType, const QString& message, + bool attachStackTrace) { + if (!m_initialized) { + return; + } + sentry_value_t event = sentry_value_new_event(); + sentry_value_t exc = sentry_value_new_exception(errorType.toLocal8Bit(), + message.toLocal8Bit()); + + if (attachStackTrace) { + sentry_value_set_stacktrace(exc, NULL, 0); + } + sentry_event_add_exception(event, exc); + sentry_capture_event(event); +} + +void SentryAdapter::onBeforeShutdown() { sentry_close(); } + +void SentryAdapter::onLoglineAdded(const QByteArray& line) { + if (!m_initialized) { + return; + } + // Todo: we could certainly catch this more early and format the data ? + // (VPN-3276) + sentry_value_t crumb = + sentry_value_new_breadcrumb("Logger", line.constData()); + sentry_add_breadcrumb(crumb); +} + +sentry_value_t SentryAdapter::onCrash(const sentry_ucontext_t* uctx, + sentry_value_t event, void* closure) { + logger.info() << "Sentry ON CRASH"; + // Do contextual clean-up before the crash is sent to sentry's backend + // infrastructure + bool shouldSend = true; + // Todo: We can use this callback to make sure + // we only send data with user consent. + // Tracked in: VPN-3158 + // We could: + // -> Maybe start a new Process for the Crash-Report UI ask for consent + // (VPN-2823) + // -> Check if a setting "upload crashes" is present. + // If we should not send it, we can discard the crash data here :) + if (shouldSend) { + return event; + } + sentry_value_decref(event); + return sentry_value_new_null(); +} + +// static +void SentryAdapter::transportEnvelope(sentry_envelope_t* envelope, + void* state) { + Q_UNUSED(state); + size_t sentry_buf_size = 0; + char* sentry_buf = sentry_envelope_serialize(envelope, &sentry_buf_size); + + // Qt Will copy this. + auto qt_owned_buffer = QByteArray(sentry_buf, sentry_buf_size); + // We can now free the envelope. + sentry_envelope_free(envelope); + sentry_free(sentry_buf); + + auto t = new TaskSentry(qt_owned_buffer); + TaskScheduler::scheduleTask(t); +} diff --git a/src/sentry/sentryadapter.h b/src/sentry/sentryadapter.h new file mode 100644 index 0000000000..b976358f60 --- /dev/null +++ b/src/sentry/sentryadapter.h @@ -0,0 +1,97 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef SENTRYADAPTER_H +#define SENTRYADAPTER_H + +#include +#include + +#include + +class SentryAdapter final : public QObject { + Q_OBJECT + Q_DISABLE_COPY_MOVE(SentryAdapter) + + public: + ~SentryAdapter(); + static SentryAdapter* instance(); + + /** + * @brief Inits Sentry. + * + * This is a no-op if the client is in production mode. + */ + void init(); + + /** + * @brief Sends an "Issue" report to Sentry + * + * @param category - "Category" of the error, any String is valid. + * @param message - Additional message content. + * @param attachStackTrace - If true a stacktrace for later debugging will be + * attached. + */ + void report(const QString& category, const QString& message, + bool attachStackTrace = false); + + /** + * @brief Event Slot for when a log-line is added. + * + * The logline will be added as a Sentry-Breadrumb, so that + * the next "report" or crash will have the last few + * loglines available + * + * @param line - UTF-8 encoded bytes of the logline. + */ + Q_SLOT void onLoglineAdded(const QByteArray& line); + + /** + * @brief Event Slot for when the client is about to Shut down + * + * This will call sentry to wrap up - this might cause new network requests + * or drisk writes. + * After calling this, any call to sentry is UB. + */ + Q_SLOT void onBeforeShutdown(); + + /** + * @brief Callback for when sentry's backend recieved a crash. + * + * In this function we can decide to either send, discard the crash + * and additonally "scrub" the minidump of data, if wanted. + * + * @param uctx provides the user-space context of the crash + * @param event used the same way as in `before_send` + * @param closure user-data that you can provide at configuration time + * (we'dont.) + * @return either the @param event or null_sentry_value , if the crash event + * should not be recorded. + */ + static sentry_value_t onCrash(const sentry_ucontext_t* uctx, + sentry_value_t event, void* closure); + + /** + * @brief Send's a Sentry Event "envelope" to the Sentry endpoint. + * + * Will be used if NONE_TRANSPORT is enabled in cmake. + * Will create a Task in the TaskSheudler to send that. + * + * @param envelope Envelope to be sent to sentry. + * The transport takes ownership of the `envelope`, and must free it once it + * is done. + * @param state + * If the transport requires state, such as an HTTP + * client object or request queue, it can be specified in the `state` + * parameter when configuring the transport. It will be passed as second + * argument to this function. We are not using that. + * + */ + static void transportEnvelope(sentry_envelope_t* envelope, void* state); + + private: + bool m_initialized = false; + SentryAdapter(); +}; +#endif // SENTRYADAPTER_H diff --git a/src/tasks/sentry/tasksentry.cpp b/src/tasks/sentry/tasksentry.cpp new file mode 100644 index 0000000000..3445dcaa44 --- /dev/null +++ b/src/tasks/sentry/tasksentry.cpp @@ -0,0 +1,38 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "tasksentry.h" +#include "errorhandler.h" +#include "leakdetector.h" +#include "logger.h" +#include "mozillavpn.h" +#include "networkrequest.h" + +namespace { +Logger logger(LOG_MAIN, "TaskSentry"); +} + +TaskSentry::TaskSentry(const QByteArray& envelope) : Task("TaskSentry") { + MVPN_COUNT_CTOR(TaskSentry); + m_envelope = envelope; +} + +TaskSentry::~TaskSentry() { MVPN_COUNT_DTOR(TaskSentry); } + +void TaskSentry::run() { + NetworkRequest* request = NetworkRequest::createForSentry(this, m_envelope); + + connect(request, &NetworkRequest::requestFailed, this, + [this](QNetworkReply::NetworkError error, const QByteArray&) { + Q_UNUSED(error); + logger.error() << "Failed to send envelope"; + emit completed(); + }); + connect(request, &NetworkRequest::requestCompleted, this, + [this](const QByteArray& data) { + Q_UNUSED(data); + logger.debug() << "Sentry sent events"; + emit completed(); + }); +} \ No newline at end of file diff --git a/src/tasks/sentry/tasksentry.h b/src/tasks/sentry/tasksentry.h new file mode 100644 index 0000000000..cdaeebbfc3 --- /dev/null +++ b/src/tasks/sentry/tasksentry.h @@ -0,0 +1,27 @@ + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef TASKSENTRY_H +#define TASKSENTRY_H + +#include "task.h" + +#include +#include + +class TaskSentry final : public Task { + Q_DISABLE_COPY_MOVE(TaskSentry) + + public: + TaskSentry(const QByteArray& envelope); + ~TaskSentry(); + + void run() override; + + private: + QByteArray m_envelope; +}; + +#endif // TASKSENTRY_H \ No newline at end of file diff --git a/taskcluster/scripts/fetch/enter_dev_shell.ps1 b/taskcluster/scripts/fetch/enter_dev_shell.ps1 index 242b00ec4d..39e4fd4cf3 100644 --- a/taskcluster/scripts/fetch/enter_dev_shell.ps1 +++ b/taskcluster/scripts/fetch/enter_dev_shell.ps1 @@ -28,8 +28,8 @@ ForEach-Object -InputObject $INCLUDE_ADDS { } $LIB_ADDS = ` - "$W10_SDK_PATH\lib\$W10_SDK_VERSION\ucrt\x64;" ,` - "$W10_SDK_PATH\lib\$W10_SDK_VERSION\um\x64;" ,` + "$W10_SDK_PATH\Lib\$W10_SDK_VERSION\ucrt\x64;" ,` + "$W10_SDK_PATH\Lib\$W10_SDK_VERSION\um\x64;" ,` "$VS_STUDIO_LOCATION\VC\Tools\MSVC\$MSVC_VERSION\ATLMFC\lib\x64;" ,` "$VS_STUDIO_LOCATION\VC\Tools\MSVC\$MSVC_VERSION\lib\x64;" ,` "$VS_STUDIO_LOCATION\DIA SDK\lib\amd64;"