2020-04-07 08:38:54 +00:00
|
|
|
// sentryIntegration.cxx - Interface with Sentry.io crash reporting
|
|
|
|
//
|
|
|
|
// Copyright (C) 2020 James Turner james@flightgear.org
|
|
|
|
//
|
|
|
|
// This program is free software; you can redistribute it and/or
|
|
|
|
// modify it under the terms of the GNU General Public License as
|
|
|
|
// published by the Free Software Foundation; either version 2 of the
|
|
|
|
// License, or (at your option) any later version.
|
|
|
|
//
|
|
|
|
// This program is distributed in the hope that it will be useful, but
|
|
|
|
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
// General Public License for more details.
|
|
|
|
//
|
|
|
|
// You should have received a copy of the GNU General Public License
|
|
|
|
// along with this program; if not, write to the Free Software
|
|
|
|
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
|
|
|
|
#include "config.h"
|
|
|
|
|
|
|
|
#include "sentryIntegration.hxx"
|
|
|
|
|
2020-10-15 15:17:30 +00:00
|
|
|
#include <cstring> // for strcmp
|
|
|
|
|
2020-09-18 09:34:40 +00:00
|
|
|
#include <simgear/debug/LogCallback.hxx>
|
2020-08-17 12:10:12 +00:00
|
|
|
#include <simgear/debug/logstream.hxx>
|
2020-04-07 08:38:54 +00:00
|
|
|
#include <simgear/misc/sg_path.hxx>
|
2020-08-17 12:10:12 +00:00
|
|
|
#include <simgear/props/props.hxx>
|
|
|
|
#include <simgear/structure/commands.hxx>
|
2020-09-23 08:54:56 +00:00
|
|
|
#include <simgear/structure/exception.hxx>
|
2020-10-26 17:57:39 +00:00
|
|
|
#include <simgear/io/iostreams/sgstream.hxx>
|
2020-04-07 08:38:54 +00:00
|
|
|
|
|
|
|
#include <Main/fg_init.hxx>
|
2020-08-17 12:10:12 +00:00
|
|
|
#include <Main/fg_props.hxx>
|
|
|
|
#include <Main/globals.hxx>
|
2020-04-07 08:38:54 +00:00
|
|
|
|
2020-09-18 09:14:23 +00:00
|
|
|
#include <flightgearBuildId.h>
|
|
|
|
|
2020-04-07 08:38:54 +00:00
|
|
|
using namespace std;
|
|
|
|
|
2020-10-13 21:04:33 +00:00
|
|
|
bool doesStringMatchPrefixes(const std::string& s, const std::initializer_list<const char*>& prefixes)
|
|
|
|
{
|
|
|
|
if (s.empty())
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
|
|
for (auto c : prefixes) {
|
|
|
|
if (s.find(c) == 0)
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto OSG_messageWhitelist = {
|
|
|
|
"PNG lib warning : iCCP: known incorrect sRGB profile",
|
|
|
|
"PNG lib warning : iCCP: profile 'ICC Profile': 1000000h: invalid rendering intent",
|
|
|
|
"osgDB ac3d reader: detected surface with less than 3",
|
2020-11-10 11:10:17 +00:00
|
|
|
"osgDB ac3d reader: detected line with less than 2",
|
|
|
|
"Detected particle system using segment(s) with less than 2 vertices"
|
2020-10-13 21:04:33 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
auto XML_messageWhitelist = {
|
|
|
|
"Cannot open file",
|
|
|
|
"not well-formed (invalid token)",
|
2020-11-03 21:26:44 +00:00
|
|
|
"mismatched tag (from 'SimGear XML Parser')",
|
|
|
|
"syntax error (from 'SimGear XML Parser')"
|
2020-10-13 21:04:33 +00:00
|
|
|
};
|
|
|
|
|
2020-04-07 08:38:54 +00:00
|
|
|
// we don't want sentry enabled for the test suite
|
|
|
|
#if defined(HAVE_SENTRY) && !defined(BUILDING_TESTSUITE)
|
|
|
|
|
2020-04-18 17:13:18 +00:00
|
|
|
static bool static_sentryEnabled = false;
|
2020-10-13 21:04:33 +00:00
|
|
|
thread_local bool perThread_reportXMLParseErrors = true;
|
2020-04-18 17:13:18 +00:00
|
|
|
|
2020-04-07 08:38:54 +00:00
|
|
|
#include <sentry.h>
|
|
|
|
|
2020-09-18 09:34:40 +00:00
|
|
|
namespace {
|
|
|
|
|
2020-09-23 08:54:56 +00:00
|
|
|
// this callback is invoked whenever an instance of sg_throwable is created.
|
|
|
|
// therefore we can log the stack trace at that point
|
|
|
|
void sentryTraceSimgearThrow(const std::string& msg, const std::string& origin, const sg_location& loc)
|
|
|
|
{
|
|
|
|
if (!static_sentryEnabled)
|
|
|
|
return;
|
|
|
|
|
2020-10-13 21:04:33 +00:00
|
|
|
// don't report the exceptions raised by easyxml.cxx, if this per-thread
|
|
|
|
// flag is set. This avoids a lot of errors when the launcher scans
|
|
|
|
// directories containing many aircraft of unknown origin/quality
|
|
|
|
// if the user tries to fly with one, we'll still get an error then,
|
|
|
|
// but that's a real failure point (from the user PoV)
|
|
|
|
if (!perThread_reportXMLParseErrors && doesStringMatchPrefixes(msg, XML_messageWhitelist)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-09-23 08:54:56 +00:00
|
|
|
sentry_value_t exc = sentry_value_new_object();
|
|
|
|
sentry_value_set_by_key(exc, "type", sentry_value_new_string("Exception"));
|
|
|
|
|
2020-09-25 12:55:28 +00:00
|
|
|
string message = msg;
|
2020-09-23 08:54:56 +00:00
|
|
|
if (!origin.empty()) {
|
2020-09-25 12:55:28 +00:00
|
|
|
message += " (from '" + origin + "')";
|
2020-09-23 08:54:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (loc.isValid()) {
|
2020-09-25 12:55:28 +00:00
|
|
|
message += " at " + loc.asString();
|
2020-09-23 08:54:56 +00:00
|
|
|
}
|
|
|
|
|
2020-09-25 12:55:28 +00:00
|
|
|
sentry_value_set_by_key(exc, "value", sentry_value_new_string(message.c_str()));
|
|
|
|
|
2020-09-23 08:54:56 +00:00
|
|
|
sentry_value_t event = sentry_value_new_event();
|
|
|
|
sentry_value_set_by_key(event, "exception", exc);
|
|
|
|
|
|
|
|
sentry_event_value_add_stacktrace(event, nullptr, 0);
|
|
|
|
sentry_capture_event(event);
|
|
|
|
}
|
|
|
|
|
2020-09-18 09:34:40 +00:00
|
|
|
class SentryLogCallback : public simgear::LogCallback
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
SentryLogCallback() : simgear::LogCallback(SG_ALL, SG_WARN)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
bool doProcessEntry(const simgear::LogEntry& e) override
|
|
|
|
{
|
|
|
|
// we need original priority here, so we don't record MANDATORY_INFO
|
|
|
|
// or DEV_ messages, which would get noisy.
|
|
|
|
const auto op = e.originalPriority;
|
|
|
|
if ((op != SG_WARN) && (op != SG_ALERT)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-10-13 21:04:33 +00:00
|
|
|
if ((e.debugClass == SG_OSG) && doesStringMatchPrefixes(e.message, OSG_messageWhitelist)) {
|
|
|
|
return true;
|
2020-09-25 12:55:28 +00:00
|
|
|
}
|
|
|
|
|
2020-11-03 17:58:32 +00:00
|
|
|
if (e.message == _lastLoggedMessage) {
|
|
|
|
_lastLoggedCount++;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_lastLoggedCount > 0) {
|
|
|
|
flightgear::addSentryBreadcrumb("(repeats " + std::to_string(_lastLoggedCount) + " times)", "info");
|
|
|
|
_lastLoggedCount = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
_lastLoggedMessage = e.message;
|
2020-09-18 09:34:40 +00:00
|
|
|
flightgear::addSentryBreadcrumb(e.message, (op == SG_WARN) ? "warning" : "error");
|
|
|
|
return true;
|
|
|
|
}
|
2020-11-03 17:58:32 +00:00
|
|
|
|
|
|
|
private:
|
|
|
|
std::string _lastLoggedMessage;
|
|
|
|
int _lastLoggedCount = 0;
|
2020-09-18 09:34:40 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
2020-04-07 08:38:54 +00:00
|
|
|
namespace flightgear
|
|
|
|
{
|
|
|
|
|
2020-08-17 12:10:12 +00:00
|
|
|
bool sentryReportCommand(const SGPropertyNode* args, SGPropertyNode* root)
|
|
|
|
{
|
|
|
|
if (!static_sentryEnabled) {
|
|
|
|
SG_LOG(SG_GENERAL, SG_WARN, "Sentry.io not enabled at startup");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
sentry_value_t exc = sentry_value_new_object();
|
|
|
|
sentry_value_set_by_key(exc, "type", sentry_value_new_string("Report"));
|
|
|
|
|
|
|
|
const string message = args->getStringValue("message");
|
|
|
|
sentry_value_set_by_key(exc, "value", sentry_value_new_string(message.c_str()));
|
|
|
|
|
|
|
|
sentry_value_t event = sentry_value_new_event();
|
|
|
|
sentry_value_set_by_key(event, "exception", exc);
|
|
|
|
// capture the C++ stack-trace. Probably not that useful but can't hurt
|
|
|
|
sentry_event_value_add_stacktrace(event, nullptr, 0);
|
|
|
|
|
|
|
|
sentry_capture_event(event);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-09-23 08:54:56 +00:00
|
|
|
bool sentrySendError(const SGPropertyNode* args, SGPropertyNode* root)
|
|
|
|
{
|
|
|
|
if (!static_sentryEnabled) {
|
|
|
|
SG_LOG(SG_GENERAL, SG_WARN, "Sentry.io not enabled at startup");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
throw sg_io_exception("Invalid flurlbe", sg_location("/Some/dummy/path/bar.txt", 100, 200));
|
|
|
|
} catch (sg_exception& e) {
|
|
|
|
SG_LOG(SG_GENERAL, SG_WARN, "caught dummy exception");
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-04-07 08:38:54 +00:00
|
|
|
void initSentry()
|
|
|
|
{
|
|
|
|
sentry_options_t *options = sentry_options_new();
|
2020-05-12 11:01:59 +00:00
|
|
|
// API key is defined in config.h, set in an environment variable prior
|
|
|
|
// to running CMake, so it can be customised. Env var at build time is:
|
|
|
|
// FLIGHTGEAR_SENTRY_API_KEY
|
|
|
|
sentry_options_set_dsn(options, SENTRY_API_KEY);
|
2020-04-07 08:38:54 +00:00
|
|
|
|
|
|
|
if (strcmp(FG_BUILD_TYPE, "Dev") == 0) {
|
|
|
|
sentry_options_set_release(options, "flightgear-dev@" FLIGHTGEAR_VERSION);
|
|
|
|
} else {
|
|
|
|
sentry_options_set_release(options, "flightgear@" FLIGHTGEAR_VERSION);
|
|
|
|
}
|
|
|
|
|
2020-09-18 09:14:23 +00:00
|
|
|
const auto buildString = std::to_string(JENKINS_BUILD_NUMBER);
|
|
|
|
sentry_options_set_dist(options, buildString.c_str());
|
2020-09-17 20:44:29 +00:00
|
|
|
|
2020-04-07 08:38:54 +00:00
|
|
|
SGPath dataPath = fgHomePath() / "sentry_db";
|
|
|
|
#if defined(SG_WINDOWS)
|
|
|
|
const auto homePathString = dataPath.wstr();
|
|
|
|
sentry_options_set_database_pathw(options, homePathString.c_str());
|
|
|
|
|
|
|
|
const auto logPath = (fgHomePath() / "fgfs.log").wstr();
|
2020-08-18 15:19:14 +00:00
|
|
|
sentry_options_add_attachmentw(options, logPath.c_str());
|
2020-04-07 08:38:54 +00:00
|
|
|
#else
|
|
|
|
const auto homePathString = dataPath.utf8Str();
|
|
|
|
sentry_options_set_database_path(options, homePathString.c_str());
|
|
|
|
|
|
|
|
const auto logPath = (fgHomePath() / "fgfs.log").utf8Str();
|
2020-08-18 15:19:14 +00:00
|
|
|
sentry_options_add_attachment(options, logPath.c_str());
|
2020-04-07 08:38:54 +00:00
|
|
|
#endif
|
|
|
|
|
2020-10-26 17:57:39 +00:00
|
|
|
sentry_value_t user = sentry_value_new_object();
|
|
|
|
|
|
|
|
const auto uuidPath = fgHomePath() / "sentry_uuid.txt";
|
|
|
|
bool generateUuid = true;
|
|
|
|
std::string uuid;
|
|
|
|
if (uuidPath.exists()) {
|
|
|
|
sg_ifstream f(uuidPath);
|
|
|
|
std::getline(f, uuid);
|
|
|
|
// if we read enough bytes, that this is a valid UUID, then accept it
|
|
|
|
if ( uuid.length() >= 36) {
|
|
|
|
generateUuid = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// we need to generate a new UUID
|
|
|
|
if (generateUuid) {
|
|
|
|
// use the Sentry APi to generate one
|
|
|
|
sentry_uuid_t su = sentry_uuid_new_v4();
|
|
|
|
char bytes[38];
|
|
|
|
sentry_uuid_as_string(&su, bytes);
|
|
|
|
bytes[37] = 0;
|
|
|
|
|
|
|
|
uuid = std::string{bytes};
|
|
|
|
// write it back to disk for next time
|
|
|
|
sg_ofstream f(uuidPath);
|
|
|
|
f << uuid << endl;
|
|
|
|
}
|
|
|
|
|
|
|
|
sentry_value_t userUuidV = sentry_value_new_string(uuid.c_str());
|
|
|
|
sentry_value_set_by_key(user, "id", userUuidV);
|
|
|
|
sentry_set_user(user);
|
|
|
|
|
2020-04-07 08:38:54 +00:00
|
|
|
sentry_init(options);
|
|
|
|
static_sentryEnabled = true;
|
2020-09-18 09:34:40 +00:00
|
|
|
|
|
|
|
sglog().addCallback(new SentryLogCallback);
|
2020-09-23 08:54:56 +00:00
|
|
|
setThrowCallback(sentryTraceSimgearThrow);
|
2020-04-07 08:38:54 +00:00
|
|
|
}
|
|
|
|
|
2020-08-17 12:10:12 +00:00
|
|
|
void delayedSentryInit()
|
|
|
|
{
|
|
|
|
if (!static_sentryEnabled)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// allow the user to opt-out of sentry.io features
|
|
|
|
if (!fgGetBool("/sim/startup/sentry-crash-reporting-enabled", true)) {
|
|
|
|
SG_LOG(SG_GENERAL, SG_INFO, "Disabling Sentry.io reporting");
|
|
|
|
sentry_shutdown();
|
|
|
|
static_sentryEnabled = false;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
globals->get_commands()->addCommand("sentry-report", &sentryReportCommand);
|
2020-09-23 08:54:56 +00:00
|
|
|
globals->get_commands()->addCommand("sentry-exception", &sentrySendError);
|
2020-08-17 12:10:12 +00:00
|
|
|
}
|
|
|
|
|
2020-04-07 08:38:54 +00:00
|
|
|
void shutdownSentry()
|
|
|
|
{
|
|
|
|
if (static_sentryEnabled) {
|
|
|
|
sentry_shutdown();
|
|
|
|
static_sentryEnabled = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool isSentryEnabled()
|
|
|
|
{
|
|
|
|
return static_sentryEnabled;
|
|
|
|
}
|
|
|
|
|
|
|
|
void addSentryBreadcrumb(const std::string& msg, const std::string& level)
|
|
|
|
{
|
|
|
|
if (!static_sentryEnabled)
|
|
|
|
return;
|
|
|
|
|
|
|
|
sentry_value_t crumb = sentry_value_new_breadcrumb("default", msg.c_str());
|
|
|
|
sentry_value_set_by_key(crumb, "level", sentry_value_new_string(level.c_str()));
|
|
|
|
sentry_add_breadcrumb(crumb);
|
|
|
|
}
|
|
|
|
|
|
|
|
void addSentryTag(const char* tag, const char* value)
|
|
|
|
{
|
|
|
|
if (!static_sentryEnabled)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (!tag || !value)
|
|
|
|
return;
|
|
|
|
|
|
|
|
sentry_set_tag(tag, value);
|
|
|
|
}
|
|
|
|
|
2020-04-20 09:47:11 +00:00
|
|
|
void sentryReportNasalError(const std::string& msg, const string_list& stack)
|
|
|
|
{
|
|
|
|
if (!static_sentryEnabled)
|
|
|
|
return;
|
|
|
|
#if 0
|
|
|
|
sentry_value_t exc = sentry_value_new_object();
|
|
|
|
sentry_value_set_by_key(exc, "type", sentry_value_new_string("Exception"));
|
|
|
|
sentry_value_set_by_key(exc, "value", sentry_value_new_string(msg.c_str()));
|
|
|
|
|
|
|
|
sentry_value_t stackData = sentry_value_new_list();
|
|
|
|
for (const auto& nasalFrame : stack) {
|
|
|
|
sentry_value_append(stackData, sentry_value_new_string(nasalFrame.c_str()));
|
|
|
|
}
|
|
|
|
sentry_value_set_by_key(exc, "stack", stackData);
|
|
|
|
|
|
|
|
|
|
|
|
sentry_value_t event = sentry_value_new_event();
|
|
|
|
sentry_value_set_by_key(event, "exception", exc);
|
|
|
|
|
|
|
|
// add the Nasal stack trace data
|
|
|
|
|
|
|
|
// capture the C++ stack-trace. Probably not that useful but can't hurt
|
|
|
|
sentry_event_value_add_stacktrace(event, nullptr, 0);
|
|
|
|
|
|
|
|
sentry_capture_event(event);
|
|
|
|
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2020-08-23 16:41:34 +00:00
|
|
|
void sentryReportException(const std::string& msg, const std::string& location)
|
|
|
|
{
|
|
|
|
if (!static_sentryEnabled)
|
|
|
|
return;
|
|
|
|
|
|
|
|
sentry_value_t exc = sentry_value_new_object();
|
|
|
|
sentry_value_set_by_key(exc, "type", sentry_value_new_string("Exception"));
|
|
|
|
|
2020-09-25 12:55:28 +00:00
|
|
|
string message = msg;
|
2020-08-23 16:41:34 +00:00
|
|
|
if (!location.empty()) {
|
2020-09-25 12:55:28 +00:00
|
|
|
message += " at " + location;
|
2020-08-23 16:41:34 +00:00
|
|
|
}
|
|
|
|
|
2020-09-25 12:55:28 +00:00
|
|
|
sentry_value_set_by_key(exc, "value", sentry_value_new_string(message.c_str()));
|
|
|
|
|
2020-08-23 16:41:34 +00:00
|
|
|
sentry_value_t event = sentry_value_new_event();
|
|
|
|
sentry_value_set_by_key(event, "exception", exc);
|
|
|
|
|
|
|
|
// capture the C++ stack-trace. Probably not that useful but can't hurt
|
|
|
|
sentry_event_value_add_stacktrace(event, nullptr, 0);
|
|
|
|
sentry_capture_event(event);
|
|
|
|
}
|
|
|
|
|
|
|
|
void sentryReportFatalError(const std::string& msg, const std::string& more)
|
|
|
|
{
|
|
|
|
if (!static_sentryEnabled)
|
|
|
|
return;
|
|
|
|
|
2020-09-25 12:55:28 +00:00
|
|
|
sentry_value_t sentryMessage = sentry_value_new_object();
|
|
|
|
sentry_value_set_by_key(sentryMessage, "type", sentry_value_new_string("Fatal Error"));
|
2020-08-23 16:41:34 +00:00
|
|
|
|
2020-09-25 12:55:28 +00:00
|
|
|
string message = msg;
|
2020-08-23 16:41:34 +00:00
|
|
|
if (!more.empty()) {
|
2020-09-25 12:55:28 +00:00
|
|
|
message += " (more: " + more + ")";
|
2020-08-23 16:41:34 +00:00
|
|
|
}
|
|
|
|
|
2020-09-25 12:55:28 +00:00
|
|
|
sentry_value_set_by_key(sentryMessage, "formatted", sentry_value_new_string(message.c_str()));
|
|
|
|
|
2020-08-23 16:41:34 +00:00
|
|
|
sentry_value_t event = sentry_value_new_event();
|
2020-11-01 11:18:55 +00:00
|
|
|
sentry_value_set_by_key(event, "message", sentryMessage);
|
2020-08-23 16:41:34 +00:00
|
|
|
|
|
|
|
sentry_event_value_add_stacktrace(event, nullptr, 0);
|
|
|
|
sentry_capture_event(event);
|
|
|
|
}
|
|
|
|
|
2020-10-13 21:04:33 +00:00
|
|
|
void sentryThreadReportXMLErrors(bool report)
|
|
|
|
{
|
|
|
|
perThread_reportXMLParseErrors = report;
|
|
|
|
}
|
|
|
|
|
2020-04-07 08:38:54 +00:00
|
|
|
} // of namespace
|
|
|
|
|
|
|
|
#else
|
|
|
|
|
|
|
|
// stubs for non-sentry case
|
|
|
|
|
|
|
|
namespace flightgear
|
|
|
|
{
|
|
|
|
|
|
|
|
void initSentry()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
void shutdownSentry()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2020-08-17 12:10:12 +00:00
|
|
|
void delayedSentryInit()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2020-04-07 08:38:54 +00:00
|
|
|
bool isSentryEnabled()
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void addSentryBreadcrumb(const std::string&, const std::string&)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
void addSentryTag(const char*, const char*)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2020-04-20 09:47:11 +00:00
|
|
|
void sentryReportNasalError(const std::string&, const string_list&)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2020-08-23 16:41:34 +00:00
|
|
|
void sentryReportException(const std::string&, const std::string&)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
void sentryReportFatalError(const std::string&, const std::string&)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2020-10-13 21:04:33 +00:00
|
|
|
void sentryThreadReportXMLErrors(bool)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2020-04-07 08:38:54 +00:00
|
|
|
} // of namespace
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
// common helpers
|
|
|
|
|
|
|
|
namespace flightgear
|
|
|
|
{
|
|
|
|
|
|
|
|
void addSentryTag(const std::string& tag, const std::string& value)
|
|
|
|
{
|
|
|
|
if (tag.empty() || value.empty())
|
|
|
|
return;
|
|
|
|
|
|
|
|
addSentryTag(tag.c_str(), value.c_str());
|
|
|
|
}
|
|
|
|
|
|
|
|
} // of namespace flightgear
|