Audacity 3.2.0
SentryReport.cpp
Go to the documentation of this file.
1/*!********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 @file SentryReport.cpp
6 @brief Define a class to report errors to Sentry.
7
8 Dmitry Vedenko
9 **********************************************************************/
10
11#include "SentryReport.h"
12
13#include <chrono>
14#include <cstring>
15#include <mutex>
16
17#include <algorithm>
18#include <cctype>
19#include <regex>
20
21#include <rapidjson/document.h>
22#include <rapidjson/writer.h>
23#include <rapidjson/prettywriter.h>
24
25#include <wx/platinfo.h>
26#include <wx/log.h>
27
28#include "CodeConversions.h"
29#include "Uuid.h"
30
31#include "IResponse.h"
32#include "NetworkManager.h"
33
35
36namespace audacity
37{
38namespace sentry
39{
40namespace
41{
42
44
50{
51public:
53 void Add(std::string parameterName, AnonymizedMessage parameterValue)
54 {
55 std::lock_guard<std::mutex> lock(mDataMutex);
56 mData.emplace_back(std::move(parameterName), std::move(parameterValue));
57 }
58
60 std::vector<ExceptionData> MoveParameters()
61 {
62 std::lock_guard<std::mutex> lock(mDataMutex);
63
64 std::vector<ExceptionData> emptyVector;
65
66 std::swap(mData, emptyVector);
67
68 return emptyVector;
69 }
70
73 {
74 static ExceptionContext instance;
75 return instance;
76 }
77
78private:
79 ExceptionContext() = default;
80
81 std::mutex mDataMutex;
82 std::vector<ExceptionData> mData;
83};
84
87 rapidjson::Value& root, rapidjson::Document::AllocatorType& allocator)
88{
89 rapidjson::Value osContext(rapidjson::kObjectType);
90
91 const wxPlatformInfo platformInfo = wxPlatformInfo::Get();
92
93 const std::string osName =
94 ToUTF8(platformInfo.GetOperatingSystemFamilyName());
95
96 osContext.AddMember("type", rapidjson::Value("os", allocator), allocator);
97
98 osContext.AddMember(
99 "name", rapidjson::Value(osName.c_str(), osName.length(), allocator),
100 allocator);
101
102 const std::string osVersion =
103 std::to_string(platformInfo.GetOSMajorVersion()) + "." +
104 std::to_string(platformInfo.GetOSMinorVersion()) + "." +
105 std::to_string(platformInfo.GetOSMicroVersion());
106
107 osContext.AddMember(
108 "version",
109 rapidjson::Value(osVersion.c_str(), osVersion.length(), allocator),
110 allocator);
111
112 root.AddMember("os", std::move(osContext), allocator);
113}
114
116rapidjson::Document CreateSentryDocument()
117{
118 using namespace std::chrono;
119 rapidjson::Document document;
120
121 document.SetObject();
122
123 document.AddMember(
124 "timestamp",
125 rapidjson::Value(
126 duration_cast<seconds>(system_clock::now().time_since_epoch())
127 .count()),
128 document.GetAllocator());
129
130 std::string eventId = Uuid::Generate().ToHexString();
131
132 document.AddMember(
133 "event_id",
134 rapidjson::Value(
135 eventId.c_str(), eventId.length(), document.GetAllocator()),
136 document.GetAllocator());
137
138 constexpr char platform[] = "native";
139
140 document.AddMember(
141 "platform",
142 rapidjson::Value(platform, sizeof(platform) - 1, document.GetAllocator()),
143 document.GetAllocator());
144
145 document["platform"].SetString(
146 platform, sizeof(platform) - 1, document.GetAllocator());
147
148 const std::string release = std::string("audacity@") +
149 std::to_string(AUDACITY_VERSION) + "." +
150 std::to_string(AUDACITY_RELEASE) + "." +
151 std::to_string(AUDACITY_REVISION);
152
153 document.AddMember(
154 "release",
155 rapidjson::Value(
156 release.c_str(), release.length(), document.GetAllocator()),
157 document.GetAllocator());
158
159 rapidjson::Value contexts = rapidjson::Value(rapidjson::kObjectType);
160
161 AddOSContext(contexts, document.GetAllocator());
162
163 document.AddMember("contexts", contexts, document.GetAllocator());
164
165 return document;
166}
167
170 rapidjson::Value& value, rapidjson::Document::AllocatorType& allocator,
171 const ExceptionData& data)
172{
173 value.AddMember(
174 rapidjson::Value(data.first.c_str(), data.first.length(), allocator),
175 rapidjson::Value(data.second.c_str(), data.second.length(), allocator),
176 allocator);
177}
178
181 const Exception& exception, rapidjson::Value& root,
182 rapidjson::Document::AllocatorType& allocator)
183{
184 root.AddMember(
185 "type",
186 rapidjson::Value(
187 exception.Type.c_str(), exception.Type.length(), allocator),
188 allocator);
189
190 root.AddMember(
191 "value",
192 rapidjson::Value(
193 exception.Value.c_str(), exception.Value.length(), allocator),
194 allocator);
195
196 rapidjson::Value mechanismObject(rapidjson::kObjectType);
197
198 mechanismObject.AddMember(
199 "type", rapidjson::Value("runtime_error", allocator), allocator);
200
201 mechanismObject.AddMember(
202 "handled", false, allocator);
203
204 auto contextData = ExceptionContext::Get().MoveParameters();
205
206 if (!exception.Data.empty() || !contextData.empty())
207 {
208 rapidjson::Value dataObject(rapidjson::kObjectType);
209
210 for (const auto& data : contextData)
211 AddExceptionDataToJson(dataObject, allocator, data);
212
213 for (const auto& data : exception.Data)
214 AddExceptionDataToJson(dataObject, allocator, data);
215
216 mechanismObject.AddMember("data", std::move(dataObject), allocator);
217 }
218
219 root.AddMember("mechanism", std::move(mechanismObject), allocator);
220}
221
222} // namespace
223
225{
226 std::replace_if(type.begin(), type.end(), [](char c) {
227 return std::isspace(c) != 0;
228 }, '_');
229
230 return { std::move(type), std::move(value) };
231}
232
234{
235 return { "runtime_error", std::move(value) };
236}
237
239{
240 Data.emplace_back(std::move(key), std::move(value));
241 return *this;
242}
243
245{
246 return { std::move(message) };
247}
248
250{
251 Params.emplace_back(std::move(value));
252 return *this;
253}
254
256 std::string parameterName, AnonymizedMessage parameterValue)
257{
258 ExceptionContext::Get().Add(std::move (parameterName), std::move (parameterValue));
259}
260
262{
263public:
264 explicit ReportImpl(const Exception& exception);
265 explicit ReportImpl(const Message& message);
266
267 void AddUserComment(const std::string& message);
268
269 std::string ToString(bool pretty) const;
270
271 void Send(CompletionHandler completionHandler) const;
272
273private:
274 rapidjson::Document mDocument;
275};
276
277
279 : mDocument(CreateSentryDocument())
280{
281 rapidjson::Value exceptionObject(rapidjson::kObjectType);
282 rapidjson::Value valuesArray(rapidjson::kArrayType);
283 rapidjson::Value valueObject(rapidjson::kObjectType);
284
285 SerializeException(exception, valueObject, mDocument.GetAllocator());
286
287 valuesArray.PushBack(std::move(valueObject), mDocument.GetAllocator());
288
289 exceptionObject.AddMember(
290 "values", std::move(valuesArray), mDocument.GetAllocator());
291
292 mDocument.AddMember(
293 "exception", std::move(exceptionObject), mDocument.GetAllocator());
294}
295
297 : mDocument(CreateSentryDocument())
298{
299 rapidjson::Value messageObject(rapidjson::kObjectType);
300
301 messageObject.AddMember(
302 "message",
303 rapidjson::Value(
304 message.Value.c_str(), message.Value.length(),
305 mDocument.GetAllocator()),
306 mDocument.GetAllocator());
307
308 if (!message.Params.empty())
309 {
310 rapidjson::Value paramsArray(rapidjson::kArrayType);
311
312 for (const AnonymizedMessage& param : message.Params)
313 {
314 paramsArray.PushBack(
315 rapidjson::Value(
316 param.c_str(), param.length(), mDocument.GetAllocator()),
317 mDocument.GetAllocator());
318 }
319
320 messageObject.AddMember(
321 "params", std::move(paramsArray), mDocument.GetAllocator());
322 }
323
324 mDocument.AddMember(
325 "message", std::move(messageObject), mDocument.GetAllocator());
326}
327
328void Report::ReportImpl::AddUserComment(const std::string& message)
329{
330 // We only allow adding comment to exceptions now
331 if (!mDocument.HasMember("exception") || message.empty())
332 return;
333
334 rapidjson::Value& topException = mDocument["exception"]["values"][0];
335
336 if (!topException.IsObject())
337 return;
338
339 rapidjson::Value& mechanism = topException["mechanism"];
340
341 // Create a data object if it still does not exist
342 if (!mechanism.HasMember("data"))
343 {
344 mechanism.AddMember(
345 "data", rapidjson::Value(rapidjson::kObjectType),
346 mDocument.GetAllocator());
347 }
348
349 // Add a comment itself
350 mechanism["data"].AddMember(
351 "user_comment",
352 rapidjson::Value(
353 message.data(), message.length(), mDocument.GetAllocator()),
354 mDocument.GetAllocator());
355}
356
357
358void Report::ReportImpl::Send(CompletionHandler completionHandler) const
359{
360 const std::string serializedDocument = ToString(false);
361
364
366 request, serializedDocument.data(), serializedDocument.size());
367
368 response->setRequestFinishedCallback(
369 [response, handler = std::move(completionHandler)](network_manager::IResponse*) {
370 const std::string responseData = response->readAll<std::string>();
371
372 wxLogDebug(responseData.c_str());
373
374 if (handler)
375 handler(response->getHTTPCode(), responseData);
376 });
377}
378
379std::string Report::ReportImpl::ToString(bool pretty) const
380{
381 rapidjson::StringBuffer buffer;
382
383 if (pretty)
384 {
385 rapidjson::PrettyWriter<rapidjson::StringBuffer> writer(buffer);
386 mDocument.Accept(writer);
387 }
388 else
389 {
390 rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
391 mDocument.Accept(writer);
392 }
393
394 return std::string(buffer.GetString());
395}
396
398{
399}
400
401Report::Report(const Exception& exception)
402 : mImpl(std::make_unique<ReportImpl>(exception))
403{
404}
405
406Report::Report(const Message& message)
407 : mImpl(std::make_unique<ReportImpl>(message))
408{
409}
410
411void Report::AddUserComment(const std::string& comment)
412{
413 mImpl->AddUserComment(comment);
414}
415
416std::string Report::GetReportPreview() const
417{
418 return mImpl->ToString(true);
419}
420
421void Report::Send(CompletionHandler completionHandler) const
422{
423 mImpl->Send(std::move (completionHandler));
424}
425
426
427} // namespace sentry
428} // namespace audacity
Declare functions to perform UTF-8 to std::wstring conversions.
Declare an interface for HTTP response.
static const AudacityProject::AttachedObjects::RegisteredFactory key
Declare a class for performing HTTP requests.
Declare a class to report errors to Sentry.
Define a class to generate the requests to Sentry.
Declare a class to generate and parse UUIDs.
static Uuid Generate()
Generate a new UUID.
Definition: Uuid.cpp:82
std::string ToHexString() const
Get a string in the xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx format.
Definition: Uuid.cpp:215
Interface, that provides access to the data from the HTTP response.
Definition: IResponse.h:50
ResponsePtr doPost(const Request &request, const void *data, size_t size)
A class, that stores anonymized message.
size_t length() const noexcept
Returns the length of the message.
const char * c_str() const noexcept
Checks, if the message is empty.
void Send(CompletionHandler completionHandler) const
std::string ToString(bool pretty) const
ReportImpl(const Exception &exception)
void AddUserComment(const std::string &message)
void Send(CompletionHandler completionHandler) const
Send the report to Sentry.
std::unique_ptr< ReportImpl > mImpl
Definition: SentryReport.h:89
std::function< void(int httpCode, std::string responseBody)> CompletionHandler
A callback, that will be called when Send completes.
Definition: SentryReport.h:69
std::string GetReportPreview() const
Get a pretty printed report preview.
Report(const Exception &exception)
Create a report from the exception and previously added exception context.
void AddUserComment(const std::string &comment)
Adds a user comment to the exception report.
static const SentryRequestBuilder & Get()
network_manager::Request CreateRequest() const
Helper class to store additional details about the exception.
void Add(std::string parameterName, AnonymizedMessage parameterValue)
Adds a new item to the exception context.
static ExceptionContext & Get()
Get an instance of the ExceptionContext.
std::vector< ExceptionData > MoveParameters()
Return the current context and reset it.
Services * Get()
Fetch the global instance, or nullptr if none is yet installed.
Definition: BasicUI.cpp:196
auto ToString(const std::optional< TimeSignature > &ts)
void swap(std::unique_ptr< Alg_seq > &a, std::unique_ptr< Alg_seq > &b)
Definition: NoteTrack.cpp:645
void AddExceptionDataToJson(rapidjson::Value &value, rapidjson::Document::AllocatorType &allocator, const ExceptionData &data)
Append the ExceptionData to the Exception JSON object.
rapidjson::Document CreateSentryDocument()
Create the minimal required Sentry JSON document.
void SerializeException(const Exception &exception, rapidjson::Value &root, rapidjson::Document::AllocatorType &allocator)
Serialize the Exception to JSON.
void AddOSContext(rapidjson::Value &root, rapidjson::Document::AllocatorType &allocator)
Append the data about the operating system to the JSON document.
void AddExceptionContext(std::string parameterName, AnonymizedMessage parameterValue)
Saves a parameter, that will be appended to the next Exception report.
std::pair< std::string, AnonymizedMessage > ExceptionData
Additional payload to the exception.
Definition: SentryReport.h:27
std::string ToUTF8(const std::wstring &wstr)
STL namespace.
A DTO for the Sentry Exception interface.
Definition: SentryReport.h:31
Exception & AddData(std::string key, AnonymizedMessage value)
Add a payload to the exception.
static Exception Create(std::string type, AnonymizedMessage value)
Create a new exception.
AnonymizedMessage Value
Message, associated with the Exception.
Definition: SentryReport.h:35
std::string Type
Exception type. Should not have spaces.
Definition: SentryReport.h:33
std::vector< ExceptionData > Data
Arbitrary payload.
Definition: SentryReport.h:37
A DTO for the Sentry Message interface.
Definition: SentryReport.h:49
AnonymizedMessage Value
A string, possibly with s placeholders, containing the message.
Definition: SentryReport.h:51
static Message Create(AnonymizedMessage message)
Create a new Message.
Message & AddParam(AnonymizedMessage value)
Add a parameter to the Message.
std::vector< AnonymizedMessage > Params
Values for the placeholders.
Definition: SentryReport.h:53