Skip to content

Commit cf1e1d6

Browse files
[Sdk 911] Crash module added (#96)
* namespace added on countly classes * request builder request module * request module completed one deadlock remains * request module integration completed mutext shared ptr added * code cleanup * RQ Module added Unit tests fixed * crash module * pr changes * crash module integrated unit tests added * mutext added * unit tests added * Update CHANGELOG.md * unit test comments added sample app updated Co-authored-by: ArtursKadikis <[email protected]>
1 parent 465dae5 commit cf1e1d6

File tree

14 files changed

+394
-102
lines changed

14 files changed

+394
-102
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
22.06.1
22
* !! Major breaking change !! We are removing the 'LogLevel' enum out of the 'Countly' class which will change how that enum can be referenced. 'Countly::LogLevel' will not work, you will have to use 'cly::LogLevel' instead.
3+
* Added functionality to record crash.
4+
* Added ability to record breadcrumbs for crash recording.
35

46
22.06.0
57
* !! Major breaking change !! We are adding the 'cly' namespace on 'Countly' class which will change how that class can be referenced. 'Countly::' will not work, you will have to use 'cly::Countly::' instead.

CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ set(COUNTLY_PUBLIC_HEADERS
2323
${CMAKE_CURRENT_SOURCE_DIR}/include/countly/constants.hpp
2424
${CMAKE_CURRENT_SOURCE_DIR}/include/countly/event.hpp
2525
${CMAKE_CURRENT_SOURCE_DIR}/include/countly/logger_module.hpp
26+
${CMAKE_CURRENT_SOURCE_DIR}/include/countly/crash_module.hpp
27+
${CMAKE_CURRENT_SOURCE_DIR}/include/countly/request_module.hpp
28+
${CMAKE_CURRENT_SOURCE_DIR}/include/countly/request_builder.hpp
2629
${CMAKE_CURRENT_SOURCE_DIR}/include/countly/views_module.hpp)
2730

2831
add_library(countly
@@ -31,6 +34,7 @@ add_library(countly
3134
${CMAKE_CURRENT_SOURCE_DIR}/src/views_module.cpp
3235
${CMAKE_CURRENT_SOURCE_DIR}/src/logger_module.cpp
3336
${CMAKE_CURRENT_SOURCE_DIR}/src/request_module.cpp
37+
${CMAKE_CURRENT_SOURCE_DIR}/src/crash_module.cpp
3438
${CMAKE_CURRENT_SOURCE_DIR}/src/request_builder.cpp
3539
${CMAKE_CURRENT_SOURCE_DIR}/src/event.cpp)
3640

@@ -85,6 +89,7 @@ if(COUNTLY_BUILD_TESTS)
8589
${CMAKE_CURRENT_SOURCE_DIR}/tests/views.cpp
8690
${CMAKE_CURRENT_SOURCE_DIR}/tests/session.cpp
8791
${CMAKE_CURRENT_SOURCE_DIR}/tests/event.cpp
92+
${CMAKE_CURRENT_SOURCE_DIR}/tests/crash.cpp
8893
${CMAKE_CURRENT_SOURCE_DIR}/tests/config.cpp)
8994

9095
target_compile_definitions(countly-tests PRIVATE COUNTLY_BUILD_TESTS)

examples/example_integration.cpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ int main() {
3535
Countly &ct = Countly::getInstance();
3636
ct.alwaysUsePost(true);
3737
ct.setDeviceID("test-device-id");
38+
// ct.setSalt("test-salt");
3839

3940
ct.setLogger(printLog);
4041
// OS, OS_version, device, resolution, carrier, app_version);
@@ -44,6 +45,8 @@ int main() {
4445
ct.SetMaxEventsPerMessage(10);
4546
ct.setAutomaticSessionUpdateInterval(5);
4647

48+
ct.crash().addBreadcrumb("start");
49+
4750
bool flag = true;
4851
while (flag) {
4952
cout << "Choose your option:" << endl;
@@ -58,6 +61,8 @@ int main() {
5861
cout << "9) Change device id without server merge" << endl;
5962
cout << "10) Set user location" << endl;
6063
cout << "11) Record a view" << endl;
64+
cout << "12) Leave breadcrumb" << endl;
65+
cout << "13) Record a crash with bread crumbs and segmentation" << endl;
6166
cout << "0) Exit" << endl;
6267
int a;
6368
cin >> a;
@@ -123,6 +128,24 @@ int main() {
123128
// Close an opened view
124129
ct.views().closeViewWithID(viewID);
125130
} break;
131+
case 12: {
132+
const std::chrono::system_clock::time_point now = std::chrono::system_clock::now();
133+
const auto timestamp = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch());
134+
135+
ct.crash().addBreadcrumb(std::to_string(timestamp.count()));
136+
} break;
137+
case 13: {
138+
std::map<std::string, std::string> segmentation = {
139+
{"platform", "ubuntu"},
140+
{"time", "60"},
141+
};
142+
143+
std::map<std::string, std::string> crashMetrics = {
144+
{"_run", "199222"}, {"_app_version", "1.0"}, {"_disk_current", "654321"}, {"_disk_total", "10585852"}, {"_os", "windows"},
145+
};
146+
147+
ct.crash().recordException("Divided by zero", "stack trace", true, crashMetrics, segmentation);
148+
} break;
126149
case 0:
127150
flag = false;
128151
break;

include/countly.hpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include "countly/views_module.hpp"
2626
#include <countly/request_builder.hpp>
2727
#include <countly/request_module.hpp>
28+
#include <countly/crash_module.hpp>
2829

2930
namespace cly {
3031
class Countly : public cly::CountlyDelegates {
@@ -141,6 +142,7 @@ class Countly : public cly::CountlyDelegates {
141142
void Stop() { stop(); }
142143

143144
inline cly::ViewsModule &views() const { return *views_module.get(); }
145+
inline cly::CrashModule &crash() const { return *crash_module.get(); }
144146

145147
void RecordEvent(const std::string &key, int count) override { addEvent(cly::Event(key, count)); }
146148

@@ -253,12 +255,13 @@ class Countly : public cly::CountlyDelegates {
253255
nlohmann::json session_params;
254256

255257
std::unique_ptr<std::thread> thread;
258+
std::unique_ptr<cly::CrashModule> crash_module;
256259
std::unique_ptr<cly::ViewsModule> views_module;
257260
std::shared_ptr<cly::CountlyConfiguration> configuration;
258261
std::shared_ptr<cly::LoggerModule> logger;
259262

260263
std::shared_ptr<cly::RequestBuilder> requestBuilder;
261-
std::unique_ptr<cly::RequestModule> requestModule;
264+
std::shared_ptr<cly::RequestModule> requestModule;
262265
std::shared_ptr<std::mutex> mutex = std::make_shared<std::mutex>();
263266

264267
bool is_queue_being_processed = false;

include/countly/crash_module.hpp

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
#ifndef CRASH_MODULE_HPP_
2+
#define CRASH_MODULE_HPP_
3+
#include <map>
4+
#include <memory>
5+
#include <string>
6+
7+
#include "countly/constants.hpp"
8+
#include "countly/countly_configuration.hpp"
9+
#include "countly/logger_module.hpp"
10+
#include "countly/request_module.hpp"
11+
12+
namespace cly {
13+
class CrashModule {
14+
15+
public:
16+
~CrashModule();
17+
CrashModule(std::shared_ptr<CountlyConfiguration> config, std::shared_ptr<LoggerModule> logger, std::shared_ptr<RequestModule> requestModule, std::shared_ptr<std::mutex> mutex);
18+
/**
19+
* Adds string value to a list which is later sent over as logs whenever a cash is reported by system.
20+
*
21+
* @param value: a bread crumb for the crash report
22+
*/
23+
void addBreadcrumb(const std::string &value);
24+
25+
/**
26+
* Public method that sends crash details to the server. Set param "fatal" to false for Custom Logged errors
27+
28+
* @param title: a string that contains description of the exception.
29+
* @param stackTrace: a string that describes the contents of the call-stack.
30+
* @param segmentation: custom key/values to be reported
31+
* @param crashMetrics: contains device information e.g app version, OS.
32+
* In crash metrics '_os', '_app_version' and '_error' are mandatory.
33+
* @param fatal: For automatically captured errors, you should set to 'true', whereas on logged errors it should be 'false'.
34+
*/
35+
void recordException(const std::string &title, const std::string &stackTrace, const bool fatal, const std::map<std::string, std::string> &crashMetrics, const std::map<std::string, std::string> &segmentation = {});
36+
37+
private:
38+
class CrashModuleImpl;
39+
std::unique_ptr<CrashModuleImpl> impl;
40+
};
41+
} // namespace cly
42+
#endif

src/countly.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
namespace cly {
2020
Countly::Countly() {
21+
crash_module = nullptr;
2122
views_module = nullptr;
2223
logger.reset(new cly::LoggerModule());
2324
configuration.reset(new cly::CountlyConfiguration("", ""));
@@ -30,6 +31,7 @@ Countly::Countly() {
3031
Countly::~Countly() {
3132
is_being_disposed = true;
3233
stop();
34+
crash_module.reset();
3335
views_module.reset();
3436
logger.reset();
3537

@@ -307,6 +309,7 @@ void Countly::start(const std::string &app_key, const std::string &host, int por
307309

308310
requestBuilder.reset(new RequestBuilder(configuration, logger));
309311
requestModule.reset(new RequestModule(configuration, logger, requestBuilder));
312+
crash_module.reset(new cly::CrashModule(configuration, logger, requestModule, mutex));
310313
views_module.reset(new cly::ViewsModule(this, logger));
311314

312315
is_sdk_initialized = true; // after this point SDK is initialized.

src/crash_module.cpp

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
#include "countly/crash_module.hpp"
2+
#include "countly/request_module.hpp"
3+
4+
#include <algorithm>
5+
#include <deque>
6+
#include <iostream>
7+
#include <iterator>
8+
9+
namespace cly {
10+
class CrashModule::CrashModuleImpl {
11+
public:
12+
std::deque<std::string> _breadCrumbs;
13+
std::shared_ptr<CountlyConfiguration> _configuration;
14+
std::shared_ptr<LoggerModule> _logger;
15+
std::shared_ptr<RequestModule> _requestModule;
16+
std::shared_ptr<std::mutex> _mutex;
17+
CrashModuleImpl(std::shared_ptr<CountlyConfiguration> config, std::shared_ptr<LoggerModule> logger, std::shared_ptr<RequestModule> requestModule, std::shared_ptr<std::mutex> mutex) : _configuration(config), _logger(logger), _requestModule(requestModule), _mutex(mutex) {}
18+
19+
~CrashModuleImpl() { _logger.reset(); }
20+
};
21+
22+
CrashModule::~CrashModule() { impl.reset(); }
23+
24+
CrashModule::CrashModule(std::shared_ptr<CountlyConfiguration> config, std::shared_ptr<LoggerModule> logger, std::shared_ptr<RequestModule> requestModule, std::shared_ptr<std::mutex> mutex) {
25+
impl.reset(new CrashModuleImpl(config, logger, requestModule, mutex));
26+
27+
impl->_logger->log(LogLevel::DEBUG, cly::utils::format_string("[CrashModule] Initialized"));
28+
}
29+
30+
void CrashModule::addBreadcrumb(const std::string &value) {
31+
impl->_logger->log(LogLevel::INFO, "[CrashModule] addBreadcrumb : " + value);
32+
33+
impl->_mutex->lock();
34+
if (impl->_breadCrumbs.size() >= impl->_configuration->breadcrumbsThreshold) {
35+
impl->_breadCrumbs.pop_front();
36+
}
37+
38+
impl->_breadCrumbs.push_back(value);
39+
impl->_mutex->unlock();
40+
}
41+
42+
void CrashModule::recordException(const std::string &title, const std::string &stackTrace, const bool fatal, const std::map<std::string, std::string> &crashMetrics, const std::map<std::string, std::string> &segmentation) {
43+
44+
impl->_logger->log(LogLevel::INFO, cly::utils::format_string("[CrashModule] recordException: title = %s, stackTrace = %s", title.c_str(), stackTrace.c_str()));
45+
46+
if (title.empty()) {
47+
impl->_logger->log(LogLevel::WARNING, "[CrashModule] recordException : The parameter 'title' can't be empty");
48+
}
49+
50+
if (stackTrace.empty()) {
51+
impl->_logger->log(LogLevel::ERROR, "[CrashModule] recordException : The parameter 'stackTrace' can't be empty");
52+
}
53+
54+
auto it = crashMetrics.find("_os");
55+
if (it == crashMetrics.end() || it->second.empty()) {
56+
impl->_logger->log(LogLevel::ERROR, "[CrashModule] recordException : The crash metric '_os' can't be empty");
57+
}
58+
59+
it = crashMetrics.find("_app_version");
60+
if (it == crashMetrics.end() || it->second.empty()) {
61+
impl->_logger->log(LogLevel::ERROR, "[CrashModule] recordException : The crash metric '_app_version' can't be empty");
62+
}
63+
64+
impl->_mutex->lock();
65+
std::ostringstream outstream;
66+
std::copy(impl->_breadCrumbs.begin(), impl->_breadCrumbs.end(), std::ostream_iterator<std::string>(outstream, "\n"));
67+
68+
nlohmann::json crash(crashMetrics);
69+
nlohmann::json segments(segmentation);
70+
71+
crash["_name"] = title;
72+
crash["_error"] = stackTrace;
73+
crash["_logs"] = outstream.str();
74+
crash["_custom"] = segments;
75+
crash["_nonfatal"] = !fatal;
76+
77+
std::map<std::string, std::string> data = {{"crash", crash.dump()}};
78+
impl->_requestModule->addRequestToQueue(data);
79+
impl->_mutex->unlock();
80+
}
81+
82+
} // namespace cly

src/request_builder.cpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,7 @@ std::string RequestBuilder::buildRequest(const std::map<std::string, std::string
2626
const std::chrono::system_clock::time_point now = std::chrono::system_clock::now();
2727
const auto timestamp = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch());
2828

29-
std::map<std::string, std::string> request = {{"app_key", _configuration->appKey},
30-
// {"device_id", _configuration->deviceId},
31-
{"timestamp", std::to_string(timestamp.count())}};
29+
std::map<std::string, std::string> request = {{"app_key", _configuration->appKey}, {"device_id", _configuration->deviceId}, {"timestamp", std::to_string(timestamp.count())}};
3230

3331
request.insert(data.begin(), data.end());
3432
return serializeData(request);
@@ -42,8 +40,10 @@ std::string RequestBuilder::serializeData(const std::map<std::string, std::strin
4240
}
4341

4442
std::string serialized_string = serialized.str();
45-
serialized_string.resize(serialized_string.size() - 1);
46-
43+
if (serialized_string.size() > 0 ) {
44+
serialized_string.resize(serialized_string.size() - 1);
45+
}
46+
4747
return serialized_string;
4848
}
4949
} // namespace cly

src/views_module.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -91,9 +91,9 @@ class ViewsModule::ViewModuleImpl {
9191
void _closeViewWithName(const std::string &name) {
9292
std::shared_ptr<ViewModuleImpl::ViewInfo> v = findViewByName(name);
9393
if (v == nullptr) {
94-
cly::LogLevel::WARNING, cly::utils::format_string("[ViewModuleImpl] _closeViewWithName: Couldn't found "
94+
_logger->log(cly::LogLevel::WARNING, cly::utils::format_string("[ViewModuleImpl] _closeViewWithName: Couldn't found "
9595
"view with name = %s",
96-
name.c_str());
96+
name.c_str()));
9797
return;
9898
}
9999
_recordView(v, {}, false);
@@ -102,9 +102,9 @@ class ViewsModule::ViewModuleImpl {
102102
void _closeViewWithID(const std::string &viewId) {
103103

104104
if (_viewsStartTime.find(viewId) == _viewsStartTime.end()) {
105-
cly::LogLevel::WARNING, cly::utils::format_string("[ViewModuleImpl] _closeViewWithID: Couldn't found "
105+
_logger->log(cly::LogLevel::WARNING, cly::utils::format_string("[ViewModuleImpl] _closeViewWithID: Couldn't found "
106106
"view with viewId = %s",
107-
viewId.c_str());
107+
viewId.c_str()));
108108
return;
109109
}
110110

tests/config.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#include "doctest.h"
33
#include "test_utils.hpp"
44
using namespace cly;
5+
using namespace test_utils;
56

67
std::string customSha256(const std::string &data) { return "SHA256"; }
78

0 commit comments

Comments
 (0)