Audacity  3.0.3
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 
34 #include "SentryRequestBuilder.h"
35 
36 namespace audacity
37 {
38 namespace sentry
39 {
40 namespace
41 {
42 
44 
49 class ExceptionContext final
50 {
51 public:
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 
78 private:
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 
116 rapidjson::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("[email protected]") +
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 {
263 public:
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 
273 private:
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 
328 void 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 
358 void Report::ReportImpl::Send(CompletionHandler completionHandler) const
359 {
360  const std::string serializedDocument = ToString(false);
361 
362  network_manager::Request request =
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 
379 std::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 
401 Report::Report(const Exception& exception)
402  : mImpl(std::make_unique<ReportImpl>(exception))
403 {
404 }
405 
406 Report::Report(const Message& message)
407  : mImpl(std::make_unique<ReportImpl>(message))
408 {
409 }
410 
411 void Report::AddUserComment(const std::string& comment)
412 {
413  mImpl->AddUserComment(comment);
414 }
415 
416 std::string Report::GetReportPreview() const
417 {
418  return mImpl->ToString(true);
419 }
420 
421 void Report::Send(CompletionHandler completionHandler) const
422 {
423  mImpl->Send(std::move (completionHandler));
424 }
425 
426 
427 } // namespace sentry
428 } // namespace audacity
audacity::sentry::AnonymizedMessage
A class, that stores anonymized message.
Definition: AnonymizedMessage.h:26
audacity::sentry::Report::AddUserComment
void AddUserComment(const std::string &comment)
Adds a user comment to the exception report.
Definition: SentryReport.cpp:411
audacity::sentry::SentryRequestBuilder::Get
static const SentryRequestBuilder & Get()
Definition: SentryRequestBuilder.cpp:20
audacity::network_manager::IResponse
Interface, that provides access to the data from the HTTP response.
Definition: IResponse.h:50
SentryReport.h
Declare a class to report errors to Sentry.
audacity::sentry::Report::ReportImpl::mDocument
rapidjson::Document mDocument
Definition: SentryReport.cpp:274
audacity::sentry::Report::CompletionHandler
std::function< void(int httpCode, std::string responseBody)> CompletionHandler
A callback, that will be called when Send completes.
Definition: SentryReport.h:69
audacity::sentry::AddExceptionContext
void AddExceptionContext(std::string parameterName, AnonymizedMessage parameterValue)
Saves a parameter, that will be appended to the next Exception report.
Definition: SentryReport.cpp:255
audacity::sentry::anonymous_namespace{SentryReport.cpp}::AddExceptionDataToJson
void AddExceptionDataToJson(rapidjson::Value &value, rapidjson::Document::AllocatorType &allocator, const ExceptionData &data)
Append the ExceptionData to the Exception JSON object.
Definition: SentryReport.cpp:169
audacity::sentry::Message::Create
static Message Create(AnonymizedMessage message)
Create a new Message.
Definition: SentryReport.cpp:244
audacity::sentry::Report::ReportImpl::AddUserComment
void AddUserComment(const std::string &message)
Definition: SentryReport.cpp:328
audacity
Definition: ErrorReportDialog.h:22
audacity::network_manager::Request
Definition: Request.h:28
SentryRequestBuilder.h
Define a class to generate the requests to Sentry.
audacity::sentry::anonymous_namespace{SentryReport.cpp}::ExceptionContext::mData
std::vector< ExceptionData > mData
Definition: SentryReport.cpp:82
audacity::sentry::Report::ReportImpl::ToString
std::string ToString(bool pretty) const
Definition: SentryReport.cpp:379
IResponse.h
Declare an interface for HTTP response.
audacity::sentry::Exception::Value
AnonymizedMessage Value
Message, associated with the Exception.
Definition: SentryReport.h:35
Uuid.h
Declare a class to generate and parse UUIDs.
audacity::sentry::Message::AddParam
Message & AddParam(AnonymizedMessage value)
Add a parameter to the Message.
Definition: SentryReport.cpp:249
audacity::sentry::Exception::Data
std::vector< ExceptionData > Data
Arbitrary payload.
Definition: SentryReport.h:37
audacity::sentry::anonymous_namespace{SentryReport.cpp}::ExceptionContext::Get
static ExceptionContext & Get()
Get an instance of the ExceptionContext.
Definition: SentryReport.cpp:72
audacity::sentry::Report::GetReportPreview
std::string GetReportPreview() const
Get a pretty printed report preview.
Definition: SentryReport.cpp:416
audacity::sentry::Report::ReportImpl::ReportImpl
ReportImpl(const Exception &exception)
Definition: SentryReport.cpp:278
audacity::sentry::Report::Report
Report(const Exception &exception)
Create a report from the exception and previously added exception context.
Definition: SentryReport.cpp:401
NetworkManager.h
Declare a class for preforming HTTP requests.
audacity::sentry::anonymous_namespace{SentryReport.cpp}::CreateSentryDocument
rapidjson::Document CreateSentryDocument()
Create the minimal required Sentry JSON document.
Definition: SentryReport.cpp:116
BasicUI::Get
Services * Get()
Fetch the global instance, or nullptr if none is yet installed.
Definition: BasicUI.cpp:26
audacity::Uuid::ToHexString
std::string ToHexString() const
Get a string in the xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx format.
Definition: Uuid.cpp:215
audacity::sentry::anonymous_namespace{SentryReport.cpp}::SerializeException
void SerializeException(const Exception &exception, rapidjson::Value &root, rapidjson::Document::AllocatorType &allocator)
Serialize the Exception to JSON.
Definition: SentryReport.cpp:180
audacity::network_manager::NetworkManager::GetInstance
static NetworkManager & GetInstance()
Definition: NetworkManager.cpp:36
audacity::sentry::anonymous_namespace{SentryReport.cpp}::ExceptionContext
Helper class to store additional details about the exception.
Definition: SentryReport.cpp:50
audacity::sentry::anonymous_namespace{SentryReport.cpp}::ExceptionContext::ExceptionContext
ExceptionContext()=default
anonymous_namespace{NoteTrack.cpp}::swap
void swap(std::unique_ptr< Alg_seq > &a, std::unique_ptr< Alg_seq > &b)
Definition: NoteTrack.cpp:735
audacity::sentry::Report::mImpl
std::unique_ptr< ReportImpl > mImpl
Definition: SentryReport.h:89
audacity::network_manager::NetworkManager::doPost
ResponsePtr doPost(const Request &request, const void *data, size_t size)
Definition: NetworkManager.cpp:63
audacity::sentry::Report::Send
void Send(CompletionHandler completionHandler) const
Send the report to Sentry.
Definition: SentryReport.cpp:421
audacity::sentry::SentryRequestBuilder::CreateRequest
network_manager::Request CreateRequest() const
Definition: SentryRequestBuilder.cpp:27
key
static const AudacityProject::AttachedObjects::RegisteredFactory key
Definition: CommandManager.cpp:201
audacity::sentry::Report::ReportImpl
Definition: SentryReport.cpp:262
audacity::Uuid::Generate
static Uuid Generate()
Generate a new UUID.
Definition: Uuid.cpp:81
audacity::sentry::Report::~Report
~Report()
Definition: SentryReport.cpp:397
audacity::sentry::anonymous_namespace{SentryReport.cpp}::AddOSContext
void AddOSContext(rapidjson::Value &root, rapidjson::Document::AllocatorType &allocator)
Append the data about the operating system to the JSON document.
Definition: SentryReport.cpp:86
audacity::sentry::AnonymizedMessage::length
size_t length() const noexcept
Returns the length of the message.
Definition: AnonymizedMessage.cpp:73
audacity::sentry::AnonymizedMessage::c_str
const char * c_str() const noexcept
Checks, if the message is empty.
Definition: AnonymizedMessage.cpp:68
audacity::sentry::Exception::Create
static Exception Create(std::string type, AnonymizedMessage value)
Create a new exception.
Definition: SentryReport.cpp:224
audacity::sentry::Message::Value
AnonymizedMessage Value
A string, possibly with s placeholders, containing the message.
Definition: SentryReport.h:51
audacity::sentry::Exception
A DTO for the Sentry Exception interface.
Definition: SentryReport.h:31
audacity::sentry::anonymous_namespace{SentryReport.cpp}::ExceptionContext::mDataMutex
std::mutex mDataMutex
Definition: SentryReport.cpp:81
CodeConversions.h
Declare functions to preform UTF-8 to std::wstring conversions.
audacity::sentry::ExceptionData
std::pair< std::string, AnonymizedMessage > ExceptionData
Additional payload to the exception.
Definition: SentryReport.h:27
audacity::sentry::Report::ReportImpl::Send
void Send(CompletionHandler completionHandler) const
Definition: SentryReport.cpp:358
audacity::ToUTF8
std::string ToUTF8(const std::wstring &wstr)
Definition: CodeConversions.cpp:19
audacity::sentry::anonymous_namespace{SentryReport.cpp}::ExceptionContext::MoveParameters
std::vector< ExceptionData > MoveParameters()
Return the current context and reset it.
Definition: SentryReport.cpp:60
audacity::sentry::Message::Params
std::vector< AnonymizedMessage > Params
Values for the placeholders.
Definition: SentryReport.h:53
audacity::sentry::Exception::Type
std::string Type
Exception type. Should not have spaces.
Definition: SentryReport.h:33
audacity::sentry::Exception::AddData
Exception & AddData(std::string key, AnonymizedMessage value)
Add a payload to the exception.
Definition: SentryReport.cpp:238
audacity::sentry::anonymous_namespace{SentryReport.cpp}::ExceptionContext::Add
void Add(std::string parameterName, AnonymizedMessage parameterValue)
Adds a new item to the exception context.
Definition: SentryReport.cpp:53
audacity::sentry::Message
A DTO for the Sentry Message interface.
Definition: SentryReport.h:49