mirror of
https://github.com/cookiengineer/audacity
synced 2025-06-15 15:49:36 +02:00
💩 Remove Sentry Reporting
This commit is contained in:
parent
29a1ae6faa
commit
bbf352d36d
15
.github/workflows/cmake_build.yml
vendored
15
.github/workflows/cmake_build.yml
vendored
@ -81,9 +81,6 @@ jobs:
|
|||||||
- name: Configure
|
- name: Configure
|
||||||
env:
|
env:
|
||||||
# Error reporing
|
# Error reporing
|
||||||
SENTRY_DSN_KEY: ${{ secrets.SENTRY_DSN_KEY }}
|
|
||||||
SENTRY_HOST: ${{ secrets.SENTRY_HOST }}
|
|
||||||
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
|
|
||||||
CRASH_REPORT_URL: ${{ secrets.CRASH_REPORT_URL }}
|
CRASH_REPORT_URL: ${{ secrets.CRASH_REPORT_URL }}
|
||||||
# Apple code signing
|
# Apple code signing
|
||||||
APPLE_CODESIGN_IDENTITY: ${{ secrets.APPLE_CODESIGN_IDENTITY }}
|
APPLE_CODESIGN_IDENTITY: ${{ secrets.APPLE_CODESIGN_IDENTITY }}
|
||||||
@ -96,18 +93,6 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
exec bash "scripts/ci/build.sh"
|
exec bash "scripts/ci/build.sh"
|
||||||
|
|
||||||
- name: Upload debug symbols
|
|
||||||
if: startsWith(github.ref, 'refs/heads/release-')
|
|
||||||
env:
|
|
||||||
SENTRY_HOST: ${{ secrets.SENTRY_HOST }}
|
|
||||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
|
||||||
SENTRY_ORG_SLUG: ${{ secrets.SENTRY_ORG_SLUG }}
|
|
||||||
SENTRY_PROJECT_SLUG: ${{ secrets.SENTRY_PROJECT_SLUG }}
|
|
||||||
#this is required to run sentry's get-cli script on platforms where 'sudo' is not available
|
|
||||||
INSTALL_DIR: ${{ github.workspace }}
|
|
||||||
run: |
|
|
||||||
exec bash "scripts/ci/upload_debug_symbols.sh"
|
|
||||||
|
|
||||||
- name: Install
|
- name: Install
|
||||||
run: |
|
run: |
|
||||||
exec bash "scripts/ci/install.sh"
|
exec bash "scripts/ci/install.sh"
|
||||||
|
@ -170,19 +170,8 @@ include( CMakePushCheckState )
|
|||||||
include( GNUInstallDirs )
|
include( GNUInstallDirs )
|
||||||
include( TestBigEndian )
|
include( TestBigEndian )
|
||||||
|
|
||||||
set_from_env(SENTRY_DSN_KEY)
|
|
||||||
set_from_env(SENTRY_HOST)
|
|
||||||
set_from_env(SENTRY_PROJECT)
|
|
||||||
set_from_env(CRASH_REPORT_URL)
|
set_from_env(CRASH_REPORT_URL)
|
||||||
|
|
||||||
cmake_dependent_option(
|
|
||||||
${_OPT}has_sentry_reporting
|
|
||||||
"Build support for sending errors to Sentry"
|
|
||||||
On
|
|
||||||
"${_OPT}has_networking;DEFINED SENTRY_DSN_KEY;DEFINED SENTRY_HOST;DEFINED SENTRY_PROJECT"
|
|
||||||
Off
|
|
||||||
)
|
|
||||||
|
|
||||||
cmake_dependent_option(
|
cmake_dependent_option(
|
||||||
${_OPT}has_crashreports
|
${_OPT}has_crashreports
|
||||||
"Enables crash reporting for Audacity"
|
"Enables crash reporting for Audacity"
|
||||||
|
@ -9,15 +9,6 @@ set( LIBRARIES
|
|||||||
lib-uuid
|
lib-uuid
|
||||||
)
|
)
|
||||||
|
|
||||||
if ( ${_OPT}has_networking )
|
|
||||||
list( APPEND LIBRARIES lib-network-manager)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# This library depends on lib-network-manager
|
|
||||||
# If Sentry reporting is disabled, an INTERFACE library
|
|
||||||
# will be defined
|
|
||||||
list( APPEND LIBRARIES lib-sentry-reporting)
|
|
||||||
|
|
||||||
foreach( LIBRARY ${LIBRARIES} )
|
foreach( LIBRARY ${LIBRARIES} )
|
||||||
add_subdirectory( "${LIBRARY}" )
|
add_subdirectory( "${LIBRARY}" )
|
||||||
endforeach()
|
endforeach()
|
||||||
|
@ -1,91 +0,0 @@
|
|||||||
/*!********************************************************************
|
|
||||||
|
|
||||||
Audacity: A Digital Audio Editor
|
|
||||||
|
|
||||||
@file AnonymizedMessage.cpp
|
|
||||||
@brief Define a class to store anonymized messages.
|
|
||||||
|
|
||||||
Dmitry Vedenko
|
|
||||||
**********************************************************************/
|
|
||||||
|
|
||||||
#include "AnonymizedMessage.h"
|
|
||||||
|
|
||||||
#include <regex>
|
|
||||||
|
|
||||||
#include "CodeConversions.h"
|
|
||||||
|
|
||||||
namespace audacity
|
|
||||||
{
|
|
||||||
namespace sentry
|
|
||||||
{
|
|
||||||
|
|
||||||
AnonymizedMessage::AnonymizedMessage(std::string message)
|
|
||||||
: mMessage(std::move(message))
|
|
||||||
{
|
|
||||||
CleanupPaths();
|
|
||||||
}
|
|
||||||
|
|
||||||
AnonymizedMessage::AnonymizedMessage(const std::wstring& message)
|
|
||||||
: AnonymizedMessage(ToUTF8(message))
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
AnonymizedMessage::AnonymizedMessage(const wxString& message)
|
|
||||||
: AnonymizedMessage(ToUTF8(message))
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
AnonymizedMessage::AnonymizedMessage(const char* message)
|
|
||||||
: AnonymizedMessage(std::string(message))
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
AnonymizedMessage::AnonymizedMessage(const wchar_t* message)
|
|
||||||
: AnonymizedMessage(ToUTF8(message))
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AnonymizedMessage::Empty() const noexcept
|
|
||||||
{
|
|
||||||
return mMessage.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t AnonymizedMessage::Length() const noexcept
|
|
||||||
{
|
|
||||||
return mMessage.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::string& AnonymizedMessage::GetString() const noexcept
|
|
||||||
{
|
|
||||||
return mMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
wxString AnonymizedMessage::ToWXString() const noexcept
|
|
||||||
{
|
|
||||||
return audacity::ToWXString(mMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* AnonymizedMessage::c_str() const noexcept
|
|
||||||
{
|
|
||||||
return mMessage.c_str();
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t AnonymizedMessage::length() const noexcept
|
|
||||||
{
|
|
||||||
return mMessage.length();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AnonymizedMessage::CleanupPaths()
|
|
||||||
{
|
|
||||||
// Finding the path boundary in the arbitrary text is a hard task.
|
|
||||||
// We assume that spaces cannot be a part of the path.
|
|
||||||
// In the worst case - we will get <path> <path>
|
|
||||||
static const std::regex re(
|
|
||||||
R"(\b(?:(?:[a-zA-Z]:)?[\\/]?)?(?:[^<>:"/|\\/?\s*]+[\\/]+)*(?:[^<>:"/|\\/?*\s]+\.\w+)?)");
|
|
||||||
|
|
||||||
mMessage = std::regex_replace(
|
|
||||||
mMessage, re, "<path>", std::regex_constants::match_not_null);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace sentry
|
|
||||||
} // namespace audacity
|
|
@ -1,70 +0,0 @@
|
|||||||
/*!********************************************************************
|
|
||||||
|
|
||||||
Audacity: A Digital Audio Editor
|
|
||||||
|
|
||||||
@file AnonymizedMessage.h
|
|
||||||
@brief Declare a class to store anonymized messages.
|
|
||||||
|
|
||||||
Dmitry Vedenko
|
|
||||||
**********************************************************************/
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <wx/string.h>
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
namespace audacity
|
|
||||||
{
|
|
||||||
namespace sentry
|
|
||||||
{
|
|
||||||
//! A class, that stores anonymized message.
|
|
||||||
/*!
|
|
||||||
Input message is anonymized by looking for path-like patterns.
|
|
||||||
Messages are stored in UTF8 format.
|
|
||||||
*/
|
|
||||||
class SENTRY_REPORTING_API AnonymizedMessage final
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
//! Creates an empty message
|
|
||||||
AnonymizedMessage() = default;
|
|
||||||
|
|
||||||
AnonymizedMessage(const AnonymizedMessage&) = default;
|
|
||||||
AnonymizedMessage(AnonymizedMessage&&) = default;
|
|
||||||
|
|
||||||
AnonymizedMessage& operator=(const AnonymizedMessage&) = default;
|
|
||||||
AnonymizedMessage& operator=(AnonymizedMessage&&) = default;
|
|
||||||
|
|
||||||
//! Creates a message from std::string
|
|
||||||
AnonymizedMessage(std::string message);
|
|
||||||
//! Creates a message from std::wstring
|
|
||||||
AnonymizedMessage(const std::wstring& message);
|
|
||||||
//! Creates a message from wxString
|
|
||||||
AnonymizedMessage(const wxString& message);
|
|
||||||
//! Creates a message from const char*
|
|
||||||
AnonymizedMessage(const char* message);
|
|
||||||
//! Creates a message from const wchar_t*
|
|
||||||
AnonymizedMessage(const wchar_t* message);
|
|
||||||
|
|
||||||
//! Checks, if the message is empty
|
|
||||||
bool Empty() const noexcept;
|
|
||||||
//! Returns the length of the message
|
|
||||||
size_t Length() const noexcept;
|
|
||||||
|
|
||||||
//! Returns the UTF8 representation of the message
|
|
||||||
const std::string& GetString() const noexcept;
|
|
||||||
//! Convert the message to wxString
|
|
||||||
wxString ToWXString() const noexcept;
|
|
||||||
|
|
||||||
// Imitate std::string interface
|
|
||||||
//! Checks, if the message is empty
|
|
||||||
const char* c_str() const noexcept;
|
|
||||||
//! Returns the length of the message
|
|
||||||
size_t length() const noexcept;
|
|
||||||
private:
|
|
||||||
void CleanupPaths();
|
|
||||||
|
|
||||||
std::string mMessage;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace sentry
|
|
||||||
} // namespace audacity
|
|
@ -1,48 +0,0 @@
|
|||||||
#[[
|
|
||||||
A library, that allows sending error reports to a Sentry server
|
|
||||||
using Exception and Message interfaces.
|
|
||||||
]]#
|
|
||||||
|
|
||||||
set( TARGET lib-sentry-reporting )
|
|
||||||
set( TARGET_ROOT ${CMAKE_CURRENT_SOURCE_DIR} )
|
|
||||||
|
|
||||||
def_vars()
|
|
||||||
|
|
||||||
if(${_OPT}has_sentry_reporting)
|
|
||||||
set( SOURCES
|
|
||||||
SentryHelper.h
|
|
||||||
|
|
||||||
AnonymizedMessage.h
|
|
||||||
AnonymizedMessage.cpp
|
|
||||||
|
|
||||||
SentryReport.h
|
|
||||||
SentryReport.cpp
|
|
||||||
|
|
||||||
SentryRequestBuilder.h
|
|
||||||
SentryRequestBuilder.cpp
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
set ( LIBRARIES PRIVATE
|
|
||||||
lib-network-manager # Required for the networking
|
|
||||||
lib-string-utils # ToUtf8
|
|
||||||
lib-uuid # UUIDs are required as an event identifier.
|
|
||||||
RapidJSON::RapidJSON # Protocol is JSON based
|
|
||||||
wxwidgets::base # Required to retreive the OS information
|
|
||||||
)
|
|
||||||
|
|
||||||
set ( DEFINES
|
|
||||||
INTERFACE
|
|
||||||
HAS_SENTRY_REPORTING=1
|
|
||||||
PRIVATE
|
|
||||||
# The variables below will be used to construct Sentry URL:
|
|
||||||
# https://${SENTRY_DSN_KEY}@${SENTRY_HOST}/api/${SENTRY_PROJECT}/store
|
|
||||||
SENTRY_DSN_KEY="${SENTRY_DSN_KEY}"
|
|
||||||
SENTRY_HOST="${SENTRY_HOST}"
|
|
||||||
SENTRY_PROJECT="${SENTRY_PROJECT}"
|
|
||||||
)
|
|
||||||
|
|
||||||
audacity_library( ${TARGET} "${SOURCES}" "${LIBRARIES}" "${DEFINES}" "" )
|
|
||||||
else()
|
|
||||||
audacity_header_only_library( ${TARGET} "SentryHelper.h" "" "" "" )
|
|
||||||
endif()
|
|
@ -1,25 +0,0 @@
|
|||||||
/**********************************************************************
|
|
||||||
|
|
||||||
Audacity: A Digital Audio Editor
|
|
||||||
|
|
||||||
SentryHelper.h
|
|
||||||
|
|
||||||
Defines a macro ADD_EXCEPTION_CONTEXT, that is a no op if Sentry reporting is disabled.
|
|
||||||
|
|
||||||
Dmitry Vedenko
|
|
||||||
|
|
||||||
**********************************************************************/
|
|
||||||
|
|
||||||
#ifndef __AUDACITY_SENTRY__
|
|
||||||
#define __AUDACITY_SENTRY__
|
|
||||||
|
|
||||||
#ifdef HAS_SENTRY_REPORTING
|
|
||||||
# include "SentryReport.h"
|
|
||||||
|
|
||||||
# define ADD_EXCEPTION_CONTEXT(name, value) audacity::sentry::AddExceptionContext(name, value)
|
|
||||||
#else
|
|
||||||
# define ADD_EXCEPTION_CONTEXT(name, value)
|
|
||||||
#endif // HAS_SENTRY_REPORTING
|
|
||||||
|
|
||||||
|
|
||||||
#endif /* __AUDACITY_SENTRY__ */
|
|
@ -1,428 +0,0 @@
|
|||||||
/*!********************************************************************
|
|
||||||
|
|
||||||
Audacity: A Digital Audio Editor
|
|
||||||
|
|
||||||
@file SentryReport.cpp
|
|
||||||
@brief Define a class to report errors to Sentry.
|
|
||||||
|
|
||||||
Dmitry Vedenko
|
|
||||||
**********************************************************************/
|
|
||||||
|
|
||||||
#include "SentryReport.h"
|
|
||||||
|
|
||||||
#include <chrono>
|
|
||||||
#include <cstring>
|
|
||||||
#include <mutex>
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <cctype>
|
|
||||||
#include <regex>
|
|
||||||
|
|
||||||
#include <rapidjson/document.h>
|
|
||||||
#include <rapidjson/writer.h>
|
|
||||||
#include <rapidjson/prettywriter.h>
|
|
||||||
|
|
||||||
#include <wx/platinfo.h>
|
|
||||||
#include <wx/log.h>
|
|
||||||
|
|
||||||
#include "CodeConversions.h"
|
|
||||||
#include "Uuid.h"
|
|
||||||
|
|
||||||
#include "IResponse.h"
|
|
||||||
#include "NetworkManager.h"
|
|
||||||
|
|
||||||
#include "SentryRequestBuilder.h"
|
|
||||||
|
|
||||||
namespace audacity
|
|
||||||
{
|
|
||||||
namespace sentry
|
|
||||||
{
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
|
|
||||||
//! Helper class to store additional details about the exception
|
|
||||||
/*! This small class is a thread safe store for the information
|
|
||||||
we want to add to the exception before the exception occurs.
|
|
||||||
For example, we may log SQLite3 return codes here, as otherwise
|
|
||||||
they wont be available when everything fails
|
|
||||||
*/
|
|
||||||
class ExceptionContext final
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
//! Adds a new item to the exception context
|
|
||||||
void Add(std::string parameterName, AnonymizedMessage parameterValue)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mDataMutex);
|
|
||||||
mData.emplace_back(std::move(parameterName), std::move(parameterValue));
|
|
||||||
}
|
|
||||||
|
|
||||||
//! Return the current context and reset it
|
|
||||||
std::vector<ExceptionData> MoveParameters()
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mDataMutex);
|
|
||||||
|
|
||||||
std::vector<ExceptionData> emptyVector;
|
|
||||||
|
|
||||||
std::swap(mData, emptyVector);
|
|
||||||
|
|
||||||
return emptyVector;
|
|
||||||
}
|
|
||||||
|
|
||||||
//! Get an instance of the ExceptionContext
|
|
||||||
static ExceptionContext& Get()
|
|
||||||
{
|
|
||||||
static ExceptionContext instance;
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
ExceptionContext() = default;
|
|
||||||
|
|
||||||
std::mutex mDataMutex;
|
|
||||||
std::vector<ExceptionData> mData;
|
|
||||||
};
|
|
||||||
|
|
||||||
//! Append the data about the operating system to the JSON document
|
|
||||||
void AddOSContext(
|
|
||||||
rapidjson::Value& root, rapidjson::Document::AllocatorType& allocator)
|
|
||||||
{
|
|
||||||
rapidjson::Value osContext(rapidjson::kObjectType);
|
|
||||||
|
|
||||||
const wxPlatformInfo platformInfo = wxPlatformInfo::Get();
|
|
||||||
|
|
||||||
const std::string osName =
|
|
||||||
ToUTF8(platformInfo.GetOperatingSystemFamilyName());
|
|
||||||
|
|
||||||
osContext.AddMember("type", rapidjson::Value("os", allocator), allocator);
|
|
||||||
|
|
||||||
osContext.AddMember(
|
|
||||||
"name", rapidjson::Value(osName.c_str(), osName.length(), allocator),
|
|
||||||
allocator);
|
|
||||||
|
|
||||||
const std::string osVersion =
|
|
||||||
std::to_string(platformInfo.GetOSMajorVersion()) + "." +
|
|
||||||
std::to_string(platformInfo.GetOSMinorVersion()) + "." +
|
|
||||||
std::to_string(platformInfo.GetOSMicroVersion());
|
|
||||||
|
|
||||||
osContext.AddMember(
|
|
||||||
"version",
|
|
||||||
rapidjson::Value(osVersion.c_str(), osVersion.length(), allocator),
|
|
||||||
allocator);
|
|
||||||
|
|
||||||
root.AddMember("os", std::move(osContext), allocator);
|
|
||||||
}
|
|
||||||
|
|
||||||
//! Create the minimal required Sentry JSON document
|
|
||||||
rapidjson::Document CreateSentryDocument()
|
|
||||||
{
|
|
||||||
using namespace std::chrono;
|
|
||||||
rapidjson::Document document;
|
|
||||||
|
|
||||||
document.SetObject();
|
|
||||||
|
|
||||||
document.AddMember(
|
|
||||||
"timestamp",
|
|
||||||
rapidjson::Value(
|
|
||||||
duration_cast<seconds>(system_clock::now().time_since_epoch())
|
|
||||||
.count()),
|
|
||||||
document.GetAllocator());
|
|
||||||
|
|
||||||
std::string eventId = Uuid::Generate().ToHexString();
|
|
||||||
|
|
||||||
document.AddMember(
|
|
||||||
"event_id",
|
|
||||||
rapidjson::Value(
|
|
||||||
eventId.c_str(), eventId.length(), document.GetAllocator()),
|
|
||||||
document.GetAllocator());
|
|
||||||
|
|
||||||
constexpr char platform[] = "native";
|
|
||||||
|
|
||||||
document.AddMember(
|
|
||||||
"platform",
|
|
||||||
rapidjson::Value(platform, sizeof(platform) - 1, document.GetAllocator()),
|
|
||||||
document.GetAllocator());
|
|
||||||
|
|
||||||
document["platform"].SetString(
|
|
||||||
platform, sizeof(platform) - 1, document.GetAllocator());
|
|
||||||
|
|
||||||
const std::string release = std::string("audacity@") +
|
|
||||||
std::to_string(AUDACITY_VERSION) + "." +
|
|
||||||
std::to_string(AUDACITY_RELEASE) + "." +
|
|
||||||
std::to_string(AUDACITY_REVISION);
|
|
||||||
|
|
||||||
document.AddMember(
|
|
||||||
"release",
|
|
||||||
rapidjson::Value(
|
|
||||||
release.c_str(), release.length(), document.GetAllocator()),
|
|
||||||
document.GetAllocator());
|
|
||||||
|
|
||||||
rapidjson::Value contexts = rapidjson::Value(rapidjson::kObjectType);
|
|
||||||
|
|
||||||
AddOSContext(contexts, document.GetAllocator());
|
|
||||||
|
|
||||||
document.AddMember("contexts", contexts, document.GetAllocator());
|
|
||||||
|
|
||||||
return document;
|
|
||||||
}
|
|
||||||
|
|
||||||
//! Append the ExceptionData to the Exception JSON object
|
|
||||||
void AddExceptionDataToJson(
|
|
||||||
rapidjson::Value& value, rapidjson::Document::AllocatorType& allocator,
|
|
||||||
const ExceptionData& data)
|
|
||||||
{
|
|
||||||
value.AddMember(
|
|
||||||
rapidjson::Value(data.first.c_str(), data.first.length(), allocator),
|
|
||||||
rapidjson::Value(data.second.c_str(), data.second.length(), allocator),
|
|
||||||
allocator);
|
|
||||||
}
|
|
||||||
|
|
||||||
//! Serialize the Exception to JSON
|
|
||||||
void SerializeException(
|
|
||||||
const Exception& exception, rapidjson::Value& root,
|
|
||||||
rapidjson::Document::AllocatorType& allocator)
|
|
||||||
{
|
|
||||||
root.AddMember(
|
|
||||||
"type",
|
|
||||||
rapidjson::Value(
|
|
||||||
exception.Type.c_str(), exception.Type.length(), allocator),
|
|
||||||
allocator);
|
|
||||||
|
|
||||||
root.AddMember(
|
|
||||||
"value",
|
|
||||||
rapidjson::Value(
|
|
||||||
exception.Value.c_str(), exception.Value.length(), allocator),
|
|
||||||
allocator);
|
|
||||||
|
|
||||||
rapidjson::Value mechanismObject(rapidjson::kObjectType);
|
|
||||||
|
|
||||||
mechanismObject.AddMember(
|
|
||||||
"type", rapidjson::Value("runtime_error", allocator), allocator);
|
|
||||||
|
|
||||||
mechanismObject.AddMember(
|
|
||||||
"handled", false, allocator);
|
|
||||||
|
|
||||||
auto contextData = ExceptionContext::Get().MoveParameters();
|
|
||||||
|
|
||||||
if (!exception.Data.empty() || !contextData.empty())
|
|
||||||
{
|
|
||||||
rapidjson::Value dataObject(rapidjson::kObjectType);
|
|
||||||
|
|
||||||
for (const auto& data : contextData)
|
|
||||||
AddExceptionDataToJson(dataObject, allocator, data);
|
|
||||||
|
|
||||||
for (const auto& data : exception.Data)
|
|
||||||
AddExceptionDataToJson(dataObject, allocator, data);
|
|
||||||
|
|
||||||
mechanismObject.AddMember("data", std::move(dataObject), allocator);
|
|
||||||
}
|
|
||||||
|
|
||||||
root.AddMember("mechanism", std::move(mechanismObject), allocator);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
Exception Exception::Create(std::string type, AnonymizedMessage value)
|
|
||||||
{
|
|
||||||
std::replace_if(type.begin(), type.end(), [](char c) {
|
|
||||||
return std::isspace(c) != 0;
|
|
||||||
}, '_');
|
|
||||||
|
|
||||||
return { std::move(type), std::move(value) };
|
|
||||||
}
|
|
||||||
|
|
||||||
Exception Exception::Create(AnonymizedMessage value)
|
|
||||||
{
|
|
||||||
return { "runtime_error", std::move(value) };
|
|
||||||
}
|
|
||||||
|
|
||||||
Exception& Exception::AddData(std::string key, AnonymizedMessage value)
|
|
||||||
{
|
|
||||||
Data.emplace_back(std::move(key), std::move(value));
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
Message Message::Create(AnonymizedMessage message)
|
|
||||||
{
|
|
||||||
return { std::move(message) };
|
|
||||||
}
|
|
||||||
|
|
||||||
Message& Message::AddParam(AnonymizedMessage value)
|
|
||||||
{
|
|
||||||
Params.emplace_back(std::move(value));
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AddExceptionContext(
|
|
||||||
std::string parameterName, AnonymizedMessage parameterValue)
|
|
||||||
{
|
|
||||||
ExceptionContext::Get().Add(std::move (parameterName), std::move (parameterValue));
|
|
||||||
}
|
|
||||||
|
|
||||||
class Report::ReportImpl
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
explicit ReportImpl(const Exception& exception);
|
|
||||||
explicit ReportImpl(const Message& message);
|
|
||||||
|
|
||||||
void AddUserComment(const std::string& message);
|
|
||||||
|
|
||||||
std::string ToString(bool pretty) const;
|
|
||||||
|
|
||||||
void Send(CompletionHandler completionHandler) const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
rapidjson::Document mDocument;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
Report::ReportImpl::ReportImpl(const Exception& exception)
|
|
||||||
: mDocument(CreateSentryDocument())
|
|
||||||
{
|
|
||||||
rapidjson::Value exceptionObject(rapidjson::kObjectType);
|
|
||||||
rapidjson::Value valuesArray(rapidjson::kArrayType);
|
|
||||||
rapidjson::Value valueObject(rapidjson::kObjectType);
|
|
||||||
|
|
||||||
SerializeException(exception, valueObject, mDocument.GetAllocator());
|
|
||||||
|
|
||||||
valuesArray.PushBack(std::move(valueObject), mDocument.GetAllocator());
|
|
||||||
|
|
||||||
exceptionObject.AddMember(
|
|
||||||
"values", std::move(valuesArray), mDocument.GetAllocator());
|
|
||||||
|
|
||||||
mDocument.AddMember(
|
|
||||||
"exception", std::move(exceptionObject), mDocument.GetAllocator());
|
|
||||||
}
|
|
||||||
|
|
||||||
Report::ReportImpl::ReportImpl(const Message& message)
|
|
||||||
: mDocument(CreateSentryDocument())
|
|
||||||
{
|
|
||||||
rapidjson::Value messageObject(rapidjson::kObjectType);
|
|
||||||
|
|
||||||
messageObject.AddMember(
|
|
||||||
"message",
|
|
||||||
rapidjson::Value(
|
|
||||||
message.Value.c_str(), message.Value.length(),
|
|
||||||
mDocument.GetAllocator()),
|
|
||||||
mDocument.GetAllocator());
|
|
||||||
|
|
||||||
if (!message.Params.empty())
|
|
||||||
{
|
|
||||||
rapidjson::Value paramsArray(rapidjson::kArrayType);
|
|
||||||
|
|
||||||
for (const AnonymizedMessage& param : message.Params)
|
|
||||||
{
|
|
||||||
paramsArray.PushBack(
|
|
||||||
rapidjson::Value(
|
|
||||||
param.c_str(), param.length(), mDocument.GetAllocator()),
|
|
||||||
mDocument.GetAllocator());
|
|
||||||
}
|
|
||||||
|
|
||||||
messageObject.AddMember(
|
|
||||||
"params", std::move(paramsArray), mDocument.GetAllocator());
|
|
||||||
}
|
|
||||||
|
|
||||||
mDocument.AddMember(
|
|
||||||
"message", std::move(messageObject), mDocument.GetAllocator());
|
|
||||||
}
|
|
||||||
|
|
||||||
void Report::ReportImpl::AddUserComment(const std::string& message)
|
|
||||||
{
|
|
||||||
// We only allow adding comment to exceptions now
|
|
||||||
if (!mDocument.HasMember("exception") || message.empty())
|
|
||||||
return;
|
|
||||||
|
|
||||||
rapidjson::Value& topException = mDocument["exception"]["values"][0];
|
|
||||||
|
|
||||||
if (!topException.IsObject())
|
|
||||||
return;
|
|
||||||
|
|
||||||
rapidjson::Value& mechanism = topException["mechanism"];
|
|
||||||
|
|
||||||
// Create a data object if it still does not exist
|
|
||||||
if (!mechanism.HasMember("data"))
|
|
||||||
{
|
|
||||||
mechanism.AddMember(
|
|
||||||
"data", rapidjson::Value(rapidjson::kObjectType),
|
|
||||||
mDocument.GetAllocator());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add a comment itself
|
|
||||||
mechanism["data"].AddMember(
|
|
||||||
"user_comment",
|
|
||||||
rapidjson::Value(
|
|
||||||
message.data(), message.length(), mDocument.GetAllocator()),
|
|
||||||
mDocument.GetAllocator());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void Report::ReportImpl::Send(CompletionHandler completionHandler) const
|
|
||||||
{
|
|
||||||
const std::string serializedDocument = ToString(false);
|
|
||||||
|
|
||||||
network_manager::Request request =
|
|
||||||
SentryRequestBuilder::Get().CreateRequest();
|
|
||||||
|
|
||||||
auto response = network_manager::NetworkManager::GetInstance().doPost(
|
|
||||||
request, serializedDocument.data(), serializedDocument.size());
|
|
||||||
|
|
||||||
response->setRequestFinishedCallback(
|
|
||||||
[response, handler = std::move(completionHandler)](network_manager::IResponse*) {
|
|
||||||
const std::string responseData = response->readAll<std::string>();
|
|
||||||
|
|
||||||
wxLogDebug(responseData.c_str());
|
|
||||||
|
|
||||||
if (handler)
|
|
||||||
handler(response->getHTTPCode(), responseData);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string Report::ReportImpl::ToString(bool pretty) const
|
|
||||||
{
|
|
||||||
rapidjson::StringBuffer buffer;
|
|
||||||
|
|
||||||
if (pretty)
|
|
||||||
{
|
|
||||||
rapidjson::PrettyWriter<rapidjson::StringBuffer> writer(buffer);
|
|
||||||
mDocument.Accept(writer);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
|
|
||||||
mDocument.Accept(writer);
|
|
||||||
}
|
|
||||||
|
|
||||||
return std::string(buffer.GetString());
|
|
||||||
}
|
|
||||||
|
|
||||||
Report::~Report()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
Report::Report(const Exception& exception)
|
|
||||||
: mImpl(std::make_unique<ReportImpl>(exception))
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
Report::Report(const Message& message)
|
|
||||||
: mImpl(std::make_unique<ReportImpl>(message))
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void Report::AddUserComment(const std::string& comment)
|
|
||||||
{
|
|
||||||
mImpl->AddUserComment(comment);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string Report::GetReportPreview() const
|
|
||||||
{
|
|
||||||
return mImpl->ToString(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Report::Send(CompletionHandler completionHandler) const
|
|
||||||
{
|
|
||||||
mImpl->Send(std::move (completionHandler));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
} // namespace sentry
|
|
||||||
} // namespace audacity
|
|
@ -1,95 +0,0 @@
|
|||||||
/*!********************************************************************
|
|
||||||
|
|
||||||
Audacity: A Digital Audio Editor
|
|
||||||
|
|
||||||
@file SentryReport.h
|
|
||||||
@brief Declare a class to report errors to Sentry.
|
|
||||||
|
|
||||||
Dmitry Vedenko
|
|
||||||
**********************************************************************/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <utility>
|
|
||||||
#include <vector>
|
|
||||||
#include <memory>
|
|
||||||
#include <functional>
|
|
||||||
|
|
||||||
#include "AnonymizedMessage.h"
|
|
||||||
|
|
||||||
namespace audacity
|
|
||||||
{
|
|
||||||
namespace sentry
|
|
||||||
{
|
|
||||||
|
|
||||||
//! Additional payload to the exception
|
|
||||||
using ExceptionData = std::pair<std::string, AnonymizedMessage>;
|
|
||||||
|
|
||||||
//! A DTO for the Sentry Exception interface
|
|
||||||
struct SENTRY_REPORTING_API Exception final
|
|
||||||
{
|
|
||||||
//! Exception type. Should not have spaces.
|
|
||||||
std::string Type;
|
|
||||||
//! Message, associated with the Exception
|
|
||||||
AnonymizedMessage Value;
|
|
||||||
//! Arbitrary payload
|
|
||||||
std::vector<ExceptionData> Data;
|
|
||||||
|
|
||||||
//! Create a new exception
|
|
||||||
static Exception Create(std::string type, AnonymizedMessage value);
|
|
||||||
//! Create a new exception with type runtime_error
|
|
||||||
static Exception Create(AnonymizedMessage value);
|
|
||||||
//! Add a payload to the exception
|
|
||||||
Exception& AddData(std::string key, AnonymizedMessage value);
|
|
||||||
};
|
|
||||||
|
|
||||||
//! A DTO for the Sentry Message interface
|
|
||||||
struct SENTRY_REPORTING_API Message final
|
|
||||||
{
|
|
||||||
//! A string, possibly with %s placeholders, containing the message
|
|
||||||
AnonymizedMessage Value;
|
|
||||||
//! Values for the placeholders
|
|
||||||
std::vector<AnonymizedMessage> Params;
|
|
||||||
//! Create a new Message
|
|
||||||
static Message Create(AnonymizedMessage message);
|
|
||||||
//! Add a parameter to the Message
|
|
||||||
Message& AddParam(AnonymizedMessage value);
|
|
||||||
};
|
|
||||||
|
|
||||||
//! Saves a parameter, that will be appended to the next Exception report
|
|
||||||
SENTRY_REPORTING_API void AddExceptionContext(
|
|
||||||
std::string parameterName, AnonymizedMessage parameterValue);
|
|
||||||
|
|
||||||
//! A report to Sentry
|
|
||||||
class SENTRY_REPORTING_API Report final
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
//! A callback, that will be called when Send completes
|
|
||||||
using CompletionHandler = std::function<void (int httpCode, std::string responseBody)>;
|
|
||||||
|
|
||||||
//! Create a report from the exception and previously added exception context
|
|
||||||
explicit Report(const Exception& exception);
|
|
||||||
|
|
||||||
//! Create a report with a single log message
|
|
||||||
explicit Report(const Message& message);
|
|
||||||
|
|
||||||
~Report();
|
|
||||||
|
|
||||||
//! Adds a user comment to the exception report
|
|
||||||
void AddUserComment(const std::string& comment);
|
|
||||||
|
|
||||||
//! Get a pretty printed report preview
|
|
||||||
std::string GetReportPreview() const;
|
|
||||||
|
|
||||||
//! Send the report to Sentry
|
|
||||||
void Send(CompletionHandler completionHandler) const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
class ReportImpl;
|
|
||||||
|
|
||||||
std::unique_ptr<ReportImpl> mImpl;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace sentry
|
|
||||||
} // namespace audacity
|
|
@ -1,53 +0,0 @@
|
|||||||
/*!********************************************************************
|
|
||||||
|
|
||||||
Audacity: A Digital Audio Editor
|
|
||||||
|
|
||||||
@file SentryRequestBuilder.h
|
|
||||||
@brief Define a class to generate the requests to Sentry.
|
|
||||||
|
|
||||||
Dmitry Vedenko
|
|
||||||
**********************************************************************/
|
|
||||||
|
|
||||||
#include "SentryRequestBuilder.h"
|
|
||||||
|
|
||||||
#include <chrono>
|
|
||||||
|
|
||||||
namespace audacity
|
|
||||||
{
|
|
||||||
namespace sentry
|
|
||||||
{
|
|
||||||
|
|
||||||
const SentryRequestBuilder& audacity::sentry::SentryRequestBuilder::Get()
|
|
||||||
{
|
|
||||||
static SentryRequestBuilder builder;
|
|
||||||
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
network_manager::Request SentryRequestBuilder::CreateRequest() const
|
|
||||||
{
|
|
||||||
using namespace std::chrono;
|
|
||||||
|
|
||||||
const std::string sentryAuth =
|
|
||||||
std::string("Sentry sentry_version=7,sentry_timestamp=") +
|
|
||||||
std::to_string(
|
|
||||||
duration_cast<seconds>(system_clock::now().time_since_epoch())
|
|
||||||
.count()) +
|
|
||||||
",sentry_client=sentry-audacity/1.0,sentry_key=" + SENTRY_DSN_KEY;
|
|
||||||
|
|
||||||
network_manager::Request request(mUrl);
|
|
||||||
|
|
||||||
request.setHeader("Content-Type", "application/json");
|
|
||||||
request.setHeader("X-Sentry-Auth", sentryAuth);
|
|
||||||
|
|
||||||
return request;
|
|
||||||
}
|
|
||||||
|
|
||||||
SentryRequestBuilder::SentryRequestBuilder()
|
|
||||||
{
|
|
||||||
mUrl = std::string("https://") + SENTRY_DSN_KEY + "@" + SENTRY_HOST +
|
|
||||||
"/api/" + SENTRY_PROJECT + "/store/";
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace sentry
|
|
||||||
} // namespace audacity
|
|
@ -1,36 +0,0 @@
|
|||||||
/*!********************************************************************
|
|
||||||
|
|
||||||
Audacity: A Digital Audio Editor
|
|
||||||
|
|
||||||
@file SentryRequestBuilder.cpp
|
|
||||||
@brief Declare a class to generate the requests to Sentry.
|
|
||||||
|
|
||||||
Dmitry Vedenko
|
|
||||||
**********************************************************************/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
#include "Request.h"
|
|
||||||
|
|
||||||
namespace audacity
|
|
||||||
{
|
|
||||||
namespace sentry
|
|
||||||
{
|
|
||||||
// This is a private class, so it is not exported
|
|
||||||
//! A helper, that creates a correct Request to Sentry
|
|
||||||
class SentryRequestBuilder final
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
static const SentryRequestBuilder& Get();
|
|
||||||
|
|
||||||
network_manager::Request CreateRequest() const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
SentryRequestBuilder();
|
|
||||||
|
|
||||||
std::string mUrl;
|
|
||||||
};
|
|
||||||
} // namespace sentry
|
|
||||||
} // namespace audacity
|
|
@ -1,16 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
((${BASH_VERSION%%.*} >= 4)) || { echo >&2 "$0: Error: Please upgrade Bash."; exit 1; }
|
|
||||||
|
|
||||||
set -euxo pipefail
|
|
||||||
|
|
||||||
# download sentry-cli
|
|
||||||
# TODO: currently this script downloads binaries and install them
|
|
||||||
# each time job is started, workarounds?
|
|
||||||
curl -sL https://sentry.io/get-cli/ | bash
|
|
||||||
|
|
||||||
SYMBOLS=$(find debug | xargs)
|
|
||||||
|
|
||||||
${INSTALL_DIR}/sentry-cli --auth-token ${SENTRY_AUTH_TOKEN} --url ${SENTRY_HOST} upload-dif \
|
|
||||||
--org ${SENTRY_ORG_SLUG} \
|
|
||||||
--project ${SENTRY_PROJECT_SLUG} ${SYMBOLS}
|
|
@ -959,10 +959,6 @@ list( APPEND SOURCES
|
|||||||
widgets/ReadOnlyText.h
|
widgets/ReadOnlyText.h
|
||||||
widgets/Ruler.cpp
|
widgets/Ruler.cpp
|
||||||
widgets/Ruler.h
|
widgets/Ruler.h
|
||||||
$<$<BOOL:${${_OPT}has_sentry_reporting}>:
|
|
||||||
widgets/ErrorReportDialog.cpp
|
|
||||||
widgets/ErrorReportDialog.h
|
|
||||||
>
|
|
||||||
widgets/Warning.cpp
|
widgets/Warning.cpp
|
||||||
widgets/Warning.h
|
widgets/Warning.h
|
||||||
widgets/WindowAccessible.cpp
|
widgets/WindowAccessible.cpp
|
||||||
|
@ -39,9 +39,6 @@
|
|||||||
#include "../Prefs.h"
|
#include "../Prefs.h"
|
||||||
#include "HelpSystem.h"
|
#include "HelpSystem.h"
|
||||||
|
|
||||||
#ifdef HAS_SENTRY_REPORTING
|
|
||||||
# include "ErrorReportDialog.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
BEGIN_EVENT_TABLE(ErrorDialog, wxDialogWrapper)
|
BEGIN_EVENT_TABLE(ErrorDialog, wxDialogWrapper)
|
||||||
EVT_COLLAPSIBLEPANE_CHANGED( wxID_ANY, ErrorDialog::OnPane )
|
EVT_COLLAPSIBLEPANE_CHANGED( wxID_ANY, ErrorDialog::OnPane )
|
||||||
@ -169,13 +166,8 @@ void ShowExceptionDialog(
|
|||||||
const TranslatableString& message, const wxString& helpPage, bool Close,
|
const TranslatableString& message, const wxString& helpPage, bool Close,
|
||||||
const wxString& log)
|
const wxString& log)
|
||||||
{
|
{
|
||||||
#ifndef HAS_SENTRY_REPORTING
|
|
||||||
ShowErrorDialog(parent, dlogTitle, message, helpPage, Close,
|
ShowErrorDialog(parent, dlogTitle, message, helpPage, Close,
|
||||||
audacity::ToWString(log));
|
audacity::ToWString(log));
|
||||||
#else
|
|
||||||
ShowErrorReportDialog(parent, dlogTitle, message, helpPage,
|
|
||||||
audacity::ToWString(log));
|
|
||||||
#endif // !HAS_SENTRY_REPORTING
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// unused.
|
// unused.
|
||||||
|
@ -1,221 +0,0 @@
|
|||||||
/**********************************************************************
|
|
||||||
|
|
||||||
Audacity: A Digital Audio Editor
|
|
||||||
|
|
||||||
ErrorReportDialog.cpp
|
|
||||||
|
|
||||||
Dmitry Vedenko
|
|
||||||
|
|
||||||
**********************************************************************/
|
|
||||||
|
|
||||||
#include "ErrorReportDialog.h"
|
|
||||||
|
|
||||||
#include <wx/app.h>
|
|
||||||
#include <wx/artprov.h>
|
|
||||||
#include <wx/button.h>
|
|
||||||
#include <wx/collpane.h>
|
|
||||||
#include <wx/dialog.h>
|
|
||||||
#include <wx/html/htmlwin.h>
|
|
||||||
#include <wx/icon.h>
|
|
||||||
#include <wx/intl.h>
|
|
||||||
#include <wx/settings.h>
|
|
||||||
#include <wx/sizer.h>
|
|
||||||
#include <wx/statbmp.h>
|
|
||||||
#include <wx/stattext.h>
|
|
||||||
#include <wx/statusbr.h>
|
|
||||||
#include <wx/textctrl.h>
|
|
||||||
#include <wx/bmpbuttn.h>
|
|
||||||
|
|
||||||
#include "AllThemeResources.h"
|
|
||||||
#include "Theme.h"
|
|
||||||
#include "HelpText.h"
|
|
||||||
#include "Prefs.h"
|
|
||||||
#include "ShuttleGui.h"
|
|
||||||
#include "HelpSystem.h"
|
|
||||||
|
|
||||||
#include "SentryReport.h"
|
|
||||||
#include "CodeConversions.h"
|
|
||||||
|
|
||||||
constexpr int MaxUserCommentLength = 2000;
|
|
||||||
|
|
||||||
BEGIN_EVENT_TABLE(ErrorReportDialog, wxDialogWrapper)
|
|
||||||
EVT_BUTTON(wxID_YES, ErrorReportDialog::OnSend)
|
|
||||||
EVT_BUTTON(wxID_NO, ErrorReportDialog::OnDontSend)
|
|
||||||
EVT_BUTTON(wxID_HELP, ErrorReportDialog::OnHelp)
|
|
||||||
END_EVENT_TABLE()
|
|
||||||
|
|
||||||
ErrorReportDialog::ErrorReportDialog(
|
|
||||||
wxWindow* parent, const TranslatableString& dlogTitle,
|
|
||||||
const TranslatableString& message, const wxString& helpUrl,
|
|
||||||
const wxString& log, const bool modal)
|
|
||||||
: wxDialogWrapper(
|
|
||||||
parent, wxID_ANY, dlogTitle, wxDefaultPosition, wxDefaultSize,
|
|
||||||
wxDEFAULT_DIALOG_STYLE)
|
|
||||||
, mHelpUrl(helpUrl)
|
|
||||||
, mIsModal(modal)
|
|
||||||
{
|
|
||||||
audacity::sentry::Exception ex = audacity::sentry::Exception::Create(
|
|
||||||
audacity::ToUTF8(dlogTitle.Debug()),message.Debug());
|
|
||||||
|
|
||||||
if (!log.empty())
|
|
||||||
ex.AddData("log", log);
|
|
||||||
|
|
||||||
mReport = std::make_unique<audacity::sentry::Report> (ex);
|
|
||||||
|
|
||||||
ShuttleGui S(this, eIsCreating);
|
|
||||||
|
|
||||||
const wxFont headingFont = wxFont(wxFontInfo(12).Bold());
|
|
||||||
const wxFont textFont = wxFont(wxFontInfo(10));
|
|
||||||
|
|
||||||
S.SetBorder(0);
|
|
||||||
|
|
||||||
S.StartHorizontalLay(wxEXPAND, 0);
|
|
||||||
{
|
|
||||||
S.AddSpace(40, 0);
|
|
||||||
S.StartVerticalLay(wxEXPAND, 0);
|
|
||||||
{
|
|
||||||
S.AddSpace(0, 32);
|
|
||||||
|
|
||||||
S.StartHorizontalLay(wxEXPAND, 1);
|
|
||||||
{
|
|
||||||
S.StartVerticalLay(0);
|
|
||||||
{
|
|
||||||
wxBitmap bitmap = wxArtProvider::GetBitmap(
|
|
||||||
wxART_WARNING, wxART_MESSAGE_BOX, wxSize(24, 24));
|
|
||||||
|
|
||||||
S.Prop(0).AddWindow(
|
|
||||||
safenew wxStaticBitmap(S.GetParent(), -1, bitmap));
|
|
||||||
|
|
||||||
S.AddSpace(0, 0, 1);
|
|
||||||
}
|
|
||||||
S.EndVerticalLay();
|
|
||||||
|
|
||||||
S.AddSpace(10, 0);
|
|
||||||
|
|
||||||
S.StartVerticalLay(0);
|
|
||||||
{
|
|
||||||
S.AddSpace(0, 7);
|
|
||||||
|
|
||||||
S.Prop(1)
|
|
||||||
.AddVariableText(message, false, 0, 560)
|
|
||||||
->SetFont(headingFont);
|
|
||||||
}
|
|
||||||
S.EndVerticalLay();
|
|
||||||
}
|
|
||||||
S.EndHorizontalLay();
|
|
||||||
|
|
||||||
S.AddSpace(0, 20);
|
|
||||||
|
|
||||||
S.AddVariableText(XO(
|
|
||||||
"Click \"Send\" to submit the report to Audacity. This information is collected anonymously."))
|
|
||||||
->SetFont(textFont);
|
|
||||||
|
|
||||||
S.AddSpace(0, 20);
|
|
||||||
|
|
||||||
S.AddVariableText(XO("Problem details"))->SetFont(textFont);
|
|
||||||
|
|
||||||
S.AddSpace(0, 6);
|
|
||||||
|
|
||||||
S.Style(wxTE_RICH | wxTE_READONLY | wxTE_MULTILINE | wxTE_DONTWRAP)
|
|
||||||
.MinSize(wxSize(0, 152))
|
|
||||||
.Name(XO("Problem details"))
|
|
||||||
.AddTextBox({}, mReport->GetReportPreview(), 0);
|
|
||||||
|
|
||||||
S.AddSpace(0, 20);
|
|
||||||
|
|
||||||
S.AddVariableText(XO("Comments"))->SetFont(textFont);
|
|
||||||
|
|
||||||
S.AddSpace(0, 6);
|
|
||||||
|
|
||||||
mCommentsControl = S.Style(wxTE_MULTILINE)
|
|
||||||
.MinSize(wxSize(0, 76))
|
|
||||||
.Name(XO("Comments"))
|
|
||||||
.AddTextBox({}, {}, 0);
|
|
||||||
|
|
||||||
mCommentsControl->SetMaxLength(MaxUserCommentLength);
|
|
||||||
|
|
||||||
S.AddSpace(0, 20);
|
|
||||||
|
|
||||||
S.StartHorizontalLay(wxEXPAND);
|
|
||||||
{
|
|
||||||
if (!mHelpUrl.empty())
|
|
||||||
{
|
|
||||||
wxBitmapButton* helpButton =
|
|
||||||
S.Id(wxID_HELP).AddBitmapButton(theTheme.Bitmap(bmpHelpIcon));
|
|
||||||
// For screen readers
|
|
||||||
helpButton->SetToolTip(XO("Help").Translation());
|
|
||||||
helpButton->SetLabel(XO("Help").Translation());
|
|
||||||
}
|
|
||||||
|
|
||||||
S.AddSpace(0, 0, 1);
|
|
||||||
|
|
||||||
S.Id(wxID_NO).AddButton(XC("&Don't send", "crash reporter button"));
|
|
||||||
|
|
||||||
S.AddSpace(13, 0);
|
|
||||||
|
|
||||||
S.Id(wxID_YES).AddButton(XC("&Send", "crash reporter button"));
|
|
||||||
}
|
|
||||||
S.EndHorizontalLay();
|
|
||||||
|
|
||||||
S.AddSpace(0, 20);
|
|
||||||
}
|
|
||||||
S.EndVerticalLay();
|
|
||||||
|
|
||||||
S.AddSpace(28, 0);
|
|
||||||
}
|
|
||||||
S.EndHorizontalLay();
|
|
||||||
|
|
||||||
S.SetBorder(2);
|
|
||||||
|
|
||||||
Layout();
|
|
||||||
GetSizer()->Fit(this);
|
|
||||||
SetMinSize(GetSize());
|
|
||||||
Center();
|
|
||||||
}
|
|
||||||
|
|
||||||
ErrorReportDialog::~ErrorReportDialog()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void ErrorReportDialog::OnSend(wxCommandEvent& event)
|
|
||||||
{
|
|
||||||
Disable();
|
|
||||||
|
|
||||||
mReport->AddUserComment(audacity::ToUTF8(mCommentsControl->GetValue()));
|
|
||||||
|
|
||||||
mReport->Send(
|
|
||||||
[this](int code, std::string body) {
|
|
||||||
CallAfter([this]() {
|
|
||||||
EndModal(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void ErrorReportDialog::OnDontSend(wxCommandEvent& event)
|
|
||||||
{
|
|
||||||
EndModal(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ErrorReportDialog::OnHelp(wxCommandEvent& event)
|
|
||||||
{
|
|
||||||
if (mHelpUrl.StartsWith(wxT("innerlink:")))
|
|
||||||
{
|
|
||||||
HelpSystem::ShowHtmlText(
|
|
||||||
this, TitleText(mHelpUrl.Mid(10)), HelpText(mHelpUrl.Mid(10)), false,
|
|
||||||
true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
HelpSystem::ShowHelp(this, mHelpUrl, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ShowErrorReportDialog(
|
|
||||||
wxWindow* parent, const TranslatableString& dlogTitle,
|
|
||||||
const TranslatableString& message, const wxString& helpPage,
|
|
||||||
const wxString& log)
|
|
||||||
{
|
|
||||||
ErrorReportDialog dlog(parent, dlogTitle, message, helpPage, log);
|
|
||||||
|
|
||||||
dlog.CentreOnParent();
|
|
||||||
dlog.ShowModal();
|
|
||||||
}
|
|
@ -1,69 +0,0 @@
|
|||||||
/**********************************************************************
|
|
||||||
|
|
||||||
Audacity: A Digital Audio Editor
|
|
||||||
|
|
||||||
ErrorReportDialog.h
|
|
||||||
|
|
||||||
Dmitry Vedenko
|
|
||||||
|
|
||||||
**********************************************************************/
|
|
||||||
|
|
||||||
#ifndef __AUDACITY_SENTRYERRORDIALOG__
|
|
||||||
#define __AUDACITY_SENTRYERRORDIALOG__
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
#include <wx/defs.h>
|
|
||||||
#include <wx/msgdlg.h>
|
|
||||||
|
|
||||||
#include "wxPanelWrapper.h" // to inherit
|
|
||||||
|
|
||||||
namespace audacity
|
|
||||||
{
|
|
||||||
namespace sentry
|
|
||||||
{
|
|
||||||
class Report;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class wxTextCtrl;
|
|
||||||
|
|
||||||
//! A dialog, that has "Send", "Don't send" and help buttons.
|
|
||||||
/*! This dialog is used in place of error dialogs for Audacity errors
|
|
||||||
when Sentry reporting is enabled.
|
|
||||||
*/
|
|
||||||
class ErrorReportDialog final : public wxDialogWrapper
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
ErrorReportDialog(
|
|
||||||
wxWindow* parent, const TranslatableString& dlogTitle,
|
|
||||||
const TranslatableString& message, const wxString& helpUrl,
|
|
||||||
const wxString& log, const bool modal = true);
|
|
||||||
|
|
||||||
~ErrorReportDialog();
|
|
||||||
|
|
||||||
private:
|
|
||||||
void OnSend(wxCommandEvent& event);
|
|
||||||
void OnDontSend(wxCommandEvent& event);
|
|
||||||
|
|
||||||
void OnHelp(wxCommandEvent& event);
|
|
||||||
|
|
||||||
std::unique_ptr<audacity::sentry::Report> mReport;
|
|
||||||
|
|
||||||
wxString mHelpUrl;
|
|
||||||
|
|
||||||
wxTextCtrl* mCommentsControl;
|
|
||||||
|
|
||||||
bool mIsModal;
|
|
||||||
|
|
||||||
DECLARE_EVENT_TABLE()
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Displays an error dialog that allows to send the error report
|
|
||||||
AUDACITY_DLL_API
|
|
||||||
void ShowErrorReportDialog(
|
|
||||||
wxWindow* parent, const TranslatableString& dlogTitle,
|
|
||||||
const TranslatableString& message, const wxString& helpPage = {},
|
|
||||||
const wxString& log = {});
|
|
||||||
|
|
||||||
#endif // __AUDACITY_SENTRYERRORDIALOG__
|
|
Loading…
x
Reference in New Issue
Block a user